ステップ4:環境分離とベストプラクティス
今回のステップの概要とこのカリキュラムとの関連について
このステップでは、ステップ3で作成したシンプルなECS環境に対して、プロダクション環境で必要なベストプラクティスを適用します。具体的には、環境分離(dev/stg/prod)の実装、cdk-nagによるセキュリティチェック、タグ付け・ネーミング規則・リソース削除保護の実装、そして他のスタックから参照可能なCloudFormationエクスポートの設定を行います。
Infrastructure as Codeにとって、このステップは「設計図を品質基準に沿って改善し、検証する作業」のような役割を果たします。物理的な建築プロジェクトで言えば、「設計図を建築基準法に適合させ、審査を受けて承認を得る」という段階に相当します。セキュリティチェックツール(cdk-nag)を使用することで、AWSのベストプラクティスやセキュリティ基準に準拠しているかを自動的に検証できます。また、環境分離により、同じCDKコードからdev/stg/prod環境を安全に管理できるようになります。
従来、環境ごとに異なる設定ファイルを手動で管理し、設定ミスや設定漏れが発生しやすい状況でした。CDK Contextを使った環境分離により、環境ごとの設定をJSON形式で一元管理し、同じコードベースから複数の環境を再現性高くデプロイできます。
このステップで学ぶこと
- 環境分離(dev/stg/prod)の実装方法
- cdk-nagによるセキュリティチェック
- AWS CDKのベストプラクティス(タグ付け、ネーミング規則、削除保護)の適用
- CloudFormationエクスポートによるスタック間の値共有
リソースの関わりと構成説明
ステップ4で設定する要素は、CDKプロジェクト全体の「品質管理」「環境分離」「他システムとの連携」を行うものです。それぞれの要素がインフラ管理にどのように関わるのかを説明します。
環境分離とインフラの関わり
環境分離「エンバイロメントセパレーション」は、開発環境(dev)、ステージング環境(stg)、本番環境(prod)を分けて管理する手法で、「試作品と製品を分けて管理する仕組み」のような役割を果たします。物理的な製造業で言えば、「試作品を作る工場、品質検査する工場、実際に販売する製品を作る工場を分ける」という状況に相当します。CDK Contextを使った環境分離により、同じCDKコードから複数の環境を作成でき、環境ごとの設定(タスク数、CPU、メモリなど)を柔軟に変更できます。これにより、開発環境で新機能を試し、ステージング環境で品質を確認し、本番環境に安全にリリースする、という段階的なデプロイが可能になります。
cdk-nagとインフラの関わり
cdk-nag「シーディーケーナグ」は、CDKコードがAWSのベストプラクティスとセキュリティ基準に準拠しているかを自動的にチェックするツールで、「建築確認検査官」のような役割を果たします。物理的な建築プロジェクトで言えば、「設計図が建築基準法に適合しているか審査する検査官」に相当します。cdk-nagは、cdk synth の実行時に、CDKコードを解析し、セキュリティ上の問題(例:セキュリティグループが0.0.0.0/0を許可している、S3バケットが暗号化されていない)を検出して警告を表示します。これにより、デプロイ前にセキュリティリスクを発見し、修正できます。
CloudFormationエクスポートとインフラの関わり
CloudFormationエクスポート「クロススタック参照」は、あるCloudFormationスタックで作成したリソースの情報(VPC ID、ALB ARNなど)を、別のスタックから参照するための「引き継ぎ書類」のような役割を果たします。物理的なプロジェクトで言えば、「前工程が完成させた部分の情報を、次工程に引き継ぐ書類」に相当します。このカリキュラムで構築したECS環境の情報(ALBのURL、ECSクラスター名、VPC IDなど)をエクスポートすることで、将来的に他のスタック(例:CI/CDパイプライン、モニタリングシステム、別のマイクロサービス)からこれらの情報を参照し、連携できます。
タグ付けとネーミング規則とインフラの関わり
AWSリソースのタグ「メタデータ」は、リソースに付与するキー・バリューペアの情報で、「商品の管理ラベル」のような役割を果たします。物理的な店舗で言えば、「商品に貼られた価格タグや管理番号のシール」に相当します。タグを使用することで、リソースの所有者、プロジェクト名、環境(dev/stg/prod)、コスト配分などを記録でき、AWSコンソールでのフィルタリング、Cost Explorerでのコスト分析、自動化スクリプトでのリソース検索が容易になります。ネーミング規則は、リソース名に一貫性を持たせることで、「このリソースは何のためのものか」を一目で理解できるようにします。
実際の手順
実際の手順では、たくさんの設定値を入力することになります。 本文中に設定値が指定されていない場合は、デフォルト値のまま作業を進めてください。
1. 環境分離の実装
dev、stg、prodの3つの環境を効率的に管理する方法を学びます。
1-0. リソースのクリーンアップと現状確認
環境分離の実装を始める前に、ステップ3で作成したリソースを削除し、現状のソースコードを確認します。
- 現在デプロイされているスタックを確認します
cdk ls --region us-east-1以下のような出力が表示されます。
CdkIacProjectStack- すべてのリソースを削除します
cdk destroy --region us-east-1確認プロンプトが表示されたら、y を入力して削除を実行します。
Are you sure you want to delete: CdkIacProjectStack (y/n)? y
CdkIacProjectStack: destroying... [1/1]
...
✅ CdkIacProjectStack: destroyedcdk destroyの実行について
cdk destroy を実行すると、CloudFormationスタックとそのスタック内のすべてのリソースが削除されます。このカリキュラムで作成したリソース(VPC、ECSクラスター、ALB、ECRリポジトリなど)がすべて削除されるため、実行前に確認してください。
削除に時間がかかる場合があります。特に、ECSサービスの削除には数分かかることがあります。削除が完了するまで待機してください。
- 現状のソースコードを確認します
環境分離の実装を始める前に、現在のCDKプロジェクトの構造を確認しましょう。
# プロジェクト構造を確認
tree -L 2 -I node_modules以下のような構造が表示されます。
.
├── bin
│ └── cdk-iac-project.ts
├── cdk.json
├── lib
│ └── cdk-iac-project-stack.ts
├── nextjs-app
│ └── // 省略
├── package-lock.json
├── package.json
├── README.md
└── //省略1-1. 既存のCDKソースコードの論理名を変数化
以下のファイルの内容を確認し、ステップ3で実装した内容を把握しておきましょう。その後、各ファイルの既存のコードを以下のアコーディオン内の内容に完全に書き換えてください。これにより、環境ごとに異なる値を設定できるようになります。
既存コードの上書きについて
以下のアコーディオン内のコードは、ステップ4で必要な変更(ECRへの自動ビルド・プッシュ機能など)を含んでいます。ステップ3で作成したファイルの内容を、以下のコードで完全に上書きしてください。
bin/cdk-iac-project.ts- 既存の内容を削除し、以下のコードに置き換えるlib/cdk-iac-project-stack.ts- 既存の内容を削除し、以下のコードに置き換える
lib/cdk-iac-project-stack.ts - CDKスタックの定義
lib/cdk-iac-project-stack.ts - CDKスタックの定義import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as ecr from 'aws-cdk-lib/aws-ecr';
// カスタムStackPropsインターフェースを定義
export interface CdkIacProjectStackProps extends cdk.StackProps {
desiredCount: number;
cpu: number;
memory: number;
}
export class CdkIacProjectStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: CdkIacProjectStackProps) {
super(scope, id, props);
// 環境名を取得(デフォルトはdev)
const envName = props.tags?.Environment || 'dev';
// VPCの作成(環境名を含める)
const vpc = new ec2.Vpc(this, 'MyVpc', {
vpcName: `iac-learning-vpc-${envName}`,
maxAzs: 2,
natGateways: 1,
subnetConfiguration: [
{
cidrMask: 24,
name: 'Public',
subnetType: ec2.SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: 'Private',
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
],
});
// ECSクラスターの作成(環境名を含める)
const cluster = new ecs.Cluster(this, 'MyCluster', {
vpc: vpc,
clusterName: `iac-learning-cluster-${envName}`,
containerInsightsV2: ecs.ContainerInsights.ENABLED,
});
// ALBセキュリティグループの作成
const albSecurityGroup = new ec2.SecurityGroup(this, 'AlbSecurityGroup', {
vpc: vpc,
description: 'Security group for ALB',
allowAllOutbound: true,
});
albSecurityGroup.addIngressRule(
ec2.Peer.anyIpv4(),
ec2.Port.tcp(80),
'Allow HTTP traffic from anywhere'
);
// Application Load Balancerの作成(環境名を含める)
const alb = new elbv2.ApplicationLoadBalancer(this, 'MyAlb', {
vpc: vpc,
internetFacing: true,
loadBalancerName: `iac-alb-${envName}`,
securityGroup: albSecurityGroup,
vpcSubnets: {
subnetType: ec2.SubnetType.PUBLIC,
},
});
// ターゲットグループの作成
const targetGroup = new elbv2.ApplicationTargetGroup(this, 'MyTargetGroup', {
vpc: vpc,
port: 3000,
protocol: elbv2.ApplicationProtocol.HTTP,
targetType: elbv2.TargetType.IP,
healthCheck: {
path: '/api/health',
interval: cdk.Duration.seconds(30),
timeout: cdk.Duration.seconds(5),
healthyThresholdCount: 2,
unhealthyThresholdCount: 3,
},
});
// ALBリスナーの作成
alb.addListener('MyListener', {
port: 80,
protocol: elbv2.ApplicationProtocol.HTTP,
defaultTargetGroups: [targetGroup],
});
// ECSセキュリティグループの作成
const ecsSecurityGroup = new ec2.SecurityGroup(this, 'EcsSecurityGroup', {
vpc: vpc,
description: 'Security group for ECS tasks',
allowAllOutbound: true,
});
// ALBからのHTTPトラフィック(ポート3000)を許可
ecsSecurityGroup.addIngressRule(
albSecurityGroup,
ec2.Port.tcp(3000),
'Allow traffic from ALB on port 3000'
);
// ECSタスク実行ロールの作成
const taskExecutionRole = new iam.Role(this, 'TaskExecutionRole', {
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonECSTaskExecutionRolePolicy'),
],
});
// ECSタスクロールの作成
const taskRole = new iam.Role(this, 'TaskRole', {
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
});
// ECSタスク定義の作成(変数化されたCPUとメモリを使用)
const taskDefinition = new ecs.FargateTaskDefinition(this, 'MyTaskDef', {
memoryLimitMiB: props.memory,
cpu: props.cpu,
executionRole: taskExecutionRole,
taskRole: taskRole,
});
// コンテナの追加(環境変数をParameter Storeから取得)
const container = taskDefinition.addContainer('NextjsContainer', {
image: ecs.ContainerImage.fromAsset('./nextjs-app', {}),
logging: ecs.LogDrivers.awsLogs({
streamPrefix: 'nextjs-app',
logRetention: 7,
}),
environment: {
NODE_ENV: 'production',
},
});
// コンテナのポートマッピング
container.addPortMappings({
containerPort: 3000,
protocol: ecs.Protocol.TCP,
});
// ECSサービスの作成(変数化されたdesiredCountを使用)
const service = new ecs.FargateService(this, 'MyService', {
cluster: cluster,
taskDefinition: taskDefinition,
desiredCount: props.desiredCount,
securityGroups: [ecsSecurityGroup],
vpcSubnets: {
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
healthCheckGracePeriod: cdk.Duration.seconds(60),
});
// サービスをALBのターゲットグループに登録
service.attachToApplicationTargetGroup(targetGroup);
// CloudFormation Outputs
new cdk.CfnOutput(this, 'VpcId', {
value: vpc.vpcId,
description: 'VPC ID',
});
new cdk.CfnOutput(this, 'ClusterName', {
value: cluster.clusterName,
description: 'ECS Cluster Name',
});
new cdk.CfnOutput(this, 'AlbDnsName', {
value: alb.loadBalancerDnsName,
description: 'ALB DNS Name',
});
new cdk.CfnOutput(this, 'ServiceName', {
value: service.serviceName,
description: 'ECS Service Name',
});
}
}bin/cdk-iac-project.ts - CDKアプリのエントリーポイント
bin/cdk-iac-project.ts - CDKアプリのエントリーポイント#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib/core';
import { CdkIacProjectStack } from '../lib/cdk-iac-project-stack';
const app = new cdk.App();
// デプロイ時に指定された環境を取得(例:cdk deploy --context env=dev)
const targetEnv = app.node.tryGetContext('env') || 'dev'; // デフォルトはdev
// すべての環境設定を取得
const environments = app.node.tryGetContext('environments') || [];
// 指定された環境の設定を検索
const envConfig = environments.find((e: any) => e.name === targetEnv);
// 環境が見つからない場合はエラー
if (!envConfig) {
throw new Error(
`環境 "${targetEnv}" が見つかりません。cdk.json の environments に定義されているか確認してください。\n` +
`利用可能な環境: ${environments.map((e: any) => e.name).join(', ')}`
);
}
// 指定された環境のスタックのみを作成
new CdkIacProjectStack(app, `IacStack-${envConfig.name}`, {
stackName: `IacStack-${envConfig.name}`,
tags: {
Environment: envConfig.name,
Project: 'IaC-Learning',
},
desiredCount: envConfig.desiredCount,
cpu: envConfig.cpu,
memory: envConfig.memory,
});【解説】論理名の変数化と環境分離の準備
環境分離を実装する前に、既存のCDKコード内でハードコードされている値を変数化することが重要です。これにより、環境ごとに異なる設定を柔軟に適用できます。
1. リソース名の変数化:
リソース名(VPC名、クラスター名、ALB名など)に環境名を含めることで、同じAWSアカウント内で複数の環境を並行して管理できます。例えば、iac-learning-vpc-dev、iac-learning-vpc-prod のように、環境名を名前に含めることで、リソースの所属環境が一目で分かります。
2. 設定値の変数化: ECSタスクのCPU、メモリ、desiredCountなどの設定値を、StackPropsから取得するように変更します。これにより、環境ごとに異なるリソースサイズを設定できます。開発環境では小さいリソース(CPU: 256、メモリ: 512MB、タスク数: 1)を、本番環境では大きいリソース(CPU: 512、メモリ: 1024MB、タスク数: 3)を設定できます。
3. 環境名の取得方法:
環境名は、スタックの tags プロパティから取得します。props.tags?.Environment || 'dev' のように、デフォルト値を設定することで、環境名が指定されていない場合でも動作します。
4. カスタムStackPropsインターフェース:
CdkIacProjectStackProps インターフェースを定義することで、スタックに渡すべきプロパティを型安全に管理できます。TypeScriptの型チェックにより、必要なプロパティが不足している場合にコンパイルエラーが発生し、設定漏れを防げます。
この変数化により、次のステップ(CDK Contextを使った環境設定)で、環境ごとの設定値をJSON形式で一元管理できるようになります。
1-2. CDK Contextを使った環境設定
- 既存の
cdk.jsonファイルを開き、contextセクションに環境設定を追加します
{
"app": "npx ts-node --prefer-ts-exts bin/cdk-iac-project.ts",
"context": {
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true,
// ... 既存の設定 ...
// 環境設定を追加
"environments": [
{
"name": "dev",
"desiredCount": 1,
"cpu": 256,
"memory": 512
},
{
"name": "staging",
"desiredCount": 2,
"cpu": 256,
"memory": 512
},
{
"name": "production",
"desiredCount": 3,
"cpu": 512,
"memory": 1024
}
]
}
}cdk.json と cdk.context.json の違い
AWS CDK公式ドキュメント「Context values and the AWS CDK 」によると:
-
cdk.json: 開発者が手動で設定するコンテキスト値を
contextキー配下に定義します。このカリキュラムの環境設定(dev/staging/production)のように、プロジェクト固有の設定値やアプリケーションパラメータをここに記述します。Gitでバージョン管理し、チーム全体で共有する必要があります。公式ドキュメントより引用:
“Context values that don’t represent cached values should be stored under the
contextkey ofcdk.json. This way, they won’t be cleared when cached values are cleared.” -
cdk.context.json: CDKツールキットが「AWS環境から自動的に取得した値をキャッシュする」ファイルです。例えば、アベイラビリティゾーンの一覧(
availability-zones:account=123456789012:region=us-east-1)や最新のAMI IDなどが含まれます。CDKが自動的に生成・更新するため、手動で編集してはいけません。公式ドキュメントより引用:
“The project file
cdk.context.jsonis where the AWS CDK caches context values retrieved from your AWS account. This practice avoids unexpected changes to your deployments when, for example, a new Availability Zone is introduced.”“Cached context values are managed by the AWS CDK and its constructs, including constructs you may write. Do not add or change cached context values by manually editing files.”
環境設定(dev/staging/production)は開発者が定義する値であり、キャッシュ値ではないため、cdk.json の context セクションに記述するのが正しい方法です。
参考: AWS CDK v2 Developer Guide - Context values and the AWS CDK
1-3. 環境ごとのデプロイコマンド
環境を指定してデプロイする方法を確認します。
- dev環境をデプロイする場合(デフォルト環境)
cdk deploy --region us-east-1または明示的に環境を指定:
cdk deploy --context env=dev --region us-east-1- staging環境をデプロイする場合
cdk deploy --context env=staging --region us-east-1- production環境をデプロイする場合
cdk deploy --context env=production --region us-east-1- 現在デプロイ可能なスタックを確認します(どの環境がデプロイされるか確認)
# dev環境(デフォルト)
cdk ls --region us-east-1
# staging環境
cdk ls --context env=staging --region us-east-1以下のような出力が表示されます(指定した環境のスタックのみ)。
IacStack-dev【解説】環境を指定してデプロイする利点
このカリキュラムで実装した方法は、デプロイ時に --context env=xxx で環境を明示的に指定する必要があります。これは一見面倒に思えますが、実は本番環境では非常に重要な安全機能です。
環境を明示的に指定する方法では、以下のようなメリットがあります:
-
誤操作の防止 -
cdk deployだけではdev環境のみがデプロイされ、production環境をデプロイするには--context env=productionを明示的に指定する必要があります。 -
変更内容の確認 - 環境を指定することで、「今から本番環境を変更する」という意識が明確になり、
cdk diff --context env=productionで変更内容を慎重に確認する習慣がつきます。 -
エラーメッセージの明確化 - 存在しない環境名を指定した場合、「環境 “prod” が見つかりません。利用可能な環境: dev, staging, production」という明確なエラーメッセージが表示されます。
実務では、さらにデプロイ前の承認プロセスを追加することもあります。例えば、production環境へのデプロイは、マネージャーの承認を得てからCI/CDパイプラインが実行される、という仕組みです。
2. cdk-nagによるセキュリティチェック
CDKコードがAWSのベストプラクティスに準拠しているかを自動チェックします。
2-1. cdk-nagのインストール
- CDKプロジェクトに
cdk-nagパッケージをインストールします
npm install cdk-nag2-2. cdk-nagの設定
bin/cdk-iac-project.tsファイルを開き、cdk-nagを有効化します
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { CdkIacProjectStack } from '../lib/cdk-iac-project-stack';
import { AwsSolutionsChecks } from 'cdk-nag';
import { Aspects } from 'aws-cdk-lib';
const app = new cdk.App();
// cdk-nagのAWS Solutions Checksを追加
Aspects.of(app).add(new AwsSolutionsChecks({ verbose: true }));
// 省略- cdk synthを実行して、セキュリティチェックを確認します
cdk synth --context env=dev --region us-east-1以下のような警告が表示されます(例)。
[Warning at /IacStack-dev/AlbSecurityGroup] AwsSolutions-EC23: The Security Group allows for 0.0.0.0/0 or ::/0 inbound access.
[Warning at /IacStack-dev/MyAlb] AwsSolutions-ELB2: The ALB is not configured with access logs.【解説】cdk-nagの警告への対応方法
cdk-nagは、AWSのWell-Architected FrameworkとAWS Solutions Libraryに基づいたセキュリティチェックを実行します。警告が表示された場合、以下の対応を検討してください。
1. 警告の内容を理解する: 各警告には、警告コード(例:AwsSolutions-EC23)と説明が含まれています。この警告コードをcdk-nagのドキュメントで検索することで、詳細な説明と推奨される対応方法を確認できます。
2. 実際にセキュリティリスクがあるか判断する:
すべての警告が実際のセキュリティリスクとは限りません。例えば、AwsSolutions-EC23(セキュリティグループが0.0.0.0/0を許可)は、ALBのようなインターネット向けリソースでは正常な設定です。警告の内容を理解し、実際にリスクがあるかを判断してください。
3. リスクがある場合は修正する:
実際にセキュリティリスクがある場合は、CDKコードを修正してリスクを解消します。例えば、AwsSolutions-ELB2(ALBがアクセスログを記録していない)の警告が表示された場合、ALBのアクセスログをS3に記録する設定を追加します。
4. 正当な理由がある場合は警告を抑制する:
正当な理由があって警告を無視したい場合、NagSuppressions.addResourceSuppressions() を使用して警告を抑制できます。ただし、抑制する際は、必ず抑制理由をコメントで記載してください。具体的な実装方法は、セクション2-4で説明します。
5. チーム内でセキュリティ基準を統一する: cdk-nagの警告をどのように扱うか(すべて修正する、一部を抑制する)をチーム内で統一します。コードレビュー時にcdk-nagの警告が表示された場合の対応方法を決めておくと、レビューがスムーズになります。
2-3. Task Definitionの環境変数をParameter Storeから取得(cdk-nag警告への対応例)
Task Definitionで環境変数を直接指定すると、機密情報が平文で保存されるリスクがあります。AWS Systems Manager Parameter StoreまたはSecrets Managerを使用して、環境変数を安全に管理します。
lib/cdk-iac-project-stack.tsで、Systems Manager Parameter Storeのパラメータを作成し、Task Definitionで参照するように修正します
import * as ssm from 'aws-cdk-lib/aws-ssm';
export class CdkIacProjectStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: CdkIacProjectStackProps) {
super(scope, id, props);
const envName = props.tags?.Environment || 'dev';
// ... 省略(前述のコード) ...
// Systems Manager Parameter Storeに環境変数を保存
const nodeEnvParameter = new ssm.StringParameter(this, 'NodeEnvParameter', {
parameterName: `/iac-learning/${envName}/NODE_ENV`,
stringValue: 'production',
description: 'NODE_ENV environment variable for Next.js application',
});
// ECSタスク実行ロールにParameter Storeへのアクセス権限を付与
taskExecutionRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'ssm:GetParameters',
'ssm:GetParameter',
'ssm:GetParametersByPath',
],
resources: [
nodeEnvParameter.parameterArn,
],
})
);
// ... 省略(前述のコード) ...
// コンテナの追加(環境変数をParameter Storeから取得)
const container = taskDefinition.addContainer('NextjsContainer', {
image: ecs.ContainerImage.fromAsset('./nextjs-app', {}),
logging: ecs.LogDrivers.awsLogs({
streamPrefix: 'nextjs-app',
logRetention: 7,
}),
secrets: {
NODE_ENV: ecs.Secret.fromSsmParameter(nodeEnvParameter),
},
});
// ... 省略(以降のコード) ...
}
}【解説】Systems Manager Parameter Storeと環境変数の管理
直接指定の問題点:
- 環境変数をTask Definitionに直接記述すると、機密情報が平文で保存される
- 環境変数を変更するたびに、Task Definitionを更新して再デプロイが必要
- コードリポジトリに機密情報が含まれるリスク
Parameter Storeの利点:
- 環境変数を一元管理でき、変更時にTask Definitionの更新が不要
- 環境ごと(dev/stg/prod)に異なる値を設定可能
- 機密情報を暗号化して保存可能(SecureString)
- アクセス権限をIAMロールで制御可能
実装のポイント:
secretsプロパティを使用して、Parameter Storeから環境変数を取得- Task Execution RoleにParameter Storeへの読み取り権限を付与
- パラメータ名は環境ごとに分ける(例:
/iac-learning/dev/NODE_ENV)
- エラーが消えたか確認します
cdk synth --context env=dev --region us-east-1以下のエラーがなくなっているはずです。
[Error at /IacStack-dev/MyTaskDef/Resource] AwsSolutions-ECS2: The ECS Task Definition includes a container definition that directly specifies environment variables. Use secrets to inject environment variables during container startup from AWS Systems Manager Parameter Store or Secrets Manager instead of directly specifying plaintext environment variables. Updates to direct environment variables require operators to change task definitions and perform new deployments.2-4. その他の警告の抑制
cdk-nagの警告のうち、このカリキュラムでは許容可能な警告を抑制します。
lib/cdk-iac-project-stack.tsに、警告の抑制設定を追加します
import { NagSuppressions } from 'cdk-nag';
export class CdkIacProjectStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: CdkIacProjectStackProps) {
super(scope, id, props);
const envName = props.tags?.Environment || 'dev';
const isProduction = envName === 'prod';
// ... 省略(VPC、セキュリティグループ、ALB、ECS等のリソース定義) ...
// VPC Flow Logの警告を抑制(開発環境では不要)
NagSuppressions.addResourceSuppressions(vpc, [
{
id: 'AwsSolutions-VPC7',
reason: 'VPC Flow Logs are not required for development environment',
},
], true); // applyToChildrenをtrueにして、VPCの全リソースに適用
// タスク実行ロールのAWS管理ポリシー使用の警告を抑制
NagSuppressions.addResourceSuppressions(taskExecutionRole, [
{
id: 'AwsSolutions-IAM4',
reason: 'AmazonECSTaskExecutionRolePolicy is AWS managed policy required for ECS task execution',
appliesTo: ['Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy'],
},
]);
// タスク実行ロールとタスクロールのワイルドカード権限の警告を抑制
NagSuppressions.addResourceSuppressions(taskExecutionRole, [
{
id: 'AwsSolutions-IAM5',
reason: 'ECR and CloudWatch Logs require wildcard permissions for resource access',
appliesTo: ['Resource::*'],
},
], true); // DefaultPolicyにも適用
NagSuppressions.addResourceSuppressions(taskRole, [
{
id: 'AwsSolutions-IAM5',
reason: 'Task role requires wildcard permissions for dynamic resource access',
appliesTo: ['Resource::*'],
},
], true); // DefaultPolicyにも適用
// ALBセキュリティグループの0.0.0.0/0アクセス許可の警告を抑制
NagSuppressions.addResourceSuppressions(albSecurityGroup, [
{
id: 'AwsSolutions-EC23',
reason: 'ALB needs to accept traffic from the internet on ports 80 and 443',
},
], true); // DefaultPolicyにも適用
// ALBのアクセスログ無効化の警告を抑制
NagSuppressions.addResourceSuppressions(alb, [
{
id: 'AwsSolutions-ELB2',
reason: 'Access logs are not required for development environment',
},
], true); // DefaultPolicyにも適用
// ... 省略(以降のコード) ...
}
}【解説】cdk-nag警告の抑制基準と環境による違い
cdk-nagの警告を抑制する際は、「なぜ抑制するのか」を明確にし、環境(dev/stg/prod)によって異なる基準を設定することが重要です。
開発環境(dev)で抑制可能な警告:
- VPC Flow Log(AwsSolutions-VPC7): 開発環境ではトラフィック分析が不要なため、コスト削減のために無効化
- S3サーバーアクセスログ(AwsSolutions-S1): ALBログバケット自体のアクセスログは開発環境では不要
- S3 SSL/TLS強制(AwsSolutions-S10): ALBログ配信はAWSサービス間通信のため、SSL/TLS強制が技術的に制限される
- 環境変数直接指定(AwsSolutions-ECS2): 機密情報を含まない設定値(NODE_ENV=productionなど)は直接指定可能
ステージング環境(stg)で修正推奨の警告:
- VPC Flow Log: 本番環境に近い環境として、有効化を検討
- ワイルドカード権限(AwsSolutions-IAM5): 可能な限り具体的なリソースARNを指定する方向で修正
本番環境(prod)で必須対応の警告:
- VPC Flow Log: セキュリティ監査のために必須
- ALBアクセスログ: トラフィック分析とトラブルシューティングのために必須
- S3サーバーアクセスログ: 本番環境では監査証跡として有効化を検討
- S3 SSL/TLS強制: 可能な限りバケットポリシーでHTTPS接続を強制(ただしALBログ配信では制限あり)
- ワイルドカード権限: 最小権限の原則に従い、具体的なリソースARNを指定
- AWS管理ポリシー: カスタムポリシーで必要な権限のみを付与
すべての環境で許容可能な警告:
- ALBセキュリティグループの0.0.0.0/0許可(AwsSolutions-EC23): インターネット向けALBでは正常な設定
- AWS管理ポリシー(AmazonECSTaskExecutionRolePolicy): ECSタスク実行に必要な標準ポリシー
- S3 SSL/TLS強制(AwsSolutions-S10): ALBログ配信はAWSサービス間通信であり、SSL/TLS強制が技術的に制限される
このカリキュラムでは、学習目的のため、開発環境の基準で警告を抑制しています。実務では、環境ごとに異なる抑制基準を設定し、本番環境ではできる限り警告を修正することが推奨されます。
3. AWS CDKのベストプラクティスの適用
タグ付け、ネーミング規則、リソース削除保護を実装します。
3-1. タグ付けの実装
- セクション2で更新した
bin/cdk-iac-project.tsに、アプリ全体に共通タグを追加します
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { CdkIacProjectStack } from '../lib/cdk-iac-project-stack';
import { AwsSolutionsChecks } from 'cdk-nag';
import { Aspects } from 'aws-cdk-lib';
const app = new cdk.App();
// cdk-nagのAWS Solutions Checksを追加
Aspects.of(app).add(new AwsSolutionsChecks({ verbose: true }));
// アプリ全体に共通タグを付ける
cdk.Tags.of(app).add('Project', 'IaC-Learning');
cdk.Tags.of(app).add('ManagedBy', 'AWS-CDK');
cdk.Tags.of(app).add('CostCenter', 'Engineering');
// 省略
【解説】タグ付け戦略とアプリレベルでのタグ設定
AWSリソースのタグは、リソース管理とコスト配分の両面で非常に重要です。CDKでは、タグを設定する2つのレベルがあります。
1. アプリレベルでのタグ設定(共通タグ):
cdk.Tags.of(app).add() を使用すると、アプリ内のすべてのスタック、すべてのリソースに自動的にタグが付与されます。プロジェクト全体で共通のタグ(Project、ManagedBy、CostCenterなど)は、アプリレベルで設定するのがベストプラクティスです。これにより、スタックごとに同じタグを何度も記述する必要がなくなります。
2. スタックレベルでのタグ設定(環境固有のタグ):
スタック作成時の tags プロパティで設定されたタグは、そのスタック内のリソースにのみ適用されます。環境固有のタグ(Environment: dev/stg/prodなど)は、スタックごとに異なる値を設定する必要があるため、スタック作成時に指定します。
3. タグを使ったコスト分析:
AWS Cost Explorerでは、タグをベースにしたコスト分析が可能です。例えば、Project: IaC-Learning タグが付いたリソースのコストを集計することで、このプロジェクトに関連するすべてのAWSリソースのコストを一元管理できます。Environment: dev タグと Environment: prod タグを使用することで、開発環境と本番環境のコストを分けて分析できます。
4. リソースの検索とフィルタリング:
AWSコンソールのResource Groupsや、AWS CLIの --filters オプションでは、タグをベースにしたリソース検索が可能です。例えば、「このプロジェクトに関連するすべてのEC2インスタンスを一覧表示したい」という場合、Project: IaC-Learning タグでフィルタリングすれば、簡単に見つけられます。
5. 自動化スクリプトでの活用: タグを使用することで、自動化スクリプトが「どのリソースに対して操作を行うべきか」を判断できます。例えば、「開発環境(Environment: dev)のリソースを毎晩停止して、翌朝起動する」というスクリプトを作成できます。
6. セキュリティとコンプライアンス: タグを使用して、「誰がこのリソースを管理しているか(Owner タグ)」「このリソースは本番環境か(Environment タグ)」を記録することで、セキュリティ監査やコンプライアンスチェックが容易になります。
推奨されるタグの例としては、Project(プロジェクト名)、Environment(環境:dev/stg/prod)、Owner(所有者)、CostCenter(コストセンター)、ManagedBy(管理方法:AWS-CDK/Terraform/Manual)、ExpirationDate(削除予定日:一時的なリソース用)などがあります。
3-2. ネーミング規則の統一
lib/cdk-iac-project-stack.tsで、リソース名に環境名を含めるように更新します(1-1で更新済みなので今回は確認だけです。)
export class CdkIacProjectStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: CdkIacProjectStackProps) {
super(scope, id, props);
const envName = props.tags?.Environment || 'dev';
// VPCの作成(環境名を含める)
const vpc = new ec2.Vpc(this, 'MyVpc', {
vpcName: `iac-learning-vpc-${envName}`,
maxAzs: 2,
natGateways: 1,
// ... 省略 ...
});
// ECSクラスターの作成(環境名を含める)
const cluster = new ecs.Cluster(this, 'MyCluster', {
vpc: vpc,
clusterName: `iac-learning-cluster-${envName}`,
containerInsightsV2: ecs.ContainerInsights.ENABLED,
});
// ECRリポジトリの作成(環境名は含めない、共通リポジトリとして使用)
const repository = new ecr.Repository(this, 'MyRepository', {
repositoryName: 'iac-learning-nextjs-app',
// ... 省略 ...
});
// ALBの作成(環境名を含める)
const alb = new elbv2.ApplicationLoadBalancer(this, 'MyAlb', {
vpc: vpc,
internetFacing: true,
loadBalancerName: `iac-alb-${envName}`,
securityGroup: albSecurityGroup,
// ... 省略 ...
});
// ... 省略(以降のリソース定義) ...
}
}【解説】ネーミング規則のベストプラクティス
AWSリソースのネーミング規則は、リソースの目的や所属を一目で理解できるようにするために重要です。統一されたネーミング規則により、以下のメリットが得られます。
1. リソースの識別が容易:
iac-learning-vpc-dev、iac-learning-vpc-prod というように、プロジェクト名と環境名を含めることで、「このVPCはどのプロジェクトのどの環境か」が一目で分かります。
2. 名前の衝突を防ぐ: AWSの一部のリソース(例:S3バケット、ECRリポジトリ)は、グローバルに一意な名前が必要です。プロジェクト名と環境名を含めることで、他のプロジェクトや他のAWSアカウントと名前が衝突する可能性を減らせます。
3. 自動化スクリプトでの活用:
ネーミング規則が統一されていると、スクリプトで名前のパターンマッチングを使用してリソースを検索できます。例えば、iac-learning-*-dev というパターンで、開発環境のすべてのリソースを検索できます。
推奨されるネーミング規則のフォーマットは <プロジェクト名>-<リソースタイプ>-<環境名> です。例えば、iac-learning-vpc-dev、iac-learning-cluster-prod、iac-learning-alb-stg などです。ただし、ECRリポジトリのように、複数の環境で共有するリソースの場合は、環境名を含めず、イメージタグで環境を区別します。
一部のAWSリソースには、名前の長さ制限があります。例えば、ECSクラスター名は255文字まで、S3バケット名は63文字までです。ネーミング規則を設計する際は、これらの制限を考慮してください。
3-3. リソース削除保護の設定
lib/cdk-iac-project-stack.tsで、本番環境のリソースに削除保護を設定します
export class CdkIacProjectStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: CdkIacProjectStackProps) {
super(scope, id, props);
const envName = props.tags?.Environment || 'dev';
const isProduction = envName === 'prod';
// ... 省略(前述のリソース定義) ...
// ECRリポジトリの作成(本番環境では削除保護)
const repository = new ecr.Repository(this, 'MyRepository', {
repositoryName: 'iac-learning-nextjs-app',
removalPolicy: isProduction ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY,
emptyOnDelete: !isProduction,
imageScanOnPush: true,
});
// ALBの削除保護(本番環境のみ)
const alb = new elbv2.ApplicationLoadBalancer(this, 'MyAlb', {
vpc: vpc,
internetFacing: true,
loadBalancerName: `iac-alb-${envName}`,
securityGroup: albSecurityGroup,
deletionProtection: isProduction, // 本番環境では削除保護を有効化
// ... 省略 ...
});
// ... 省略(以降のリソース定義) ...
}
}【解説】削除保護とRemovalPolicyの使い分け
AWSリソースの削除保護は、誤ってリソースを削除してしまうことを防ぐための重要な機能です。特に、本番環境のデータベースやストレージなど、削除するとデータが失われるリソースには、必ず削除保護を設定すべきです。
**RemovalPolicy(削除ポリシー)**は、CloudFormationスタックを削除したときに、リソースをどう扱うかを指定します。
cdk.RemovalPolicy.DESTROY:スタック削除時にリソースも削除(デフォルト)cdk.RemovalPolicy.RETAIN:スタック削除時にリソースを保持(削除されない)cdk.RemovalPolicy.SNAPSHOT:スタック削除時にスナップショットを作成してから削除(RDSなど)
**DeletionProtection(削除保護)**は、リソース自体に削除保護を設定し、削除操作を実行しても削除されないようにします。削除するには、まず削除保護を無効化してから削除する必要があります。ALB、RDS、DynamoDBなどが対応しています。
本番環境では、以下のリソースに削除保護を設定することが推奨されます。RDSデータベース(DeletionProtection + RemovalPolicy.RETAIN + 自動バックアップ)、S3バケット(バージョニング有効化 + RemovalPolicy.RETAIN)、DynamoDBテーブル(DeletionProtection + RemovalPolicy.RETAIN + Point-in-Time Recovery)、ALB(DeletionProtection)、ECRリポジトリ(RemovalPolicy.RETAIN)などです。
開発環境では、コスト削減のために、不要になったリソースを簡単に削除できるよう、cdk.RemovalPolicy.DESTROY を設定します。ただし、重要なデータを保存しているリソースには、開発環境でも削除保護を設定することを検討してください。
4. CloudFormationエクスポートによるスタック間の値共有
次のCI/CDカリキュラムで使用するために、インフラ情報をエクスポートします。
4-1. CloudFormation Outputsの設定
lib/cdk-iac-project-stack.tsで、CloudFormation OutputsにexportNameを追加します
export class CdkIacProjectStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: CdkIacProjectStackProps) {
super(scope, id, props);
// ... 省略(前述のコード) ...
// VPC IDをエクスポート
new cdk.CfnOutput(this, 'VpcId', {
value: vpc.vpcId,
description: 'VPC ID',
exportName: `IacStack-${envName}-VpcId`,
});
// ECSクラスター名をエクスポート
new cdk.CfnOutput(this, 'ClusterName', {
value: cluster.clusterName,
description: 'ECS Cluster Name',
exportName: `IacStack-${envName}-ClusterName`,
});
// ECSクラスターARNをエクスポート
new cdk.CfnOutput(this, 'ClusterArn', {
value: cluster.clusterArn,
description: 'ECS Cluster ARN',
exportName: `IacStack-${envName}-ClusterArn`,
});
// ALB DNSをエクスポート
new cdk.CfnOutput(this, 'AlbDnsName', {
value: alb.loadBalancerDnsName,
description: 'ALB DNS Name',
exportName: `IacStack-${envName}-AlbDnsName`,
});
// ALB ARNをエクスポート
new cdk.CfnOutput(this, 'AlbArn', {
value: alb.loadBalancerArn,
description: 'ALB ARN',
exportName: `IacStack-${envName}-AlbArn`,
});
// ターゲットグループARNをエクスポート
new cdk.CfnOutput(this, 'TargetGroupArn', {
value: targetGroup.targetGroupArn,
description: 'Target Group ARN',
exportName: `IacStack-${envName}-TargetGroupArn`,
});
// ECSサービス名をエクスポート
new cdk.CfnOutput(this, 'ServiceName', {
value: service.serviceName,
description: 'ECS Service Name',
exportName: `IacStack-${envName}-ServiceName`,
});
}
}- デプロイして、エクスポートされた値を確認します
cdk deploy --context env=dev --region us-east-1デプロイが完了すると、以下のような出力が表示されます。
Outputs:
IacStack-dev.VpcId = vpc-0123456789abcdef0 (IacStack-dev-VpcId)
IacStack-dev.ClusterName = iac-learning-cluster-dev (IacStack-dev-ClusterName)
IacStack-dev.AlbDnsName = iac-alb-dev-XXXXXXXXXXXX.us-east-1.elb.amazonaws.com (IacStack-dev-AlbDnsName)
...【解説】CloudFormationエクスポートとクロススタック参照
CloudFormationエクスポートは、あるスタックで作成したリソースの情報を、別のスタックから参照するための機能です。exportName を指定することで、他のスタックから Fn::ImportValue 関数でこの値を参照できます。
使用例):
const clusterName = cdk.Fn.importValue('IacStack-dev-ClusterName');
const serviceName = cdk.Fn.importValue('IacStack-dev-ServiceName');
// CodePipelineでECSサービスにデプロイ
new codepipeline_actions.EcsDeployAction({
actionName: 'DeployToECS',
service: ecs.Service.fromServiceAttributes(this, 'EcsService', {
cluster: ecs.Cluster.fromClusterAttributes(this, 'EcsCluster', {
clusterName: clusterName,
vpc: vpc,
}),
serviceName: serviceName,
}),
// ... 省略 ...
});注意点: CloudFormationエクスポートを使用している場合、エクスポート元のスタックを削除する前に、エクスポートを参照しているスタックをすべて削除する必要があります。そうしないと、「Export xxx is still in use by stack yyy」というエラーが表示され、スタック削除に失敗します。
また、エクスポート名はリージョン内で一意である必要があるため、環境名やスタック名を含めることで、名前の衝突を防ぎます。
このステップで何をしたのか
このステップでは、ステップ3で作成したシンプルなECS環境に対して、プロダクション環境で必要なベストプラクティスを適用しました。具体的には、環境分離(dev/stg/prod)の実装、cdk-nagによるセキュリティチェック、タグ付け・ネーミング規則・削除保護の実装、そしてCloudFormationエクスポートによるスタック間の値共有を行いました。
このインフラでどのような影響があるのか
この構成により、同じCDKコードから複数の環境(dev/stg/prod)を安全に管理できるようになりました。cdk-nagによるセキュリティチェックにより、デプロイ前にセキュリティリスクを発見し、修正できます。タグ付けとネーミング規則により、リソース管理とコスト配分が容易になり、削除保護により、誤ってリソースを削除するリスクが軽減されました。
CloudFormationエクスポートにより、このIaCスタックで構築したECS環境の情報を、将来的に他のスタック(CI/CDパイプライン、モニタリングシステムなど)から参照できるようになりました。これにより、スタック間の疎結合を保ちながら、必要な情報を共有する仕組みが整いました。
これは、物理的な建築プロジェクトで言えば、「設計図を建築基準法に適合させ、審査を受けて承認を得て、次の施工工程に引き継ぐ書類を作成した」という段階に相当します。これで、IaCカリキュラムは完了しました。
技術比較まとめ表
| 技術領域 | AWS | オンプレミス |
|---|---|---|
| 環境分離 | CDK Context + CloudFormationスタック 同じコードで複数環境を管理、スタック単位で削除可能 | Kubernetes Namespace、Terraform Workspace 環境ごとに設定ファイルを管理、手動で削除 |
| セキュリティチェック | cdk-nag CDKコード専用、AWS Well-Architected準拠、cdk synth時に自動実行 | tfsec、Checkov Terraform/Ansible対応、CI/CDパイプラインに統合して自動実行 |
| タグ付けとコスト管理 | AWS Cost Explorer タグベースのコスト分析、自動レポート生成、AWS Budgetsでアラート設定 | 自社のコスト管理システム 手動でコスト集計、Excelやスプレッドシートで管理 |
| スタック間の値共有 | CloudFormation Exports Fn::ImportValueでクロススタック参照、リージョン内で一意 | Terraform Remote State S3/ConsulにStateを保存し、terraform_remote_stateで参照 |
| 削除保護 | RemovalPolicy + DeletionProtection スタック削除時の動作を制御、リソースごとに設定可能 | 手動削除保護 インフラ削除前にチェックリストで確認、手作業で削除を防止 |
学習において重要な技術的違い
1. 環境分離とインフラの再現性
- AWS:CDK ContextとCloudFormationスタックで、同じコードから複数の環境を作成可能。環境ごとの設定(タスク数、リソースサイズ)をJSON形式で一元管理し、スタック削除で環境全体を一括削除できる
- オンプレミス:Kubernetes NamespaceやTerraform Workspaceで環境を分離するが、環境ごとに設定ファイルを管理する必要があり、削除時にリソースの削除漏れが発生しやすい
2. セキュリティチェックの自動化
- AWS:cdk-nagは、CDKコード専用のセキュリティチェックツールで、AWS Well-Architected Frameworkに準拠。cdk synth時に自動実行され、デプロイ前に警告を表示
- オンプレミス:TerraformのtfsecやCheckovは、マルチクラウド対応のセキュリティチェックツール。CI/CDパイプラインに統合して自動実行する設定を手動で行う
3. タグ付けとコスト管理の可視化
- AWS:Cost Explorerで、タグをベースにしたコスト分析が自動的に可能。タグを設定するだけで、プロジェクトごと、環境ごとのコストを可視化できる
- オンプレミス:VMwareやOpenStackでもタグ付けは可能だが、コスト分析は自社のコスト管理システムで手動集計。Excelやスプレッドシートでコストを管理することが多い
4. スタック間の値共有とインフラの疎結合
- AWS:CloudFormation ExportsとFn::ImportValueで、スタック間の値共有が標準機能として提供。エクスポート元のスタックを削除する前に、参照元のスタックを削除する必要がある
- オンプレミス:Terraform Remote Stateで、S3やConsulにStateを保存し、他のTerraformプロジェクトから参照。State管理の設定とロックメカニズム(DynamoDB)の構築が必要
実践チェック:CDKコードと実行結果で証明しよう
下記のチェック項目について、実際にCDKコードを記述し、cdk deploy で構築できていることを確認し、各項目ごとに該当画面のスクリーンショットを撮影して提出してください。
-
cdk.jsonのcontextセクションに環境設定(dev、staging、production)が定義されている -
cdk deploy --context env=devでdev環境のみがデプロイされる -
cdk ls --context env=stagingでstaging環境のスタック名が表示される -
すべてのリソースに共通タグ(Project、ManagedBy、CostCenter)がアプリレベルで設定されている
-
環境固有のタグ(Environment)がスタックレベルで設定されている
-
リソース名に環境名が含まれ、ネーミング規則が統一されている(例:
iac-learning-vpc-dev) -
本番環境のリソースに削除保護(RemovalPolicy.RETAIN、DeletionProtection)が設定されている
-
cdk-nagがインストールされ、
cdk synth実行時にセキュリティチェックが実行される -
ALBアクセスログがS3バケットに記録される設定になっている
-
cdk-nagの警告のうち、許容可能な警告(AwsSolutions-VPC7、AwsSolutions-EC23など)が
NagSuppressions.addResourceSuppressions()で抑制されている -
cdk synth実行後、抑制した警告が表示されず、ELBログ警告のみが解消されている -
CloudFormation Outputsに
exportNameが設定され、スタックの「Outputs」タブで確認できる -
エクスポートされた値(VpcId、ClusterName、AlbDnsNameなど)がCloudFormationコンソールで確認できる
提出方法: 各項目ごとにスクリーンショットを撮影し、まとめて提出してください。 ファイル名やコメントで「どの項目か」が分かるようにしてください。 AWSコンソールのスクリーンショットでは、リソース名、タグ、エクスポート値が見えるようにしてください。
構成図による理解度チェック
ステップ3の構成図に、このステップで追加した要素を追記して、最終的なインフラ構成を完成させましょう。
なぜ構成図を更新するのか?
ステップ4では、物理的なAWSリソースは追加していませんが、環境分離、セキュリティチェック、タグ付け、エクスポート設定など、「インフラの品質管理と他システムとの連携」に関する重要な要素を追加しました。構成図を更新することで、「このインフラがどのように管理されているか」「他のスタックとどのように連携するか」を視覚的に理解できます。
- 環境分離の理解: 同じCDKコードから複数の環境(dev/stg/prod)がどのように作成されるかを理解する
- セキュリティと品質管理: cdk-nagがどのタイミングで実行され、どのようにセキュリティを担保するかを理解する
- スタック間連携: CloudFormationエクスポートで、どの情報が他のスタックに引き継がれるかを確認する
構成図の書き方
ステップ3で作成した構成図をベースに、以下の要素を追記してみましょう。
- 環境分離: 構成図のタイトルに「dev環境」「staging環境」「production環境」を明記し、同じ構成が複数環境存在することを示す
- cdk-nag: CDKコードとCloudFormationスタックの間に、「cdk synth時にセキュリティチェック」という注釈を付ける
- CloudFormationスタック: AWSリソースがCloudFormationスタックで管理されていることを明示
- CloudFormationエクスポート: スタックからエクスポートされた値(VpcId、ClusterName、AlbDnsNameなど)を点線の矢印で示し、「他のスタックから参照可能」と注釈を付ける
- タグとネーミング規則: リソースにタグ(Project、Environment)が付いていることを、アイコンの近くに小さく記載
💡 ヒント: 環境分離を表現するために、同じ構成図を3つ並べて「dev」「stg」「prod」とラベル付けすると分かりやすいです。CloudFormationエクスポートは、スタックから外部に向かう矢印で表現しましょう。
理解度チェック:なぜ?を考えてみよう
AWSの各リソースや設計には、必ず”理由”や”目的”があります。 下記の「なぜ?」という問いに自分なりの言葉で答えてみましょう。 仕組みや設計意図を自分で説明できることが、真の理解につながります。 ぜひ、単なる暗記ではなく「なぜそうなっているのか?」を意識して考えてみてください。
Q. 環境分離で、デプロイ時に --context env=xxx で環境を明示的に指定する理由は何ですか?
Q. cdk-nagの警告が表示された場合、すべての警告を修正する必要がありますか?警告を抑制(無視)しても良い場合はどのようなケースですか?
Q. AWSリソースにタグを付ける理由は何ですか?アプリレベル(cdk.Tags.of(app).add())とスタックレベル(tags プロパティ)でタグを設定する使い分けは何ですか?
Q. CloudFormationエクスポートを使用する理由は何ですか?環境変数やパラメータストア(Systems Manager Parameter Store)で値を共有する方法と比較して、どのようなメリット・デメリットがありますか?
Q. 本番環境のリソースに削除保護(RemovalPolicy.RETAIN、DeletionProtection)を設定する理由は何ですか?開発環境では削除保護を設定しない理由は何ですか?
今回のステップで利用したAWSサービス名一覧
- CDK Context:環境固有の設定をJSON形式で管理し、複数環境を効率的にデプロイ
- cdk-nag:CDKコードがAWS Well-Architected Frameworkに準拠しているかをチェックするツール
- CloudFormation Exports:スタック間でリソース情報を共有するための機能、Fn::ImportValueで参照
- AWS Cost Explorer:タグベースのコスト分析とレポート生成ツール
- S3:ALBアクセスログを保存するオブジェクトストレージ
カリキュラム完了おめでとうございます!
これで、AWS CDKを使ったInfrastructure as Code(IaC)の学習カリキュラムが完了しました。このカリキュラムを通じて、以下のスキルを習得しました。
習得したスキル
-
AWS CDKの基礎とVPC構築
- AWS CDKのインストールと基本概念
- VPC、サブネット、インターネットゲートウェイのコード化
- TypeScriptによるインフラ定義
-
ECSクラスターとALBの構築
- ECS on Fargateクラスターの作成
- Application Load Balancerとターゲットグループの設定
- セキュリティグループとIAMロールの管理
-
Next.jsアプリケーションのコンテナ化とデプロイ
- Dockerfileの作成とマルチステージビルド
- Amazon ECRへのイメージプッシュ
- ECSタスク定義とサービスの作成
- ECSセキュリティグループとトラフィック制御
-
環境分離とベストプラクティス
- 環境分離(dev/stg/prod)の実装
- cdk-nagによるセキュリティチェック
- タグ付け、ネーミング規則、削除保護の実装
- CloudFormationエクスポートによるスタック間の値共有
次のステップ
このカリキュラムで構築したECS環境を基盤として、さらなる学習を進めることができます。CloudFormationエクスポートで設定した値を使って、CI/CDパイプラインやモニタリングシステムなど、別のスタックとの連携が可能です。
また、以下の発展的な学習も検討してください:
- AWS CDK Pipelinesの学習:CDKコード自体を自動的にデプロイするセルフミューテーティングパイプライン
- Kubernetes(EKS)への移行:ECS on FargateからAmazon EKSへの移行
- マルチリージョン・マルチアカウント構成:AWS Organizationsを使ったエンタープライズIaC
- AWS Copilotの学習:ECSデプロイをさらに簡単にするCLIツール
フリーランスエンジニアとして、このスキルを武器に、高単価案件を獲得してください!