AWS CDKで実現する!5つの効率的なインフラ構築テクニック【2024年保存版】

目次

目次へ

AWS CDK とは?初心者でもわかる基礎知識

Infra Structure as Code を革新する AWS CDK

AWS Cloud Development Kit(AWS CDK)は、プログラミング言語を使用してAWSインフラストラクチャをコードとして定義できる革新的なフレームワークです。従来のYAMLやJSONベースのテンプレート記述から脱却し、TypeScript、Python、Java、C#などの使い慣れたプログラミング言語でインフラを定義できることが最大の特徴です。

AWS CDKの主な特徴:

  • 型安全性: コンパイル時にエラーを検出可能
  • 自動補完: IDEのインテリジェンス機能が活用可能
  • モジュール化: コードの再利用が容易
  • 抽象化: 複雑なインフラ構成をシンプルに記述可能

CloudFormation との決定的な違い3つ

  1. 開発体験の向上
  • CloudFormation:
    yaml Resources: MyBucket: Type: AWS::S3::Bucket Properties: BucketName: my-unique-bucket-name VersioningConfiguration: Status: Enabled
  • AWS CDK(TypeScript): import * as s3 from 'aws-cdk-lib/aws-s3'; const bucket = new s3.Bucket(this, 'MyBucket', { bucketName: 'my-unique-bucket-name', versioned: true });
  1. ロジックの組み込み
  • 条件分岐やループが自然に記述可能
  • ユーティリティ関数の作成と再利用が容易
  • 環境変数やパラメータの取り扱いが柔軟
  1. カスタムコンストラクト
  • 複数のAWSリソースを1つのコンポーネントとして定義可能
  • ベストプラクティスを組織内で共有可能
  • サードパーティ製のコンストラクトライブラリが利用可能

TypeScript で始める AWS CDK

TypeScriptは、AWS CDKで最も人気のある言語です。その理由と基本的な使い方を見ていきましょう。

TypeScriptを選ぶ理由:

  1. 強力な型システム
  • リソースのプロパティ名や値の型チェック
  • コンパイル時のエラー検出
  • IDEによる優れたコード補完
  1. 豊富なエコシステム
  • npm パッケージの活用
  • 多数のサードパーティライブラリ
  • コミュニティサポート

基本的な構文例:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as s3 from 'aws-cdk-lib/aws-s3';
export class MyInfraStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// VPCの作成
const vpc = new ec2.Vpc(this, 'MyVPC', {
maxAzs: 2,
natGateways: 1
});
// S3バケットの作成
const bucket = new s3.Bucket(this, 'MyBucket', {
versioned: true,
encryption: s3.BucketEncryption.S3_MANAGED
});
}
}
import * as cdk from 'aws-cdk-lib'; import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as s3 from 'aws-cdk-lib/aws-s3'; export class MyInfraStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // VPCの作成 const vpc = new ec2.Vpc(this, 'MyVPC', { maxAzs: 2, natGateways: 1 }); // S3バケットの作成 const bucket = new s3.Bucket(this, 'MyBucket', { versioned: true, encryption: s3.BucketEncryption.S3_MANAGED }); } }
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as s3 from 'aws-cdk-lib/aws-s3';

export class MyInfraStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // VPCの作成
    const vpc = new ec2.Vpc(this, 'MyVPC', {
      maxAzs: 2,
      natGateways: 1
    });

    // S3バケットの作成
    const bucket = new s3.Bucket(this, 'MyBucket', {
      versioned: true,
      encryption: s3.BucketEncryption.S3_MANAGED
    });
  }
}

このコードは以下の要素で構成されています:

  • import文: 必要なCDKモジュールのインポート
  • Stackクラス: インフラ定義の基本単位
  • コンストラクト: VPCやS3バケットなどのAWSリソース
  • プロパティ: リソースの設定値

AWS CDKは、このTypeScriptコードを自動的にCloudFormationテンプレートに変換し、AWSリソースをデプロイします。このプロセスにより、インフラストラクチャのバージョン管理、テスト、再利用が容易になります。

AWS CDK 開発環境構築の完全ガイド

Node.js と AWS CDK のインストール手順

AWS CDKを使用するための環境構築を、順を追って説明します。

  1. Node.jsのインストール
  • Node.js公式サイトから最新のLTS版をダウンロード
  • インストール後、ターミナルで動作確認
    bash node --version npm --version
  1. AWS CDKのインストール
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# グローバルにAWS CDKをインストール
npm install -g aws-cdk
# バージョン確認
cdk --version
# グローバルにAWS CDKをインストール npm install -g aws-cdk # バージョン確認 cdk --version
   # グローバルにAWS CDKをインストール
   npm install -g aws-cdk

   # バージョン確認
   cdk --version
  1. AWS認証情報の設定
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# AWS CLIのインストール(まだの場合)
pip install awscli
# 認証情報の設定
aws configure
# AWS CLIのインストール(まだの場合) pip install awscli # 認証情報の設定 aws configure
   # AWS CLIのインストール(まだの場合)
   pip install awscli

   # 認証情報の設定
   aws configure

必要な情報:

  • AWS Access Key ID
  • AWS Secret Access Key
  • Default region name
  • Default output format

VSCodeによる効率的な開発環境セットアップ

VSCodeでAWS CDKを快適に開発するための設定を紹介します。

  1. 必須拡張機能のインストール
  • AWS Toolkit
  • TypeScript and JavaScript Language Features
  • ESLint
  • Prettier
  1. 推奨するVSCode設定
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"typescript.updateImportsOnFileMove.enabled": "always",
"javascript.updateImportsOnFileMove.enabled": "always"
}
{ "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, "typescript.updateImportsOnFileMove.enabled": "always", "javascript.updateImportsOnFileMove.enabled": "always" }
   {
     "editor.formatOnSave": true,
     "editor.codeActionsOnSave": {
       "source.fixAll.eslint": true
     },
     "typescript.updateImportsOnFileMove.enabled": "always",
     "javascript.updateImportsOnFileMove.enabled": "always"
   }
  1. デバッグ設定
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug CDK App",
"program": "${workspaceRoot}/bin/your-app.ts",
"preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": ["${workspaceRoot}/cdk.out/**/*.js"],
"console": "integratedTerminal"
}
]
}
{ "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Debug CDK App", "program": "${workspaceRoot}/bin/your-app.ts", "preLaunchTask": "tsc: build - tsconfig.json", "outFiles": ["${workspaceRoot}/cdk.out/**/*.js"], "console": "integratedTerminal" } ] }
   {
     "version": "0.2.0",
     "configurations": [
       {
         "type": "node",
         "request": "launch",
         "name": "Debug CDK App",
         "program": "${workspaceRoot}/bin/your-app.ts",
         "preLaunchTask": "tsc: build - tsconfig.json",
         "outFiles": ["${workspaceRoot}/cdk.out/**/*.js"],
         "console": "integratedTerminal"
       }
     ]
   }

初めてのCDKプロジェクト作成方法

新規プロジェクトの作成から初期設定までを解説します。

  1. プロジェクトの初期化
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# プロジェクトディレクトリの作成
mkdir my-cdk-project
cd my-cdk-project
# CDKプロジェクトの初期化(TypeScript使用)
cdk init app --language typescript
# プロジェクトディレクトリの作成 mkdir my-cdk-project cd my-cdk-project # CDKプロジェクトの初期化(TypeScript使用) cdk init app --language typescript
   # プロジェクトディレクトリの作成
   mkdir my-cdk-project
   cd my-cdk-project

   # CDKプロジェクトの初期化(TypeScript使用)
   cdk init app --language typescript
  1. プロジェクト構造の理解
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
my-cdk-project/
├── bin/
│ └── my-cdk-project.ts # エントリーポイント
├── lib/
│ └── my-cdk-project-stack.ts # メインのスタック定義
├── test/
│ └── my-cdk-project.test.ts # テストファイル
├── cdk.json # CDK設定ファイル
├── package.json
└── tsconfig.json
my-cdk-project/ ├── bin/ │ └── my-cdk-project.ts # エントリーポイント ├── lib/ │ └── my-cdk-project-stack.ts # メインのスタック定義 ├── test/ │ └── my-cdk-project.test.ts # テストファイル ├── cdk.json # CDK設定ファイル ├── package.json └── tsconfig.json
   my-cdk-project/
   ├── bin/
   │   └── my-cdk-project.ts    # エントリーポイント
   ├── lib/
   │   └── my-cdk-project-stack.ts    # メインのスタック定義
   ├── test/
   │   └── my-cdk-project.test.ts    # テストファイル
   ├── cdk.json    # CDK設定ファイル
   ├── package.json
   └── tsconfig.json
  1. 依存パッケージのインストール
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# 必要なパッケージのインストール
npm install @aws-cdk/assert @types/jest @types/node typescript
# 必要なパッケージのインストール npm install @aws-cdk/assert @types/jest @types/node typescript
   # 必要なパッケージのインストール
   npm install @aws-cdk/assert @types/jest @types/node typescript
  1. 初期設定の確認と修正
  • cdk.jsonの確認
    json { "app": "npx ts-node --prefer-ts-exts bin/my-cdk-project.ts", "context": { "@aws-cdk/core:enableDiffNoFail": "true", "@aws-cdk/core:newStyleStackSynthesis": "true" } }
  1. 動作確認
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# コードのビルド
npm run build
# スタックの一覧表示
cdk ls
# デプロイ前の変更確認
cdk diff
# デプロイ(初回は bootstrap が必要)
cdk bootstrap
cdk deploy
# コードのビルド npm run build # スタックの一覧表示 cdk ls # デプロイ前の変更確認 cdk diff # デプロイ(初回は bootstrap が必要) cdk bootstrap cdk deploy
   # コードのビルド
   npm run build

   # スタックの一覧表示
   cdk ls

   # デプロイ前の変更確認
   cdk diff

   # デプロイ(初回は bootstrap が必要)
   cdk bootstrap
   cdk deploy

このセットアップにより、AWS CDKを使用した開発を即座に開始できる環境が整います。次のステップでは、この環境を使用して実際のインフラストラクチャコードを書いていきます。

実践!AWS CDKによるインフラ構築の5つのテクニック

VPCとサブネットの効率的な定義方法

VPCの構築は多くのAWSインフラストラクチャの基盤となります。AWS CDKを使用することで、複雑なVPC構成も簡単に定義できます。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import * as ec2 from 'aws-cdk-lib/aws-ec2';
// 基本的なVPC構成
const vpc = new ec2.Vpc(this, 'MainVPC', {
maxAzs: 2, // 使用するAZの数
natGateways: 1, // コスト最適化のため1つのNATゲートウェイを使用
subnetConfiguration: [
{
name: 'Public',
subnetType: ec2.SubnetType.PUBLIC,
cidrMask: 24,
},
{
name: 'Private',
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
cidrMask: 24,
},
{
name: 'Isolated',
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
cidrMask: 28,
}
]
});
// VPCエンドポイントの追加
vpc.addInterfaceEndpoint('SecretsEndpoint', {
service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER
});
import * as ec2 from 'aws-cdk-lib/aws-ec2'; // 基本的なVPC構成 const vpc = new ec2.Vpc(this, 'MainVPC', { maxAzs: 2, // 使用するAZの数 natGateways: 1, // コスト最適化のため1つのNATゲートウェイを使用 subnetConfiguration: [ { name: 'Public', subnetType: ec2.SubnetType.PUBLIC, cidrMask: 24, }, { name: 'Private', subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, cidrMask: 24, }, { name: 'Isolated', subnetType: ec2.SubnetType.PRIVATE_ISOLATED, cidrMask: 28, } ] }); // VPCエンドポイントの追加 vpc.addInterfaceEndpoint('SecretsEndpoint', { service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER });
import * as ec2 from 'aws-cdk-lib/aws-ec2';

// 基本的なVPC構成
const vpc = new ec2.Vpc(this, 'MainVPC', {
  maxAzs: 2,  // 使用するAZの数
  natGateways: 1,  // コスト最適化のため1つのNATゲートウェイを使用
  subnetConfiguration: [
    {
      name: 'Public',
      subnetType: ec2.SubnetType.PUBLIC,
      cidrMask: 24,
    },
    {
      name: 'Private',
      subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
      cidrMask: 24,
    },
    {
      name: 'Isolated',
      subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
      cidrMask: 28,
    }
  ]
});

// VPCエンドポイントの追加
vpc.addInterfaceEndpoint('SecretsEndpoint', {
  service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER
});

EC2インスタンスの柔軟なデプロイ戦略

EC2インスタンスのデプロイでは、セキュリティグループやIAMロールなども含めて統合的に管理します。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as iam from 'aws-cdk-lib/aws-iam';
// セキュリティグループの定義
const securityGroup = new ec2.SecurityGroup(this, 'WebServerSG', {
vpc,
description: 'Allow web traffic',
allowAllOutbound: true
});
securityGroup.addIngressRule(
ec2.Peer.anyIpv4(),
ec2.Port.tcp(80),
'Allow HTTP traffic'
);
// IAMロールの定義
const role = new iam.Role(this, 'WebServerRole', {
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore')
]
});
// EC2インスタンスの作成
const instance = new ec2.Instance(this, 'WebServer', {
vpc,
vpcSubnets: {
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS
},
role,
securityGroup,
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
machineImage: new ec2.AmazonLinuxImage({
generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2
}),
userData: ec2.UserData.forLinux()
});
// User Dataスクリプトの追加
instance.userData.addCommands(
'yum update -y',
'yum install -y httpd',
'systemctl start httpd',
'systemctl enable httpd'
);
import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as iam from 'aws-cdk-lib/aws-iam'; // セキュリティグループの定義 const securityGroup = new ec2.SecurityGroup(this, 'WebServerSG', { vpc, description: 'Allow web traffic', allowAllOutbound: true }); securityGroup.addIngressRule( ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic' ); // IAMロールの定義 const role = new iam.Role(this, 'WebServerRole', { assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore') ] }); // EC2インスタンスの作成 const instance = new ec2.Instance(this, 'WebServer', { vpc, vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, role, securityGroup, instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage({ generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2 }), userData: ec2.UserData.forLinux() }); // User Dataスクリプトの追加 instance.userData.addCommands( 'yum update -y', 'yum install -y httpd', 'systemctl start httpd', 'systemctl enable httpd' );
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as iam from 'aws-cdk-lib/aws-iam';

// セキュリティグループの定義
const securityGroup = new ec2.SecurityGroup(this, 'WebServerSG', {
  vpc,
  description: 'Allow web traffic',
  allowAllOutbound: true
});

securityGroup.addIngressRule(
  ec2.Peer.anyIpv4(),
  ec2.Port.tcp(80),
  'Allow HTTP traffic'
);

// IAMロールの定義
const role = new iam.Role(this, 'WebServerRole', {
  assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
  managedPolicies: [
    iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore')
  ]
});

// EC2インスタンスの作成
const instance = new ec2.Instance(this, 'WebServer', {
  vpc,
  vpcSubnets: {
    subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS
  },
  role,
  securityGroup,
  instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
  machineImage: new ec2.AmazonLinuxImage({
    generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2
  }),
  userData: ec2.UserData.forLinux()
});

// User Dataスクリプトの追加
instance.userData.addCommands(
  'yum update -y',
  'yum install -y httpd',
  'systemctl start httpd',
  'systemctl enable httpd'
);

S3バケットのセキュアな構築方法

S3バケットでは、セキュリティとコンプライアンスを考慮した設定が重要です。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as kms from 'aws-cdk-lib/aws-kms';
// KMSキーの作成
const key = new kms.Key(this, 'BucketKey', {
enableKeyRotation: true,
description: 'Key for S3 bucket encryption'
});
// セキュアなS3バケットの作成
const bucket = new s3.Bucket(this, 'SecureBucket', {
encryption: s3.BucketEncryption.KMS,
encryptionKey: key,
versioned: true,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
removalPolicy: cdk.RemovalPolicy.RETAIN,
lifecycleRules: [
{
transitions: [
{
storageClass: s3.StorageClass.INTELLIGENT_TIERING,
transitionAfter: Duration.days(90)
}
],
noncurrentVersionExpiration: Duration.days(90)
}
]
});
// バケットポリシーの追加
bucket.addToResourcePolicy(new iam.PolicyStatement({
effect: iam.Effect.DENY,
actions: ['s3:*'],
resources: [bucket.arnForObjects('*')],
principals: [new iam.AnyPrincipal()],
conditions: {
'Bool': {
'aws:SecureTransport': false
}
}
}));
import * as s3 from 'aws-cdk-lib/aws-s3'; import * as kms from 'aws-cdk-lib/aws-kms'; // KMSキーの作成 const key = new kms.Key(this, 'BucketKey', { enableKeyRotation: true, description: 'Key for S3 bucket encryption' }); // セキュアなS3バケットの作成 const bucket = new s3.Bucket(this, 'SecureBucket', { encryption: s3.BucketEncryption.KMS, encryptionKey: key, versioned: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, removalPolicy: cdk.RemovalPolicy.RETAIN, lifecycleRules: [ { transitions: [ { storageClass: s3.StorageClass.INTELLIGENT_TIERING, transitionAfter: Duration.days(90) } ], noncurrentVersionExpiration: Duration.days(90) } ] }); // バケットポリシーの追加 bucket.addToResourcePolicy(new iam.PolicyStatement({ effect: iam.Effect.DENY, actions: ['s3:*'], resources: [bucket.arnForObjects('*')], principals: [new iam.AnyPrincipal()], conditions: { 'Bool': { 'aws:SecureTransport': false } } }));
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as kms from 'aws-cdk-lib/aws-kms';

// KMSキーの作成
const key = new kms.Key(this, 'BucketKey', {
  enableKeyRotation: true,
  description: 'Key for S3 bucket encryption'
});

// セキュアなS3バケットの作成
const bucket = new s3.Bucket(this, 'SecureBucket', {
  encryption: s3.BucketEncryption.KMS,
  encryptionKey: key,
  versioned: true,
  blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
  removalPolicy: cdk.RemovalPolicy.RETAIN,
  lifecycleRules: [
    {
      transitions: [
        {
          storageClass: s3.StorageClass.INTELLIGENT_TIERING,
          transitionAfter: Duration.days(90)
        }
      ],
      noncurrentVersionExpiration: Duration.days(90)
    }
  ]
});

// バケットポリシーの追加
bucket.addToResourcePolicy(new iam.PolicyStatement({
  effect: iam.Effect.DENY,
  actions: ['s3:*'],
  resources: [bucket.arnForObjects('*')],
  principals: [new iam.AnyPrincipal()],
  conditions: {
    'Bool': {
      'aws:SecureTransport': false
    }
  }
}));

Lambda関数のスマートなデプロイ

Lambda関数のデプロイでは、依存関係の管理やIAM権限の設定が重要です。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as path from 'path';
// Lambda関数の作成
const handler = new lambda.Function(this, 'MyHandler', {
runtime: lambda.Runtime.NODEJS_18_X,
handler: 'index.handler',
code: lambda.Code.fromAsset(path.join(__dirname, 'lambda')),
environment: {
BUCKET_NAME: bucket.bucketName,
},
tracing: lambda.Tracing.ACTIVE, // X-Ray トレーシングの有効化
timeout: Duration.seconds(30),
memorySize: 256,
architecture: lambda.Architecture.ARM_64, // Gravitonプロセッサの使用
});
// バケットアクセス権限の付与
bucket.grantRead(handler);
// カスタムメトリクスの追加
handler.addFunctionUrl({
authType: lambda.FunctionUrlAuthType.IAM,
cors: {
allowedOrigins: ['*'],
allowedMethods: [lambda.HttpMethod.ALL],
allowedHeaders: ['*']
}
});
import * as lambda from 'aws-cdk-lib/aws-lambda'; import * as path from 'path'; // Lambda関数の作成 const handler = new lambda.Function(this, 'MyHandler', { runtime: lambda.Runtime.NODEJS_18_X, handler: 'index.handler', code: lambda.Code.fromAsset(path.join(__dirname, 'lambda')), environment: { BUCKET_NAME: bucket.bucketName, }, tracing: lambda.Tracing.ACTIVE, // X-Ray トレーシングの有効化 timeout: Duration.seconds(30), memorySize: 256, architecture: lambda.Architecture.ARM_64, // Gravitonプロセッサの使用 }); // バケットアクセス権限の付与 bucket.grantRead(handler); // カスタムメトリクスの追加 handler.addFunctionUrl({ authType: lambda.FunctionUrlAuthType.IAM, cors: { allowedOrigins: ['*'], allowedMethods: [lambda.HttpMethod.ALL], allowedHeaders: ['*'] } });
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as path from 'path';

// Lambda関数の作成
const handler = new lambda.Function(this, 'MyHandler', {
  runtime: lambda.Runtime.NODEJS_18_X,
  handler: 'index.handler',
  code: lambda.Code.fromAsset(path.join(__dirname, 'lambda')),
  environment: {
    BUCKET_NAME: bucket.bucketName,
  },
  tracing: lambda.Tracing.ACTIVE,  // X-Ray トレーシングの有効化
  timeout: Duration.seconds(30),
  memorySize: 256,
  architecture: lambda.Architecture.ARM_64,  // Gravitonプロセッサの使用
});

// バケットアクセス権限の付与
bucket.grantRead(handler);

// カスタムメトリクスの追加
handler.addFunctionUrl({
  authType: lambda.FunctionUrlAuthType.IAM,
  cors: {
    allowedOrigins: ['*'],
    allowedMethods: [lambda.HttpMethod.ALL],
    allowedHeaders: ['*']
  }
});

IAMポリシーの堅実な管理方法

最小権限の原則に基づいたIAMポリシーの管理方法を示します。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import * as iam from 'aws-cdk-lib/aws-iam';
// カスタマー管理ポリシーの作成
const customPolicy = new iam.ManagedPolicy(this, 'CustomPolicy', {
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
's3:GetObject',
's3:PutObject'
],
resources: [
bucket.arnForObjects('*')
],
conditions: {
'StringEquals': {
'aws:PrincipalTag/Environment': ['prod', 'dev']
}
}
})
]
});
// IAMロールの作成と関連付け
const role = new iam.Role(this, 'CustomRole', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
managedPolicies: [
customPolicy,
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')
]
});
// インラインポリシーの追加
role.addToPolicy(new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['kms:Decrypt'],
resources: [key.keyArn]
}));
import * as iam from 'aws-cdk-lib/aws-iam'; // カスタマー管理ポリシーの作成 const customPolicy = new iam.ManagedPolicy(this, 'CustomPolicy', { statements: [ new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: [ 's3:GetObject', 's3:PutObject' ], resources: [ bucket.arnForObjects('*') ], conditions: { 'StringEquals': { 'aws:PrincipalTag/Environment': ['prod', 'dev'] } } }) ] }); // IAMロールの作成と関連付け const role = new iam.Role(this, 'CustomRole', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), managedPolicies: [ customPolicy, iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole') ] }); // インラインポリシーの追加 role.addToPolicy(new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: ['kms:Decrypt'], resources: [key.keyArn] }));
import * as iam from 'aws-cdk-lib/aws-iam';

// カスタマー管理ポリシーの作成
const customPolicy = new iam.ManagedPolicy(this, 'CustomPolicy', {
  statements: [
    new iam.PolicyStatement({
      effect: iam.Effect.ALLOW,
      actions: [
        's3:GetObject',
        's3:PutObject'
      ],
      resources: [
        bucket.arnForObjects('*')
      ],
      conditions: {
        'StringEquals': {
          'aws:PrincipalTag/Environment': ['prod', 'dev']
        }
      }
    })
  ]
});

// IAMロールの作成と関連付け
const role = new iam.Role(this, 'CustomRole', {
  assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
  managedPolicies: [
    customPolicy,
    iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')
  ]
});

// インラインポリシーの追加
role.addToPolicy(new iam.PolicyStatement({
  effect: iam.Effect.ALLOW,
  actions: ['kms:Decrypt'],
  resources: [key.keyArn]
}));

これらのテクニックを組み合わせることで、セキュアで管理しやすいインフラストラクチャを構築できます。各コンポーネントは再利用可能なモジュールとして設計されており、異なるプロジェクトや環境での再利用も容易です。

AWS CDKのベストプラクティス2024

スタック分割によるメンテナンス性の向上

大規模なインフラストラクチャをCDKで管理する場合、適切なスタック分割が重要です。

  1. 責務に基づくスタック分割
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// networking-stack.ts
export class NetworkingStack extends cdk.Stack {
public readonly vpc: ec2.Vpc;
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
this.vpc = new ec2.Vpc(this, 'MainVPC', {
maxAzs: 2,
natGateways: 1
});
}
}
// database-stack.ts
interface DatabaseStackProps extends cdk.StackProps {
vpc: ec2.Vpc;
}
export class DatabaseStack extends cdk.Stack {
public readonly cluster: rds.DatabaseCluster;
constructor(scope: cdk.App, id: string, props: DatabaseStackProps) {
super(scope, id, props);
this.cluster = new rds.DatabaseCluster(this, 'Database', {
engine: rds.DatabaseClusterEngine.auroraPostgres({
version: rds.AuroraPostgresEngineVersion.VER_13_4
}),
vpc: props.vpc,
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3,
ec2.InstanceSize.MEDIUM
)
});
}
}
// app.ts
const app = new cdk.App();
const networkingStack = new NetworkingStack(app, 'NetworkingStack');
const databaseStack = new DatabaseStack(app, 'DatabaseStack', {
vpc: networkingStack.vpc
});
// networking-stack.ts export class NetworkingStack extends cdk.Stack { public readonly vpc: ec2.Vpc; constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); this.vpc = new ec2.Vpc(this, 'MainVPC', { maxAzs: 2, natGateways: 1 }); } } // database-stack.ts interface DatabaseStackProps extends cdk.StackProps { vpc: ec2.Vpc; } export class DatabaseStack extends cdk.Stack { public readonly cluster: rds.DatabaseCluster; constructor(scope: cdk.App, id: string, props: DatabaseStackProps) { super(scope, id, props); this.cluster = new rds.DatabaseCluster(this, 'Database', { engine: rds.DatabaseClusterEngine.auroraPostgres({ version: rds.AuroraPostgresEngineVersion.VER_13_4 }), vpc: props.vpc, instanceType: ec2.InstanceType.of( ec2.InstanceClass.T3, ec2.InstanceSize.MEDIUM ) }); } } // app.ts const app = new cdk.App(); const networkingStack = new NetworkingStack(app, 'NetworkingStack'); const databaseStack = new DatabaseStack(app, 'DatabaseStack', { vpc: networkingStack.vpc });
// networking-stack.ts
export class NetworkingStack extends cdk.Stack {
  public readonly vpc: ec2.Vpc;

  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    this.vpc = new ec2.Vpc(this, 'MainVPC', {
      maxAzs: 2,
      natGateways: 1
    });
  }
}

// database-stack.ts
interface DatabaseStackProps extends cdk.StackProps {
  vpc: ec2.Vpc;
}

export class DatabaseStack extends cdk.Stack {
  public readonly cluster: rds.DatabaseCluster;

  constructor(scope: cdk.App, id: string, props: DatabaseStackProps) {
    super(scope, id, props);

    this.cluster = new rds.DatabaseCluster(this, 'Database', {
      engine: rds.DatabaseClusterEngine.auroraPostgres({
        version: rds.AuroraPostgresEngineVersion.VER_13_4
      }),
      vpc: props.vpc,
      instanceType: ec2.InstanceType.of(
        ec2.InstanceClass.T3,
        ec2.InstanceSize.MEDIUM
      )
    });
  }
}

// app.ts
const app = new cdk.App();
const networkingStack = new NetworkingStack(app, 'NetworkingStack');
const databaseStack = new DatabaseStack(app, 'DatabaseStack', {
  vpc: networkingStack.vpc
});
  1. 環境別のスタック管理
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// config/environment-config.ts
export interface EnvironmentConfig {
readonly environment: string;
readonly instanceType: ec2.InstanceType;
readonly minCapacity: number;
readonly maxCapacity: number;
}
export const environmentConfigs: { [key: string]: EnvironmentConfig } = {
dev: {
environment: 'dev',
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.SMALL),
minCapacity: 1,
maxCapacity: 2
},
prod: {
environment: 'prod',
instanceType: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.LARGE),
minCapacity: 2,
maxCapacity: 10
}
};
// config/environment-config.ts export interface EnvironmentConfig { readonly environment: string; readonly instanceType: ec2.InstanceType; readonly minCapacity: number; readonly maxCapacity: number; } export const environmentConfigs: { [key: string]: EnvironmentConfig } = { dev: { environment: 'dev', instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.SMALL), minCapacity: 1, maxCapacity: 2 }, prod: { environment: 'prod', instanceType: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.LARGE), minCapacity: 2, maxCapacity: 10 } };
// config/environment-config.ts
export interface EnvironmentConfig {
  readonly environment: string;
  readonly instanceType: ec2.InstanceType;
  readonly minCapacity: number;
  readonly maxCapacity: number;
}

export const environmentConfigs: { [key: string]: EnvironmentConfig } = {
  dev: {
    environment: 'dev',
    instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.SMALL),
    minCapacity: 1,
    maxCapacity: 2
  },
  prod: {
    environment: 'prod',
    instanceType: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.LARGE),
    minCapacity: 2,
    maxCapacity: 10
  }
};

カスタムコンストラクトを活用した再利用性の実現

カスタムコンストラクトを作成することで、共通のインフラパターンを再利用可能にします。

  1. 基本的なカスタムコンストラクト
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// constructs/secure-bucket.ts
export interface SecureBucketProps {
readonly bucketName?: string;
readonly lifecycleDays?: number;
readonly logRetention?: number;
}
export class SecureBucket extends Construct {
public readonly bucket: s3.Bucket;
constructor(scope: Construct, id: string, props?: SecureBucketProps) {
super(scope, id);
// KMSキーの作成
const key = new kms.Key(this, 'BucketKey', {
enableKeyRotation: true,
description: `Key for ${id} bucket encryption`
});
// セキュアなバケットの作成
this.bucket = new s3.Bucket(this, 'Bucket', {
bucketName: props?.bucketName,
encryption: s3.BucketEncryption.KMS,
encryptionKey: key,
versioned: true,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
lifecycleRules: props?.lifecycleDays ? [
{
expiration: Duration.days(props.lifecycleDays)
}
] : undefined
});
// アクセスログの設定
if (props?.logRetention) {
new logs.LogGroup(this, 'AccessLogs', {
retention: props.logRetention
});
}
}
}
// constructs/secure-bucket.ts export interface SecureBucketProps { readonly bucketName?: string; readonly lifecycleDays?: number; readonly logRetention?: number; } export class SecureBucket extends Construct { public readonly bucket: s3.Bucket; constructor(scope: Construct, id: string, props?: SecureBucketProps) { super(scope, id); // KMSキーの作成 const key = new kms.Key(this, 'BucketKey', { enableKeyRotation: true, description: `Key for ${id} bucket encryption` }); // セキュアなバケットの作成 this.bucket = new s3.Bucket(this, 'Bucket', { bucketName: props?.bucketName, encryption: s3.BucketEncryption.KMS, encryptionKey: key, versioned: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, lifecycleRules: props?.lifecycleDays ? [ { expiration: Duration.days(props.lifecycleDays) } ] : undefined }); // アクセスログの設定 if (props?.logRetention) { new logs.LogGroup(this, 'AccessLogs', { retention: props.logRetention }); } } }
// constructs/secure-bucket.ts
export interface SecureBucketProps {
  readonly bucketName?: string;
  readonly lifecycleDays?: number;
  readonly logRetention?: number;
}

export class SecureBucket extends Construct {
  public readonly bucket: s3.Bucket;

  constructor(scope: Construct, id: string, props?: SecureBucketProps) {
    super(scope, id);

    // KMSキーの作成
    const key = new kms.Key(this, 'BucketKey', {
      enableKeyRotation: true,
      description: `Key for ${id} bucket encryption`
    });

    // セキュアなバケットの作成
    this.bucket = new s3.Bucket(this, 'Bucket', {
      bucketName: props?.bucketName,
      encryption: s3.BucketEncryption.KMS,
      encryptionKey: key,
      versioned: true,
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      lifecycleRules: props?.lifecycleDays ? [
        {
          expiration: Duration.days(props.lifecycleDays)
        }
      ] : undefined
    });

    // アクセスログの設定
    if (props?.logRetention) {
      new logs.LogGroup(this, 'AccessLogs', {
        retention: props.logRetention
      });
    }
  }
}
  1. L3コンストラクトの作成
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// constructs/web-application.ts
export interface WebApplicationProps {
readonly vpc: ec2.Vpc;
readonly domain: string;
readonly env: string;
}
export class WebApplication extends Construct {
constructor(scope: Construct, id: string, props: WebApplicationProps) {
super(scope, id);
// ALBの作成
const alb = new elbv2.ApplicationLoadBalancer(this, 'ALB', {
vpc: props.vpc,
internetFacing: true
});
// ECSクラスターの作成
const cluster = new ecs.Cluster(this, 'Cluster', {
vpc: props.vpc,
containerInsights: true
});
// Fargeサービスの作成
const fargateService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'Service', {
cluster,
loadBalancer: alb,
desiredCount: 2,
taskImageOptions: {
image: ecs.ContainerImage.fromAsset('./docker'),
environment: {
NODE_ENV: props.env
}
}
});
// Auto Scalingの設定
const scaling = fargateService.service.autoScaleTaskCount({
maxCapacity: 4,
minCapacity: 1
});
scaling.scaleOnCpuUtilization('CpuScaling', {
targetUtilizationPercent: 70
});
}
}
// constructs/web-application.ts export interface WebApplicationProps { readonly vpc: ec2.Vpc; readonly domain: string; readonly env: string; } export class WebApplication extends Construct { constructor(scope: Construct, id: string, props: WebApplicationProps) { super(scope, id); // ALBの作成 const alb = new elbv2.ApplicationLoadBalancer(this, 'ALB', { vpc: props.vpc, internetFacing: true }); // ECSクラスターの作成 const cluster = new ecs.Cluster(this, 'Cluster', { vpc: props.vpc, containerInsights: true }); // Fargeサービスの作成 const fargateService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'Service', { cluster, loadBalancer: alb, desiredCount: 2, taskImageOptions: { image: ecs.ContainerImage.fromAsset('./docker'), environment: { NODE_ENV: props.env } } }); // Auto Scalingの設定 const scaling = fargateService.service.autoScaleTaskCount({ maxCapacity: 4, minCapacity: 1 }); scaling.scaleOnCpuUtilization('CpuScaling', { targetUtilizationPercent: 70 }); } }
// constructs/web-application.ts
export interface WebApplicationProps {
  readonly vpc: ec2.Vpc;
  readonly domain: string;
  readonly env: string;
}

export class WebApplication extends Construct {
  constructor(scope: Construct, id: string, props: WebApplicationProps) {
    super(scope, id);

    // ALBの作成
    const alb = new elbv2.ApplicationLoadBalancer(this, 'ALB', {
      vpc: props.vpc,
      internetFacing: true
    });

    // ECSクラスターの作成
    const cluster = new ecs.Cluster(this, 'Cluster', {
      vpc: props.vpc,
      containerInsights: true
    });

    // Fargeサービスの作成
    const fargateService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'Service', {
      cluster,
      loadBalancer: alb,
      desiredCount: 2,
      taskImageOptions: {
        image: ecs.ContainerImage.fromAsset('./docker'),
        environment: {
          NODE_ENV: props.env
        }
      }
    });

    // Auto Scalingの設定
    const scaling = fargateService.service.autoScaleTaskCount({
      maxCapacity: 4,
      minCapacity: 1
    });

    scaling.scaleOnCpuUtilization('CpuScaling', {
      targetUtilizationPercent: 70
    });
  }
}

効果的なテスト戦略の実現方法

CDKのテストは、ユニットテスト、スナップショットテスト、アサーションテストの3つのレベルで実施します。

  1. ユニットテスト
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// test/secure-bucket.test.ts
import { Template } from 'aws-cdk-lib/assertions';
import * as cdk from 'aws-cdk-lib';
import { SecureBucket } from '../lib/constructs/secure-bucket';
describe('SecureBucket', () => {
test('creates encrypted bucket', () => {
const app = new cdk.App();
const stack = new cdk.Stack(app, 'TestStack');
new SecureBucket(stack, 'TestBucket', {
bucketName: 'test-bucket'
});
const template = Template.fromStack(stack);
template.hasResourceProperties('AWS::S3::Bucket', {
BucketName: 'test-bucket',
VersioningConfiguration: {
Status: 'Enabled'
},
PublicAccessBlockConfiguration: {
BlockPublicAcls: true,
BlockPublicPolicy: true,
IgnorePublicAcls: true,
RestrictPublicBuckets: true
}
});
});
});
// test/secure-bucket.test.ts import { Template } from 'aws-cdk-lib/assertions'; import * as cdk from 'aws-cdk-lib'; import { SecureBucket } from '../lib/constructs/secure-bucket'; describe('SecureBucket', () => { test('creates encrypted bucket', () => { const app = new cdk.App(); const stack = new cdk.Stack(app, 'TestStack'); new SecureBucket(stack, 'TestBucket', { bucketName: 'test-bucket' }); const template = Template.fromStack(stack); template.hasResourceProperties('AWS::S3::Bucket', { BucketName: 'test-bucket', VersioningConfiguration: { Status: 'Enabled' }, PublicAccessBlockConfiguration: { BlockPublicAcls: true, BlockPublicPolicy: true, IgnorePublicAcls: true, RestrictPublicBuckets: true } }); }); });
// test/secure-bucket.test.ts
import { Template } from 'aws-cdk-lib/assertions';
import * as cdk from 'aws-cdk-lib';
import { SecureBucket } from '../lib/constructs/secure-bucket';

describe('SecureBucket', () => {
  test('creates encrypted bucket', () => {
    const app = new cdk.App();
    const stack = new cdk.Stack(app, 'TestStack');
    new SecureBucket(stack, 'TestBucket', {
      bucketName: 'test-bucket'
    });

    const template = Template.fromStack(stack);

    template.hasResourceProperties('AWS::S3::Bucket', {
      BucketName: 'test-bucket',
      VersioningConfiguration: {
        Status: 'Enabled'
      },
      PublicAccessBlockConfiguration: {
        BlockPublicAcls: true,
        BlockPublicPolicy: true,
        IgnorePublicAcls: true,
        RestrictPublicBuckets: true
      }
    });
  });
});
  1. スナップショットテスト
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// test/stack.test.ts
import * as cdk from 'aws-cdk-lib';
import { Template } from 'aws-cdk-lib/assertions';
import * as MyStack from '../lib/my-stack';
test('Stack creates expected resources', () => {
const app = new cdk.App();
const stack = new MyStack.MyStack(app, 'MyTestStack');
const template = Template.fromStack(stack);
expect(template.toJSON()).toMatchSnapshot();
});
// test/stack.test.ts import * as cdk from 'aws-cdk-lib'; import { Template } from 'aws-cdk-lib/assertions'; import * as MyStack from '../lib/my-stack'; test('Stack creates expected resources', () => { const app = new cdk.App(); const stack = new MyStack.MyStack(app, 'MyTestStack'); const template = Template.fromStack(stack); expect(template.toJSON()).toMatchSnapshot(); });
// test/stack.test.ts
import * as cdk from 'aws-cdk-lib';
import { Template } from 'aws-cdk-lib/assertions';
import * as MyStack from '../lib/my-stack';

test('Stack creates expected resources', () => {
  const app = new cdk.App();
  const stack = new MyStack.MyStack(app, 'MyTestStack');
  const template = Template.fromStack(stack);

  expect(template.toJSON()).toMatchSnapshot();
});
  1. カスタムアサーション
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// test/assertions.ts
export function hasSecureS3Bucket(template: Template, bucketName: string) {
template.hasResourceProperties('AWS::S3::Bucket', {
BucketName: bucketName,
BucketEncryption: {
ServerSideEncryptionConfiguration: [
{
ServerSideEncryptionByDefault: {
SSEAlgorithm: 'aws:kms'
}
}
]
},
PublicAccessBlockConfiguration: {
BlockPublicAcls: true,
BlockPublicPolicy: true,
IgnorePublicAcls: true,
RestrictPublicBuckets: true
},
VersioningConfiguration: {
Status: 'Enabled'
}
});
}
// test/assertions.ts export function hasSecureS3Bucket(template: Template, bucketName: string) { template.hasResourceProperties('AWS::S3::Bucket', { BucketName: bucketName, BucketEncryption: { ServerSideEncryptionConfiguration: [ { ServerSideEncryptionByDefault: { SSEAlgorithm: 'aws:kms' } } ] }, PublicAccessBlockConfiguration: { BlockPublicAcls: true, BlockPublicPolicy: true, IgnorePublicAcls: true, RestrictPublicBuckets: true }, VersioningConfiguration: { Status: 'Enabled' } }); }
// test/assertions.ts
export function hasSecureS3Bucket(template: Template, bucketName: string) {
  template.hasResourceProperties('AWS::S3::Bucket', {
    BucketName: bucketName,
    BucketEncryption: {
      ServerSideEncryptionConfiguration: [
        {
          ServerSideEncryptionByDefault: {
            SSEAlgorithm: 'aws:kms'
          }
        }
      ]
    },
    PublicAccessBlockConfiguration: {
      BlockPublicAcls: true,
      BlockPublicPolicy: true,
      IgnorePublicAcls: true,
      RestrictPublicBuckets: true
    },
    VersioningConfiguration: {
      Status: 'Enabled'
    }
  });
}

これらのベストプラクティスを適用することで、メンテナンス性が高く、再利用可能で、品質の担保されたCDKプロジェクトを実現できます。

チーム開発でのAWS CDK活用術

GitHubを使用したバージョン管理の実践

チーム開発においてGitHubを効果的に活用し、AWS CDKプロジェクトを管理する方法を解説します。

  1. 効果的なリポジトリ構成
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
project-root/
├── bin/
│ └── app.ts # エントリーポイント
├── lib/
│ ├── constructs/ # 共有コンストラクト
│ │ ├── secure-bucket.ts
│ │ └── web-service.ts
│ ├── stacks/ # 環境別スタック
│ │ ├── network-stack.ts
│ │ └── app-stack.ts
│ └── configs/ # 環境設定
│ ├── dev.ts
│ └── prod.ts
├── test/ # テストコード
├── .github/ # GitHub関連設定
└── cdk.json # CDK設定
project-root/ ├── bin/ │ └── app.ts # エントリーポイント ├── lib/ │ ├── constructs/ # 共有コンストラクト │ │ ├── secure-bucket.ts │ │ └── web-service.ts │ ├── stacks/ # 環境別スタック │ │ ├── network-stack.ts │ │ └── app-stack.ts │ └── configs/ # 環境設定 │ ├── dev.ts │ └── prod.ts ├── test/ # テストコード ├── .github/ # GitHub関連設定 └── cdk.json # CDK設定
project-root/
├── bin/
│   └── app.ts                # エントリーポイント
├── lib/
│   ├── constructs/          # 共有コンストラクト
│   │   ├── secure-bucket.ts
│   │   └── web-service.ts
│   ├── stacks/             # 環境別スタック
│   │   ├── network-stack.ts
│   │   └── app-stack.ts
│   └── configs/            # 環境設定
│       ├── dev.ts
│       └── prod.ts
├── test/                   # テストコード
├── .github/               # GitHub関連設定
└── cdk.json              # CDK設定
  1. ブランチ戦略の実装
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// lib/configs/base-config.ts
export interface EnvironmentConfig {
readonly environment: string;
readonly tags: { [key: string]: string };
readonly vpc: {
readonly maxAzs: number;
readonly natGateways: number;
};
}
// lib/configs/dev.ts
import { EnvironmentConfig } from './base-config';
export const devConfig: EnvironmentConfig = {
environment: 'development',
tags: {
Environment: 'dev',
Team: 'infrastructure'
},
vpc: {
maxAzs: 2,
natGateways: 1
}
};
// lib/configs/base-config.ts export interface EnvironmentConfig { readonly environment: string; readonly tags: { [key: string]: string }; readonly vpc: { readonly maxAzs: number; readonly natGateways: number; }; } // lib/configs/dev.ts import { EnvironmentConfig } from './base-config'; export const devConfig: EnvironmentConfig = { environment: 'development', tags: { Environment: 'dev', Team: 'infrastructure' }, vpc: { maxAzs: 2, natGateways: 1 } };
// lib/configs/base-config.ts
export interface EnvironmentConfig {
  readonly environment: string;
  readonly tags: { [key: string]: string };
  readonly vpc: {
    readonly maxAzs: number;
    readonly natGateways: number;
  };
}

// lib/configs/dev.ts
import { EnvironmentConfig } from './base-config';

export const devConfig: EnvironmentConfig = {
  environment: 'development',
  tags: {
    Environment: 'dev',
    Team: 'infrastructure'
  },
  vpc: {
    maxAzs: 2,
    natGateways: 1
  }
};

CI/CDパイプラインの構築方法

AWS CDKプロジェクトの継続的インテグレーション/デリバリーを実現します。

  1. デプロイメントパイプライン
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import * as cdk from 'aws-cdk-lib';
import * as pipelines from 'aws-cdk-lib/pipelines';
export class CdkPipelineStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const pipeline = new pipelines.CodePipeline(this, 'Pipeline', {
synth: new pipelines.ShellStep('Synth', {
input: pipelines.CodePipelineSource.gitHub('owner/repo', 'main'),
commands: [
'npm ci',
'npm run build',
'npx cdk synth'
]
})
});
// 開発環境のデプロイステージ
const devStage = new ApplicationStage(this, 'Dev', {
env: { account: '111111111111', region: 'ap-northeast-1' }
});
pipeline.addStage(devStage, {
pre: [
new pipelines.ShellStep('UnitTest', {
commands: ['npm test']
})
]
});
}
}
import * as cdk from 'aws-cdk-lib'; import * as pipelines from 'aws-cdk-lib/pipelines'; export class CdkPipelineStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { synth: new pipelines.ShellStep('Synth', { input: pipelines.CodePipelineSource.gitHub('owner/repo', 'main'), commands: [ 'npm ci', 'npm run build', 'npx cdk synth' ] }) }); // 開発環境のデプロイステージ const devStage = new ApplicationStage(this, 'Dev', { env: { account: '111111111111', region: 'ap-northeast-1' } }); pipeline.addStage(devStage, { pre: [ new pipelines.ShellStep('UnitTest', { commands: ['npm test'] }) ] }); } }
import * as cdk from 'aws-cdk-lib';
import * as pipelines from 'aws-cdk-lib/pipelines';

export class CdkPipelineStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const pipeline = new pipelines.CodePipeline(this, 'Pipeline', {
      synth: new pipelines.ShellStep('Synth', {
        input: pipelines.CodePipelineSource.gitHub('owner/repo', 'main'),
        commands: [
          'npm ci',
          'npm run build',
          'npx cdk synth'
        ]
      })
    });

    // 開発環境のデプロイステージ
    const devStage = new ApplicationStage(this, 'Dev', {
      env: { account: '111111111111', region: 'ap-northeast-1' }
    });

    pipeline.addStage(devStage, {
      pre: [
        new pipelines.ShellStep('UnitTest', {
          commands: ['npm test']
        })
      ]
    });
  }
}
  1. 環境別デプロイメント設定
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// lib/stage.ts
export class ApplicationStage extends cdk.Stage {
constructor(scope: Construct, id: string, props?: cdk.StageProps) {
super(scope, id, props);
const config = loadConfig(props?.env?.account);
const networkStack = new NetworkStack(this, 'Network', {
env: props?.env,
config: config.network
});
new ApplicationStack(this, 'Application', {
env: props?.env,
vpc: networkStack.vpc,
config: config.application
});
}
}
// lib/stage.ts export class ApplicationStage extends cdk.Stage { constructor(scope: Construct, id: string, props?: cdk.StageProps) { super(scope, id, props); const config = loadConfig(props?.env?.account); const networkStack = new NetworkStack(this, 'Network', { env: props?.env, config: config.network }); new ApplicationStack(this, 'Application', { env: props?.env, vpc: networkStack.vpc, config: config.application }); } }
// lib/stage.ts
export class ApplicationStage extends cdk.Stage {
  constructor(scope: Construct, id: string, props?: cdk.StageProps) {
    super(scope, id, props);

    const config = loadConfig(props?.env?.account);

    const networkStack = new NetworkStack(this, 'Network', {
      env: props?.env,
      config: config.network
    });

    new ApplicationStack(this, 'Application', {
      env: props?.env,
      vpc: networkStack.vpc,
      config: config.application
    });
  }
}

コードレビューのポイントと注意点

効果的なコードレビューのために、以下の点に注意を払います:

  1. アーキテクチャレベルのレビュー
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// 推奨パターン
export class WebServiceStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// VPCの作成は別スタックで行い、参照として受け取る
const vpc = ec2.Vpc.fromLookup(this, 'VPC', {
vpcId: props.vpcId
});
// セキュリティグループの定義
const securityGroup = new ec2.SecurityGroup(this, 'WebSG', {
vpc,
description: 'Security group for web servers',
allowAllOutbound: false // 明示的なアウトバウンドルールの定義
});
// 必要最小限の権限を持つIAMロール
const role = new iam.Role(this, 'WebServerRole', {
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName(
'AmazonSSMManagedInstanceCore'
)
]
});
}
}
// 推奨パターン export class WebServiceStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // VPCの作成は別スタックで行い、参照として受け取る const vpc = ec2.Vpc.fromLookup(this, 'VPC', { vpcId: props.vpcId }); // セキュリティグループの定義 const securityGroup = new ec2.SecurityGroup(this, 'WebSG', { vpc, description: 'Security group for web servers', allowAllOutbound: false // 明示的なアウトバウンドルールの定義 }); // 必要最小限の権限を持つIAMロール const role = new iam.Role(this, 'WebServerRole', { assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName( 'AmazonSSMManagedInstanceCore' ) ] }); } }
// 推奨パターン
export class WebServiceStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // VPCの作成は別スタックで行い、参照として受け取る
    const vpc = ec2.Vpc.fromLookup(this, 'VPC', {
      vpcId: props.vpcId
    });

    // セキュリティグループの定義
    const securityGroup = new ec2.SecurityGroup(this, 'WebSG', {
      vpc,
      description: 'Security group for web servers',
      allowAllOutbound: false  // 明示的なアウトバウンドルールの定義
    });

    // 必要最小限の権限を持つIAMロール
    const role = new iam.Role(this, 'WebServerRole', {
      assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName(
          'AmazonSSMManagedInstanceCore'
        )
      ]
    });
  }
}
  1. コスト最適化のレビュー
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// コスト最適化の例
export class OptimizedStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Auto Scalingの適切な設定
const asg = new autoscaling.AutoScalingGroup(this, 'ASG', {
vpc,
minCapacity: 1,
maxCapacity: 3,
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3A, // ARM版インスタンスの使用
ec2.InstanceSize.SMALL
)
});
// スケーリングポリシーの設定
asg.scaleOnCpuUtilization('CpuScaling', {
targetUtilizationPercent: 70,
cooldown: cdk.Duration.seconds(300)
});
}
}
// コスト最適化の例 export class OptimizedStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // Auto Scalingの適切な設定 const asg = new autoscaling.AutoScalingGroup(this, 'ASG', { vpc, minCapacity: 1, maxCapacity: 3, instanceType: ec2.InstanceType.of( ec2.InstanceClass.T3A, // ARM版インスタンスの使用 ec2.InstanceSize.SMALL ) }); // スケーリングポリシーの設定 asg.scaleOnCpuUtilization('CpuScaling', { targetUtilizationPercent: 70, cooldown: cdk.Duration.seconds(300) }); } }
// コスト最適化の例
export class OptimizedStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Auto Scalingの適切な設定
    const asg = new autoscaling.AutoScalingGroup(this, 'ASG', {
      vpc,
      minCapacity: 1,
      maxCapacity: 3,
      instanceType: ec2.InstanceType.of(
        ec2.InstanceClass.T3A,  // ARM版インスタンスの使用
        ec2.InstanceSize.SMALL
      )
    });

    // スケーリングポリシーの設定
    asg.scaleOnCpuUtilization('CpuScaling', {
      targetUtilizationPercent: 70,
      cooldown: cdk.Duration.seconds(300)
    });
  }
}
  1. セキュリティレビューのポイント
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// セキュリティベストプラクティス
export class SecureStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// 暗号化の有効化
const bucket = new s3.Bucket(this, 'DataBucket', {
encryption: s3.BucketEncryption.KMS_MANAGED,
enforceSSL: true,
versioned: true,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL
});
// セキュアなバケットポリシー
bucket.addToResourcePolicy(new iam.PolicyStatement({
effect: iam.Effect.DENY,
principals: [new iam.AnyPrincipal()],
actions: ['s3:*'],
resources: [bucket.arnForObjects('*')],
conditions: {
'Bool': {
'aws:SecureTransport': false
}
}
}));
}
}
// セキュリティベストプラクティス export class SecureStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // 暗号化の有効化 const bucket = new s3.Bucket(this, 'DataBucket', { encryption: s3.BucketEncryption.KMS_MANAGED, enforceSSL: true, versioned: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL }); // セキュアなバケットポリシー bucket.addToResourcePolicy(new iam.PolicyStatement({ effect: iam.Effect.DENY, principals: [new iam.AnyPrincipal()], actions: ['s3:*'], resources: [bucket.arnForObjects('*')], conditions: { 'Bool': { 'aws:SecureTransport': false } } })); } }
// セキュリティベストプラクティス
export class SecureStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // 暗号化の有効化
    const bucket = new s3.Bucket(this, 'DataBucket', {
      encryption: s3.BucketEncryption.KMS_MANAGED,
      enforceSSL: true,
      versioned: true,
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL
    });

    // セキュアなバケットポリシー
    bucket.addToResourcePolicy(new iam.PolicyStatement({
      effect: iam.Effect.DENY,
      principals: [new iam.AnyPrincipal()],
      actions: ['s3:*'],
      resources: [bucket.arnForObjects('*')],
      conditions: {
        'Bool': {
          'aws:SecureTransport': false
        }
      }
    }));
  }
}

これらの実践により、チームでのAWS CDK開発を効率的かつ安全に進めることができます。定期的なコードレビューとベストプラクティスの共有を通じて、プロジェクトの品質を継続的に向上させることが重要です。