Amazon Web Services ブログ
CDK Watch を活用した開発速度の向上
本稿は、2021 年 12 月 2 日に AWS Developer Tools Blog で公開された “Increasing development speed with CDK Watch” を翻訳したものです。
AWS Cloud Development Kit (CDK) の CLI に導入されている操作モード cdk watch
、および cdk deploy
のフラグ --hotswap
と --no-rollback
を紹介します。 cdk watch
はコードとアセットの変更を監視し、ファイル変更が検出されるたびに最適な形式のデプロイを自動的に実行することで、開発を効率化できます。これにより、CDK アプリケーションに変更を加えるたびに cdk deploy
を実行する必要がなくなります。cdk watch
では --hotswap
フラグが使用できる変更の場合は使用され、AWS CloudFormation でのフルデプロイを行わずにインプレースで更新されます。AWS Lambda ハンドラーコード、Amazon ECS コンテナイメージ、AWS Step Functions ステートマシンなどの CDK アセットでは、CDK CLI が各 AWS サービスの API を使用して直接更新します。それ以外のアセットでは、CloudFormation のフルデプロイが実行されます。また、--no-rollback
フラグを使用することで CloudFormation の更新失敗時にロールバックが行われないようになるため、デプロイ失敗時に再実行するまでの時間を短縮できます。
以下の手順を実行することで、cdk watch
および --hotswap
、--no-rollback
フラグの動きを確認できます。この記事では TypeScript で CDK を使用しますが、watch
は CDK でサポートされているすべての言語で機能します。最初に空の CDK アプリケーションを作成し、TypeScript と Express を使用したシンプルなコンテナアプリケーションを追加します。次に、アプリケーションをデプロイするために必要なインフラストラクチャを作成する CDK スタックを記述します。最後に、cdk watch
を使用してアプリケーションコードに繰り返し変更を加えていきます。
前提条件
- AWS アカウントを持っていること
- ローカルに CDK がインストールされていること
セットアップ
CDK CLI V2 がインストールされていることを確認してください (cdk watch
は V1 でも動作しますが、この記事ではすべて V2 を使用しています)。まだインストールしていない場合は、AWS CDK 開発者ガイドの手順を参照してインストールしてください。インストールが正しく行われたことを確認するには、ターミナルで cdk --version
コマンドを実行します。次のように出力されれば問題ありません。
※ 本ブログ記事では、CDK CLI バージョン 2.141.0 で動作確認しています。他のバージョンでは挙動が異なる可能性があるので、ご注意ください。
最初に、ターミナルで次のコマンドを実行し、TypeScript の CDK アプリケーションを作成します。
アプリケーションコード
cdk-watch
ディレクトリ上で、Docker イメージをビルドするために必要なディレクトリとファイルを作成します。
次に、アプリケーションの依存関係を宣言する package.json
を作成する必要があります。記載する必要がある依存関係は Express のみです。TypeScript はアプリケーションデプロイ前に JavaScript にコンパイルされるため、依存関係として宣言する必要はありません。docker-app/package.json
のファイルを作成し、次の内容を追加してください。
{
"name": "simple-webpage",
"version": "1.0.0",
"description": "Demo web app running on Amazon ECS",
"license": "MIT-0",
"dependencies": {
"express": "^4.17.1"
},
"devDependencies": {
"@types/express": "^4.17.13"
}
}
次に、ウェブページとして表示させるための HTML ファイルを作成する必要があります。docker-app/index.html
のファイルを作成し、次の内容を追加してください。
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Simple Webpage </title>
</head>
<body>
<div align="center">
<h2>Hello World</h2>
<hr width="25%">
</div>
</body>
</html>
作成した HTML ファイルが、サイトにアクセスした際に表示されるようにするための Express コードを作成します。 docker-app/webpage.ts
のファイルを作成し、次のコードを追加してください。
import * as express from 'express';
const app = express();
app.get("/", (req, res) => {
res.sendFile(__dirname + "/index.html");
});
app.listen(80, function () {
console.log("server started on port 80");
});
最後に、アプリケーションを起動する Dockerfile
を作成します。docker-app/Dockerfile
のファイルを作成し、次のコードを追加してください。
インフラストラクチャコード
次に、Web ページをホストするインフラストラクチャを定義する CDK スタックを作成します。aws_ecs_patterns
モジュールの ApplicationLoadBalancedFargateService
コンストラクトを使用することで、スタックを大幅に単純化できます。lib/cdk-watch-stack.ts
を次の例のように修正してください。
import {
Stack,
StackProps,
aws_ec2 as ec2,
aws_ecs as ecs,
aws_ecs_patterns as ecs_patterns,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';
export class CdkWatchStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const vpc = new ec2.Vpc(this, 'Vpc', {
maxAzs: 2,
natGateways: 1,
});
new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'EcsService', {
vpc,
taskImageOptions: {
image: ecs.ContainerImage.fromAsset('docker-app'),
containerPort: 80,
},
});
}
}
cdk.json
の build キーで指定されたコマンドは、cdk watch
実行時を含むすべてのデプロイ時に、synthesis ステップの前に実行されます。今回作成した TypeScript アプリケーションは JavaScript にコンパイルする必要があるので、cdk.json
の "app"
キーと同じ階層に以下のコードを追加してください。
これにより、完全にサーバーレスな Docker アプリケーションが作成されます。これらの変更を行ったら、次のコマンドを実行してください。
デプロイが完了すると、以下のように出力されるはずです。
Bash
✅ CdkWatchStack
Outputs:
CdkWatchStack.EcsServiceLoadBalancerDNS6D595ACE = CdkWa-EcsSe-18QPSCKV5G8XP-xxxxxxxxxx.us-east-2.elb.amazonaws.com
CdkWatchStack.EcsServiceServiceURLE56F060F = http://CdkWa-EcsSe-18QPSCKV5G8XP-xxxxxxxxxx.us-east-2.elb.amazonaws.com
Stack ARN:
arn:aws:cloudformation:us-east-2:xxxxxxxxxxxx:stack/CdkWatchStack/1b15db20-428a-11ec-b96f-xxxxxxxxxxxx
Outputs セクションの 2 行目に記載されているリンクを開いてください。「Hello World」と書かれたページが表示されるはずです。
アプリケーションコードの変更
アプリケーションをデプロイしたら、cdk watch
を使用して変更を加えていくことができます。ターミナルで cdk watch
を実行すると、以下のような出力が表示されます。
'watch' is observing directory '' for changes
'watch' is observing the file 'cdk.context.json' for changes
'watch' is observing directory 'bin' for changes
'watch' is observing directory 'docker-app' for changes
'watch' is observing directory 'lib' for changes
'watch' is observing the file 'bin/cdk-watch.ts' for changes
'watch' is observing the file 'lib/cdk-watch-stack.ts' for changes
'watch' is observing the file 'docker-app/Dockerfile' for changes
'watch' is observing the file 'docker-app/index.html' for changes
'watch' is observing the file 'docker-app/package.json' for changes
'watch' is observing the file 'docker-app/webpage.ts' for changes
アプリケーションコードを変更する場合、cdk watch
を使用することでデプロイを高速化できます。以下の変更を index.html
に加え、動作を確認してみましょう。
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title> Simple Webpage </title>
</head>
<body>
<div align="center">
<h2>Hello World</h2>
<hr width="25%">
<p>A paragraph</p>
</div>
</body>
</html>
ターミナルを見ると、cdk watch
が変更を検知してデプロイする様子が確認できます。
この警告メッセージは、今回の変更がホットスワップでデプロイされることを意味しています。つまり、このデプロイは更新対象のリソースが提供しているサービス API を直接実行することで行われ、CloudFormation がバイパスされます。このため、CloudFormation テンプレートとデプロイされたアプリケーションコードの間にドリフトが発生します。このようなドリフト発生を回避するため、本番環境では絶対にホットスワップを使用してはいけません。ホットスワップにより高速なデプロイができますが、CloudFormation のように安全にデプロイできる機能ではないため、ホットスワップは高速なコーディング・コンパイル・テストのループを回す必要がある開発環境での使用に最適です。watch
実行中にホットスワップを無効にしたい場合は、watch
実行時に --no-hotswap
フラグを指定してください。CloudFormation とアプリケーション間のドリフトを完全に取り除く必要がある場合は、cdk deploy
を実行することで CloudFormation フルデプロイが行われます。cdk watch
を実行せずにホットスワップデプロイを行いたい場合は、cdk deploy --hotswap
を実行してください。
デプロイが完了したら、ページを更新してください。Hello World のページが以下のように更新されているはずです。
インフラストラクチャコードの変更
すべてのリソース変更がホットスワップできるわけではありません。Lambda 関数のコード変更、ECS サービスのコンテナ定義変更、Step Functions のステーマシン定義変更などがホットスワップに対応しています。他のリソースに変更が入った場合は、ホットスワップデプロイではなく CloudFormation フルデプロイを行う必要があります。この動作を確認するために、lib/cdk-watch-stack.ts
のコードを以下のように変更してください。
import {
Stack,
StackProps,
aws_ec2 as ec2,
aws_ecs as ecs,
aws_ecs_patterns as ecs_patterns,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';
export class CdkWatchStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// Fargate does not work with default VPCs
const vpc = new ec2.Vpc(this, 'Vpc', {
maxAzs: 2, // ALB requires 2 AZs
natGateways: 2, //changing this property does not trigger a hotswap, and a full deployment occurs instead
});
new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'EcsService', {
vpc,
taskImageOptions: {
image: ecs.ContainerImage.fromAsset('docker-app'),
containerPort: 80,
},
});
}
}
ターミナルウィンドウを確認してください。アセットの公開が完了すると、次のメッセージが出力されます。