ようへいの日々精進XP

よかろうもん

Amazon Elasticsearch Service クラスタを作成するだけの CloudFormation テンプレートを作った #ただそれだけ

追記 (2018/06/09)

初めて VPC 内に Amazon Elasticsearch Service クラスタを作成する際, AWSServiceRoleForAmazonElasticsearchService という IAM ロールを作成する必要があります. この IAM ロールを CloudFormation で作成する方法を見つけることが出来ませんでした.

ということで, 一度, 手動で VPC 内に Amazon Elasticsearch Service クラスタを作っておくことを今のところはおすすめ致します... 手動で作っておくことで, AWSServiceRoleForAmazonElasticsearchService が作成されます.

尚, ポリシーについては, AmazonElasticsearchServiceRolePolicy というポリシーが付与され, ポリシーのドキュメントは以下のような内容となります.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1480452973134",
            "Action": [
                "ec2:CreateNetworkInterface",
                "ec2:DeleteNetworkInterface",
                "ec2:DescribeNetworkInterfaces",
                "ec2:ModifyNetworkInterfaceAttribute",
                "ec2:DescribeSecurityGroups",
                "ec2:DescribeSubnets",
                "ec2:DescribeVpcs"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}

どうも

#ただそれだけ の CloudFormation 初心者, 川原です.

タイトルの通り, Amazon Elasticsearch Service クラスタVPC 内にサクっと作成する CloudFormation テンプレートを作成しました. 作成したテンプレートは, 以下のリポジトリにアップしています.

github.com

以上

簡単ですが, 共有させて頂きます...で終わるのも寂しいので, CloudFormation 職人の皆さんから見ると, 「なんだ今更」と言われてしまうかもしれませんが, 作成にあたって学んだことなどをメモしていきます.

パラメータのタイプ

CloudFormation テンプレート内にParameters で指定する値のタイプについて.

docs.aws.amazon.com

文字列や数値, 配列等の各種タイプについて, 以下のようなタイプがあります. (上記のドキュメントより引用, 抜粋させていただきました.)

タイプ 内容 parameters セクションで指定するサンプル
String リテラル文字列 "FooBarBaz"
Number 整数又は浮動小数 "12345678"
List カンマで区切られた, 整数又は浮動小数 "123, 456"
CommaDelimitedList カンマで区切られたリテラル文字列 "foo, bar, baz"
AWS 固有のパラメーター型 EC2 キーペア名, VPC ID 等の AWS の値 AWS::EC2::Image::Id
SSM パラメータータイプ Systems Manager パラメーターストア内の既存のパラメーターに対応するパラメーター AWS::SSM::Parameter::Name

これらのパラメータを指定する際に個人的に注意したのが, Number タイプを指定する際に値を指定する場合.

  {
    "ParameterKey": "EsInstanceCount",
    "ParameterValue": 1
  },

数値なので, 1 と書いてしまうと, 以下のようなエラーとなってしまいます.

$ bundle exec rake es:create:demo
./deploy.sh ${AWS_PROFILE} ${STACK_NAME_PREFIX} demo create

Parameter validation failed:
Invalid type for parameter Parameters[3].ParameterValue, value: 1, type: <type 'int'>, valid types: <type 'basestring'>

パラメータで指定する場合には数値であっても "" で括って文字列として指定する必要があります. ドキュメントにも以下のように記述されています.

整数または浮動小数点値。AWS CloudFormation は、この型のパラメーターを数値として検証しますが、テンプレート内の他の場所で使用した場合には (Ref 組み込み関数を使用した場合など) 文字列として扱います。

ということで, 以下のように書く必要があります.

  {
    "ParameterKey": "EsInstanceCount",
    "ParameterValue": "1"
  },

うむ.

「文字列値のリスト」タイプをパラメータとして付与したい場合

テンプレートの VPCOptions.SecurityGroupIdsVPCOptions.SubnetIds に指定する値のタイプは, 「文字列値のリスト」で定義する必要があります.

Parameters:
...
  EsSubnetIds:
    Type: CommaDelimitedList
Resources:
  ElasticsearchDomain:
    Type: AWS::Elasticsearch::Domain
...
      VPCOptions:
        SubnetIds: !Ref EsSubnetIds
...

「文字列値のリスト」を分かりやすく YAML で書くと以下のような感じになると思います.

foo:
  - bar
  - baz

foo の要素である barbaz をパラメータでどのように渡すかという話になりますが, 結論から言うと, CommaDelimitedList タイプを利用して書きました. CommaDelimitedList タイプは先述の通り, パラメータにおいては「カンマで区切られた文字列」を指定します. これを !Ref で展開すると, ["foo", "bar"] という配列として解釈されます.

これを CloudFormation テンプレートに落とし込むと以下のようになると思います.

Parameters:
  FooBarBaz:
    Default: "bar, baz"
    Type: CommaDelimitedList
Resources:
    foo: !Ref FooBarBaz 

また, JSON のテンプレートは以下のようになると思います.

  {
    "ParameterKey": "FooBarBaz",
    "ParameterValue": "bar, baz"
  }

フムフム.

テンプレートのチェックはあくまでもテンプレートのみのチェック

テンプレートを検証する為に, AWS CLI では validate-template というサブコマンドが用意されています.

aws --region=${_AWS_REGION} cloudformation validate-template --template-body file://template.yml

自分のようなうっかり者には, このサブコマンドは非常にありがたいのですが, 検証の対象はあくまでもテンプレートのみが対象となっています. 例えば, 先述の Number パラメータを数値で定義したようなパラメータに異常な値があったとしても, validate-template コマンドは検証成功を返します.

$ aws cloudformation validate-template --template-body file://template.yml; echo $?
{
    "Parameters": [
        {
            "NoEcho": false,
            "ParameterKey": "EsZoneAwareness"
        },
        {
            "NoEcho": false,
            "ParameterKey": "EsSubnetIds"
        },
        {
            "NoEcho": false,
            "ParameterKey": "EsDomainEnvironment"
        },
        {
            "NoEcho": false,
            "ParameterKey": "EsSecurityId"
        },
        {
            "NoEcho": false,
            "ParameterKey": "EsDomainNamePrefix"
        },
        {
            "NoEcho": false,
            "ParameterKey": "EsInstanceType"
        },
        {
            "NoEcho": false,
            "ParameterKey": "EsInstanceCount"
        },
        {
            "NoEcho": false,
            "ParameterKey": "EsVersion"
        },
        {
            "NoEcho": false,
            "ParameterKey": "EsStorageVolumeSize"
        }
    ]
}
0

パラメータを含めた状態で検証して頂けると嬉しいかぎりですが, とりあえずはこんなものだと思って利用したいと思います.

テンプレートの汎用化

異なる環境に対して, 同じような構成でクラスタを構築するにあたって, テンプレートを環境毎に用意して良いのは小学生までという自負はあるものの, どのようにすれば良いのか試行錯誤しました. AWS CLI を利用する場合, スタックの作成や, スタックの更新の際にパラメータをコマンドラインオプションや JSON ファイルで渡すことができるので, 以下のように, 環境毎のパラメータ情報を持った JSON ファイルを用意することにしました.

[
  {
    "ParameterKey": "EsDomainEnvironment",
    "ParameterValue": "demo"
  },
  {
    "ParameterKey": "EsDomainNamePrefix",
    "ParameterValue": "oreno-elasticsearch"
  },
  {
    "ParameterKey": "EsInstanceType",
    "ParameterValue": "t2.small.elasticsearch"
  },
  {
    "ParameterKey": "EsInstanceCount",
    "ParameterValue": "1"
  },
  {
    "ParameterKey": "EsVersion",
    "ParameterValue": "6.2"
  },
  {
    "ParameterKey": "EsZoneAwareness",
    "ParameterValue": "false"
  },
  {
    "ParameterKey": "EsStorageVolumeSize",
    "ParameterValue": "10"
  },
  {
    "ParameterKey": "EsSecurityId",
    "ParameterValue": "sg-xxxxxxxx"
  },
  {
    "ParameterKey": "EsSubnetIds",
    "ParameterValue": "subnet-xxxxxxx1, subnet-xxxxxxx2"
  }
]

上記のようなパラメータを記載した JSON ファイルを例えば demo.json というファイル名で保存して, create-stack や update-stack の際に, 以下のように --parameters オプションの引数に渡して利用します.

aws --profile=${_AWS_PROFILE} --region=${_AWS_REGION} \
  cloudformation create-stack \
    --stack-name ${_ENV}-${_STACK_NAME_PREFIX} \
    --parameters file://parameters/demo.json \
    --template-body file://template.yml \
    --capabilities CAPABILITY_IAM

wait コマンドを利用する

AWS CLI の wait コマンド, とても便利だと思います. 例えば, 以下のように EC2 を起動する run-instances した後に利用すると, EC2 のステータス (instance status) が OK になるまで待機してくれるので, 正常に起動することを確認する為の処理を自前で実装する必要が無いという利点があります.

# EC2 を起動 (※このコードはサンプルです)
aws ec2 run-instances --instance-ids i-xxxxxxxxxxxxx

# 起動させた EC2 のインスタンスステータスが OK になるまで待機 (※このコードはサンプルです)
aws ec2 wait instance-status-ok --instance-ids i-xxxxxxxxxxxxx

cloudformation でも wait コマンドが用意されていて, 以下のようなコマンドをサポートしています.

  • change-set-create-complete
  • stack-create-complete
  • stack-delete-complete
  • stack-exists
  • stack-update-complete

例えば, stack-create-complete コマンドの場合, スタックのステータスが CREATE_COMPLETE になるまで待機してくれるので, スタックが正常に作成されたかをわざわざチェックするような実装が不要となります. ただし, Amazon Elasticsearch Service クラスタを作成する場合, EC2 等を作成するスタックと比べると待機時間が長く, 一瞬, 通信が切れてしまったのかなと思うほどでした.

終わり

以上, メモでした.

素敵な CloudFormation と Elasticsearch 生活をお送りください.