虎の穴開発室ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

本番で使えるFargate環境構築をCDKでやってみる

こんにちは、虎の穴ラボのはっとりです。

今回は以前に書いた 本番で使えるFargate環境構築 で紹介した内容を踏まえ、さらに一歩進んでAWS CDKを使用したFargate環境の構築方法をご紹介します。

toranoana-lab.hatenablog.com

インフラストラクチャをコード(IaC: Infrastructure as Code)として管理することの重要性が、近年ますます強調されています。従来の手動設定と比べ、IaCは再現性とスケーラビリティを確保する上で非常に有効です。コードとしてインフラを定義することで、環境間の一貫性を保ちつつ、チーム間でのコラボレーションが容易になります。

本記事では、AWS CDK(Cloud Development Kit)を使用して、実際のFargate環境を構築する方法を紹介します。AWS CDKを使えば、複雑な設定をシンプルなコードにまとめることができ、再利用可能なインフラストラクチャを簡単に管理できます。特に、Fargateのようなコンテナベースのアーキテクチャでは、コードによる管理が効果的です。

CDKについて

CDKを使うと、AWSのインフラをコードで定義し、作成することができます。

CDKで使える言語はいくつかありますが、今回はTypeScriptを選びました。

CDKはインフラをコードにできるだけでなく、IAMやセキュリティグループ等関連するリソースも作ってくれるところが便利です。

前提条件

本番で使えるFargate環境構築で構築した内容と今回の内容で違うものがあるので整理します。

  • VPCエンドポイントを追加しています。
    • 前回の記事作成時はFargateのバージョンが1.3でしたが、現在のバージョン1.4ではVPCエンドポイントが必要になります。
  • アプリケーション間の接続にはサービス検出の代わりにService Connectを使用します。
    • Service Connectは前回の記事作成時点ではまだありませんでした。
  • ElastiCache、CodePipelineは今回の構築対象から外しています。

全体構成

上記の構成をCDKで作成します。

ソースコード

完成したソースコード全体は こちら にあります。

github.com

ディレクトリ構成は以下のようになります。

├── apps
│   ├── api
│   └── page
├── bin
│   ├── build_and_push.sh
│   ├── deploy.sh
│   └── sample-fargate-app.ts
├── cdk.json
├── lib
│   ├── constructs
│   │   ├── elastic-container-service.ts
│   │   ├── load-balancer.ts
│   │   ├── relational-database-service.ts
│   │   ├── security-groups.ts
│   │   ├── vpc-endpoints.ts
│   │   └── vpc-subnets.ts
│   ├── sample-fargate-app-stack.ts
│   └── settings.ts
├── package.json
└── tsconfig.json

apps

アプリケーションを配置しています。※ChatGPTに作成してもらったものに少し手を入れた簡単なアプリケーションです。

apiアプリは、データベースに格納されたデータを返します。

apiアプリケーションの一部抜粋

app.get('/api/samples', async (req, res) => {
  try {
    const results = await query('SELECT * FROM samples');
    res.json(results);
  } catch (error) {
    console.error('Error executing query:', error);
    res.status(500).send('Internal Server Error');
  }
});

pageアプリは、apiから取得したデータをHTML形式で一覧表示します。

pageアプリケーションの一部抜粋

// Caution: XSS対策されていないので、実際のアプリケーションではこのまま使用しないでください
let tableRows = samples
  .map((sample) => {
    return `<tr>${Object.values(sample)
      .map((value) => `<td>${value}</td>`)
      .join('')}</tr>`;
  })
  .join('');

let html = `
  <html>
  <head>
    <title>Samples Table</title>
  </head>
  <body>
    <h1>Samples</h1>
    <table border="1">
      <tr>${Object.keys(samples[0])
        .map((key) => `<th>${key}</th>`)
        .join('')}</tr>
      ${tableRows}
    </table>
  </body>
  </html>
`;

res.send(html);

bin

CDKのエントリポイント(bin/sample-fargate-app.ts)と各種シェルスクリプトを配置しています。

lib

CDKのソースコードを配置しています。作成するリソースのカテゴリ毎にモジュールを分割し、lib/constructsディレクトリに配置しています。

環境変数を使って、作成するリソースの内容を変更したり、リソースの作成を制御しています。
※ 各環境変数の説明については .env.sample にあります

  • lib/constructs/load-balancer.ts (ロードバランサー作成) の一部抜粋
    • ACM_ARNがあるかないかでプロトコル(HTTPS/HTTP)を制御
const appListener = alb.addListener(
  ACM_ARN ? 'HttpsListener' : 'HttpListener',
  {
    port: ACM_ARN ? 443 : 80,
    protocol: ACM_ARN
      ? elbv2.ApplicationProtocol.HTTPS
      : elbv2.ApplicationProtocol.HTTP,
    certificates: ACM_ARN
      ? [elbv2.ListenerCertificate.fromArn(ACM_ARN)]
      : undefined,
    defaultAction: elbv2.ListenerAction.fixedResponse(404, {
      contentType: 'text/plain',
      messageBody: 'Not Found',
    }),
  },
);
  • lib/constructs/elastic-container-service.ts (ECSサービス作成)の一部抜粋
    • API_APP_CONTAINER_TAGがあればECSサービスを作成する
if (apiRepo && API_APP_CONTAINER_TAG) {
    // (タスク定義とECSサービス作成処理)
}

素のCloudFormationでこれをやろうとすると、JSONやYAMLで 条件関数 などを駆使する必要がありますが、CDKを使えば自分の慣れた言語で分岐処理が書けるのがよいところです。

CDK利用準備

先に AWS CLI をインストールし、 セットアップ を済ませる必要があります。

CDKを使ったリソース作成手順

まず、AWSアカウントで初めてCDKを使う場合は以下のコマンドを実行しておく必要があります。

npm run cdk -- bootstrap

ソースコードからリソースを作成するには以下のシェルスクリプトを実行します。

bin/deploy.sh

ECRリポジトリもCDKで作成するので、初回デプロイ時はアプリケーションのイメージが用意されていません。

そのため、ECSサービスは環境変数でイメージダイジェストの値が指定されるまで作成されないようにしました。

アプリケーションのイメージをビルドしてECRリポジトリへPushするには以下のシェルスクリプトを実行します。

bin/build_and_push.sh

ここが重要です。アプリケーションのイメージが用意できたのでもう一度以下のシェルスクリプトを実行します。

bin/deploy.sh

ECRリポジトリが作成されていた場合は、そのリポジトリからlatestタグのdigest値を取得し、環境変数に設定しCDKデプロイを実行することにより、ECSサービスが作成されます。

bin/deploy.shでdigest値を取得する処理

aws ecr describe-repositories \
  --repository-names $API_APP_REPOSITORY_NAME \
  > /dev/null 2>&1

if [ $? -eq 0 ]; then
  export API_APP_CONTAINER_TAG=$(aws ecr describe-images \
    --repository-name $API_APP_REPOSITORY_NAME \
    --query 'imageDetails[?imageTags[?contains(@, `latest`)]].imageDigest' \
    --output text)
fi

以上でFargate環境が出来上がり、アプリケーションが動きます。

アプリケーションの動作確認をする

デプロイが完了すると、以下のようにロードバランサーのURLが出力されます。

Outputs:
SampleFargateAppStack.LoadBalancerOutputApplicationUrl***** = *********************************************
S

アクセスすると以下のようにページが表示されます。

(ロードバランサーのURL)/api/samples にアクセスすると以下のようにデータベースの内容をJSONで返されます。

[{"id":1,"name":"とらのあな","value":"同人誌販売"},{"id":2,"name":"とらのあなラボ","value":"開発"}]

破棄する

破棄するには以下のコマンドを実行します。

npm run cdk -- destroy

CloudWatchロググループとECRリポジトリは削除されませんので、これらは手動で削除する必要があります。

まとめ

Fargate環境をCDKを使って構築してみました。

CDKを活用することで、インフラの構築と管理が格段に効率化されます。特にFargateのようなサーバーレスコンテナオーケストレーション環境では、手動の設定作業を大幅に削減できる点が魅力です。

今後のプロジェクトでも、ぜひCDKを取り入れて、迅速かつ安定したデプロイを実現してみてください。

この環境を使ったCI/CDパイプラインの構築方法についても別の機会に紹介したいと思います。