AWS CDKを使用してECS Fargate構築する

公開日 2025年1月19日
更新日 2025年1月19日
17 分で読める
AWS

この記事では、AWS Cloud Development Kit(CDK)を使用してECS Fargateベースのサービスを構築する方法を詳しく解説します。VPC構成、アプリケーションロードバランサー(ALB)の作成、タスク定義、自動スケーリング設定などを含みます。


プロジェクト背景

目標は、AWS ECS Fargateを基盤とするコンテナ化されたサービスをデプロイし、アプリケーションロードバランサー(ALB)を通じて外部に公開することです。本サービスには以下の特徴があります:

  • プライベートVPCとセキュリティグループでネットワークセキュリティを保護する。
  • ECRエンドポイントを設定し、プライベートサブネットから安全にコンテナイメージをプルする。
  • CPUおよびメモリ負荷に基づいて動的にサービスインスタンスを調整する自動スケーリングを設定する。

前提条件

本サンプルコードを用いてECS Fargateを構築するには、以下の条件が満たされている必要があります:

  1. VPC(publicサブネット、privateサブネット、privateセキュリティグループを含む)を既に作成済み。
  2. ECSTaskExecutionRoleとECSTaskRoleを既に作成済み。
  3. ECRリポジトリを既に作成済み。
  4. サーバーコードをDockerイメージにパッケージ化し、ECRにプッシュ済み。

CDKコード

以下はCDKスタックのコード例です。

import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import { Cluster, ContainerImage, LogDriver } from 'aws-cdk-lib/aws-ecs';
import { ApplicationLoadBalancedFargateService } from 'aws-cdk-lib/aws-ecs-patterns';
import { ApplicationLoadBalancer, ApplicationProtocol } from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import { Role } from 'aws-cdk-lib/aws-iam';
import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs';
import { Construct } from 'constructs';

export class ServerStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // VPC ID
    const vpcId = 'xxx';

    // プライベートセキュリティグループID
    const privateSecurityGroupId = 'xxx';

    // ECSタスク実行ロールARN
    const ecsTaskExecutionRoleArn = 'xxx';

    // ECSタスクロールARN
    const ecsTaskRoleArn = 'xxx';

    // ECRリポジトリURI
    const ecrRepositoryURI = 'xxxxxx';

    // ECRイメージタグ
    const ecrImageTag = 'xxxxxx';

    // -------------------------------------------

    // VPCを取得する
    const vpc = ec2.Vpc.fromLookup(this, 'VPC', { vpcId: vpcId });

    // Private Security Groupを取得する
    const privateSecurityGroup = ec2.SecurityGroup.fromLookupById(this, 'PrivateSecurityGroup', privateSecurityGroupId);

    // ECR DKRエンドポイントを作成する(プライベートサブネット内でECRから安全にイメージをプル)
    new ec2.InterfaceVpcEndpoint(this, `EcrDkrEndpoint`, {
      vpc: vpc,
      service: ec2.InterfaceVpcEndpointAwsService.ECR_DOCKER,
      subnets: { subnets: vpc.privateSubnets },
      securityGroups: [privateSecurityGroup],
    });

    // ECR API Interface Endpointを作成する(プライベートサブネット内でECR APIに安全にアクセス)
    new ec2.InterfaceVpcEndpoint(this, `EcrApiEndpoint`, {
      vpc: vpc,
      service: ec2.InterfaceVpcEndpointAwsService.ECR,
      subnets: { subnets: vpc.privateSubnets },
      securityGroups: [privateSecurityGroup],
    });

    // ECR API Interface Endpointを作成する(プライベートサブネットからCloudWatchにログを吐き出す)
    new ec2.InterfaceVpcEndpoint(this, `EcrLog`, {
      vpc: vpc,
      service: ec2.InterfaceVpcEndpointAwsService.CLOUDWATCH_LOGS,
      subnets: { subnets: vpc.privateSubnets },
      securityGroups: [privateSecurityGroup],
    });

    // ECS Task Execution Roleを取得する
    const ecsTaskExecutionRole = Role.fromRoleArn(this, `EcsTaskExecutionRole`, ecsTaskExecutionRoleArn, {
      mutable: false,
    });

    // ECS Task Roleを取得する
    const ecsTaskRole = Role.fromRoleArn(this, `EcsTaskRole`, ecsTaskRoleArn, {
      mutable: false,
    });

    // ALBを作成する
    const alb = new ApplicationLoadBalancer(this, `ALB`, {
      vpc: vpc,
      vpcSubnets: { subnets: vpc.publicSubnets },
      internetFacing: true,
      loadBalancerName: `<ALB_NAME>`,
      securityGroup: privateSecurityGroup,
      idleTimeout: cdk.Duration.seconds(60),
    });

    // Log Groupを作成する
    const ecsLogGroup = new LogGroup(this, `LogGroup`, {
      logGroupName: '<LOG_GROUP_NAME>',
      retention: RetentionDays.ONE_MONTH,
    });

    // ECS Clusterを作成する
    const cluster = new Cluster(this, `ECSCluster`, {
      vpc: vpc,
      clusterName: `<CLUSTER_NAME>`,
    });

    // ECS Serviceを作成する
    const service = new ApplicationLoadBalancedFargateService(this, `ECSService`, {
      cluster: cluster,
      protocol: ApplicationProtocol.HTTP,
      targetProtocol: ApplicationProtocol.HTTP,
      securityGroups: [privateSecurityGroup],
      listenerPort: 80,
      loadBalancer: alb,
      openListener: false,
      publicLoadBalancer: false,
      taskSubnets: { subnets: vpc.privateSubnets },
      memoryLimitMiB: 1024,
      cpu: 512,
      // デフォルトで起動するタスクの数
      desiredCount: 2,
      // サービスの更新やスケーリング中に、健康なタスクの最小割合を維持します。
      // たとえば、desiredCount が 2 で minHealthyPercent が 50% の場合、更新中に少なくとも 1 つの健康なタスクが実行され続ける必要があります。
      minHealthyPercent: 50,
      // サービスの更新やスケーリング中に、実行可能な健康タスクの最大割合を設定します。
      // たとえば、desiredCount が 2 で maxHealthyPercent が 200% の場合、更新中に最大 4 タスク(現在の 2 タスク + 新規 2 タスク)が同時に実行される可能性があります。
      maxHealthyPercent: 200,
      healthCheckGracePeriod: cdk.Duration.seconds(500),
      taskImageOptions: {
        image: ContainerImage.fromRegistry(`${ecrRepositoryURI}:${ecrImageTag}`),
        logDriver: LogDriver.awsLogs({
          logGroup: ecsLogGroup,
          streamPrefix: 'api',
        }),
        executionRole: ecsTaskExecutionRole,
        taskRole: ecsTaskRole,
        // serverポート番号
        containerPort: 3000,
        // 環境変数
        environment: {
          XX: 'XX',
        },
      },
      serviceName: `<SERVICE_NAME>`,
    });

    // Health Checkを設定する
    service.targetGroup.configureHealthCheck({
      path: '<health check path in your server>', // サーバーヘルスチェック用のAPIパス
    });

    // Auto Scalingの設定
    const autoScaling = service.service.autoScaleTaskCount({
      minCapacity: 1, // ECSサービス内のタスクインスタンスの最小数
      maxCapacity: 10, // ECSサービス内のタスクインスタンスの最大数
    });

    // scaleOnCpuUtilization と scaleOnMemoryUtilization:
    // - CPU使用率とメモリ使用率に基づいて、自動スケーリングのルールを設定します。
    // - 使用率が目標値(targetUtilizationPercent: 60)を超えると、タスクが自動的にスケールアウトされます。
    // - 使用率が目標値を下回ると、タスクが自動的にスケールインされます。
    //
    // scaleInCooldown と scaleOutCooldown:
    // - スケールイン・スケールアウトのクールダウン時間を設定し、頻繁なスケーリングによる揺らぎを回避します。
    // - 60秒に設定すると、直近のスケール操作から最低60秒間は次のスケーリングが行われません。
    autoScaling.scaleOnCpuUtilization(`ScalingOnCPU`, {
      targetUtilizationPercent: 60,
      scaleInCooldown: cdk.Duration.seconds(60),
      scaleOutCooldown: cdk.Duration.seconds(60),
    });
    autoScaling.scaleOnMemoryUtilization(`ScalingOnMemory`, {
      targetUtilizationPercent: 60,
      scaleInCooldown: cdk.Duration.seconds(60),
      scaleOutCooldown: cdk.Duration.seconds(60),
    });
  }
}

まとめ

このコードは、AWS CDKを使用して、ECS Fargateサービスのネットワーク構成、セキュリティ、ロードバランシング、ログ記録、自動スケーリングを包括的に設定する方法を示しています。このようなInfrastructure as Code(IaC)のアプローチは、デプロイ効率と一貫性を大幅に向上させます。

この記事が、AWS CDKを使用して独自のコンテナサービスを構築する際の参考になれば幸いです。コード実装やデプロイプロセスに関して質問がありましたら、ぜひコメントでお知らせください!

概要

技術的洞察、経験、思考を共有する個人ブログ

クイックリンク

お問い合わせ

  • Email: hushukang_blog@proton.me
  • GitHub

© 2025 CODE赤兎. 無断転載禁止