とある事情で、AWS Fargate上で稼働しているSpring Bootコンテナのスレッドダンプを取得する必要があった。いくつか詰まる所があったのでメモしておく。
- [Fargate上のコンテナでコマンドを実行したい]
- [SSMエージェントのセットアップ]
- [Spring Bootアプリのスレッドダンプを取得する]
- [Run Commandでシェルスクリプトを(再)実行する]
- [まとめ]
[Fargate上のコンテナでコマンドを実行したい]
こちらのブログを参考に、Systems Managerを利用してFargate上のコンテナでコマンドを実行することに決めた。
同じSSM Agentで動かせるため、2021年3月に登場したばかりのAmazon ECS Execを利用することも検討した。
しかし今回は、対象のコンテナが10個以上あり、すべてのコンテナにそれぞれ手動で接続するのが面倒だったため、残念ながら今回はECS Execの採用を見送った。
[SSMエージェントのセットアップ]
先ほどのブログを参考にすすめる。
Dockerfileに以下の行を追加する。
#ssm-agentのインストール RUN wget https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/debian_amd64/amazon-ssm-agent.deb && \ dpkg -i amazon-ssm-agent.deb && \ rm -f amazon-ssm-agent.deb && \ cp /etc/amazon/ssm/seelog.xml.template /etc/amazon/ssm/seelog.xml #AWS CLIのインストール RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \ unzip awscliv2.zip && \ ./aws/install && \ rm -rf ./aws && \ rm -f ./awscliv2.zip
つぎに、コンテナ起動時に実行するentrypoint.sh
に以下の修正を入れる。
if [ "$SSM_ACTIVATE" = "true" ]; then # アクティベーションの作成 ACTIVATE_PARAMETERS=$(aws ssm create-activation \ --default-instance-name "${SSM_INSTANCE_NAME}" \ --description "${SSM_INSTANCE_NAME}" \ --iam-role "service-role/AmazonEC2RunCommandRoleForManagedInstances" \ --region "ap-northeast-1") export ACTIVATE_CODE=$(echo $ACTIVATE_PARAMETERS | jq -r .ActivationCode) export ACTIVATE_ID=$(echo $ACTIVATE_PARAMETERS | jq -r .ActivationId) # コンテナのマネージドインスタンスへの登録 amazon-ssm-agent -register -code "${ACTIVATE_CODE}" -id "${ACTIVATE_ID}" -region "ap-northeast-1" -y # ssm-userからrootユーザーにスイッチするための権限付与 echo "ssm-user ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/ssm-agent-users # SSMエージェントの登録 nohup amazon-ssm-agent > /dev/null & fi
SSMエージェントのセットアップが上手くできたか確認するために、AWS Systems Manager Session Managerで接続しようとしたところ、以下のエラーが出た。
日本語がわかりにくいので、英語で確認すると以下のように表示された。SSMエージェントのバージョンは正しいが、インスタンスの設定が足りないらしい。
エラーメッセージ末尾のLearn moreのリンクは以下のページになっていた。
Troubleshooting Session Manager - AWS Systems Manager
読み進めていくと、タスクのIAMロールに権限が足りていないようだった。
こちらのドキュメントを参考に、以下のIAMポリシーを追加した。Systems Managerの設定を確認したところ、セッションデータの暗号化が無効になっていたため、ドキュメントで指定されていたkms:Decrypt
の権限は不要と判断して削除した。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ssm:UpdateInstanceInformation", "ssmmessages:CreateControlChannel", "ssmmessages:CreateDataChannel", "ssmmessages:OpenControlChannel", "ssmmessages:OpenDataChannel" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "s3:GetEncryptionConfiguration" ], "Resource": "*" } ] }
これでタスクにSession Managerを利用して接続し、コマンドを実行できるようになった。
[Spring Bootアプリのスレッドダンプを取得する]
スレッドダンプの取得方法は下記の記事を参考にした。
JDKに付属しているjstackコマンドを利用するとスレッドダンプを取得できるらしい。
Session Managerで接続して確認したところ、現在利用している環境のUbuntuベースのコンテナ内にはJREしかインストールされていなかった。そのため、以下のコマンドで別途JDKをインストールする必要がある。Spring BootアプリがJava8で動いていたので、JDKのバージョンはそれに合わせてある。
apt-get install -y openjdk-8-jdk
これでフルパスを指定しなくとも、jstackコマンドを利用できるようになった。
最終的に、AWS Systems Manager Run Commandで全コンテナから一括でスレッドダンプを取得することを考えると、JavaのプロセスIDをpsコマンドの結果から取り出す必要がある。
今回はPID=$(ps aux | grep tomcat | grep java | cut -d ' ' -f 9)
と無理やりcutコマンドでプロセスIDを取得した。
正しくはawkコマンドを使い以下のようにすると良さそう。
PID=$(ps aux | grep tomcat | grep java | awk '{ print $2 }')
あとはjstackコマンドを利用してスレッドダンプをファイルに書き出すだけ。
ファイル名から、どのコンテナのいつ時点のスレッドダンプか分かるようにしておきたかったので、コンテナ内で利用できたアプリ名の環境変数APP_NAMEをファイル名に組み込んでいる。
FILENAME=/tmp/ThreadDump.$APP_NAME.$(date "+%Y%m%d-%H%M%S").txt jstack $PID > $FILENAME
このままだと、各コンテナにファイルを集めにいかなければいけない。それは面倒なので、S3バケットを事前に作成しておき、AWS CLIを利用してS3にファイルをアップロードする。
aws s3 cp $FILENAME s3://my_threaddump_bucket
以上のコマンドをまとめると、こうなる。
# 事前準備:jstackコマンドをインストール apt-get install -y openjdk-8-jdk # スレッドダンプを取得 PID=$(ps aux | grep tomcat | grep java | awk '{ print $2 }') FILENAME=/tmp/ThreadDump.$APP_NAME.$(date "+%Y%m%d-%H%M%S").txt jstack $PID > $FILENAME # S3にアップロード aws s3 cp $FILENAME s3://my_threaddump_bucket
[Run Commandでシェルスクリプトを(再)実行する]
あとは、Systems Managerの画面で、「Run Command」メニューを選択し、AWS-RunShellScript
ドキュメントを選択して、先ほど作成したシェルスクリプトをコピペする。
「Targets」のところで「Choose instances manually」をクリックして、対象のコンテナをぽちぽち選択したら、「Run」ボタンを押して実行するだけ。
それぞれのコンテナでシェルスクリプトが実行されて、指定したS3バケットにファイルがアップロードされてくるのを待っていると、最も遅いコンテナでも10秒かからずにスレッドダンプを取得できた。
このあと、実際のスレッドダンプを確認してもらったところ、いくつかのコンテナにリクエストを送ってからスレッドダンプを再取得するようにとの追加の依頼があった。
もしSession Managerとかで各コンテナに接続してスレッドダンプを取得する方法を採用していたら、ものすごく面倒なことになるところだ。
今回はSystems Manager Run Commandを利用しているので、以前実行したコマンドを同じコンテナに対して再実行できた。おかげでかなりの時間を節約することができた。
[まとめ]
- Fargateで動くDockerコンテナにSSM AgentをセットアップしてSession Manager / Run Command / ECS Execで接続できるようにした
- 複数コンテナで同じコマンドを実行するためにRun Commandを採用した
- Run Commandは同じコンテナ群に対して同一コマンドの再実行をサポートしていたのが便利だった