はじめに
- 複数の EC2 インスタンスを管理している
- それらに同一のコマンドを実行したい
- コマンドの実行結果をまとめて参照したい
このような要件がある運用において、コストをなるべく抑えつつ簡素な仕組みで動くアーキテクチャを考えてみました。
当記事ではアーキテクチャの留意点やソースコードを踏まえつつまとめます。
前提
管理対象の EC2 インスタンスには SSM Agent がインストールされており、インスタンスプロファイルにはログ出力用の S3 バケットへのアクセス権限が適切に割り当てられているものとします。
サンプルコード
下記リポジトリをご参照ください。以降は当リポジトリのコードに基づいて解説をしていきます。
アーキテクチャ
AWS Step Functions にてワークフローが構築されており、マルチステップで EC2 インスタンスへのコマンド実行 および その実行結果の取得、CSV 形式のサマリファイルの作成を行います。
EC2 インスタンス上で実行されたコマンドの実行結果は中央ログバケットに保管され、Step Functions にて参照します。
なお、当アーキテクチャは AWS SAM によって管理、展開するものとなっています。
- SAM のテンプレート template.yaml
- Step Functions 自体のワークフロー定義 statemachine/app.asl.json
Step Functions のワークフロー
Step Functions での具体的なワークフローは下記の通りです。
構成要素
Step Functions は 3つの Lambda で構成されてます。それぞれ以下の役割を持ちます。
-
Send Run Command
前 State から渡されたインスタンス ID のインスタンスに対して
Systems Manager Run Command を用いて、コマンドを実行します。
(今回は便宜上、Pass State
を使ってインスタンス ID が渡るようにしています。) -
Poll Run Command
Send Run Command
で実行した Run Command の実行完了を確認します。
実行中のインスタンスが 1つでも存在する場合、その時点で実行中と判定し待機します。 -
Make Run Command Summary
実行した Run Command の実行ログを取得し、1つの CSV ファイルを作成します。
このアーキテクチャのねらい
非同期の待機処理でコスト最適化
Poll Run Command
にて Run Command の実行完了を確認しますが、
実行中のインスタンスが 1つでも存在する場合、すぐさま Lambda の処理を終了し Wait State
での待機に遷移します。
これにより、長時間のコマンド実行であってもコストを肥大化させることなくポーリングを実現します。
(サンプルコードでは動作確認のため、Wait State
での待機秒数を極端に短くしています)
S3 バケットへのログ出力
Run Command のログ出力は CloudWatch Logs および S3 バケットへのファイル出力をサポートしますが、
後続の処理の取り回しやすさを保つため、以下のような構造化されたフォルダ構成でファイルを出力するようにします。
# Run Command の実行ログ
s3://${LOG_BUCKET}/${step_functions_execution_id}/logs/${run_command_id}/*
# CSV ファイル
s3://${LOG_BUCKET}/${step_functions_execution_id}/_output/summary.csv
そのために各 State には、Step Functions の実行 ID を参照できるよう定義をしてます。(サンプルコード上では こちら)
実際に動かしてみる
今回は Wait State
の動作確認のため、以下のコマンドを実行します。
echo 'started'
echo 'sleep 20 seconds'
sleep 20
echo 'finished'
実行を開始すると順に State が処理され、待機が発生します。
Run Command の実行完了を確認すると、次の State に遷移します。
完了すると CSV ファイルが作成されていることが確認できました。
あとがき
当記事では触れませんでしたが、1つ目の Send Run Command
Lambda で他の AWS アカウントに AssumeRole をした上で、Run Command を実行するように Lambda を実装をすれば、クロスアカウントにも対応可能です。(その際は S3 のバケットポリシーやインスタンスプロファイルを整備しなければいけませんが)
また、一度の Run Command で指定できるインスタンスの数は最大 50 なので
今後の拡張として、Map State
を使用して並列化を図りつつ、より多くのインスタンス群に対しても高速に処理ができるようにしていきたいと思います。
今回は拡張性を重視して Lambda を採用しましたが、Step Functions では SSM Send Command の直接呼び出しもサポートしているため、こちらをうまく使えば 1つ目の Lambda を不要にできるかもしれません。