Amazon Elasticsearch ServiceをCloudFormationだけで作成する

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

まいど、大阪の市田です。
CloudFormationでAmazon Elasticsearch Service(以下 Elasticsearch Service)を作成する機会があったので、注意点を踏まえつつ紹介したいと思います。

CloudFormationで作成する時の注意点

早速ですが、Elasticsearch ServiceをCloudFormationで作成する際は、ドメインの作成時に「Elasticsearch Service」用のIAMロールとして「Service Linked Role」が必要になるという点に気をつける必要があります。

他のAWSサービスでも「Service Linked Role」が必要なものはありますが、多くの場合は明示的な指定がなくても自動的に作成されます。
しかし、Elasticsearch Serviceの場合は自動作成されないので何らかの方法で事前に作成しておく必要があります。(マネジメントコンソールで作成する場合は自動的に作成されます。

Service Linked Roleについては、下記で紹介していますので参考にして頂ければと思います。

IAMのService-Linked RolesがCloudFormationに対応したので、とてもナイスなリリースということを詳しく書いてみた。

CloudFormationだけで作成する方法

色々と試してみて一番いいと思ったのは、「Service Linked Role」を作成するテンプレートはElasticsearch Service自体を作成するテンプレートとは別に分ける方法です。

「Service Linked Role」を作成するテンプレートは下記の通りシンプルです。

Resources:
  EsServiceLinkedRole:
    Type: "AWS::IAM::ServiceLinkedRole"
    Properties:
      AWSServiceName: es.amazonaws.com
      Description: "Service Linked Role for Amazon Elasticsearch Service"

なお、この「Service Linked Role」は一度作成すれば、複数のElasticsearchを作成する場合でも共通で利用されますが、すでに同じRoleがある場合に重複して作成しようとするとエラーになります。

次に、上記のCloudFormationが無事「CREATE_COMPLETE」になれば、 Elasticsearch Service を作成します。下記はTokyoリージョンで作成する場合の一例です。

Parameters:
  ESDomainName:
    Description: "Your Elasticsearch Domain Name"
    Type: String
    MinLength: 3
    MaxLength: 28
    AllowedPattern: "^[a-z0-9+-]*$"

Resources:
# VPC
  MyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.100.0.0/16
      EnableDnsSupport: 'true'
      EnableDnsHostnames: 'true'
      InstanceTenancy: default
      Tags:
      - Key: Name
        Value: MyES-VPC

# Subnet
  MyEsSubnet01:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId:
        Ref: MyVPC
      CidrBlock: 10.100.0.0/24
      MapPublicIpOnLaunch: true
      AvailabilityZone: ap-northeast-1a
      Tags:
      - Key: Name
        Value: MyES-Subnet-1a
  MyEsSubnet02:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId:
        Ref: MyVPC
      CidrBlock: 10.100.1.0/24
      MapPublicIpOnLaunch: true
      AvailabilityZone: ap-northeast-1c
      Tags:
      - Key: Name
        Value: MyES-Subnet-1c

# Security Group
  # For Amazon Elasticsearch Service
  ElasticsearchSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: elasticsearch-sg
      VpcId:
        Ref: MyVPC
      Tags:
      - Key: Name
        Value: elasticsearch-sg
      SecurityGroupIngress:
      # for Elasticsearch Access
      - 
        IpProtocol: tcp
        FromPort: '443'
        ToPort: '443'
        CidrIp: !GetAtt MyVPC.CidrBlock

# Elasticsearch on VPC
  MyElasticsearch:
    Type: AWS::Elasticsearch::Domain
    Properties: 
      AccessPolicies:
        Version: 2012-10-17
        Statement: 
          - Effect: Allow
            Principal:
              AWS: '*'
            Action: 'es:*'
            Resource: !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${ESDomainName}/*  
      DomainName: !Ref ESDomainName
      EBSOptions:
        EBSEnabled: true
        VolumeSize: 10
        VolumeType: gp2
      ElasticsearchClusterConfig:
        InstanceCount: 2
        InstanceType: t2.small.elasticsearch
        ZoneAwarenessEnabled: true
      ElasticsearchVersion: 6.2
      SnapshotOptions:
        AutomatedSnapshotStartHour: 17
      VPCOptions: 
        SubnetIds:
          - !Ref MyEsSubnet01
          - !Ref MyEsSubnet02
        SecurityGroupIds:
          - !Ref ElasticsearchSecurityGroup

このように、ロールとElasticsearch Serviceを分けて作成するようにすれば、次回以降 Elasticsearch Service を作成する場合でも、都度「Service Linked Role」を作成する必要がありません。

CloudFormationが失敗する場合

さて、上記では 2回CloudFormationを実行する必要がありましたが、場合によっては 1回で終わらせたいという場合もあるかと思います。しかし上記のテンプレートを1つにまとめるとテンプレートの内容が正しくても失敗する場合があります。
(初めてElasitcsearch ServiceとService Linked Roleを作成する場合においてもです。)

パターンを全て確認した訳ではありませんが、「Service Linked Role の作成直後にElasticsearch Service を作成する」テンプレートの場合はCloudFormationが失敗します。

例えば、下記のように作成済みのVPC上に初めてElasticsearch Service を作成する場合です。初めて作る想定で「Service Linked Role」も作るようにしてみます。
下記のテンプレートでは、DependsOnを使って「Service Linked Role」の作成完了後に、Elasticsearch Service を作成するようにしています。(20行目)

Parameters:
  ESDomainName:
    Description: "Your Elasticsearch Domain Name"
    Type: String
    MinLength: 3
    MaxLength: 28
    AllowedPattern: "^[a-z0-9+-]*$"

Resources:
# ES Service Linked Role
  EsServiceLinkedRole:
    Type: "AWS::IAM::ServiceLinkedRole"
    Properties:
      AWSServiceName: es.amazonaws.com
      Description: "This is a test"

# Elasticsearch on VPC
  MyElasticsearch:
    Type: AWS::Elasticsearch::Domain
    DependsOn: EsServiceLinkedRole
    Properties: 
      AccessPolicies:
        Version: 2012-10-17
        Statement: 
          - Effect: Allow
            Principal:
              AWS: '*'
            Action: 'es:*'
            Resource: !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${ESDomainName}  
      DomainName: !Ref ESDomainName
      EBSOptions:
        EBSEnabled: true
        VolumeSize: 10
        VolumeType: gp2
      ElasticsearchClusterConfig:
        InstanceCount: 2
        InstanceType: t2.small.elasticsearch
        ZoneAwarenessEnabled: true
      ElasticsearchVersion: 6.2
      SnapshotOptions:
        AutomatedSnapshotStartHour: 17
      VPCOptions: 
        SubnetIds:
          - subnet-xxxxxxx
          - subnet-xxxxxxx
        SecurityGroupIds:
          - sg-xxxxxxxxx

しかし「Service Linked Role」の作成が「CREATE_COMPLETE」になってもElasticsearch Service から使用可能になるまでに時間が多少かかってしまうので、上記のような単一のテンプレート内容だと失敗します。

01-cfn-fail

1回のCloudFormationで全部作成する方法

どうしても1回の作業だけで全て作成したい場合は、「Service Linked Role」の作成と「Elasticsearch Service」の作成タイミングの時間を明示的に空けるようにすればよいかと思います。

例えば、下記例のようにVPC作成前に「Service Linked Role」を作成してしまうといった方法です。
(この場合は、あえてDependsOnを下記のタイミングで書かなくても問題なく作成できました)

# VPC
  MyVPC:
    Type: "AWS::EC2::VPC"
    DependsOn: EsServiceLinkedRole
    Properties:
(中略)

# ES Service Linked Role
  EsServiceLinkedRole:
    Type: "AWS::IAM::ServiceLinkedRole"
(中略)

# Elasticsearch on VPC
  MyElasticsearch:
    Type: "AWS::Elasticsearch::Domain"
    Properties: 
      AccessPolicies:
        Version: 2012-10-17
        Statement: 
          - Effect: Allow
(以下略)

ただし、「Service Linked Role」は重複して作成しようとするとエラーになってしまう点などを考えると、1回のCloudFormation実行だけで作るのはおすすめしません。使い捨てで一度しか利用しないケースなど、適用できる場面は限られるかと思います。

ベストな作成方法

改めてベストな作成方法を考えてみると、CloudFormationで全部作成したい場合は、やはり冒頭で紹介したように「Service Linked Role」と「Elasticsearch Service」でそれぞれCloudFormationを分けて実行するのがよいでしょう。

「Service Linked Role」は初回の実行だけでよいので、大きな手間にもならないかと思います。

もちろん、CloudFormationにこだわらないということであれば、AWS CLIなどで実行してしまう方が楽かと思います。とはいえ、環境によってAWS CLIが利用できないといった理由があるかもしれませんので、状況によって使い分けていただければと思います。

最後に

今回は、実際の案件で「できる限りCloudFormationだけでAWS環境を作成したい」というリクエストを受けて、試行錯誤した内容をご紹介いたしました。

この記事が皆様のCloudFormationライフのお役に立てれば幸いです。
以上になります。