You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免AWS CodePipeline在ECS镜像未变时部署服务并持久化缓存?

AWS Copilot多服务流水线部署优化问题

使用AWS Copilot执行copilot init pipeline和copilot pipeline deploy命令,基于AWS CodePipeline与CodeBuild部署多服务,相关配置如下:

配置片段

流水线清单

name: ******
version: 1
source:
  provider: GitHub
  properties:
    branch: *******
    repository: ***********
    connection_name: ********
stages:
  - name: ***********
    deployments:
      - service1
      - service2
      - service3:
          depends_on: [service1, service2]

Buildspec.yaml

# Buildspec runs in the build stage of your pipeline.
version: 0.2
phases:
  install:
    commands:
      - echo "cd into $CODEBUILD_SRC_DIR"
      - cd $CODEBUILD_SRC_DIR
      # Download the copilot linux binary.
      - wget -q https://ecs-cli-v2-release.s3.amazonaws.com/copilot-linux-v1.34.0 -O copilot-linux
      - chmod +x ./copilot-linux
  build:
    commands:
      - echo "Building base image..."
      - docker build -t base-image -f Dockerfile .
     
  post_build:
    commands:   
      - ls -la
      - export COLOR="false"
      - export CI="true"
      - pipeline=$(cat $CODEBUILD_SRC_DIR/copilot/pipelines/production/manifest.yml | ruby -ryaml -rjson -e 'puts JSON.pretty_generate(YAML.load(ARGF))')
      - pl_envs=$(echo $pipeline | jq -r '.stages[].name')
      # Find all the local services in the workspace.
      - svc_ls_result=$(./copilot-linux svc ls --local --json)
      - svc_list=$(echo $svc_ls_result | jq '.services')
      - >
        if [ ! "$svc_list" = null ]; then
          svcs=$(echo $svc_ls_result | jq -r '.services[].name');
        fi
      # Find all the local jobs in the workspace.
      - job_ls_result=$(./copilot-linux job ls --local --json)
      - job_list=$(echo $job_ls_result | jq '.jobs')
      - >
        if [ ! "$job_list" = null ]; then
          jobs=$(echo $job_ls_result | jq -r '.jobs[].name');
        fi
      # Raise error if no services or jobs are found.
      - >
        if [ "$svc_list" = null ] && [ "$job_list" = null ]; then
          echo "No services or jobs found for the pipeline to deploy. Please create at least one service or job and push the manifest to the remote." 1>&2;
          exit 1;
        fi
      # Generate the cloudformation templates.
      # The tag is the build ID but we replaced the colon ':' with a dash '-'.
      # We truncate the tag (from the front) to 128 characters, the limit for Docker tags
      # (https://docs.docker.com/engine/reference/commandline/tag/)
      # Check if the `svc package` commanded exited with a non-zero status. If so, echo error msg and exit.
      - >
        for env in $pl_envs; do
          tag=$(echo ${CODEBUILD_BUILD_ID##*:}-$env | sed 's/:/-/g' | rev | cut -c 1-128 | rev)
          for svc in $svcs; do
          ./copilot-linux svc package -n $svc -e $env --output-dir './infrastructure' --tag $tag --upload-assets;
          if [ $? -ne 0 ]; then
            echo "Cloudformation stack and config files were not generated. Please check build logs to see if there was a manifest validation error." 1>&2;
            exit 1;
          fi
          done;
          for job in $jobs; do
          ./copilot-linux job package -n $job -e $env --output-dir './infrastructure' --tag $tag --upload-assets;
          if [ $? -ne 0 ]; then
            echo "Cloudformation stack and config files were not generated. Please check build logs to see if there was a manifest validation error." 1>&2;
            exit 1;
          fi
          done;
        done;
      - ls -lah ./infrastructure
artifacts:
  files:
    - "infrastructure/*"

服务Dockerfile

FROM base-image
ARG micro_service=service1
WORKDIR /usr/src/app
ADD ./$micro_service/ ./$micro_service
EXPOSE 8000
WORKDIR /usr/src/app/$micro_service
# Build Service
RUN npm run build
RUN chmod +x start.sh
CMD ["./start.sh"]

注:base image包含所有服务的通用代码,目前仅在构建阶段临时缓存,未推送到ECR。

当前问题

流水线运行时会构建Docker镜像并推送到ECR,但即使镜像内容未变更(依赖缓存复用),ECS任务定义仍会被更新,导致所有服务被重新部署。

具体疑问与解决方案

1. 镜像未变更时阻止ECS任务更新

当前每次构建都会用CODEBUILD_BUILD_ID生成新标签,即使镜像内容一致,新标签也会触发任务定义更新。解决思路:

  • 基于镜像内容生成唯一标签:用Docker镜像的摘要(digest)作为标签,或者计算服务代码+base镜像的哈希值作为标签,确保内容不变则标签不变。修改Buildspec中生成tag的逻辑:
    # 替代原tag生成命令,先构建服务镜像并获取摘要,提取前16位作为标签
    tag=$(docker build -t temp-svc-$svc --build-arg micro_service=$svc . | grep "Successfully built" | awk '{print $3}' | cut -c 1-16)-$env
    
  • 跳过重复资产上传:在执行copilot svc package前,检查ECR中是否已存在该标签的镜像,若存在则去掉--upload-assets参数,避免生成新的任务定义。

2. 仅部署变更的服务

当前流水线会遍历所有服务执行package,导致全量部署。优化方案:

  • 通过Git diff识别变更服务:在Buildspec的build阶段前,添加Git命令对比当前提交与上一次构建的提交,找出变更的服务目录:
    # 获取上一次构建的commit ID(存储在S3中)
    last_commit=$(aws s3 cp s3://your-bucket/last-commit.txt - || echo "")
    # 对比变更文件所属服务
    changed_svcs=$(git diff --name-only $last_commit HEAD | grep -E "^(service1|service2|service3)/" | cut -d'/' -f1 | sort -u)
    # 若无变更服务,直接退出构建
    if [ -z "$changed_svcs" ]; then echo "No services changed, skipping build"; exit 0; fi
    # 更新存储的last_commit
    echo $CODEBUILD_RESOLVED_SOURCE_VERSION > last-commit.txt && aws s3 cp last-commit.txt s3://your-bucket/
    
  • 修改循环逻辑仅处理变更服务:将post_build中的for svc in $svcs替换为for svc in $changed_svcs,只对变更的服务执行package和部署。

3. 持久化base image缓存

当前base image仅在单次构建中缓存,无法跨构建复用。解决方法:

  • 将base image推送到ECR:在Buildspec的build阶段,构建base image后推送到ECR仓库,后续构建先拉取该镜像作为缓存:
    # 构建并推送base image到ECR
    base_image_tag=$(docker build -t base-image . | grep "Successfully built" | awk '{print $3}')
    aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin your-ecr-account-id.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
    docker tag base-image:latest your-ecr-account-id.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/base-image:$base_image_tag
    docker tag base-image:latest your-ecr-account-id.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/base-image:latest
    docker push your-ecr-account-id.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/base-image --all-tags
    
  • 修改服务Dockerfile引用ECR中的base image:将FROM base-image改为FROM your-ecr-account-id.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/base-image:latest,构建服务镜像时优先拉取该镜像作为缓存层。
  • 启用CodeBuild Docker层缓存:在CodeBuild项目配置中开启Docker layer caching,复用之前构建的镜像层,进一步减少构建时间。

内容的提问来源于stack exchange,提问作者Ziaf Nadeem

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 09:40:55