简单的AWS:从EC2到可扩展的ECS
#aws #node #云 #containers

这个新闻通讯启发了我的第一本书Node.js on AWS: From Zero to Highly Available and Scalable Hero。它长120页,更具描述性,其中包括缓存,监视和跟踪等几种最佳实践。

注意:此内容最初发表在 Simple AWS newsletter 上。了解AWS解决方案背后的原因。免费订阅! 3000名工程师和技术专家已经拥有。

用例:将EC2上的应用程序转换为ECS上的可扩展应用程序

设想

您有一个您在node.js中写的很酷的应用程序。您是一位出色的开发人员,但您最初是从AWS的0个知识开始的。首先,您刚刚启动了一个EC2实例,SSH在那里并部署了该应用程序。然后,您订阅了简单的AWS,然后阅读the issue on a self-healing, single-instance environment,因此您应用了它。更好,但没有扩展。您找到了the old issue about ECS,您了解了基本概念,但是您不知道如何从EC2上的应用程序转到ECS群集上的应用

服务

  • ECS:一种容器编排服务,可帮助管理集群上的docker容器。

  • 弹性容器注册表(ECR):用于存放,管理和部署Docker Images的托管容器注册表。

  • fargate:用于容器的无服务器计算引擎,消除了管理基础EC2实例的需求。

Architecture diagam of ECS with one task

解决方案逐步

在您的本地机器上安装Docker

遵循官方Docker网站的说明:

Windows:https://docs.docker.com/desktop/windows/install/

macos:https://docs.docker.com/desktop/mac/install/

linux:https://docs.docker.com/engine/install/

创建一个Dockerfile

在您的应用程序的根目录中,创建一个名为“ Dockerfile”的文件(无文件扩展名)。将以下作为起点,根据需要进行调整。

# Use the official Node.js image as the base image
FROM node:latest

# Set the working directory for the app
WORKDIR /app

# Copy package.json and package-lock.json into the container
COPY package*.json ./

# Install the app's dependencies
RUN npm ci

# Copy the app's source code into the container
COPY . .

# Expose the port your app listens on
EXPOSE 3000

# Start the app
CMD ["npm", "start"]

构建Docker图像并在本地进行测试

在您应用程序的根目录中,构建Docker映像。构建完成后,使用新图像启动本地容器。在您的浏览器或Curl或Postman中测试该应用程序,以确保其正常工作。如果不是,请返回并修复Dockerfile。

docker build -t cool-nodejs-app .
docker run -p 3000:3000 cool-nodejs-app

创建ECR注册表

首先,使用AWS管理控制台或AWS CLI创建一个新的ECR存储库。

aws ecr create-repository --repository-name cool-nodejs-app

将Docker图像推到ECR

创建ECR注册表的命令将输出输出中的指令,以使用您的ECR存储库对Docker进行身份验证。跟着他们。然后,将图像标记为ECR,用适当的值代替{AWSAccountId}{AWSRegion}

docker tag cool-nodejs-app:latest {AWSAccountId}.dkr.ecr.{AWSRegion}.amazonaws.com/cool-nodejs-app:latest
docker push {AWSAccountId}.dkr.ecr.{AWSRegion}.amazonaws.com/cool-nodejs-app:latest

创建ECS任务定义

我们将使用CloudFormation进行此操作。使用以下内容创建一个名为“ ECS-Task-definition.yaml”的文件。然后,在AWS控制台中,使用“ ecs-task-definition.yaml”文件作为模板创建一个新的CloudFormation堆栈。

Resources:
  ECSTaskRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal: !Sub ${AWS::AccountId}
              Service:
                - ecs-tasks.amazonaws.com
            Action:
              - sts:AssumeRole
      Policies:
        - PolicyName: ECRReadOnlyAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ecr:GetAuthorizationToken
                Resource: "*"
              - Effect: Allow
                Action:
                  - ecr:BatchCheckLayerAvailability
                  - ecr:GetDownloadUrlForLayer
                  - ecr:GetRepositoryPolicy
                  - ecr:DescribeRepositories
                  - ecr:ListImages
                  - ecr:DescribeImages
                  - ecr:BatchGetImage
                Resource: !Sub "arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/cool-nodejs-app"

  CoolNodejsAppTaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: cool-nodejs-app
      TaskRoleArn: !Ref ECSTaskRole
      ExecutionRoleArn: !Ref ECSTaskRole
      RequiresCompatibilities:
        - FARGATE
      NetworkMode: awsvpc
      Cpu: '256'
      Memory: '512'
      ExecutionRoleArn: !Ref TaskExecutionRole
      ContainerDefinitions:
        - Name: cool-nodejs-app
          Image: !Sub "arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/cool-nodejs-app:latest"
          PortMappings:
            - ContainerPort: 3000
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref CloudWatchLogsGroup
              awslogs-region: !Sub ${AWS::Region}
              awslogs-stream-prefix: ecs

创建ECS群集和服务

您需要现有的VPC(您可以使用默认的VPC)。更新您的云形式模板(“ ecs-task-definition.yaml”),以包括ECS群集,服务以及网络和负载平衡的必要资源。用VPC的ID替换{VPCID},然后用一个或多个子网ID替换{subnetids}。然后,在控制台上,转到CloudFormation并使用修改的模板更新现有的堆栈。

Resources:
  # ... (Existing Task Definition and related resources)

  CoolNodejsAppService:
    Type: AWS::ECS::Service
    Properties:
      ServiceName: cool-nodejs-app-service
      Cluster: !Ref CoolNodejsAppCluster
      TaskDefinition: !Ref CoolNodejsAppTaskDefinition
      DesiredCount: 2
      LaunchType: FARGATE
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          Subnets:
            - {SubnetIDs}
      LoadBalancers:
        - TargetGroupArn: !Ref AppTargetGroup
          ContainerName: cool-nodejs-app
          ContainerPort: 3000

  CoolNodejsAppCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: cool-nodejs-app-cluster

  AppLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: app-load-balancer
      Scheme: internet-facing
      Type: application
      IpAddressType: ipv4
      LoadBalancerAttributes:
        - Key: idle_timeout.timeout_seconds
          Value: '60'
      Subnets:
        - 
      SecurityGroups:
        - !Ref AppLoadBalancerSecurityGroup

  AppLoadBalancerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: app-load-balancer-security-group
      VpcId: {VPCID}
      GroupDescription: Security group for the app load balancer
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0

  AppTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: app-target-group
      Port: 3000
      Protocol: HTTP
      TargetType: ip
      VpcId: {VPCID}
      HealthCheckEnabled: true
      HealthCheckIntervalSeconds: 30
      HealthCheckPath: /healthcheck
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 2
      UnhealthyThresholdCount: 2

为ECS服务设置自动尺度

将以下资源添加到CloudFormation模板中,以基于CPU利用率设置自动缩放策略,并使用修改的模板更新CloudFormation堆栈。

Resources:
  # ... (Existing resources)

  AppScalingTarget:
    Type: AWS::ApplicationAutoScaling::ScalableTarget
    Properties:
      MaxCapacity: 10
      MinCapacity: 2
      ResourceId: !Sub "service/${CoolNodejsAppCluster}/${CoolNodejsAppService}"
      RoleARN: !Sub "arn:aws:iam::${AWS::AccountId}:role/aws-service-role/ecs.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_ECSService"
      ScalableDimension: ecs:service:DesiredCount
      ServiceNamespace: ecs

  AppScalingPolicy:
    Type: AWS::ApplicationAutoScaling::ScalingPolicy
    Properties:
     PolicyName: app-cpu-scaling-policy
     PolicyType: TargetTrackingScaling
     ScalingTargetId: !Ref AppScalingTarget
     TargetTrackingScalingPolicyConfiguration:
       PredefinedMetricSpecification:
         PredefinedMetricType: ECSServiceAverageCPUUtilization
       TargetValue: 50
       ScaleInCooldown: 300
       ScaleOutCooldown: 300

测试新应用

完成云形式堆栈更新完成后,转到ECS控制台,单击Cool-Nodejs-App-Cluster群集,您会找到Cool-Nodejs-App-Service服务。该服务将根据您的任务定义和所需的计数启动任务,因此您应该看到2个任务。要测试该应用程序,请转到EC2控制台,在左侧查找负载平衡器选项,单击名为App-Load-Balancer的负载平衡器,然后查找DNS名称。将名称粘贴到您的浏览器中或使用卷发或邮递员。如果我们做到了,您应该看到与本地运行应用程序时相同的输出。恭喜!

解决方案解释

  • 安装Docker: ECS运行容器。 Docker是我们用于容器化应用程序的工具。容器将应用程序及其依赖项包装在一起,确保在不同阶段和平台上保持一致的环境。对于ECS,我们都需要这种方式,但是我们获得了能够在本地或在EC上运行的额外好处,而无需任何额外的努力。

  • 创建一个dockerfile: dockerfile是一个脚本,告诉docker如何构建docker映像。我们指定了基本映像,复制了应用程序的文件,安装依赖项,暴露了应用程序的端口,并定义了命令以启动应用程序。在启动开发时写这篇文章非常容易,但是为现有应用程序这样做更困难。

  • 构建Docker映像并在本地进行测试:您知道软件的行为的程度是您对其进行了测试的程度。因此,我们编写了Dockerfile,构建了图像并对其进行了测试!

  • 创建ECR注册表: Amazon弹性容器注册表(ECR)是存储Docker容器图像的托管容器注册表。这是一个文物的仓库,我们的文物是码头图像。 ECR与EC无缝集成,使我们能够直接从那里拉出图像而没有太多麻烦。

  • 将Docker图像推向ECR:我们正在构建我们的Docker图像并将其推到注册表中,以便我们可以从那里拉出。

  • 创建ECS任务定义:一个任务定义描述了任务应使用的docker映像,任务应具有多少CPU和内存以及其他配置。它们基本上是任务蓝图。我们使用CloudFormation构建了我们的,因为它使其更可维护(并且为您提供模板比显示主机屏幕截图更容易)。顺便说一句,我们还提供了任务定义从ECR注册表中取出图像的权限。

  • 创建ECS群集和服务:集群包含所有服务和任务。任务是我们应用程序的实例,基本上是Docker容器以及某些ECS包装和配置。我们没有手动启动任务,我们将创建一个服务,该服务是相同任务的分组。服务公开了应用程序的单个端点(在这种情况下使用应用程序加载平衡器),并控制该端点背后的任务(包括启动必要任务)。

  • 为ECS服务设置自动缩放:在此步骤之前,我们的ECS服务有2个任务,仅此而已,没有任何类型的自动缩放。在这里,我们制定了告诉ECS服务何时创建更多任务或破坏现有任务的政策。我们将基于CPU利用率来执行此操作,这在大多数情况下是最佳参数(也是最容易设置的)。

  • 测试新应用程序:好吧,现在我们已经部署了所有内容了。是时候测试它了!查找与我们的服务相对应的负载平衡器的DNS名称,将其粘贴在您的浏览器,卷发或邮递员中,然后将其用于试驾。如果有效,请给自己轻拍!如果没有,请给我发送电子邮件。

讨论

我们做到了!采用了一个无法扩展的EC2实例,并使其可扩展,可靠(如果任务失败,ECS启动了一个新的)且高度可用(如果您在不同的AZS中至少选择了2个子网)。而且并不难。

现实生活可能会更难。该解决方案在假设该应用程序在同一实例中不存储状态的假设工作。任何会话数据(或任何需要在实例中共享的任何数据,无论其暂时或持久性)都应存储在单独的存储中,例如数据库。即使您在其余数据中使用RDS/Aurora,DynamoDB也非常适合会话数据。

我看到的大多数使用单个EC2实例的人和关系数据库都在同一实例中运行数据库。您应该将其移至RDS/Aurora。这是从将应用程序移动到ECS的单独步骤,我们将来可以在它上做一个问题。

本地环境很容易,直到您使用3个DEV使用旧Mac,2使用M1或M2 Mac,Windows上的2个和一个孤独的家伙运行一个模糊的Linux发行版(这是同一个人认为VI比VS更好代码)。 Docker修复了。

是的,我使用了Fargate。我作弊了吗?也许是我认为您已经知道自动扩展组。我和Fargate一起去了,所以我们可以专注于ECS,但是如果您想在EC2自动缩放组上看到这一点,请点击回复并让我知道!

为什么不是ECS而不是普通的EC2自动扩展组?对于一项服务,这几乎是相同的努力。对于多种服务,EC摘要许多复杂性。

最佳实践

卓越运营

  • 使用CI/CD管道:我手动运行所有这些,但是您应该添加管道。创建基础架构后,所有管道都需要做的就是使用Docker Build构建Docker Image,用Docker Tag标记它,然后用Docker Push推动它。

  • 在管道中使用IAM角色:当然,您不想让任何人写信给您的ECR注册表。 CI/CD管道将需要进行身份验证。您可以使用长期寿命的证书(不是很好,但有效),也可以让管道扮演IAM角色。详细信息取决于您使用的工具,但请尝试执行此操作。

  • 健康检查:为您的ECS服务和应用负载平衡器配置健康检查,以确保只有健康的任务才能接收流量。

  • 将基础结构用作代码:在此问题中,您已经一半了!您已经在CloudFormation中完成了ECS群集,任务定义和服务!

  • 实现日志聚合:使用CloudWatch日志,Elasticsearch或您喜欢的任何工具为您的ECS任务设置日志聚合。所有任务都是相同的,跨任务的日志应汇总。

  • 使用蓝色/绿色部署: blue/green是一种策略,它包括与旧版本与旧版本并行部署新版本,对其进行测试,将流量路由到它,对其进行监视,而且,当您确定它按预期工作时,然后关闭旧版本。我真的应该在此问题。

安全

  • 在秘密经理中存储秘密:数据库凭据,API密钥,其他敏感数据?秘密经理。与上一期相同的推理。

  • 任务IAM角色:为每个ECS任务分配IAM角色(在任务定义下执行),因此它具有与其他AWS服务互动的权限。我们实际上是在解决方案中做到的,因此任务可以访问ECR!

  • 启用网络隔离:我现在告诉您使用默认VPC。对于真实用例,您应该使用专用的VPC(它们是免费的!),然后将任务放入私有子网中。

  • 使用安全组和NACL:在我们的最后一期中,我经常提到防御。基本上,在包括网络在内的多个层面上保护自己。

  • 定期旋转秘密: Secrets Manager降低了被妥协的秘密机会。但是,如果是的话,您找不到呢?定期旋转密码和所有秘密。

可靠性

  • 多AZ部署:在不同的AZS和EC中选择多个子网将以高度可靠的方式部署您的任务。请记住,对于AWS高度可用意味着它可以快速自动从一个AZ的失败中恢复。

  • 使用连接排水:在某个时候您需要杀死一个任务,但可能会服务请求。连接排水告诉LB不要向该任务发送新的请求,而要等待几秒钟(可配置,使用300)杀死它。这样,任务可以完成处理这些请求,并且用户不会受到影响。

  • 设置自动任务重试:配置自动检索因瞬态错误而失败的任务。这样,您的任务可以自动从临时错误恢复。

性能效率

  • 优化任务尺寸:调整CPU和内存分配以符合您的应用程序要求。通过扔钱来解决不良表现,听起来很糟糕,但是某些过程和语言本质上是资源密集的。

  • 使用容器见解:启用CloudWatch中的容器见解以监视,故障排除和优化ECS任务。这为您提供了对容器性能的宝贵见解,并帮助您确定潜在的瓶颈或优化区域。

  • 使用队列进行写入:之前,您的应用程序和数据库均未缩放。现在,您的应用程序的扩展非常好,但是您的数据库仍然无法扩展。突然的用户不再降低您的应用程序层,但是随之而来的写作请求激增可能会降低数据库。为了保护这一点,请将所有写入添加到队列中,并以最大速率从队列中消耗另一种服务。

  • 使用缓存:如果您多次访问相同的数据,则可以缓存。这也将保护您的数据库免受读取的爆发。

  • 优化自动缩放策略:定期审查和调整缩放策略,因此您的服务不会太晚缩放并过早地扩展。

成本优化

  • 配置储蓄计划:获取Fargate的储蓄计划。

  • 优化自动缩放策略:定期审查和调整缩放策略,因此您的服务不会过早扩展,并且在太晚时扩展。是的,这是性能效率的相反的。底线是您需要找到最佳位置。

  • 右尺寸的ECS任务:您将钱花在绩效问题上,现在效果更好。在可能的情况下优化代码,然后再次右键(这次降低资源)以获取这笔钱。与自动缩放一样,找到最佳点。

  • 定期清洁您的ECR注册表: ECR每GB存储的费用。我们通常会推出和推动图像,并且永远不会删除它们。您不需要3个月前的图像(如果您这样做,您总是可以重建它们!)

  • 使用EC2自动缩放组:就像我在讨论部分中提到的那样,我选择了Fargate,因此我们可以专注于EC,而不会因自动缩放实例而分心。 Fargate非常便宜,易于使用,0维护和快速缩放。 EC2实例的自动缩放组更难使用(不是那么多),需要维护工作,尺度不那么快,但是便宜得多。选择正确的一个。好消息是,它不难迁移,所以我建议所有新应用程序Fargate。


了解AWS解决方案背后的原因。 加入3000多个开发人员,技术线索和专家,学习如何与Simple AWS newsletter一起构建云解决方案而不是通过考试。

  • 现实世界情景
  • 解决方案背后的原因
  • 如何运用最佳实践

Subscribe for free!

如果您想进一步了解我,可以找到我on LinkedInwww.guilleojeda.com