AWS CDK + App Runner + SSMでRDSへの踏み台を作る

App RunnerでAppでないものをRunしてみました。


目次

目次


概要

RDS・AuroraなどのVPC環境へ直接ローカルから接続する踏み台サーバApp Runnerで作ってみました。

AWS CDKで構築しています。


元ネタ

2023年5月開催「App Runner Night !! (AWS Startup Community)」にて話した内容のご紹介になります。

登壇資料は以下になります。

speakerdeck.com


アーキテクチャ

ざっくり

Systems Manager セッションマネージャーリモートホストへのポートフォワード機能を使って、ローカルから直接プライベートサブネットのRDSなどへトンネリングする環境を、App Runnerで構築してみました。

これにより、プライベートサブネットにあるMySQLPostgreSQLに、ローカルPCのターミナル等から直接アクセスできるようになります。


※以前ECS on Fargateで作ったものをApp Runnerで置き換えてみた、というモチベーションになります。

go-to-k.hatenablog.com


※Systems Manager セッションマネージャーのリモートホストへのポートフォワード機能については以下をご覧ください。

go-to-k.hatenablog.com


処理フロー

処理のフローとしては、以下の流れになります。

App Runner

  • SSMエージェントを入れたコンテナをApp Runnerで立ち上げ、マネージドインスタンスとしてSystems Managerに登録
  • SSMエージェントが発行したインスタンスIDをSSMパラメータストアに登録

クライアント(ローカルPC)


ヘルスチェックは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に上げてありますので、興味がある方はご覧下さい。

github.com


ピックアップ

本当は該当コード載せて解説したかったのですが、量が多すぎて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


具体的には、以下のような処理をしています。

  • まだドキュメントが登録されていなければ登録(作成)
  • されていれば、ローカルファイルと差分があれば更新

https://github.com/go-to-k/bastion-tunnel-app-runner/blob/13c539234a8e3ebb2d0c125dbb6568e2ce3af888/lib/resource/ssm-run-shell.ts#L22-L49


またSSMマネージドインスタンスとして登録するためには、Systems Managerのインスタンスティアの設定をアドバンスドインスタンスティアに変更する必要があるため、その処理もここで行なっています。

https://github.com/go-to-k/bastion-tunnel-app-runner/blob/13c539234a8e3ebb2d0c125dbb6568e2ce3af888/lib/resource/ssm-run-shell.ts#L12-L20


CDK実行時に呼び出し

(Top Level Await・・・)

https://github.com/go-to-k/bastion-tunnel-app-runner/blob/13c539234a8e3ebb2d0c125dbb6568e2ce3af888/bin/bastion-tunnel-app-runner.ts#L12-L14


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コードは以下をご覧下さい。

go-to-k.hatenablog.com


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を自前で作らずとも構築できるモジュールがあります。

docs.aws.amazon.com


今回「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をアタッチすることになる
  • つまり、更新だけで以下のフローになる
    1. デフォルトのAutoScalingConfigurationをアタッチ
    2. 旧AutoScalingConfigurationを削除する
    3. 新規で(更新後として使いたい)AutoScalingConfigurationを作成する
    4. そのARNを出力して終了
    5. CDKレイヤーで、App RunnerのService L2コンストラクトでそのARNを指定してアタッチする


補足

踏み台(トンネル)の使い方

以下READMEをご覧下さい。

コマンド登録により普段使いが楽になります。ポート変更などもオプションにて可能です。接続ログも出力しています。

https://github.com/go-to-k/bastion-tunnel-app-runner/tree/main#tunnel%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9


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+カスタムリソースなどニッチな話満載でしたが、中々面白いのではないかなと思います。