App RunnerでAppでないものをRunしてみました。
目次
概要
RDS・AuroraなどのVPC環境へ直接ローカルから接続する踏み台サーバをApp Runnerで作ってみました。
AWS CDKで構築しています。
元ネタ
2023年5月開催「App Runner Night !! (AWS Startup Community)」にて話した内容のご紹介になります。
登壇資料は以下になります。
アーキテクチャ
ざっくり
Systems Manager セッションマネージャーのリモートホストへのポートフォワード機能を使って、ローカルから直接プライベートサブネットのRDSなどへトンネリングする環境を、App Runnerで構築してみました。
これにより、プライベートサブネットにあるMySQLやPostgreSQLに、ローカルPCのターミナル等から直接アクセスできるようになります。
※以前ECS on Fargateで作ったものをApp Runnerで置き換えてみた、というモチベーションになります。
※Systems Manager セッションマネージャーのリモートホストへのポートフォワード機能については以下をご覧ください。
処理フロー
処理のフローとしては、以下の流れになります。
App Runner
- SSMエージェントを入れたコンテナをApp Runnerで立ち上げ、マネージドインスタンスとしてSystems Managerに登録
- SSMエージェントが発行したインスタンスIDをSSMパラメータストアに登録
クライアント(ローカルPC)
- SSMパラメータストアに登録したインスタンスIDを取得
- 取得したインスタンスIDに対し、SSMセッションマネージャーでリモートホストへのポートフォワードを張る
- AWS-StartPortForwardingSessionToRemoteHost
- リモートホスト(MySQL, PostgreSQLなど)のポートへ通信できるようになる
ヘルスチェックはnetcatで
App Runnerはhttpエンドポイントに対するヘルスチェックが必須になります。
しかしnginxを入れたり、Node.jsなどのランタイムを入れてexpressで受け付けたりせずに簡単にヘルスチェックできないかなあと思っていた矢先、netcat
コマンド(nc
)で簡易Webサーバが実装できるということを思い出し導入してみました。
具体的には、以下のようなシェルを用意し、Dockerfileで呼ぶようにするだけでHTTPヘルスチェックを受け付けられるようになります。
...(省略) function hello() { while true; do ( echo "HTTP/1.1 200 Ok" echo echo "OK" ) | nc -l -p 8080; done } hello &
また、App RunnerはHTTPヘルスチェックとTCPヘルスチェックの選択が可能です。上記はHTTPヘルスチェックですが、TCPヘルスチェックの場合以下のようにもう少しシンプルに受け付けることが可能です。
nc -l -p 8080 &
Dockerfileはこんな感じです。
...(省略) COPY ./scripts/deploy_scripts/run.sh /run.sh EXPOSE 8080 CMD ["bash", "/run.sh"]
GitHub(全コード記載)
CDK・Dockerfile・シェルなどを含む全コードはGitHubに上げてありますので、興味がある方はご覧下さい。
ピックアップ
本当は該当コード載せて解説したかったのですが、量が多すぎてURLリンクベースでご紹介します。
Dockerfile
https://github.com/go-to-k/bastion-tunnel-app-runner/blob/main/Dockerfile
SSMマネージドインスタンスへの登録シェル
SSMパラメータストアへのインスタンスIDの登録もしています。
また、先ほどご紹介したヘルスチェックも同じシェルに書いています。
https://github.com/go-to-k/bastion-tunnel-app-runner/blob/main/scripts/deploy_scripts/run.sh
SSMセッションマネージャー設定(SSMドキュメント更新)
SSMセッションマネージャーではタイムアウトなどの設定をSSMドキュメント(SSM-SessionManagerRunShell
)を通して変更できます。(本記事ではタイムアウトを300分にしている。)
その設定をsessionManagerRunShell.json
などのローカルファイルに書いておき、CDK実行時にAWS SDKで登録するようにしています。
sessionManagerRunShell.json
https://github.com/go-to-k/bastion-tunnel-app-runner/blob/main/lib/sessionManagerRunShell.json
SSMドキュメント登録用SDKコード
https://github.com/go-to-k/bastion-tunnel-app-runner/blob/main/lib/resource/ssm-run-shell.ts
具体的には、以下のような処理をしています。
- まだドキュメントが登録されていなければ登録(作成)
- されていれば、ローカルファイルと差分があれば更新
またSSMマネージドインスタンスとして登録するためには、Systems Managerのインスタンスティアの設定をアドバンスドインスタンスティアに変更する必要があるため、その処理もここで行なっています。
CDK実行時に呼び出し
(Top Level Await・・・)
CDK: SSMコンストラクト
SSMパラメータストアを作ったり、サービスロールを作ったり。
https://github.com/go-to-k/bastion-tunnel-app-runner/blob/main/lib/construct/ssm.ts
CDK: App Runnerコンストラクト
CDK + App Runnerのメインとなるコードです。
https://github.com/go-to-k/bastion-tunnel-app-runner/blob/main/lib/construct/app-runner.ts
本コードの特徴は以下となります。
- ①ECR連携でデプロイしている
- ②VPCコネクターを構築している
- ③CDK/CloudFormation未対応な「AutoScalingConfiguration」を、カスタムリソースLambdaを用いて作成している
①ECR連携・GitHub連携
GitHub連携(マネージドランタイム)によるCDKコードは以下をご覧下さい。
②VPCコネクター
このあたりになります。
https://github.com/go-to-k/bastion-tunnel-app-runner/blob/main/lib/construct/app-runner.ts#L162-L187
③AutoScalingConfigurationのカスタムリソースLambda
このあたりになります。
https://github.com/go-to-k/bastion-tunnel-app-runner/blob/main/lib/construct/app-runner.ts#L49-L107
カスタムリソースLambda自体のコードは以下になります。
https://github.com/go-to-k/bastion-tunnel-app-runner/blob/main/lib/auto-scaling-configuration.ts
※こちらのLambdaコードでは、App RunnerのサービスARNをCloudFormationスタックのOutputから取得するようにしているので、本CDKスタックではサービスARNを吐くCfnOutputを作成しています。
https://github.com/go-to-k/bastion-tunnel-app-runner/blob/main/lib/construct/app-runner.ts#L218-L221
そして、カスタムリソースLambdaで作成したAutoScalingConfigurationのARNを、App RunnerのL2コンストラクトをエスケープハッチ(escape hatch)したL1コンストラクトに対して指定します。
https://github.com/go-to-k/bastion-tunnel-app-runner/blob/main/lib/construct/app-runner.ts#L210-L211
ここでカスタムリソースに関してですが、CDKにはAWS APIを呼ぶだけの簡単なモノであればLambdaを自前で作らずとも構築できるモジュールがあります。
今回「AutoScalingConfiguration」というものをカスタムリソースで作成するのですが、作成(onCreate)だけであれば上記モジュールでcreate APIを呼んで作成することが可能なのですが、削除に関しては「それ自身のARN」が必要になります。
ARN自体はリソース作成時にランダム文字列で生成されるものなので、カスタムリソース定義時点ではまだどんな文字列になるかわからず、削除定義(onDelete)にてARNを指定することができません。
作成だけ定義するのもあれだったので、Lambdaを使って、削除時は「動的にListしてARNを取得して指定する」ことで作成だけでなく削除もできるようにしました。(更新も可能にしてあります。)
※AutoScalingConfiguration更新時のフローも厄介です。。。
- AutoScalingConfigurationには更新APIがない
- AutoScalingConfigurationを削除+作成が必要
- App Runnerサービスに紐づいているAutoScalingConfigurationは削除できない
- 先に別のAutoScalingConfigurationをアタッチしてから削除する必要がある
- 新たな(更新後の)AutoScalingConfigurationのアタッチは、カスタムリソース内でなくApp RunnerのService L2コンストラクトで行う
- そのためカスタムリソース内では、一度デフォルトのAutoScalingConfigurationをアタッチすることになる
- つまり、更新だけで以下のフローになる
- デフォルトのAutoScalingConfigurationをアタッチ
- 旧AutoScalingConfigurationを削除する
- 新規で(更新後として使いたい)AutoScalingConfigurationを作成する
- そのARNを出力して終了
- CDKレイヤーで、App RunnerのService L2コンストラクトでそのARNを指定してアタッチする
補足
踏み台(トンネル)の使い方
以下READMEをご覧下さい。
コマンド登録により普段使いが楽になります。ポート変更などもオプションにて可能です。接続ログも出力しています。
Blue/Greenデプロイによる旧コンテナ削除のラグ
App Runnerではデプロイ成功後、Blue/Greenデプロイの挙動により、旧コンテナが5分ほど削除されずに残ります。
デプロイ時のSIGTERMによるGraceful Shutdownは、デプロイ成功から5分後に発火するので、ご注意下さい。
https://github.com/go-to-k/bastion-tunnel-app-runner/tree/main#%E6%B3%A8%E6%84%8F
最後に
App RunnerでAppでないものをRunしてみました。また、それをCDKで構築してみました。
ユースケースや実現方法にCDK+カスタムリソースなどニッチな話満載でしたが、中々面白いのではないかなと思います。