はじめに
第2回では、ECS ClusterとFargate用Task Definitionを作成しました。
この時点では、
- ECRにイメージはある
- Task Definitionも定義済み
- しかし、まだ外部からアクセスする入口が存在しない
という状態です。
第3回では、外部公開のためのALB(Application Load Balancer)を構築します。
本シリーズの構成
1. ECR編
2. ECS Task Definition編
3. ALB編(本記事)
4. ECS Service編
5. セキュリティ編
構成全体と今回の作業範囲
AWS全体構成の中で、本記事がどの範囲を扱うのかを確認します。
ALBとは?
ALB(Application Load Balancer)は、HTTP/HTTPS通信を処理するAWSのロードバランサーです。
ALBの主な役割は次のとおりです。
- インターネットからのHTTP/HTTPSリクエストを受け取る
- 登録されたターゲットへリクエストを転送する(負荷分散)
- ヘルスチェックで正常なターゲットのみを利用する
- パスやドメインに応じて転送先を切り替える
3-1. ALB用Stackを作成する
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;
constructor(scope: Construct, id: string, props: AlbStackProps) {
super(scope, id, props);
// ALB用Security Group
// allowAllOutbound: trueを明示
const albSg = new ec2.SecurityGroup(this, 'AlbSecurityGroup', {
vpc: props.vpc,
allowAllOutbound: true,
description: 'Security group for public ALB',
});
// インターネットからのHTTP(80)を許可
albSg.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: albSg,
});
// 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,
});
}
}
ポイント解説
VPCについて
今回は、第2回でEcsStack内から参照したVPCをそのまま利用します。
そのため、AlbStackでは新たにVPCを取得せず、外部から渡されたVPCを使用する構成にします。
ALBの通信を制御するSecurity Groupの設定
const albSg = new ec2.SecurityGroup(this, 'AlbSecurityGroup', {
vpc: props.vpc,
allowAllOutbound: true,
description: 'Security group for public ALB',
});
ALBがECS(ターゲット)へリクエストを転送するためには、ALB側のSecurity Groupでアウトバウンド通信が許可されている必要があります。
今回は検証構成として、ALBからのアウトバウンド通信をすべて許可しています。
ALBをインターネット向けに公開する設定
internetFacing: true
ALBをインターネット向けに公開するための設定です。
trueを指定すると、ALBはPublic Subnetに配置され、インターネットから直接アクセス可能になります。
Fargate構成に合わせたターゲットタイプの設定
targetType: elbv2.TargetType.IP
Fargateでは、コンテナはEC2インスタンス上で動作するのではなく、
タスクごとに作成されるENI(Elastic Network Interface)に紐付きます。
そのため、ALBが通信する相手は「EC2インスタンス」ではなく「IPアドレス」です。
よって、ターゲットタイプはINSTANCEではなくIPを指定します。
ターゲットの正常性を判定するHealthCheckの設定
healthCheck: {
path: '/',
healthyHttpCodes: '200',
},
ALBは設定したパスへ一定間隔でリクエストを送り、そのレスポンスをもとにターゲットの状態を判定します。
以下の条件を満たすと Healthy と判断されます。
- healthyHttpCodes で指定したステータスコードが返る
- かつ、接続がタイムアウトしない
- これが連続して成功回数のしきい値を満たす
上記の設定により、/にアクセスしてHTTP 200を返す場合のみ正常と判定されます。
Next.jsのトップページは通常/で200を返すため、基本的な構成としてはこの設定で問題ありません。
3-2. ecs-stackを修正する
第2回ではEcsStack内でVPCを取得していましたが、
他のStackから利用できるようpublic readonly vpcとして公開する形に修正します。
対象ファイル
lib/ecs-stack.ts
import * as ecs from 'aws-cdk-lib/aws-ecs';
//...
export class EcsStack extends cdk.Stack {
public readonly vpc: ec2.IVpc; // 追加
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// VPC(デフォルトVPCを使用)
this.vpc = ec2.Vpc.fromLookup(this, 'DefaultVpc', {
isDefault: true,
});
// クラスター作成
const cluster = new ecs.Cluster(this, 'Cluster', {
vpc: this.vpc,
});
//...
}
}
3-3. binにStackを追加する
対象ファイル
bin/cdk-nextjs-infra.ts
import { AlbStack } from '../lib/alb-stack';
//...
const ecsStack = new EcsStack(app, 'NextjsInfraEcsStack', { env });
new AlbStack(app, 'NextjsInfraAlbStack', {
env,
vpc: ecsStack.vpc,
});
3-4. デプロイ
cdk deploy NextjsInfraAlbStack --profile <プロファイル名>
成功すると、OutputsにALBのDNS名が表示されます。
例:
Outputs:
NextjsAlbStack.AlbDnsName = xxxxxxxxx.ap-northeast-1.elb.amazonaws.com
本記事で作成したスタックに含まれるAWSリソースは、削除するまで料金が発生します。
検証が不要になった場合は、以下のコマンドでスタックを削除してください。
cdk destroy <スタック名> --profile <プロファイル名>
AWSコンソールで確認
① EC2 → ロードバランサー
- ALBが作成されているか
- 状態がアクティブになっているか
② EC2 → ターゲットグループ
- TargetGroupが作成されているか
※ まだECS Serviceを作成していないため、登録ターゲットは0です。
最終コード(今回追加・修正したファイル)
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;
constructor(scope: Construct, id: string, props: AlbStackProps) {
super(scope, id, props);
// ALB用Security Group
// allowAllOutbound: trueを明示
const albSg = new ec2.SecurityGroup(this, 'AlbSecurityGroup', {
vpc: props.vpc,
allowAllOutbound: true,
description: 'Security group for public ALB',
});
// インターネットからのHTTP(80)を許可
albSg.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: albSg,
});
// 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/ecs-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 ecr from 'aws-cdk-lib/aws-ecr';
import * as logs from 'aws-cdk-lib/aws-logs';
export class EcsStack extends cdk.Stack {
public readonly vpc: ec2.IVpc; // 追加
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// VPC(デフォルトVPCを使用)
this.vpc = ec2.Vpc.fromLookup(this, 'DefaultVpc', {
isDefault: true,
});
// ECS Cluster
const cluster = new ecs.Cluster(this, 'Cluster', {
vpc: this.vpc,
});
const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDefinition', {
cpu: 256,
memoryLimitMiB: 512,
});
const repository = ecr.Repository.fromRepositoryName(
this,
'ImportedRepository',
'my-nextjs-app'
);
const container = taskDefinition.addContainer('Container', {
// ECRリポジトリからイメージを指定
// ※本番は固定タグ推奨
image: ecs.ContainerImage.fromEcrRepository(repository),
// CloudWatch Logsへログを出力する設定
logging: ecs.LogDrivers.awsLogs({
streamPrefix: 'nextjs',
logRetention: logs.RetentionDays.ONE_WEEK,
}),
});
container.addPortMappings({
containerPort: 3000,
});
}
}
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';
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 });
new AlbStack(app, 'NextjsInfraAlbStack', {
env,
vpc: ecsStack.vpc,
});
ここまでの成果
外部公開の入口となるALBを構築し、ECSを接続するための準備が整いました。
現時点ではタスクは起動していませんが、次回ECS Serviceを作成することでアプリが実際に表示される状態になります。
次回
次回はECS Serviceを作成し、ALBと接続します。
タスクを常時起動させ、ALB経由でNext.jsアプリが表示されるところまで進めます。