log
码中赤兔

使用 AWS CDK 构建 ECS Fargate

发布于 2025年1月19日
更新于 2025年1月19日
21 分钟阅读
AWS

本文将详细介绍如何使用 AWS Cloud Development Kit(CDK)来构建一个基于 ECS Fargate 的服务,包括 VPC 配置、负载均衡器 (ALB) 创建、任务定义以及自动扩展配置等。


项目背景

目标是部署一个容器化的服务,使用 AWS ECS Fargate 作为底层运行环境,并通过 Application Load Balancer (ALB) 提供对外访问。该服务需要具备以下特点:

  • 使用私有 VPC 和安全组保护服务的网络安全。
  • 配置 ECR 接口端点以支持私有容器镜像拉取。
  • 配置自动扩展以根据 CPU 和内存负载动态调整服务实例。

前提条件

通过本实例中的代码构建ECS Fargate需要满足以下条件

  1. 已生成VPC以及public subnet,private subnet,private security group
  2. 已生成ECSTaskExecutionRole,ECSTaskRole
  3. 已生成ECR
  4. 已经把server代码打包成docker文件并且上传到ECR

CDK代码

以下是CDK Stack的代码。

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

    // Private Security Group ID
    const privateSecurityGroupId = 'xxx';

    // ECS Task Execution Role ARN
    const ecsTaskExecutionRoleArn = 'xxx';

    // ECS Task Role ARN
    const ecsTaskRoleArn = 'xxx';

    // ECR Repository URI
    const ecrRepositoryURI = 'xxxxxx';

    // ECR Image Tag
    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 Interface Endpoint,用于在Private Subnet中安全的从ECR中拉取Docker镜像
    new ec2.InterfaceVpcEndpoint(this, `EcrDkrEndpoint`, {
      vpc: vpc,
      service: ec2.InterfaceVpcEndpointAwsService.ECR_DOCKER,
      subnets: { subnets: vpc.privateSubnets },
      securityGroups: [privateSecurityGroup],
    });

    // 生成 ECR API Interface Endpoint,用于在Private Subnet中安全的访问ECR API服务
    new ec2.InterfaceVpcEndpoint(this, `EcrApiEndpoint`, {
      vpc: vpc,
      service: ec2.InterfaceVpcEndpointAwsService.ECR,
      subnets: { subnets: vpc.privateSubnets },
      securityGroups: [privateSecurityGroup],
    });

    // 生成 ECR API Interface Endpoint,用于在Private Subnet中向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,
      // 默认启动的Task数量
      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>', // server中用来健康检查的api path
    });

    // 配置 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 服务,包括网络、安全、负载均衡、日志记录和自动扩展等功能。这种基础设施即代码(IaC)的方式大大提高了部署的效率和一致性。

通过这篇文章的讲解,相信您能够更加轻松地使用 CDK 构建自己的容器化服务。如果您对代码实现或部署流程有任何疑问,欢迎留言讨论!

关于

分享技术见解、经验和思考的个人博客

联系方式

  • Email: hushukang_blog@proton.me
  • GitHub

© 2025 码中赤兔. 版权所有