Elasticsearch Service の EBS ボリュームを自動拡張する仕組みを考えた

この記事を書いたメンバー:

那須 隆

Elasticsearch Service の EBS ボリュームを自動拡張する仕組みを考えた

目次

ひさびさに 1 週間毎朝 30 分ウォーキングしただけで股関節を痛めました、那須です。 ちょっとした運動は毎日継続することが大切だと気づきました。

自社業務とはまったく関係ないところで、Amazon Elasticsearch Service (以下 ES ) の運用をしています。 この ES、諸事情により日々インデックスがたまっていきます。
先日までこの ES の EBS ボリュームサイズも都度手作業で拡張していました。 それがさすがにつらい…ということで自動拡張する仕組みを考えて実装しました。 今のところはうまく動いているので、似たような悩みを持っている方向けにどうやったかをご紹介します。

構成

以下のような構成で作成してきます。

さあやってみよう!

実際にやってみた手順をご紹介しますね。 基本は CloudFormation でのデプロイです。
すべてを CloudFormation でデプロイしたかったのですが、SSM Automation ドキュメントの作成だけは AWS CLI v2 で作成しています。 理由は、CloudFormation でSSM Automation ドキュメントのテンプレートを更新すると別のドキュメントに置換されてしまって過去の内容が旧バージョンとして残らないのが気に入らなかったから、だけです。
そんなの関係ない!って方は CloudFormation でデプロイしてもいいと思います。

CloudFormation でデプロイしても新バージョン作成して更新できるよ!って方がいれば教えてください。 もしできるならめっちゃ嬉しいです!

まずは IAM ロールを準備

SSM Automation ドキュメント実行のための IAM ロールを作成します。 サービス es の参照アクションと UpdateElasticsearchDomainConfig アクションだけを許可するだけで十分です。
ファイル名は SSM-AutomationServiceRole.yml にしました。

 AWSTemplateFormatVersion: '2010-09-09'
Description: AWS CloudFormation template IAM Roles for Systems Manager | Automation for ES EBS size update
 
Resources:
  EBSSizeUpdateForESAutomationRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - ssm.amazonaws.com
          Action: sts:AssumeRole
      Path: "/"
      RoleName: EBSSizeUpdateForESAutomationRole
  EsEbsAutomationPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action:
              - "es:Describe*"
              - "es:UpdateElasticsearchDomainConfig"
              - "es:Get*"
              - "es:List*"
            Resource: "*"
      ManagedPolicyName: EBSSizeUpdateForESAutomationPolicy
      Roles:
        - Ref: EBSSizeUpdateForESAutomationRole
 
Outputs:
  Role:
    Value: !GetAtt EBSSizeUpdateForESAutomationRole.Arn
    Export:
      Name: EBSSizeUpdateForESAutomationRoleArn

この yaml ファイルを使って CloudFormation スタックを作成しましょう。 

 aws cloudformation create-stack --stack-name CreateESEBSUpdateAutomationRole --template-body file://SSM-AutomationServiceRole.yml --capabilities CAPABILITY_NAMED_IAM
aws cloudformation wait stack-create-complete --stack-name CreateESEBSUpdateAutomationRole
RoleArn=`aws cloudformation describe-stacks --stack-name CreateESEBSUpdateAutomationRole | jq -r '.Stacks[].Outputs[].OutputValue'`

RoleArn 変数を準備している理由は、この後の SSM Automation ドキュメント作成で IAM ロールの ARN を指定する箇所があるので、そこで動的に指定したいためです。
IAM ロールの準備はこれで OK です。

SSM Automation ドキュメントを作成する

実際に ES の EBS ボリュームを拡張する仕組みである SSM Automation のドキュメントのコンテンツを準備します。 ファイル名は ESEBSUpdateAutomation_source.yml にしました。
ステップは以下の 4 つあります。

1. ES の EBS ボリューム拡張を行う時刻と拡張サイズを決める(本番環境は 30GB、それ以外は 10GB 追加するようにしています)
2. EBS ボリューム拡張時刻まで待つ
3. ES EBS ボリューム拡張を行う
4. 結果を確認する(タイムアウトは 2 時間にしています)

 description:
schemaVersion: '0.3'
assumeRole: EBSSizeUpdateForESAutomationRole.Arn
outputs:
  - Schedule.Schedule
  - UpdateES.ChangedSize
parameters:
  DomainName:
    type: String
mainSteps:
  - name: Schedule
    action: 'aws:executeScript'
    inputs:
      Runtime: python3.7
      Handler: script_handler
      Script: |
        def script_handler(events, context):
          from datetime import datetime, timedelta, timezone
          import boto3
 
          JST = timezone(timedelta(hours=+9), 'JST')
          now = datetime.now(JST)
          if now.hour >= 2:
              schedule = now + timedelta(days=1)
          else:
              schedule = now
          schedule = schedule.replace(hour=2,minute=0,second=0,microsecond=0)
           
          client = boto3.client('es')
          response = client.describe_elasticsearch_domain_config(
            DomainName = events['DomainName']
          )
          CurrentEBSSize = int(response['DomainConfig']['EBSOptions']['Options']['VolumeSize'])
          if events['DomainName'] == 'production':
            increase_size = 30
          else:
            increase_size = 10
          TargetEBSSize = CurrentEBSSize + increase_size
           
          return {'schedule': schedule.isoformat(), 'CurrentEBSSize': CurrentEBSSize, 'TargetEBSSize': TargetEBSSize}
      InputPayload:
        DomainName: '{{ DomainName }}'
    outputs:
      - Selector: $.Payload.schedule
        Type: String
        Name: Schedule
      - Type: Integer
        Name: CurrentEBSSize
        Selector: $.Payload.CurrentEBSSize
      - Name: TargetEBSSize
        Selector: $.Payload.TargetEBSSize
        Type: Integer
    isEnd: false
    nextStep: WaitForExecute
  - name: WaitForExecute
    action: 'aws:sleep'
    inputs:
      Timestamp: '{{ Schedule.Schedule }}'
    nextStep: UpdateES
  - name: UpdateES
    action: 'aws:executeAwsApi'
    inputs:
      Service: es
      Api: UpdateElasticsearchDomainConfig
      DomainName: '{{ DomainName }}'
      EBSOptions:
        VolumeSize: '{{ Schedule.TargetEBSSize }}'
    outputs:
      - Name: ChangedSize
        Selector: $.DomainConfig.EBSOptions.Options.VolumeSize
        Type: Integer
    nextStep: WaitForProcessing
  - name: WaitForProcessing
    action: 'aws:waitForAwsResourceProperty'
    inputs:
      Service: es
      Api: DescribeElasticsearchDomain
      DomainName: '{{ DomainName }}'
      PropertySelector: $.DomainStatus.Processing
      DesiredValues:
        - 'False'
    timeoutSeconds: 7200

3 行目に assumeRole を指定しているのですが、ここで先ほど作成した IAM ロールの ARN を指定します。
IAM ロールの ARN が指定できれば、あとはその内容の通りに SSM Automation ドキュメントを作成するコマンドを実行するだけです。 ドキュメント名は ESEBSUpdateAutomation にしました。 

 DocumentName=ESEBSUpdateAutomation
sed -e "s;EBSSizeUpdateForESAutomationRole.Arn;'${RoleArn}';" ./ESEBSUpdateAutomation_source.yml > ESEBSUpdateAutomation.yml
aws ssm create-document --content file://ESEBSUpdateAutomation.yml --name $DocumentName --document-format YAML --document-type Automation

これで SSM Automation ドキュメントの準備はOKです。

EventBridge でひっかける

仕組みが準備できたので、あとは実行するための EventBridge ルールを作成するための CloudFormation テンプレートを作成します。 実行トリガーは、特定の CloudWatch Alarm でアラーム状態になった時としています。
テンプレート名は ESEBSUpdateEvents.yml としました。

AWSTemplateFormatVersion: "2010-09-09"
Description: A template for EventBridge
Parameters:
  AutomationDocumentName:
    Type: String
    Description: Specify the automation document name for execution.
 
Resources:
  AlarmToAutomationRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - events.amazonaws.com
            Action: sts:AssumeRole
      Path: "/"
      RoleName: AlarmToAutomationRole
  AlarmToAutomationPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action:
              - "ssm:StartAutomationExecution"
            Resource: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:automation-definition/ESEBSUpdateAutomation:$DEFAULT"
      ManagedPolicyName: AlarmToAutomationPolicy
      Roles:
        - !Ref AlarmToAutomationRole
 
  AlarmToAutomationEventRule:
    Type: AWS::Events::Rule
    Properties:
      Description: Execute SSM Automation from CloudWatch Alarm
      EventPattern:
        source:
          - aws.cloudwatch
        detail-type:
          - CloudWatch Alarm State Change
        detail:
          alarmName:
            - ES_Free­Storage­Space_production
            - ES_Free­Storage­Space_staging
            - ES_Free­Storage­Space_development
          state:
            value:
              - ALARM
      Name: ExecuteSSMAutomation-ESEBSIncrease
      State: ENABLED
      Targets:
        - Arn: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:automation-definition/${AutomationDocumentName}
          Id: AlarmToAutomationEventRule
          InputTransformer:
            InputPathsMap:
              DomainName: $.detail.configuration.metrics[0].metricStat.metric.dimensions.DomainName
            InputTemplate: '{"DomainName": [<DomainName>]}'
          RoleArn: !GetAtt AlarmToAutomationRole.Arn

このテンプレートで CloudFormation スタックを作成しましょう。 

 aws cloudformation create-stack --stack-name ESEBSUpdateEvents --template-body file://ESEBSUpdateEvents.yml --parameters ParameterKey=AutomationDocumentName,ParameterValue=$DocumentName --capabilities CAPABILITY_NAMED_IAM
aws cloudformation wait stack-create-complete --stack-name ESEBSUpdateEven

ちょっと原因がわかってないのですが、この手順で EventBridge ルールがうまく動かないことがあります。 その場合は、イベントパターンがカスタムで定義されていると思うので、AWS サービスから選択する形で定義し直してください。 出来上がりはまったく同じイベントパターンができあがりますが、これで大丈夫です。

さあ、これで準備完了です!

いざテスト!

CloudWatch Alarm でアラーム状態にすれば、ES EBS ボリュームを拡張するために SSM Automation がアップを始めます。 アラーム状態にするには、以下のようなコマンド一発実行すれば OK です。

 aws cloudwatch set-alarm-state --alarm-name ES_Free­Storage­Space_staging --state-reason "test" --state-value ALARM

指定時刻になれば ES EBS ボリュームサイズが拡張されていると思います!

さいごに

ES に限らず EBS ボリュームの拡張っていろんな場面であると思います。 そのたびに手作業でやっていると大変なので、もし気軽に拡張できるシステムなのであればこのように自動化してしまいましょう。 これで EBS ボリュームの空き容量のことを気にしておく必要はなくなりますよ!(不要なデータがあるならそれを削除した方がいいに越したことはないです













カテゴリー
タグ

SAPシステムや基幹システムのクラウド移行・構築・保守、
DXに関して
お気軽にご相談ください

03-6260-6240 (受付時間 平日9:30〜18:00)