拓冰建站拓冰建站
首页 / 资讯中心 / 正文

AWS ECS部署实战:从镜像构建到服务编排的完整指南

前阵子帮一个朋友把内部服务从自建虚拟机迁到AWS ECS整套流程走下来我对“部署流程”这四个字的理解又深了一层。很多人觉得ECS部署无非就是“打个镜像、起个服务”但真正上手之后才会发现从镜像构建、ECR仓库、任务定义、服务编排再到网络、权限、成本控制每个环节都有很多文档里不写、但实际项目中躲不开的细节。这篇文章就把我这次迁移过程中整理的完整流程、关键参数和踩坑记录分享出来适合刚接触ECS、想系统走通一套部署流程的朋友参考也适合已经跑过一两个服务、但还想把背后的机制理清楚的开发者看看。1. 部署流程的整体思路与核心概念1.1 为什么选ECS而不是自己维护K8s我在选型的时候其实最先考虑的是EKS毕竟Kubernetes生态更丰富社区资料也多。但认真核算下来EKS的控制平面虽然托管了worker节点、Ingress、监控、升级这些事还是得有人盯着对于一个小团队来说运维精力会被占掉不少。ECS的好处在于它是AWS原生的容器编排服务集群、任务、服务这些概念都被抽象得很干净控制台操作和API设计也统一跟ALB、IAM、CloudWatch的集成几乎是开箱即用的。ECS有两种启动类型Fargate和EC2。Fargate模式下AWS帮你管理底层实例你不用关心节点是什么时候扩容、什么时候重启按vCPU和内存计费适合业务波动不太剧烈、希望省心运维的场景。EC2模式下你需要自己维护一组ECS节点底层实例可以由ASG托管适合有GPU诉求、或者想要充分利用已有EC2实例的场景。我这次迁移的是普通Web服务最终选了Fargate原因很简单不用维护节点部署流程更纯粹。从部署流程的角度来说ECS可以被理解为“把容器跑在AWS上的标准管道”。你写好的Docker镜像放到ECR通过任务定义描述它需要多少资源、暴露什么端口、用什么环境变量然后服务负责维持期望的运行实例数集群则是这一切运行时的物理或逻辑边界。理解这四个层次后面的一切都好说了。1.2 一次部署涉及的四层核心概念我第一次接触ECS时最困惑的是Task Definition、Task、Service、Cluster这几个词之间的关系。后来我用一个生活化的类比才彻底捋清楚Task Definition是菜谱Task是照着菜谱做出来的一道菜Service是餐馆里负责持续上菜的机制Cluster则是厨房本身。菜谱写明需要什么食材、多少火候Task Definition写的是镜像地址、CPU、内存、端口映射、环境变量、日志驱动。菜谱不会变但菜可以按需做很多份Task就是运行中的一个容器实例。Service负责“持续上菜”它会保证任何时候都有指定数量的Task在跑某个Task挂了它就拉一个新的起来部署新版本时它还会按你设定的策略滚动替换。Cluster把所有这些东西圈在一起在Fargate模式下它更像一个逻辑分组用来区分不同项目或不同环境的服务。这套模型其实比K8s的Pod、Deployment、Node那套要直观得多。对于大多数中小型业务来说ECS的抽象层级刚刚好不需要你操心调度细节但又能让你清楚地控制部署行为。1.3 从代码到线上一次ECS部署的完整链路一次完整的ECS部署可以拆成下面这条链路本地代码构建Docker镜像、把镜像推送到ECR、登记Task Definition、创建Service、Service在Cluster里拉起Task、流量通过负载均衡器进入Task。每一步都有对应的AWS服务和工具整条链路走通之后后续迭代就是改代码、推镜像、更新Task Definition、触发Service滚动更新。这里特别想说的是很多人第一次部署时容易把注意力全放在控制台按钮上结果出了问题不知道从哪里查。我建议在脑子里面提前画好这条链路的职责边界ECR管镜像Task Definition管配置Service管生命周期Cluster管运行边界ALB管流量分发。哪一步出了问题就回到对应的环节去排查比漫无目的地翻日志高效得多。2. 实操全记录从零开始走完一次部署2.1 前置准备AWS账号、CLI与本地环境开始之前需要准备好三样东西一个有权创建ECS相关资源的AWS账号、本地安装好的AWS CLI以及一个能构建镜像的Docker环境。AWS CLI的配置没什么好说的aws configure填好Access Key和Secret Key区域建议在命令行参数里显式指定避免多环境切换时搞混。本地Docker环境需要注意一个细节如果你在Linux上构建镜像构建产物是Linux容器的格式没问题但如果你的开发机是Mac默认构建出来的镜像也可能是ARM架构如果ECS任务使用的是x86 CPU就会导致镜像无法执行或者性能异常。稳妥的做法是在Task Definition里指定CPU架构或者用docker buildx build --platform linux/amd64显式构建目标平台。我之前在一次部署中忽略了这一点结果镜像在本地跑得没问题部署到Fargate之后任务一直异常退出查了很久才发现是架构不匹配。这类问题在实操中非常容易出现提前把平台参数定死能省掉一大段时间。2.2 构建镜像并推送到ECR构建镜像的第一步自然是为应用写Dockerfile。这里不展开Dockerfile的最佳实践但有几个点值得提醒基础镜像尽量用带精确版本的官方镜像不要用latest多阶段构建可以显著减小最终镜像体积部署速度会快很多启动命令最好显式写进CMD或ENTRYPOINT这样在定义Task时能清晰看出容器实际执行的是什么。镜构建好之后推送到ECR的操作流程其实比较固定。先在ECR控制台创建仓库或者用CLI创建aws ecr create-repository \ --repository-name my-app \ --region ap-northeast-1创建完成后ECR会返回一个仓库URI格式一般是account_id.dkr.ecr.region.amazonaws.com/my-app。推送前需要先登录aws ecr get-login-password --region ap-northeast-1 | \ docker login --username AWS --password-stdin account_id.dkr.ecr.ap-northeast-1.amazonaws.com然后打标签并推送docker tag my-app:latest account_id.dkr.ecr.ap-northeast-1.amazonaws.com/my-app:latest docker push account_id.dkr.ecr.ap-northeast-1.amazonaws.com/my-app:latest在镜像tag策略上我个人的习惯是除了打latest之外还会再打一个带版本号的tag比如my-app:1.0.0。好处是当需要回滚时可以直接把Task Definition里的镜像地址指向上一个版本的tag而不是依赖latest这种容易漂移的引用。2.3 注册任务定义一份部署的源头Task Definition是整个部署流程的源头它决定了容器以什么样的配置运行。在控制台上创建Task Definition时核心需要关注的信息包括启动类型Fargate或EC2、CPU和内存大小、容器镜像地址、端口映射、环境变量、日志驱动以及IAM角色。如果你熟悉JSON用CLI注册会更快先写一份JSON文件再用aws ecs register-task-definition --cli-input-json file://task-def.json注册。一份Fargate模式下最简单的任务定义大概长这样{ family: my-app-task, networkMode: awsvpc, requiresCompatibilities: [FARGATE], cpu: 256, memory: 512, executionRoleArn: arn:aws:iam::123456789012:role/ecsTaskExecutionRole, taskRoleArn: arn:aws:iam::123456789012:role/my-app-task-role, containerDefinitions: [ { name: my-app-container, image: 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/my-app:latest, portMappings: [ { containerPort: 8080, protocol: tcp } ], environment: [ { name: SPRING_PROFILES_ACTIVE, value: prod } ], logConfiguration: { logDriver: awslogs, options: { awslogs-group: /ecs/my-app, awslogs-region: ap-northeast-1, awslogs-stream-prefix: my-app } } } ] }这里有几个值得展开的细节networkMode使用awsvpc模式时每个Task会获得独立的ENI和私有IP这种方式能直接挂安全组和ALB集成也最自然Fargate只支持这种模式executionRoleArn是ECS Agent用来拉取镜像、写日志时使用的角色taskRoleArn则是容器内部的应用调用AWS API时使用的角色两者职责不同别搞混。Environment变量这里只展示了简单的键值对如果敏感信息比较多建议用ECS的Secrets Manager集成直接通过valueFrom引用密钥避免环境变量泄露在任务定义里。2.4 创建集群与配置服务Task Definition注册好之后接下来就是创建Cluster和Service。在控制台创建Cluster的流程很直观选Fargate启动类型命名后直接创建即可。Cluster本身在Fargate模式下几乎不需要多余的配置它更多是一个逻辑容器把同一套环境下的服务归类管理。Service的创建才是重头戏。你需要在Service配置里选择刚刚注册的Task Definition指定服务名称、期望运行的任务数量Desired Count、部署方式以及是否挂载负载均衡器。对于面向公网的服务强烈建议在Service前面挂一个ALB这样既能做流量分发又能通过Target Group的健康检查判断容器是否真的可用。有几项配置在创建时容易忽略但实际运维中影响很大Deployment configurationMinimum healthy percent和Maximum percent决定了滚动更新的行为。比如设成100%和200%意味着更新时会先起一批新Task确认健康后再停止旧Task可以实现零停机发布。如果业务对资源预算敏感可以设成50%和100%牺牲一点并发度换取成本可控。Health check grace period容器启动较慢时比如Java应用启动需要几十秒如果不设置这个宽限期ALB的健康检查可能在容器还没就绪时就判定失败导致Task被反复杀灭重启。我一般会结合真实启动时间设置60到120秒。Deployment circuit breaker这个建议开启。它会在连续多次部署失败时自动停止部署避免坏版本反复滚动影响线上。2.5 验证线上运行效果Service创建完成之后等待一小段时间Task就会进入RUNNING状态。此时可以通过几个层面验证部署是否真的成功先看Service事件流里有没有service my-app-service has reached a steady state这类信息再去Target Group页面确认目标实例的健康状态最后通过ALB的域名访问应用观察是否返回预期结果。如果应用逻辑里有对下游依赖的调用建议在验证阶段就把日志和链路追踪打开确认容器内部确实能连通数据库、缓存等依赖而不只是“进程起来了”。这一步看起来多余但能帮你省掉很多“线上看起来正常、实际功能异常”的烦恼。3. 那些文档里不写但项目中躲不开的细节3.1 网络配置安全组和子网怎么放ECS的网络配置是新手最容易翻车的环节。在awsvpc网络模式下每个Task都会绑定一个ENI这个ENI会落到你指定的子网里并受安全组规则限制。如果你的Task需要被ALB访问安全组必须放行来自ALB安全组的流量如果Task需要访问数据库它的安全组也要在数据库侧被允许入站。我的建议是不要把所有服务都放进同一个全开安全组里而是按照访问关系设计安全组ALB一个安全组应用Task一个安全组数据库一个安全组。应用Task的安全组只放行来自ALB安全组的流量数据库的安全组只放行来自应用Task安全组的流量。这样即使某个ECS服务被打穿横向移动的路径也会被切断一大半。子网的选择也有讲究。如果Task不需要公网出网能力尽量放在私有子网通过NAT网关做公网访问如果只是测试环境可以暂时放在公网子网并分配公网IP但生产环境千万不要这么干。这里还隐含一个排查点如果你发现Task反复启动失败但日志里没有明显错误先检查子网路由和安全组尤其要确认NAT网关或互联网网关的路由表是否配置正确。3.2 IAM角色为什么任务起不来先查这里IAM角色的坑我在ECS项目里踩过不止一次。先说两个角色各自的用途execution role是ECS Agent在拉起任务时用的它决定Agent能不能从ECR拉镜像、能不能往CloudWatch写日志甚至能不能挂载EFStask role是容器内应用运行时用的比如你的应用要读S3、要调DynamoDB就得把这个权限赋予task role。实际操作中有一个很常见的排查场景新建了一个Task Definition没有显式指定executionRoleArn然后任务一直处于PENDING最后事件里报错CannotPullContainerError。这种大概率就是execution role没有配置或者没有ecr:GetDownloadUrlForLayer和ecr:BatchGetImage这些权限。解决办法很简单创建并配置好ECS官方提供的AmazonECSTaskExecutionRolePolicy托管策略然后在Task Definition里显式指定这个角色。另一个容易忽略的是EC2启动类型下的ecsInstanceRole。EC2模式的节点必须配置这个角色并把AmazonEC2ContainerServiceforEC2Role托管策略挂上否则节点无法向ECS控制面注册。我在迁移早期因为复用了一个已有的EC2实例没注意它上面的角色不对导致集群里始终看不到节点折腾了一下午。3.3 日志配置排查全靠它容器跑起来之后日志是你了解内部状态的唯一窗口。ECS任务默认不会保留容器日志必须在Task Definition的logConfiguration里显式指定awslogs驱动。如果这一步没配好容器启动后什么输出都看不到排查问题会非常痛苦。配置awslogs驱动时注意awslogs-group对应的CloudWatch Log Group最好提前创建好虽然ECS在部分情况下会自动创建但显式创建加设置保留期限更稳妥。awslogs-stream-prefix可以帮你按服务区分日志流建议用一个容易识别的名字比如服务名加环境名。还有一个实操小技巧如果Task频繁重启导致多个日志流混在一起可以在容器启动命令里加一个标识符比如把HOSTNAME环境变量打出来这样不同实例的日志就能区分开。HOSTNAME在ECS里是Task ID这一招在排查多实例问题时非常管用。3.4 成本控制ASG设0之后到底扣不扣费这个热搜问题我在各个群里见过太多次了很多人以为把ECS相关的Auto Scaling Group的Desired Capacity设为0服务停了费用就停了。这里必须说实话如果你用的是EC2启动类型的ECSASG的Desired设为0它只会保证“不创建新的实例”但已经存在的实例不会被自动终止。只要EC2实例还在运行费用就照常扣。换句话说ASG的Desired Capacity只管期望实例数实际计费按实例的运行时长来算。想真正省钱要么把EC2实例手动终止要么把ASG的Min和Desired都改成0同时确认实例确实被缩容释放了。如果你用的是Fargate情况好很多因为没有常驻实例概念没有Task跑就没有计算费用我这次选择Fargate成本可控也是一个重要原因。如果你需要应对周期性流量可以利用ECS的定时扩缩容把工作时间的Desired Count调高非工作时间调低到0。Fargate模式下这样能省得很明显而且不用维护底层节点。对于EC2模式如果不想在低谷期保留空闲节点可以配合ASG的缩容策略但EC2实例的分配和释放不像Fargate那么灵活需要多做一轮测试。4. 常见问题排查与避坑速查4.1 任务一直PENDING资源还是权限Task卡在PENDING状态是新人必踩的经典问题。Fargate模式下最常见的原因是VPC子网里可用IP不足或者指定的CPU/内存组合超过账号在当前区域的配额。遇到这种情况先看Service的事件流里面有具体的报错信息通过控制台左侧“Events”页签就能找到。如果报错语义不明确建议打开CloudTrail搜索RunTask相关的事件能拿到更底层的错误。配额问题也很常见尤其是新账号Fargate的vCPU配额默认可能只有几十个跑大任务时容易触顶。提前在配额页面把目标区域的Fargate vCPU配额调高比等部署时报错再处理要省心得多。还有一个容易被忽略的点如果子网是IPv6-only或者没有正确关联路由表也会导致PENDING。检查子网是否能正常访问AWS服务以及是否启用了自动分配公网IP如果确实需要公网访问是排查网络侧问题的基本操作。4.2 镜像拉取失败先看ECR和execution roleCannotPullContainerError是最常见的启动失败错误之一。排查路径我建议按这个顺序来先确认镜像URI有没有写错比如账号ID、区域、仓库名是否完全匹配再确认宿主机或执行环境能否访问ECRFargate模式下需要子网能访问公网或通过VPC Endpoint连接ECR最后确认execution role有没有ECR的拉取权限。这里有个壳点需要提醒即使ECR仓库和Task在同一个区域拉取失败也未必是权限问题。VPC内访问ECR流量默认走公网如果你的子网没有NAT网关又没有配置ECR VPC Endpoint任务一样拉不到镜像。在生产环境我更推荐直接给VPC加上ECR的Interface Endpoint把拉取流量收敛在内网既稳定又少一层公网依赖。4.3 服务反复重启健康检查、command、资源限制如果服务创建成功但Task一直在RUNNING和STOPPED之间循环罪魁祸首通常是这三类原因容器启动后进程退出、健康检查失败、资源限制触发OOM。先看Task状态和退出码再用CloudWatch日志确认应用有没有打印异常堆栈。如果是健康检查失败导致的循环需要区分是ALB健康检查失败还是容器自身健康检查失败。ALB健康检查失败Target Group里会显示实例不健康通常需要调整健康检查的路径和阈值容器自身健康检查失败则要检查信号是否就绪。一个经验是Web应用的健康检查路径不要写一个太重接口简单返回200就行否则高峰期容易误判不健康。资源限制导致的OOM在Fargate模式下通常表现为Task的StoppedReason里出现OutOfMemoryError。遇到这个情况不要盲目调大内存先观察应用的真实内存水位再决定是调大内存还是优化代码。Fargate的CPU和内存是组合选择的有的组合不能随意搭配调整时先看一眼文档里的兼容表。4.4 问题排查速查表现象常见原因排查方向任务一直PENDING子网IP不足、配额超限、网络不通查看Service事件、CloudTrail、VPC路由表拉取镜像失败ECR URI错误、权限不足、网络不通检查镜像地址、execution role、NAT/VPC Endpoint任务反复重启进程退出、健康检查失败、内存OOM看退出码、CloudWatch日志、Target Group状态无法从公网访问安全组放行不足、ALB未关联、路由缺失检查ALB监听器、Target Group、Task安全组费用异常EC2实例未终止、ASG设置不当查看EC2实例列表、ASG的Min/Max/Desired4.5 几个我保留至今的实操习惯最后分享几个我自己保留的实操习惯。每次改Task Definition之前我习惯先看一眼当前线上运行的版本在配置界面里确认改的就是目标版本避免改错服务每次推送新镜像之后用带版本号的tag部署而不是依赖latest这样回滚时非常容易开启Deployment circuit breaker它防止坏版本反复滚动影响线上。这些习惯虽然不起眼但关键时刻能帮你省掉很多操作失误带来的麻烦。另外建议给每个Service加上足够的Tags。ECS的账单明细里能按Tag拆分成本如果把项目和环境的Tag打清楚月底看账单时就不用靠猜来判断钱花在哪儿了。这个习惯我是在一次月底对账时养成的从那以后所有AWS资源我都会统一标上project和env标签成本和资源归属一目了然。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门