EventBridgeとRunCommandを利用したCodeDeployエージェントのメモリ肥大化への対応

記事タイトルとURLをコピーする

カスタマーサクセス部の北中です。
CodeDeployエージェントが使用するメモリ量が肥大化し、EC2インスタンスのメモリを圧迫してしまう事象に出会いました。
EventBridgeとRunCommandを利用し、CodeDeployエージェントを自動的に再起動する仕組みを実装したのでブログにしてみました。
同じような事象で困っている方の参考になれば幸いです。

背景

GitHubのissue(※1)にもある通り、CodeDeployエージェントを使用したデプロイを行っている場合、デプロイ後にエージェントが使用するメモリ容量が肥大化する事象が報告されています。
ワークアラウンドとしてappspec.ymlファイルのValidateServiceフックから実行されるスクリプトでCodeDeployエージェントの再起動を行う方法(※2)がありますが、諸事情でappspec.ymlファイルの修正が難しかったためEventBridgeとRunCommandを使用し、AWSコンソール上の操作で完結できる形でエージェントの自動再起動を実装しました。

※1 High memory consumption #32 , High memory consumption and memory leak (still an issue) #308
※2 https://github.com/aws/aws-codedeploy-agent/issues/32#issuecomment-287872722

構成

大まかな流れは以下の通りです。
再起動コマンドの実行時に何らかの理由でエージェントが正常に起動しなかった場合、次回のデプロイが失敗してしまうため、再起動コマンドを実行した後にエージェントのステータスを確認し、再起動失敗に気付くことができる形にしています。

  1. EventBridgeにてCodePipelineの成功イベントを検知
  2. EventBridgeルールをトリガーし、エージェント再起動用のRunCommandを実行
  3. RunCommand内のコマンドにて、エージェントの再起動及び再起動後のエージェントのステータス確認を実施
  4. エージェントが起動してない場合はRunCommandを失敗させる
  5. RunCommandの失敗イベントによって再起動失敗通知用のEventBridgeルールをトリガーし、SNS経由でエージェント再起動に失敗したことを通知

実装手順

前提

  • SNS、CodeDeploy、CodePipelineについては設定済みとします。
  • この手順ではNameタグで対象を指定する形としています。

Systems Managerのドキュメント作成

まずRunCommandで使用するドキュメントを作成します。
Systems Managerの画面より「ドキュメント」へ遷移し、「ドキュメントの作成」→「コマンドまたはセッション」を押下します。
以下の通り入力し、「ドキュメントの作成」を押下します。

  • 名前:任意
  • ドキュメントタイプ:Command
  • コンテンツ:以下参照
{
  "schemaVersion": "2.2",
  "description": "aws:runShellScript",
  "mainSteps": [
    {
      "action": "aws:runShellScript",
      "name": "runShellScript",
      "inputs": {
        "timeoutSeconds": "60",
        "runCommand": [
          "systemctl restart codedeploy-agent;if systemctl is-active --quiet codedeploy-agent;then exit 0;else exit 1;fi"
        ]
      }
    }
  ]
}

「systemctl restart codedeploy-agent;if systemctl is-active --quiet codedeploy-agent;then exit 0;else exit 1;fi」にて、CodeDeployエージェントを再起動した後、エージェントの起動状態を確認し、起動していればRunCommandが成功、起動していない場合はRunCommandが失敗するようにしています。

EventBridge用のIAMロールの作成

以下の許可ポリシー及び信頼ポリシーを設定したIAMロールを作成します。

  • 許可ポリシー
    ※<リージョン名>、<AWSアカウントID>、<EC2のNameタグ値>、<作成したドキュメント名>については実際の環境に合わせて置き換えてください。
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": "ssm:SendCommand",
            "Effect": "Allow",
            "Resource": [
                "arn:aws:ec2:<リージョン名>:<AWSアカウントID>:instance/*"
            ],
            "Condition": {
                "StringEquals": {
                    "ec2:ResourceTag/Name": [
                        "<EC2のNameタグ値>"
                    ]
                }
            }
        },
        {
            "Action": "ssm:SendCommand",
            "Effect": "Allow",
            "Resource": [
                "arn:aws:ssm:<リージョン名>:<AWSアカウントID>:document/<作成したドキュメント名>"
            ]
        }
    ]
}
  • 信頼ポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "events.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

RunCommand実行用EventBridgeルールの作成

EventBridgeの画面より「ルール」へ遷移し、「ルールを作成」を押下します。
以下の通り入力し、「次へ」を押下します。

  • 名前:任意
  • イベントバス:default
  • 選択したイベントバスでルールを有効にする:有効
  • ルールタイプ:イベントパターンを持つルール

「イベントパターンを構築」にて以下の通り入力し、「次へ」を押下します。

  • イベントソース:AWS イベントまたは EventBridge パートナーイベント
  • イベントパターン:「カスタムパターン (JSON エディタ)」を選択し、以下を入力 ※<パイプライン名>は対象のCodePipelineの名前に置き換えてください。
{
  "source": ["aws.codepipeline"],
  "detail-type": ["CodePipeline Pipeline Execution State Change"],
  "detail": {
    "pipeline": ["<パイプライン名>"],
    "state": ["SUCCEEDED"]
  }
}

「ターゲットを選択」にて以下の通り入力し、「次へ」を押下します。

  • ターゲットタイプ:AWS のサービス
  • ターゲットを選択:Systems Manager 実行コマンド
  • ドキュメント:作成したドキュメントを選択
  • ターゲットキー:tag:Name
  • ターゲット値:CodeDeployエージェントをインストールしているEC2のNameタグ値
  • 実行ロール:作成したEventBridge用のIAMロール

「タグを設定」にて「次へ」を押下します。

「レビューと作成」にて内容を確認し、問題なければ「ルールの作成」を押下します。

RunCommand失敗検知用EventBridgeルールの作成

再度「ルールを作成」を押下します。
以下の通り入力し、「次へ」を押下します。

  • 名前:任意
  • イベントバス:default
  • 選択したイベントバスでルールを有効にする:有効
  • ルールタイプ:イベントパターンを持つルール

「イベントパターンを構築」にて以下の通り入力し、「次へ」を押下します。

  • イベントソース:AWS イベントまたは EventBridge パートナーイベント
  • イベントパターン:「カスタムパターン (JSON エディタ)」を選択し、以下を入力
{
  "source": ["aws.ssm"],
  "detail-type": ["EC2 Command Status-change Notification"],
  "detail": {
    "document-name": ["arn:aws:ssm:<リージョン名>:<AWSアカウントID>:document/<作成したドキュメント名>"],
    "status": ["Failed"]
  }
}

「ターゲットを選択」にて以下の通り入力し、「次へ」を押下します。

  • ターゲットタイプ:AWS のサービス
  • ターゲットを選択:SNSトピック
  • ターゲットの場所:このアカウントのターゲット
  • トピック:対象のSNSトピックを選択
  • 許可:実行ロールを使用(推奨)
  • 実行ロール:この特定のリソースについて新しいロールを作成

「タグを設定」にて「次へ」を押下します。

「レビューと作成」にて内容を確認し、問題なければ「ルールの作成」を押下します。

動作確認

正常時の動作確認

設定が完了したので正常時の動作確認を行います。まずパイプライン実行前のCodeDeployエージェントの起動時間を確認します。

CodeCommit上のファイルを変更し、コミットすることでパイプラインを起動します。

パイプラインが正常に完了した後、CodeDeployエージェントの起動時間を再度確認すると意図した通りにCodeDeployエージェントが再起動したことを確認できました。

RunCommandの実行履歴を確認しても、コマンドが実行されていることが確認できます。

異常時の動作確認

続いてCodeDeployエージェントの再起動に失敗した場合の動作確認を行います。 意図的に再起動を失敗させるため、以下のコマンドを実行しCodeDeployエージェントの実行ファイルを別のディレクトリに移動させます。

sudo mv /opt/codedeploy-agent/bin/codedeploy-agent /home/ec2-user/

再度パイプラインを起動すると、デプロイは成功するもののRunCommandが失敗します。

SNSトピックで設定した通知先を確認すると、RunCommandの失敗が通知されています。

まとめ

今回はCodeDeployエージェントの自動再起動をEventBridgeとRunCommandを利用して実装してみました。 CodeDeployエージェントの再起動に限らず、AWS上でのイベントをトリガーにコマンドを実行したい場合も今回の手順で実装可能ですので参考にしていただければ幸いです。

北中 雄士(執筆記事の一覧)

2024年4月 中途入社