CDK を使って ALB + Fargate を構築する

公開日 2025年4月27日
更新日 2025年4月27日
12 分で読める
AWS

コンテナ化とサーバーレス化を活用することで、サービスアーキテクチャにおける運用作業を大幅に簡素化できます。本記事では、AWS CDK (v2) を用いて Fargate 上にサーバーレスサービスをデプロイする方法を紹介します。

1. プロジェクト構成の概要

開始前に、AWS CDK がインストールされ、TypeScript プロジェクトが初期化されているものとします。ディレクトリ構成の一例は以下のとおりです。

my-cdk-project/
├── bin/
│   └── my-cdk-project.ts  // CDK アプリケーションのエントリーポイント
├── lib/
│   └── server-stack.ts    // これから作成する Stack
├── package.json
└── tsconfig.json

2. CDK コアコード

import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecr from 'aws-cdk-lib/aws-ecr';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as log 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);

    // 1. 既存の VPC とセキュリティグループをインポート
    const albSg = ec2.SecurityGroup.fromSecurityGroupId(this, 'alb-sg', 'alb-sg-id');
    const ecsSg = ec2.SecurityGroup.fromSecurityGroupId(this, 'ecs-sg', 'ecs-sg-id');
    const vpc = ec2.Vpc.fromLookup(this, 'vpc', { vpcId: 'vpc-id' });
    const albSubnetA = ec2.Subnet.fromSubnetId(this, 'alb-subnet-a', 'alb-subnet-a-id');
    const albSubnetC = ec2.Subnet.fromSubnetId(this, 'alb-subnet-c', 'alb-subnet-c-id');
    const ecsSubnetA = ec2.Subnet.fromSubnetId(this, 'ecs-subnet-a', 'ecs-subnet-a-id');
    const ecsSubnetC = ec2.Subnet.fromSubnetId(this, 'ecs-subnet-c', 'ecs-subnet-c-id');

    // 2. 既存の IAM ロールをインポート
    const taskRole = iam.Role.fromRoleArn(this, 'task-role', 'task-role-arn', {
      mutable: false,
    });
    const taskExecutionRole = iam.Role.fromRoleArn(this, 'task-execution-role', 'task-execution-role-arn', {
      mutable: false,
    });

    // 3. ECS クラスターを作成
    const cluster = new ecs.Cluster(this, 'ecs-cluster', {
      vpc: vpc,
      clusterName: 'ecs-cluster',
    });

    // 4. Fargate タスク定義を作成
    const taskDefinition = new ecs.FargateTaskDefinition(this, 'task-definition', {
      taskRole: taskRole,
      executionRole: taskExecutionRole,
      cpu: 512,
      memoryLimitMiB: 1024,
    });

    // 5. ECR リポジトリをインポート
    const repository = ecr.Repository.fromRepositoryArn(this, 'ecr-repository', 'ecr-arn');

    // 6. CloudWatch Logs のロググループを作成
    const logGroup = new log.LogGroup(this, 'server-log', {
      logGroupName: '/aws/server-log',
      retention: log.RetentionDays.THREE_YEARS,
    });

    // 7. コンテナイメージを構成
    taskDefinition.addContainer('task', {
      image: ecs.ContainerImage.fromEcrRepository(repository, 'xxxxx'),
      logging: ecs.LogDriver.awsLogs({
        logGroup: logGroup,
        streamPrefix: 'api',
      }),
      portMappings: [{ containerPort: 80 }],
      environment: {
        NODE_ENV: 'it',
      },
      readonlyRootFilesystem: true,
    });

    // 8. Fargate サービスをデプロイ
    const service = new ecs.FargateService(this, 'service', {
      serviceName: 'server_name',
      cluster: cluster,
      taskDefinition: taskDefinition,
      securityGroups: [ecsSg],
      vpcSubnets: { subnets: [ecsSubnetA, ecsSubnetC] },
    });

    // 9. アプリケーションロードバランサー (ALB) を作成
    const alb = new elbv2.ApplicationLoadBalancer(this, `alb`, {
      loadBalancerName: 'alb',
      securityGroup: albSg,
      vpc: vpc,
      vpcSubnets: { subnets: [albSubnetA, albSubnetC] },
    });

    // 10. ターゲットグループとヘルスチェックを作成
    const targetGroup = new elbv2.ApplicationTargetGroup(this, 'alb-tg', {
      targetGroupName: '',
      vpc: vpc,
      port: 80,
      protocol: elbv2.ApplicationProtocol.HTTP,
      targetType: elbv2.TargetType.IP,
      crossZoneEnabled: true,
      healthCheck: {
        path: '/health',
        healthyHttpCodes: '200',
      },
      targets: [service],
    });

    // 11. リスナーとルーティングルールを追加
    const listener = alb.addListener('alb-listener', {
      port: 80,
      protocol: elbv2.ApplicationProtocol.HTTP,
      open: false,
      defaultAction: elbv2.ListenerAction.fixedResponse(404, { contentType: 'application/json' }),
    });

    new elbv2.CfnListenerRule(this, 'server-rule', {
      listenerArn: listener.listenerArn,
      priority: 1,
      conditions: [
        {
          field: 'path-pattern',
          values: ['/api/*'], // このパスに一致するリクエストのみ Fargate に転送
        },
      ],
      actions: [{ type: 'forward', targetGroupArn: targetGroup.targetGroupArn }],
    });
  }
}

3. まとめ

上記の例を使えば、既存の VPC、サブネット、セキュリティグループ、IAM ロール、ECR リポジトリ、ロググループを再利用し、CDK のコマンド一発でスタック全体をデプロイできます。

  1. 高可用性:マルチ AZ ロードバランシング + Fargate サーバーレス
  2. セキュリティ:詳細なセキュリティグループ設定、コンテナのルートファイルシステムを読み取り専用化
  3. 可観測性:CloudWatch Logs によるリアルタイムログ取得

さらに HTTPS 対応、自動スケーリング、ブルー/グリーンデプロイなどの本番環境向け機能を追加して拡張可能です。コメント欄で皆さんの最適化アイデアをぜひ共有してください!

概要

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

クイックリンク

お問い合わせ

  • Email: hushukang_blog@proton.me
  • GitHub

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