🐾

AWS CDK で ECS Fargate を プライベートサブネット に構築する

2024/09/08に公開

はじめに

AWS CDK (TypeScript) を使用して、VPC、ALB、ECS (Fargate)、ECR の簡単な Webアプリの構成を作成しました。なお、ECS は プライベートサブネット に配置しますが、NATゲートウェイは使用せず、外部アクセスを VPCエンドポイント経由 で行うようにしました。

構成イメージは下記になります。

前提

  • CDK 実行環境が整備されている
  • Dockerfile が作成済み

事前準備:コンテナレジストリ へ イメージをプッシュ

CDK 実行前に、ECRリポジトリの作成/イメージのビルドとプッシュをしておきます。
手順は下記の通りです。

1. ECRリポジトリの作成

  • ECR (Elastic Container Registry) に移動します。
  • 「リポジトリの作成」をクリックし、リポジトリ名を入力します。
  • 必要に応じて設定を調整し、「リポジトリの作成」をクリックします。

2. Dockerイメージのビルドとプッシュ

  • Dockerfileを作成し、アプリケーションのDockerイメージをビルドします。
    docker build -t <リポジトリ名>:<タグ> .
    
  • ECRにログインします。
    aws ecr get-login-password --region <リージョン> | docker login --username AWS --password-stdin <アカウントID>.dkr.ecr.<リージョン>.amazonaws.com
    
  • イメージをECRにプッシュします。
    docker tag <リポジトリ名>:<タグ> <アカウントID>.dkr.ecr.<リージョン>.amazonaws.com/<リポジトリ名>:<タグ>
    docker push <アカウントID>.dkr.ecr.<リージョン>.amazonaws.com/<リポジトリ名>:<タグ>
    

CDK スタックのデプロイ

ckd deployコマンドでCDK スタックをデプロイします。
CDKコードの中核となる\lib\cdk-study-stack.tsは下記になります🖊️

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';

import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecr from 'aws-cdk-lib/aws-ecr';
import * as logs from 'aws-cdk-lib/aws-logs';
import * as ecsp from 'aws-cdk-lib/aws-ecs-patterns';
import { RemovalPolicy } from 'aws-cdk-lib';

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

    // 変数定義
    const projectName = "sample"; 
    const envName = "dev"; 
    const cidr = '10.0.0.0/16'; 
    const repoName = "myapp"; 
    const imageTag = "v1"; 

    // VPCの作成
    const vpc = new ec2.Vpc(this, `${projectName}-${envName}-vpc`, {
      cidr: cidr,
      maxAzs: 2, 
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: `${projectName}-${envName}-public`, 
          subnetType: ec2.SubnetType.PUBLIC,
        },
        {
          cidrMask: 24,
          name: `${projectName}-${envName}-private`, 
          subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
        },
      ]
    });

    // VPCエンドポイントの追加
    vpc.addInterfaceEndpoint(`${projectName}-${envName}-ecr-api-vpce`, {
      service: ec2.InterfaceVpcEndpointAwsService.ECR 
    });
    vpc.addInterfaceEndpoint(`${projectName}-${envName}-ecr-dkr-vpce`, {
      service: ec2.InterfaceVpcEndpointAwsService.ECR_DOCKER 
    });
    vpc.addInterfaceEndpoint(`${projectName}-${envName}-logs-vpce`, {
      service: ec2.InterfaceVpcEndpointAwsService.CLOUDWATCH_LOGS 
    });
    vpc.addGatewayEndpoint(`${projectName}-${envName}-s3-vpce`, {
      service: ec2.GatewayVpcEndpointAwsService.S3, 
      subnets: [
        {
          subnets: vpc.isolatedSubnets 
        }
      ]
    });
    
    // CloudWatch Logsのロググループを作成
    const logGroup = new logs.LogGroup(this, `${projectName}-${envName}-log-gp`, {
      logGroupName: `/aws/ecs/${projectName}-${envName}`, 
      removalPolicy: RemovalPolicy.DESTROY, 
    });

    // ECRリポジトリの取得
    const repo = ecr.Repository.fromRepositoryName(this, `${projectName}-${envName}-repo`, repoName);
    
    // ECSクラスターの作成
    const cluster = new ecs.Cluster(this, `${projectName}-${envName}-cluster`, { vpc });
    
    // ALB + Fargateサービスの作成
    new ecsp.ApplicationLoadBalancedFargateService(this, `${projectName}-${envName}-service`, {
      cluster,
      memoryLimitMiB: 512, 
      desiredCount: 2, 
      cpu: 256, 
      assignPublicIp: true, 
      loadBalancerName: `${projectName}-${envName}-lb`, 
      publicLoadBalancer: true, 
      taskImageOptions: {
        family: `${projectName}-${envName}-taskdef`, 
        image: ecs.ContainerImage.fromEcrRepository(repo, imageTag), 
        containerPort: 8080, 
        logDriver: new ecs.AwsLogDriver({
          streamPrefix: `container`, 
          logGroup: logGroup, 
        })
      },
    });
  }
}

確認

デプロイすると、無事クラスター内でタスクが実行されていることを確認できました🙆

  • ECS に必要な VPCエンドポイント が作成されている

  • CloudWatch logs へログが出力されている

  • ALB の DNS名へアクセスするとアプリケーションが表示される

  • タスク定義に適切なタスク実行ロールが付与されている

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "ecr:BatchCheckLayerAvailability",
                "ecr:BatchGetImage",
                "ecr:GetDownloadUrlForLayer"
            ],
            "Resource": "arn:aws:ecr:<リージョン>:<アカウントID>:repository/${repoName}",
            "Effect": "Allow"
        },
        {
            "Action": "ecr:GetAuthorizationToken",
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:<リージョン>:<アカウントID>:log-group:/aws/ecs/${projectName}-${envName}:*",
            "Effect": "Allow"
        }
    ]
}

以上、どなたかの参考になれば幸いです。
えみり〜でした|ωΦ)ฅ

参考

Discussion