CDKのローカル開発環境を整える
はじめに
AWS CDKを使って開発を行ってきましたが、ローカル環境の構築ではcdk-localがあるのを知りつつも既存プロジェクトが巨大だったので導入を諦めていました。
ただ、新しいプロジェクトが始まりそうで、新プロジェクトではcdk-localを使用して本環境とローカル環境の構築を一本化したいなぁと考えてます。今回はCDKのローカル開発環境を整えるための手順をまとめて、実際にcdk-localを使ってローカル環境へのデプロイを試してみたいと思います。
まだ検討・学習の段階なので今回はLocalStackは無料の範囲で確認していきます。
開発環境と言語
- 言語: TypeScript
- IDE: Visual Studio Code
CDKおよびLocalStack
CDKとLocalStackの説明は公式ドキュメントや他の方の解説の方が丁寧と思うので割愛します。。。
LocalStack
インストール
LocalStackのインストール方法はOSによって異なるので、公式サイトをご確認ください。
LocalStackの開始
localstack start -d
LocalStackの終了
localstack stop
使用可能なサービス
以下のコマンドで確認ができます。
localstack status services
CDKのインストール
今回はグローバルにインストールします。
npm install -g aws-cdk-local aws-cdk
プロジェクトの初期化
プロジェクトの初期化は以下のコマンドになります。cdk
コマンドでも作成できますが、別途Local用のプロファイルを作成しなければいけないようなので、cdklocal
で初期化します。
言語はtypescriptを採用します。
cdklocal init --language typescript
Visual Studio Codeの設定
コードスニペットの設定
Stackファイルのテンプレート
cdk init
を実行した際にのサンプルコードとして以下のコードが出力されます。
import * as cdk from 'aws-cdk-lib'
import { Construct } from 'constructs'
export class CdkStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props)
// The code that defines your stack goes here
}
}
Stackを新規で生成する際にこちらをコピペしていくことになると思うので、スニペットに登録しておきます。共同開発を想定して、プロジェクトにスニペットを登録しておきます。
スニペットの登録は、コマンドパレットから Snippets: Configure Snipetts
-> New snippets file for ${projectname}
-> ファイル名入力でts-cdk
と入力します。テンプレートが出力されるので、以下の内容を記載します。
{
"Stack file template": {
"scope": "javascript,typescript",
"prefix": "stack",
"body": [
"import * as cdk from 'aws-cdk-lib'",
"import { Construct } from 'constructs'",
"",
"// FIXME: Change the stack name",
"export class MyCdkStack extends cdk.Stack {",
" constructor(scope: Construct, id: string, props?: cdk.StackProps) {",
" super(scope, id, props)",
"",
" // The code that defines your stack goes here",
" }",
"}",
""
],
"description": "Stack file template for AWS CDK"
}
}
Constructのテンプレート
Constructは適切に分割していきたいので、Constructのテンプレートも用意しておきます。こちらも同様にスニペットに登録します。
{
"Construct template": {
"scope": "javascript,typescript",
"prefix": "construct",
"body": [
"// import { Construct } from 'constructs'",
"",
"// FIXME: Change the construct name",
"export class MyConstruct extends Construct {",
" constructor(scope: Construct, id: string) {",
" super(scope, id);",
" }",
"}"
],
"description": "Construct template for AWS CDK"
}
}
実装してみる
実際のAWSでのSI業務ではVPCを構築することが多いので、VPCを構築してみます。
import * as cdk from 'aws-cdk-lib'
import { Construct } from 'constructs'
export class VpcStack extends cdk.Stack {
public readonly vpc: cdk.aws_ec2.Vpc
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props)
this.vpc = new cdk.aws_ec2.Vpc(this, 'MyVpc', {
maxAzs: 3, // Default is all AZs in the region
natGateways: 1, // Number of NAT gateways to create
subnetConfiguration: [
{
cidrMask: 24,
name: 'public',
subnetType: cdk.aws_ec2.SubnetType.PUBLIC
},
{
cidrMask: 24,
name: 'private',
subnetType: cdk.aws_ec2.SubnetType.PRIVATE_WITH_EGRESS
}
]
})
}
}
bin/cdk.ts
には以下のように記載します。
import * as cdk from 'aws-cdk-lib'
import { VpcStack } from '../lib/vpc-stack'
const app = new cdk.App()
new VpcStack(app, 'VpcStack')
ローカルへのデプロイ
Bootstrap
初回デプロイ実行前に以下のコマンドでbootstrapを実行します。
cdklocal bootstrap
デバッグの設定
VSCode設定の追加
デバッグを行うために、launch.json
を以下のように設定します。
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug CDKLocal Synth",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/node_modules/aws-cdk-local/bin/cdklocal",
"cwd": "${workspaceFolder}",
"args": ["synth"],
"outFiles": ["${workspaceFolder}/**/*.js"]
},
{
"type": "node",
"request": "launch",
"name": "Debug CDK Synth",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/node_modules/aws-cdk/bin/cdk",
"cwd": "${workspaceFolder}",
"args": ["synth"],
"outFiles": ["${workspaceFolder}/**/*.js"]
}
]
}
Debug CDKLocal Synth
はLocalStack向けのデバッグ設定、Debug CDK Synth
は本番環境向けのデバッグ設定です。
プロジェクトへcdklocalの追加
デフォルトではcdklocal
はプロジェクトに含まれていないため、依存関係に追加しておきます。
npm install --save-dev aws-cdk-local
これでデバッグが可能になります。
デプロイ
デバッグしてみて問題なさそうだったので実際にデプロイしてみましょう。
cdklocal deploy
エラーになりました。
❌ VpcStack failed: _ToolkitError: The stack named VpcStack failed to deploy: CREATE_FAILED (An error occurred (InvalidAllocationID.NotFound) when calling the CreateNatGateway operation: Allocation ID '['unknown']' not found.): An error occurred (InvalidAllocationID.NotFound) when calling the CreateNatGateway operation: Allocation ID '['unknown']' not found.
GitHub Copilotに聞いてみると、以下のように指摘されました。
このエラーの原因は、cdklocal(LocalStack)でNAT Gatewayを作成しようとした際に、Elastic IP(EIP)の割り当てIDが見つからないためです。
LocalStackはNAT GatewayやEIPの完全なエミュレーションをサポートしていないため、natGatewaysを1以上に設定するとこのエラーが発生します。
LocalStackではNAT Gatewayのエミュレーションができなそうですが、実際のAWS環境ではNAT Gatewayを使用したいです。そのため、cdklocalによる実行時のみNAT Gatewayを使用しないように、natGateways: 0
と設定します。
LocalStackを使うときはAWS_ENDPOINT_URLやLOCALSTACK_HOSTNAMEなどの環境変数がセットされているようなので、以下のソースを追加します。(GitHub Copilotによる提案で、裏付けは取ってません)
export function isLocalStack(): boolean {
// Check if the environment variable LOCALSTACK_HOST is set
return (
process.env.AWS_ENDPOINT_URL !== undefined ||
process.env.LOCALSTACK_HOSTNAME !== undefined ||
process.env.LOCALSTACK_HOST !== undefined
)
}
function buildVpcConfig(): cdk.aws_ec2.VpcProps {
if (isLocalStack()) {
return {
maxAzs: 1, // Use a single AZ for LocalStack
natGateways: 0, // No NAT gateways in LocalStack
subnetConfiguration: [
{
cidrMask: 24,
name: 'public',
subnetType: cdk.aws_ec2.SubnetType.PUBLIC
}
]
}
}
return {
maxAzs: 3, // Default is all AZs in the region
natGateways: 1, // Number of NAT gateways to create
subnetConfiguration: [
{
cidrMask: 24,
name: 'public',
subnetType: cdk.aws_ec2.SubnetType.PUBLIC
},
{
cidrMask: 24,
name: 'private',
subnetType: cdk.aws_ec2.SubnetType.PRIVATE_WITH_EGRESS
}
]
}
}
再度実行してみます。
VpcStack: deploying... [1/1]
VpcStack: creating CloudFormation changeset...
✅ VpcStack
✨ Deployment time: 10.11s
Stack ARN:
arn:aws:cloudformation:us-east-1:000000000000:stack/VpcStack/b4100619
✨ Total time: 12.36s
成功しました!LocalStack向けに実行されているかを判定して、ローカル向けの設定を別途定義すればソースは一本化できそうです。ただ、isLocalStack
の判定が増えるとコードが複雑になりそうですね。今後改善を検討していきたいと思います。
まとめ
今回初めてcdklocalを使ってローカル環境の構築を行いました。LocalStackを使うことで、AWSのサービスをローカルでエミュレートできるため、cdklocalで構築したDBをそのままローカル環境で利用できれば別途ローカル向けのdocker-composeやローカル環境構築用のスクリプト作成が不要になりそうです。今後は実際にDBやS3などのサービスを使った開発も行って、ローカルにデプロイしたアプリケーションから実際にLocalStackのサービスを利用できるかを確認していきたいと思います。
Discussion