はじめに
第4回では、ECS Serviceを作成し、ALB経由でNext.jsアプリをAWS上で公開しました。
ここまでで、以下の構成が動作しています。
- ECR:コンテナイメージ
- ECS(Fargate):アプリ実行
- ALB:外部公開
- ECS Service:タスク常時稼働
この構成は検証用の最小構成であり、通信制御の面で改善の余地があります。
今回はSecurity Groupを見直し、ECSへの通信をALB経由のみに制限します。
これにより、ECSタスクへ直接アクセスすることはできず、ALB経由の通信のみ許可されます。
本シリーズの構成
1. ECR編
2. ECS Task Definition編
3. ALB編
4. ECS Service編
5. セキュリティ編(本記事)
構成全体と今回の作業範囲
今回はECS ServiceのSecurity Groupを見直し、通信経路を整理します。
変更するのはECS ServiceとTaskのネットワーク設定です。
5-1. ALBのSecurity Groupを外部参照できるようにする
ECSタスクへの通信をALB経由のみに制限するために、
まずはALBのSecurity GroupをServiceStackから参照できるようにします。
対象ファイル
lib/alb-stack.ts
AlbStackでSecurity Groupを公開する
ALB用Security Groupを外部参照できるようSecurity Groupプロパティを追加します。
public readonly albSecurityGroup: ec2.SecurityGroup;
このSecurity GroupプロパティをALBに適用するように修正します。
// albSgをthis.albSecurityGroupに変更
// ALB用Security Group
this.albSecurityGroup = new ec2.SecurityGroup(this, 'AlbSecurityGroup', {
vpc: props.vpc,
allowAllOutbound: true,
description: 'Security group for public ALB',
});
// インターネットからのHTTP(80)を許可
this.albSecurityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP from Internet');
// ALB作成(作成したSGを適用)
const alb = new elbv2.ApplicationLoadBalancer(this, 'LoadBalancer', {
vpc: props.vpc,
internetFacing: true, // インターネット公開
securityGroup: this.albSecurityGroup,
});
5-2. ServiceStackでALBからの通信を許可する
ServiceStackでALBのSecurity Groupを利用するため、
ServiceStackPropsにALBのSecurity Groupを受け取るプロパティを追加します。
対象ファイル
lib/service-stack.ts
interface ServiceStackProps extends cdk.StackProps {
cluster: ecs.Cluster;
taskDefinition: ecs.FargateTaskDefinition;
targetGroup: elbv2.ApplicationTargetGroup;
vpc: ec2.IVpc;
albSecurityGroup: ec2.SecurityGroup;
}
ECS Service用のSecurity Groupに対して、ALBのSecurity Groupからの3000番ポート通信のみを許可します。
serviceSg.addIngressRule(
props.albSecurityGroup,
ec2.Port.tcp(3000),
'Allow traffic from ALB'
);
5-3. ServiceStackにALBのSecurity Groupを渡す
ALBのSecurity GroupをServiceStackで利用するため、
bin側でalbSecurityGroupをServiceStackに渡します。
対象ファイル
bin/cdk-nextjs-infra.ts
new ServiceStack(app, 'NextjsInfraServiceStack', {
env,
cluster: ecsStack.cluster,
taskDefinition: ecsStack.taskDefinition,
targetGroup: albStack.targetGroup,
vpc: ecsStack.vpc,
albSecurityGroup: albStack.albSecurityGroup,
});
5-4. デプロイ
設定変更後、再度デプロイします。
cdk deploy NextjsInfraServiceStack --profile <プロファイル名>
本記事で作成したスタックに含まれるAWSリソースは、削除するまで料金が発生します。
検証が不要になった場合は、以下のコマンドでスタックを削除してください。
cdk destroy <スタック名> --profile <プロファイル名>
5-5. 動作確認
ALBのDNSへアクセス
AWSコンソール EC2 → ロードバランサー からDNS名を確認します。
http://<ALB DNS>
Next.js画面が表示されれば成功です。
Security Groupの確認
ECSへの通信がALBからのみに制限されているかを確認します。
AWSコンソールで以下を開きます。
EC2 → セキュリティグループ → 作成したセキュリティグループ
インバウンドルール
- タイプが
カスタム TCPになっていることを確認 - ポート範囲が
3000になっていることを確認 - Sourceが
sg-xxxxxxxx (ALBのSecurity Group)になっていることを確認
ALB経由の通信のみ許可されている
この設定により、ECSタスクへの通信はALB経由のみに制限されます。
最終コード(今回追加・修正したファイル)
lib/alb-stack.ts
コード全体
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
interface AlbStackProps extends cdk.StackProps {
vpc: ec2.IVpc;
}
export class AlbStack extends cdk.Stack {
public readonly targetGroup: elbv2.ApplicationTargetGroup;
public readonly albSecurityGroup: ec2.SecurityGroup;
constructor(scope: Construct, id: string, props: AlbStackProps) {
super(scope, id, props);
// ALB用Security Group
this.albSecurityGroup = new ec2.SecurityGroup(this, 'AlbSecurityGroup', {
vpc: props.vpc,
allowAllOutbound: true,
description: 'Security group for public ALB',
});
// インターネットからのHTTP(80)を許可
this.albSecurityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP from Internet');
// ALB作成(作成したSGを適用)
const alb = new elbv2.ApplicationLoadBalancer(this, 'LoadBalancer', {
vpc: props.vpc,
internetFacing: true, // インターネット公開
securityGroup: this.albSecurityGroup,
});
// Target Group作成(ECSを登録する場所)
this.targetGroup = new elbv2.ApplicationTargetGroup(this, 'TargetGroup', {
vpc: props.vpc,
port: 3000,
protocol: elbv2.ApplicationProtocol.HTTP,
targetType: elbv2.TargetType.IP, // FargateはIP指定
// ヘルスチェック設定
healthCheck: {
path: '/',
healthyHttpCodes: '200',
},
});
// Listener作成(HTTP:80)
alb.addListener('HttpListener', {
port: 80,
defaultTargetGroups: [this.targetGroup],
});
new cdk.CfnOutput(this, 'AlbDnsName', {
value: alb.loadBalancerDnsName,
});
}
}
lib/service-stack.ts
コード全体
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
interface ServiceStackProps extends cdk.StackProps {
cluster: ecs.Cluster;
taskDefinition: ecs.FargateTaskDefinition;
targetGroup: elbv2.ApplicationTargetGroup;
vpc: ec2.IVpc;
albSecurityGroup: ec2.SecurityGroup;
}
export class ServiceStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: ServiceStackProps) {
super(scope, id, props);
const serviceSg = new ec2.SecurityGroup(this, 'ServiceSecurityGroup', {
vpc: props.vpc,
allowAllOutbound: true,
});
serviceSg.addIngressRule(
props.albSecurityGroup,
ec2.Port.tcp(3000),
'Allow traffic from ALB'
);
const service = new ecs.FargateService(this, 'Service', {
cluster: props.cluster,
taskDefinition: props.taskDefinition,
desiredCount: 1,
assignPublicIp: true,
securityGroups: [serviceSg],
});
service.attachToApplicationTargetGroup(props.targetGroup);
}
}
bin/cdk-nextjs-infra.ts
コード全体
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import { EcrStack } from '../lib/ecr-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, 'NextjsInfraEcrStack', { env });
const ecsStack = new EcsStack(app, 'NextjsInfraEcsStack', { env });
const albStack = new AlbStack(app, 'NextjsInfraAlbStack', {
env,
vpc: ecsStack.vpc,
});
new ServiceStack(app, 'NextjsInfraServiceStack', {
env,
cluster: ecsStack.cluster,
taskDefinition: ecsStack.taskDefinition,
targetGroup: albStack.targetGroup,
vpc: ecsStack.vpc,
albSecurityGroup: albStack.albSecurityGroup,
});
ここまでの成果
今回の変更により、通信経路は次のように整理されました。
Internet
↓
ALB
↓
ECS(Task)
- ECS Service用Security Groupを明示
- ECSへの通信を ALBからのみに制限
- 通信経路を明確化
ECSタスクへの通信経路をALB経由のみに限定することで、
構成の安全性が向上しました。
まとめ
本シリーズでは、AWS CDKを使ってNext.jsアプリをAWS上へデプロイする手順を段階的に構築し、最終的に以下の構成が完成しました。
- ECR:コンテナイメージの保存
- ECS(Fargate):コンテナ実行環境
- ALB:外部アクセスの入口
- ECS Service:タスクの常時稼働
- Security Group:通信経路の制御
この構成により、Next.jsアプリをコンテナ化し、AWS上で安定して稼働させる基本的な環境を構築できます。
次のステップ
今回の構成でもアプリケーションを公開することはできますが、実際の本番運用ではさらに以下のような設定を追加することが一般的です。
例えば
- HTTPS化(SSL/TLS)
- 独自ドメイン設定
- WAFによる攻撃対策
- CI/CDによる自動デプロイ
などです。
これらのより実践的な内容については、別記事(別シリーズ)として投稿しようと思っています。
→ 別シリーズ投稿しました。
AWS CDKでNext.jsを本番構成にする【第1回】:VPC構成編はこちら