AWS CloudFormationの実行をInvokeで管理する

はじめまして、高野です。MonotaROでは、データ基盤の開発と運用をしています。

この記事では、AWS CloudFormationの実行をInvokeで管理する方法を紹介します。Invokeとは、Fabricの一部が切り出される形で作られた、Python製のタスクランナーです。一般的な用途や使い方は、GNU MakeやRakeをイメージすると分かりやすいかもしれません。

背景

弊社のデータ基盤はAWSとGCPのマルチクラウドで構築されており、インフラリソースの管理にはCloudFormationとDeployment Managerを併用しています。これらを運用していくにあたり、下記のニーズがありました。

  • テンプレートだけでなく、実行時に指定するスタック名やパラメータもバージョン管理したい
  • 本番環境と開発環境でパラメータの切り替えを行いたい
  • CloudFormationとDeployment Managerは、同様のインタフェースで実行したい

シェルスクリプト、AnsibleClickなども候補にあがりましたが、スクリプト言語の柔軟性やシェルコマンドとの相性から、Invokeの採用に至りました。

Rake等でも同じことが可能と思いますが、弊社の主要な開発言語と同じPythonで実装されている点もInvokeを選定した理由の一つです。

Invokeを使ってみる

Invokeの基本的な使い方を確認しましょう。今回使用するPythonのバージョンは3.6.3、Invokeは0.22.1です。 Ubuntu 17.10のコンソールから実行しています。

Invokeのインストールはpipから行います。

$ pip install invoke

"tasks.py"というファイルを作成し、下記の内容で保存します。二つのタスクを作成しています。

from invoke import task

# Pythonのコマンドを実行する
@task
def print_hello(ctx):
    print("Hello!")

# シェルコマンドを実行する. 
# オプションで出力形式を変更可能にしている.
@task
def list_files(ctx, long_format=False):
    if long_format:
        ctx.run("ls -l")
    else:
        ctx.run("ls")

それではタスクを実行してみましょう。inv(もしくはinvoke)コマンドを使います。

# "-l"で実行可能なタスクの一覧を表示します
$ inv -l
Available tasks:

  list-files
  print-hello

# タスクを実行します
$ inv print-hello
Hello!

# タスクのヘルプを見てみましょう
$ inv -h list-files
Usage: inv[oke] [--core-opts] list-files [--options] [other tasks here ...]

Docstring:
  none

Options:
  -l, --long-format

# タスクを実行します. オプションはタスク名の後に指定します.
$ inv list-files -l
-rw-r--r-- 1 foo foo  184  3月 10 23:36 tasks.py

他にも、実行時にパラメータを与える、タスク間の依存関係を設定する、ヘルプメッセージをカスタマイズする、などのことができます。詳しくは公式ドキュメントを参照してください。

CloudFormationの実行を管理する

Invokeの基本的な操作が分かったところで、CloudFormationをInvokeから実行してみましょう。この例では、CloudFormationからS3のバケットを作成します。また、AWS CLIのインストールとクレデンシャルの設定は完了済みとします。

ディレクトリ構成は下記にします。

.
├── tasks.py
└── templates
       └── cfn-s3.yaml

"tasks.py"の内容は、下記のように変更しましょう。

from invoke import task
import os

def get_template_path(filename):
    current_dir = os.path.dirname(os.path.abspath(__file__))
    template_path = current_dir + "/templates/" + filename
    return template_path

@task
def cfn_s3(ctx, create=False):
    if create:
        action = "create-stack"
    else:
        action = "update-stack"

    stack_name = "invoke-test"
    template_path = get_template_path("cfn-s3.yaml")

    cmd = "aws cloudformation {action} --stack-name {name} --template-body file://{path}"\
          .format(action=action, name=stack_name, path=template_path)
    print("Execute CloudFormation. command: {cmd}".format(cmd=cmd))
    ctx.run(cmd)

templates/cfn-s3.yamlを下記の内容で作成します。

AWSTemplateFormatVersion: 2010-09-09
Resources:
  S3Bucket:
    Type: 'AWS::S3::Bucket'

それでは、実際に実行してみましょう。

# タスク名を確認
$ inv -l
Available tasks:

  cfn-s3


# 実行.
# 初回(スタック作成時)は"-c"オプションをつける.
$ inv cfn-s3 -c
Execute CloudFormation. command: aws cloudformation create-stack --stack-name invoke-test --template-body file:///path/to/templates/cfn-s3.yaml
{
    "StackId": "xxxxx"
}

コマンドの実行が完了したら、AWSのマネジメントコンソールから"invoke-test"スタックが作成されていることを確認してください。

まとめ

Invokeを使うことで、CloudFormationの実行時の設定を管理することができるようになりました。また、シンプルなタスク名でCloudFormationを実行できるようになりました。

ここから発展させて、環境ごとのパラメーターの切り替えやCloudFormation以外のデプロイタスクの追加を行うことができます。

次回は、環境ごとのパラメータの切り替えについて紹介できればと思います。ここまで読んでいただき、ありがとうございました。