2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS CDKでNext.jsをデプロイする【第4回】〜ECS Service構築〜

2
Last updated at Posted at 2026-03-06

はじめに

第3回では、ALB(Application Load Balancer)を構築しました。

ここまでで、以下のリソースが作成されています。

  • ECR:コンテナイメージの保存場所
  • Task Definition:コンテナの実行設定
  • ALB:外部からアクセスする入口

しかし、この状態ではまだコンテナは起動していません。

ECSでは、Task Definitionを作成しただけではコンテナは実行されず、
タスクを起動・維持する仕組み(Service) が必要になります。

そこで今回はECS Serviceを作成し、Next.jsアプリをAWS上で実際に起動させます。

本シリーズの構成

1. ECR編
2. ECS Task Definition編
3. ALB編
4. ECS Service編(本記事)
5. セキュリティ編

構成全体と今回の作業範囲

AWS全体構成の中で、本記事がどの範囲を扱うのかを確認します。

今回の作業はECS Serviceの作成です。
ServiceがTask Definitionをもとにタスクを起動します。
そして、指定された数(desiredCount)のタスクが常に動作するように管理します。
起動したタスクにはALB経由でアクセスできるようになります。

ECS Serviceとは?

ECS Serviceは、タスクを指定した数(desiredCount)だけ起動し、その状態を維持する仕組みです。
タスクが停止した場合、Serviceが自動的に新しいタスクを起動します。

また、ALB(Target Group)と連携し、外部からのリクエストを起動中のタスクへ分散する役割も担います。

Task Definitionとの違い

ECSでは、Task DefinitionとServiceで役割が分かれています。

Task Definition

  • コンテナの実行設定(イメージ・ポート・環境変数など)
  • いわば「コンテナの設計図」

Service

  • Task Definitionを使ってタスクを起動する
  • 指定した数のタスクを常に維持する

4-1. EcsStackのリソースを外部参照できるようにする

ServiceStackでは、EcsStackで作成したclustertaskDefinitionを参照してServiceを作成します。

第3回では、AlbStackから利用できるようにvpcをクラスのプロパティとして公開しました。
今回もそれと同様に、ServiceStackから参照できるようclustertaskDefinitionをプロパティとして公開します。

Stack内で作成したリソースを他のStackから利用する場合、そのリソースをクラスのプロパティとして公開する必要があります。

コンストラクタ内のconst変数のままだと、Stack外から参照できません。

1. EcsStackクラスにプロパティを追加

public readonly cluster: ecs.Cluster;
public readonly taskDefinition: ecs.FargateTaskDefinition;

2. コンストラクタ内で this.cluster / this.taskDefinition に代入する

修正後のコード

export class EcsStack extends cdk.Stack {
  public readonly vpc: ec2.IVpc;
  public readonly cluster: ecs.Cluster;
  public readonly taskDefinition: ecs.FargateTaskDefinition;

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

    this.vpc = ec2.Vpc.fromLookup(this, 'DefaultVpc', {
      isDefault: true,
    });

    this.cluster = new ecs.Cluster(this, 'Cluster', {
      vpc: this.vpc,
    });

    this.taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDefinition', {
      cpu: 256,
      memoryLimitMiB: 512,
    });

    const repository = ecr.Repository.fromRepositoryName(
      this,
      'ImportedRepository',
      'my-nextjs-app'
    );

    const container = this.taskDefinition.addContainer('Container', {
      image: ecs.ContainerImage.fromEcrRepository(repository),
      logging: ecs.LogDrivers.awsLogs({
        streamPrefix: 'nextjs',
        logRetention: logs.RetentionDays.ONE_WEEK,
      }),
    });

    container.addPortMappings({ containerPort: 3000 });
  }
}

4-2. Service用Stackを作成する

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;
}

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,
    });

    const service = new ecs.FargateService(this, 'Service', {
      cluster: props.cluster,
      taskDefinition: props.taskDefinition,
      desiredCount: 1,  // 常時稼働させるタスク(コンテナ)の数
      assignPublicIp: true, // 検証用
      securityGroups: [serviceSg],
    });

    service.attachToApplicationTargetGroup(props.targetGroup);
  }
}

ServiceStackでは、既存のStackから以下のリソースを受け取ります。

  • ECS Cluster
  • Task Definition
  • ALB Target Group
  • VPC

ポイント解説

desiredCount

desiredCount: 1

常に1つのタスクを起動する設定です。
タスクが停止した場合、自動的に再起動されます。

assignPublicIp

assignPublicIp: true

Public Subnetに配置する設定です。(検証用)

attachToApplicationTargetGroup

service.attachToApplicationTargetGroup(props.targetGroup);

ECS Serviceが起動したタスクをALBのTargetGroupに登録します。
これにより、ALBからのリクエストがECSタスクへ転送されます。

4-3. binにStackを追加

ServiceStackから他Stackのリソースを参照できるように、Stackを変数として保持します。
(ecsStack, albStack)

import { ServiceStack } from '../lib/service-stack';

const ecsStack = new EcsStack(app, 'NextjsInfraEcsStack', { ... }); 

const albStack = new AlbStack(app, 'NextjsInfraAlbStack', { ... });

new ServiceStack(app, 'NextjsInfraServiceStack', {
  env,
  cluster: ecsStack.cluster,
  taskDefinition: ecsStack.taskDefinition,
  targetGroup: albStack.targetGroup,
  vpc: ecsStack.vpc,
});

4-4. デプロイ

cdk deploy NextjsServiceStack --profile <プロファイル名>

本記事で作成したスタックに含まれるAWSリソースは、削除するまで料金が発生します。
検証が不要になった場合は、以下のコマンドでスタックを削除してください。

cdk destroy <スタック名> --profile <プロファイル名>

AWSコンソールで確認

①ECS → クラスター
作成したクラスターを開き、以下を確認します。

  • サービスが作成されているか
  • タスクが1つ実行中になっているか

②EC2 → ターゲットグループ
作成したターゲットグループを開き、ターゲットタブで以下を確認します。

  • ターゲットが1つ登録されているか
  • ヘルスステータスが Healthy になっているか

動作確認

EC2 → ロードバランサー
対象ALBの 「DNS名」を確認し、ブラウザからアクセス

http://<DNS名>
  • Next.js画面が表示されるか

最終コード(今回追加・修正したファイル)

lib/ecs-stack.ts

コード全体
export class EcsStack extends cdk.Stack {
  public readonly vpc: ec2.IVpc;
  public readonly cluster: ecs.Cluster;
  public readonly taskDefinition: ecs.FargateTaskDefinition;

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

    this.vpc = ec2.Vpc.fromLookup(this, 'DefaultVpc', {
      isDefault: true,
    });

    this.cluster = new ecs.Cluster(this, 'Cluster', {
      vpc: this.vpc,
    });

    this.taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDefinition', {
      cpu: 256,
      memoryLimitMiB: 512,
    });

    const repository = ecr.Repository.fromRepositoryName(
      this,
      'ImportedRepository',
      'my-nextjs-app'
    );

    const container = this.taskDefinition.addContainer('Container', {
      image: ecs.ContainerImage.fromEcrRepository(repository),
      logging: ecs.LogDrivers.awsLogs({
        streamPrefix: 'nextjs',
        logRetention: logs.RetentionDays.ONE_WEEK,
      }),
    });

    container.addPortMappings({ containerPort: 3000 });
  }
}

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;
}

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,
    });

    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,
});

ここまでの成果

成果

  • ECS Serviceを作成した
  • タスクが常時起動するようになった
  • ALBと接続された
  • 外部アクセスが可能になった

ALBのDNSからアプリにアクセスできる状態になりました。

次回

次回はセキュリティ編として、以下を実施していきます。

  • Security Groupの最適化
  • 不要なPublic設定の削除
  • 疎通確認
  • 本番構成への修正

第4回:セキュリティ編はこちら

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?