はじめに
前シリーズ「AWS CDKでNext.jsをデプロイする」では、次の構成を作成しました。
- ECR:コンテナイメージ保存
- ECS(Fargate):アプリ実行
- ALB:外部公開
- ECS Service:タスク常時稼働
- Security Group:通信制御
これにより、Next.jsアプリをAWS上で公開する最小構成が完成しました。
ただし、この構成は検証用のシンプルな構成です。
実際の本番環境では、VPCを利用してネットワークを分離する構成が一般的です。
例えば次のような構成です。
- 専用VPCを作成する
- Public SubnetとPrivate Subnetを分離する
- アプリケーションはPrivate Subnetに配置する
本記事では、この 本番環境を想定した VPC 構成 を AWS CDK で構築していきます。
本シリーズの構成
1. PC構成編(本記事)
2. Route53独自ドメイン設定編
3. ACM HTTPS化編
4. WAFセキュリティ編
5. ECS Auto Scaling編
6. CI/CD構築編
VPC構成
※ALB(Application Load Balancer)は、論理的には1つのリソースですが、実際には複数AZのPublic Subnetにまたがって配置されます。
本番環境では、VPC内部をPublic SubnetとPrivate Subnetに分ける構成が一般的です。
ALBはインターネットからのリクエストを受け付け、Target Groupを通じて複数のECSタスクへ分散します。
また、クロスAZでトラフィックを分散するため、異なるAZに配置されたECSタスクへもルーティングされます。
一方で、ECS(Fargate)は各AZのPrivate Subnet上に配置されます。
これにより、ECSタスクはインターネットから直接アクセスできず、ALB経由の通信のみを受け付ける構成となります。
本番ではECSタスクは複数稼働させます。
(AZ障害対策・負荷分散・無停止デプロイ)
図ではこの本番構成(複数タスク・複数AZ)を示しています。
本記事ではネットワーク構成にフォーカスし、desiredCountやAutoScalingの設定は 第5回「ECS Auto Scaling を設定する」 で解説します。
Private Subnetは外部へ直接アクセスできないため、ECRのイメージ取得や外部API呼び出しは
通常はNAT Gatewayを経由して行われます。
(VPC Endpointを利用することでNATを経由しない構成も可能です)
また、VPC 作成時にはサブネットごとにルートテーブルが自動作成され、次のように設定されます。
- Public Subnet:0.0.0.0/0 → Internet Gateway
- Private Subnet:0.0.0.0/0 → NAT Gateway
ネットワーク設計のポイント
本記事で採用している構成には、いくつかの設計意図があります。
- インターネットからの入口はALBのみに限定する
- アプリケーションはPrivate Subnetに配置し、直接アクセスを防ぐ
- 外部通信はNAT Gateway経由に制限する
- 可用性向上のため、複数AZにリソースを分散する
これにより、
- セキュリティ(外部からの直接アクセス遮断)
- 可用性(AZ障害耐性)
- 運用性(通信経路の明確化)
をバランスよく満たす構成となります。
インフラ設計(VPCをNetworkStackとして分離する)
VPCはネットワーク基盤のため、アプリケーションStackとは分離して管理します。
今回のStack構成は次の通りです。
NetworkStack
└ VPC
EcsStack
└ ECS Cluster
AlbStack
└ ALB
ServiceStack
└ ECS Service
依存関係は次のようになります。
NetworkStack
↓ ↓
AlbStack EcsStack
↓ ↓
ServiceStack
ALBとECSはどちらもVPCに依存しており、ECS ServiceはALBのTarget GroupおよびECS Clusterの両方に依存する構成になります。
1-1. NetworkStack(VPC作成)
VPCを作成するStackを作成します。
lib/network-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
export class NetworkStack extends cdk.Stack {
public readonly vpc: ec2.Vpc;
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
this.vpc = new ec2.Vpc(this, 'AppVpc', {
maxAzs: 2,
natGateways: 2,
});
}
}
この設定により次のリソースが作成されます。
- Public Subnet ×2
- Private Subnet ×2
- NAT Gateway ×2
本番環境では、各AZに NAT Gatewayを1台ずつ配置する構成が推奨されます。
理由:
NAT Gateway は AZ障害時にフェイルオーバーしない
クロスAZ通信は遅延・料金増につながる
Private Subnet のタスク(ECS)は同じAZ内のNATにルーティングされるのが正しい
1-2. EcsStack
NetworkStackで作成したVPCを利用します。
lib/ecs-stack.ts
interface EcsStackProps extends cdk.StackProps {
vpc: ec2.IVpc;
}
export class EcsStack extends cdk.Stack {
public readonly cluster: ecs.Cluster;
constructor(scope: Construct, id: string, props: EcsStackProps) {
super(scope, id, props);
this.cluster = new ecs.Cluster(this, 'NextjsCluster', {
vpc: props.vpc,
});
//...
}
}
1-3. ServiceStack(Private Subnet配置)
ECS Serviceは Private Subnet に配置します。
lib/service-stack.ts
assignPublicIp: false,
vpcSubnets: {
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
}
1-4. bin配下のtsファイルを修正する
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import { EcrStack } from '../lib/ecr-stack';
import { NetworkStack } from '../lib/network-stack';
import { EcsStack } from '../lib/ecs-stack';
import { AlbStack } from '../lib/alb-stack';
import { ServiceStack } from '../lib/service-stack';
const app = new cdk.App();
const env = {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
};
new EcrStack(app, 'NextjsEcrStack', {
env
});
const networkStack = new NetworkStack(app, 'NextjsNetworkStack', {
env,
});
const ecsStack = new EcsStack(app, 'NextjsEcsStack', {
env,
vpc: networkStack.vpc,
});
const albStack = new AlbStack(app, 'NextjsAlbStack', {
env,
vpc: networkStack.vpc,
});
new ServiceStack(app, 'NextjsServiceStack', {
env,
cluster: ecsStack.cluster,
taskDefinition: ecsStack.taskDefinition,
targetGroup: albStack.targetGroup,
vpc: networkStack.vpc,
albSecurityGroup: albStack.albSecurityGroup,
});
1-5. デプロイ
デフォルトVPCから専用VPCに移行する場合の注意点
今回の構成では新しいVPCを作成するため、
既存の ALB / ECS / ECS Service がデフォルトVPCに紐づいている場合は
そのまま更新できないケースがあります。
その場合は、以下の「既存Stack削除」を実施してから再デプロイしてください。
1. 既存Stack削除(移行時のみ)
cdk destroy NextjsServiceStack NextjsAlbStack NextjsEcsStack --profile <プロファイル名>
※ ECRまで作り直す必要はなく、通常はそのまま利用できます。
※ 新規環境の場合は、destroyは不要です。
2. デプロイ
cdk deploy NextjsServiceStack --profile <プロファイル名>
ServiceStackが依存している以下のStackも自動的にデプロイされます。
- NetworkStack
- EcsStack
- AlbStack
AWSコンソールで確認
VPC
1.AWSコンソール → VPC を開く
2.左メニュー → 「お使いのVPC」をクリック
3.作成したVPC(vpc-xxxx)をクリック
4.「リソースマップ」タブを開く
以下を確認します。
- Public Subnet が2つ存在する
- Private Subnet が2つ存在する
また、それぞれのサブネットをクリックし、詳細画面で以下を確認します。
Public Subnet
- パブリック IPv4 アドレスを自動割り当て →
はい
Private Subnet
- パブリック IPv4 アドレスを自動割り当て →
いいえ
ECS
1.AWSコンソール → ECS を開く
2.クラスター → 作成されたクラスター → タスクタブ → 対象のタスクをクリック
3.「設定」セクションで以下を確認します。
- サブネット →
subnet-xxxx - パブリック IP →
- - プライベート IP →
10.x.x.x
次に、サブネットのリンク(subnet-xxxx)をクリックし、サブネット詳細画面で以下を確認します。
- パブリック IPv4 アドレスを自動割り当て →
いいえ
これにより、ECSタスクが Private Subnet に配置されていることを確認できます。
ALB
1.AWSコンソール → EC2 を開く
2.左メニュー ロードバランサー をクリック
3.作成された ALB をクリック
4.「詳細」セクションで以下を確認します。
- スキーム →
internet-facing - VPC →
作成したVPC - ステータス →
Active
次に、アベイラビリティーゾーンに表示されるサブネットのリンク(subnet-xxxx)をクリックし、サブネット詳細画面で以下を確認します。
- パブリック IPv4 アドレスを自動割り当て →
はい
※ サブネットは2つ表示されるため、それぞれ確認します。
これにより、ALBが Public Subnet に配置されていることを確認できます。
NAT Gateway
1.AWSコンソール → VPC を開く
2.左メニュー → NATゲートウェイ をクリック
3.以下を確認します。
- NAT Gatewayが2つ作成されている
- 各 NAT Gatewayが別々のPublic Subnet(AZ-A / AZ-B)に属している
- ステータス →
Available
次回
次回はRoute53を使って独自ドメインを設定し、ALB経由でアプリを公開します。
これにより、ALBのデフォルトドメインではなく、独自ドメインでアクセスできる環境が完成します。