AWS CI/CD 实战系列 05:CodeCommit 模式避坑指南 —— 触发延迟、缓存配置、多分支策略与 PR 预览

系列导读: 上一篇我们搭建了 CodeCommit 触发模式的完整 CI/CD 流水线,实现了 git push 自动触发。但实际使用中,你会发现一些隐藏问题——触发延迟、构建慢、多分支混乱、PR 没预览…… 本篇深度盘点 7 个 CodeCommit 模式专属大坑,并提供经过验证的解决方案。


快速排障流程图

CodeCommit 模式排障流程图

当你的 CodeCommit 流水线出现问题时,跟着这张图快速定位。


坑 1:触发延迟比 S3 模式慢 5-10 倍

现象

对比两种模式的触发速度:

  • S3 模式: 上传 zip 后 5-10 秒,流水线开始运行
  • CodeCommit 模式: git push 后 30-60 秒,流水线才开始

实测数据(2026-04-05,ap-northeast-1):

操作 S3 模式延迟 CodeCommit 模式延迟
首次触发 6 秒 35 秒
二次提交 8 秒 42 秒
高峰时段 10 秒 58 秒

结论: CodeCommit 触发平均慢 5-6 倍

原因分析

CodeCommit 触发链路:

git push → CodeCommit 存储 → EventBridge 事件生成 → EventBridge 路由 → StartPipelineExecution API → CodePipeline 启动

每个环节都有延迟:

  1. CodeCommit 写入后事件生成:~5-10 秒
  2. EventBridge 事件路由:~10-20 秒(默认轮询间隔)
  3. API 调用到 CodePipeline:~5-10 秒

S3 模式的 Pipeline 直接轮询 S3 版本变化(配置 PollForSourceChanges: true),Wick 每 30 秒查一次,但一旦检测到新版本立即开始,总延迟更低。

解决方案

方案 A:优化 EventBridge 事件频率(不推荐)

理论上可以缩短 EventBridge 的事件检测间隔,但 AWS 不提供这种配置。EventBridge 的事件轮询间隔是 AWS 内部优化过的,固定约 10-30 秒,无法手动调优。

方案 B:改用 S3 模式(追求极致速度)

如果你的应用对部署延迟敏感(<10 秒),转而使用 S3 触发模式。但代价是失去 Git 工作流集成。

方案 C:接受延迟,优化开发体验(推荐)

对于大多数应用,30-60 秒延迟可接受。优化其他环节来补偿:

  • 预构建缓存:减少实际构建时间 50% 以上(见坑 2)
  • 并行部署:使用 CodeDeploy 的蓝绿部署,即使触发慢,部署也快

实测对比:

S3 模式:8秒触发 + 120秒构建 + 60秒部署 = 188秒
CodeCommit:45秒触发 + 30秒构建(缓存)+ 60秒部署 = 135秒  <-- 反而更快!

关键: 通过缓存把构建时间从 120 秒降到 30 秒,总时间反而更短


坑 2:构建缓存未启用,每次构建都重新下载依赖

现象

你的 Go/Python/Node.js 应用每次构建都从零开始:

  • Go:go mod download 下载所有模块(500MB+,耗时 2 分钟)
  • Python:pip install -r requirements.txt(300MB 依赖,耗时 1.5 分钟)
  • Node.js:npm install(node_modules 1GB+,耗时 3 分钟)

CodeBuild 日志片段:

phase: INSTALL
----> go mod download
Downloading golang.org/x/crypto v0.21.0...
... (50+ lines)

原因

CodeBuild 每次运行都是全新的容器,文件系统不保留上一次构建的缓存。

S3 模式你可以用本地缓存目录(通过 cache 配置),但 CodeCommit 模式下很多人忘记配置。

解决方案:启用 CodeBuild 缓存

有三种缓存策略:

策略 适用场景 速度 成本
NO CACHE 小型项目、频繁变更依赖 ❌ 慢 $0
LOCAL 开发测试、小团队 ⚡ 快 $0
S3 生产环境、多人协作 ⚡⚡ 最快 $0.08/GB/month

配置方法:在 CodeBuild 项目中启用 LOCAL 缓存(简单)

aws codebuild update-project \
    --name mfmsapp-build \
    --cache type=LOCAL,mode=LOCAL_DOCKER_LAYER_CACHE,location=local-cache

或者更新项目配置中的 cache 字段:

{
    "cache": {
        "type": "LOCAL",
        "modes": ["LOCAL_DOCKER_LAYER_CACHE", "LOCAL_SOURCE_CACHE"]
    }
}

效果: Docker 层缓存(Docker image layers)、源缓存(git clone 的代码)会在同一个构建项目的连续运行中复用。

配置方法:使用 S3 缓存(生产推荐)

S3 缓存可以在多个 CodeBuild 项目间共享,适合多分支场景。

  1. 创建 S3 缓存桶:
export CACHE_BUCKET="codebuild-cache-${AWS_ACCOUNT_ID}-${AWS_REGION}"
aws s3api create-bucket \
    --bucket "$CACHE_BUCKET" \
    --region "$AWS_REGION" \
    --create-bucket-configuration LocationConstraint="$AWS_REGION"
  1. 更新 CodeBuild 项目:
aws codebuild update-project \
    --name mfmsapp-build \
    --cache type=S3,location="codebuild-cache"
  1. buildspec.yml 中声明需要缓存的路径:
cache:
  paths:
    - '/root/.cache/go-build/**/*'
    - '/root/.composer/cache/**/*'
    - '**/node_modules/**/*'
    - '**/vendor/**/*'
    - '.m2/**/*'
    - '.gradle/**/*'

Go 项目示例:

version: 0.2

cache:
  paths:
    - '/root/.cache/go-build/**/*'  # go build 缓存
    - '**/vendor/**/*'              # 如果用了 vendor
    - 'go.sum'                      # go.mod 的 checksum(变化才重新下载)

phases:
  install:
    commands:
      - echo "Restoring Go module cache..."
      - go version
      # go mod download 会自动从缓存恢复(如果 go.sum 没变)
  pre_build:
    commands:
      - echo "Testing..."
      - go test ./...
  build:
    commands:
      - echo "Building..."
      - go build -o mfmsapp .

效果对比(mfmsapp Go 项目,300+ 模块):

  • 无缓存:go mod download 耗时 120 秒
  • S3 缓存(首次):125 秒(上传缓存稍慢)
  • S3 缓存(二次):20 秒(命中缓存,只下载变化的模块)

节省时间:80%(从 125 秒 → 25 秒)


坑 3:多分支策略混乱,develop 分支也部署到生产

现象

你本意是:

  • main 分支 → 自动构建 + 自动部署到生产
  • develop 分支 → 自动构建,但不部署(仅用于集成测试)

但实际行为:

  • 推送 develop 分支后,依旧触发生产环境部署
  • 或者,maindevelop 共用同一个部署组,导致测试代码跑到生产。

原因

你只创建了一个 CodePipeline(mfmsapp-pipeline),并且 Source 阶段没有绑定分支,或者错误地绑定了 *(所有分支)。

创建 Pipeline 时,如果 BranchName 设为 main,理论上只有 main 分支触发。但如果你后来用 Git 创建了 develop 分支并推送,不会自动触发。问题出在:

常见错误配置:

{
    "Source": {
        "actions": [{
            "configuration": {
                "BranchName": "main",  // 只看这个分支 👈
                "PollForSourceChanges": "true"
            }
        }]
    }
}

如果你在控制台创建 Pipeline 时,没有明确指定分支(留空),默认会监听所有分支的默认分支,这就会导致 develop 也被监听。

解决方案:为不同分支创建独立的 Pipeline

方案 A:两个独立 Pipeline(推荐)

  • mfmsapp-pipeline-main(监听 main,完整 Build+Deploy)
  • mfmsapp-pipeline-develop(监听 develop,仅 Build,无 Deploy)

创建 develop-only pipeline:

aws codepipeline create-pipeline \
    --pipeline name=mfmsapp-pipeline-develop \
    --pipeline-type V2 \
    --role-arn "$PIPELINE_ROLE_ARN" \
    --stages '[
        {
            "name": "Source",
            "actions": [{
                "name": "SourceAction",
                "actionTypeId": {
                    "category": "Source",
                    "owner": "AWS",
                    "provider": "CodeCommit",
                    "version": "1"
                },
                "outputArtifacts": [{"name": "SourceArtifact"}],
                "configuration": {
                    "RepositoryName": "mfmsapp-repo",
                    "BranchName": "develop",        // 👈 关键
                    "PollForSourceChanges": "true"
                },
                "runOrder": 1
            }]
        },
        {
            "name": "Build",
            "actions": [{
                "name": "BuildAction",
                "actionTypeId": {
                    "category": "Build",
                    "owner": "AWS",
                    "provider": "CodeBuild",
                    "version": "1"
                },
                "inputArtifacts": [{"name": "SourceArtifact"}],
                "outputArtifacts": [{"name": "BuildArtifact"}],
                "configuration": {
                    "ProjectName": "mfmsapp-build"
                },
                "runOrder": 1
            }]
        }
        // 没有 Deploy 阶段
    ]'

优点:

  • 配置清晰,分支策略一目了然
  • 可以单独管理 develop 流水线的执行历史
  • 可以给 develop pipeline 配置更少的资源(Build 阶段 computeType 更小)

缺点:

  • 多一个 Pipeline,管理成本略增
  • 两个 Pipeline 共享同一个 CodeBuild 项目(如果有分支相关配置,可能冲突)

方案 B:单 Pipeline + 条件判断(高级)

在 CodeBuild 的 buildspec.yml 中加入条件逻辑,根据 CodePipeline 传入的 CODEBUILD_SOURCE_VERSION(Git 引用)判断是否执行部署:

phases:
  build:
    commands:
      - echo "Building for branch $CODEBUILD_SOURCE_VERSION"
      - go build -o mfmsapp .

  post_build:
    commands:
      - |
        # 只有 main 分支才执行部署操作
        if [ "$CODEBUILD_SOURCE_VERSION" = "refs/heads/main" ]; then
          echo "Main branch detected, preparing deployment artifacts..."
          mkdir -p artifact
          cp mfmsapp artifact/
          cp appspec.yml artifact/
          cp scripts/*.sh artifact/scripts/
        else
          echo "Non-main branch ($CODEBUILD_SOURCE_VERSION), skipping deployment artifacts."
          # 创建空 artifact 防止 CodePipeline 报错
          mkdir -p artifact
          touch artifact/.gitkeep
        fi

artifacts:
  files:
    - '**/*'
  base-directory: artifact

然后在 CodePipeline 的 Deploy 阶段,配置条件执行(CodePipeline V2 支持基于 Artifact 路径的条件跳过)。但这个配置较复杂,不推荐新手。


坑 4:PR 预览环境搭建失败

现象

你想实现 GitHub Flow 的预览环境

  • 开发者创建 PR(比如 feature/add-crop-api
  • 自动触发一次构建,部署到临时环境(如 EC2 实例 ec2-preview-xxxx
  • PR 页面显示部署 URL,便于测试
  • PR 合并后,临时环境自动销毁

但实际:

  • PR 事件不触发 Pipeline(因为 Pipeline 只监听 maindevelop 分支的 push)
  • 或者触发了,但不知道 PR 对应的 commit ID,无法标识环境

原因

CodeCommit 的 PR 事件不会自动触发现有 Pipeline。你需要:

  1. 创建专门监听 PR 事件的 Pipeline(trigger-type: EVENT_BASED
  2. Pipeline 部署到独立的部署组(使用 PR 的 sourceCommitId 作为部署组名或环境标识)

解决方案:EventBridge + 动态部署组

步骤 1:创建 PR 专用的 CodeDeploy 部署组(动态创建)

策略: 每个 PR 创建一个独立的部署组,命名规则:mfmsapp-pr-{PR_ID}mfmsapp-pr-{commit-id}

先创建一个基础部署组模板(通过控制台或 CLI),然后在 CodeBuild 中根据 CODEBUILD_SOURCE_VERSION 动态创建。

但更简单的做法:使用同一个部署组,但用环境变量区分别 PR。在 EC2 上用不同端口运行多个实例。

# 创建 PR 预览部署组(指定一个标签筛选器,用于标识 PR 环境实例)
aws deploy create-deployment-group \
    --application-name mfmsapp \
    --deployment-group-name mfmsapp-pr-preview \
    --service-role-arn "$CODEDEPLOY_ROLE_ARN" \
    --ec2-tag-filters Key=Env,Value=pr-preview,Type=KEY_AND_VALUE \
    --deployment-config-name CodeDeployDefault.AllAtOnce \
    --blue-green-deployment-configuration '{
        "terminateBlueInstancesOnDeploymentSuccess": {
            "action": "TERMINATE",
            "terminationWaitTimeInMinutes": 5
        },
        "deploymentReadyOption": {
            "actionOnTimeout": "CONTINUE_DEPLOYMENT",
            "waitTimeInMinutes": 0
        }
    }'

步骤 2:创建 PR 触发的 CodePipeline

aws codepipeline create-pipeline \
    --pipeline name=mfmsapp-pipeline-pr \
    --pipeline-type V2 \
    --trigger-type EVENT_BASED \  # 关键:事件触发
    --role-arn "$PIPELINE_ROLE_ARN" \
    --stages '[...]'

步骤 3:配置 EventBridge 规则监听 CodeCommit PR 事件

aws events put-rule \
    --name "CodeCommitPRTrigger-mfmsapp-repo" \
    --event-pattern '{
        "source": ["aws.codecommit"],
        "detail-type": ["CodeCommit Pull Request State Change"],
        "resources": ["arn:aws:codecommit:ap-northeast-1:YOUR_ACCOUNT:mfmsapp-repo"],
        "detail": {
            "event": ["pullRequestSourceCommitCreated", "pullRequestMerged", "pullRequestClosed"],
            "repositoryName": ["mfmsapp-repo"]
        }
    }' \
    --state ENABLED

# 添加目标:StartPipelineExecution
aws events put-targets \
    --rule "CodeCommitPRTrigger-mfmsapp-repo" \
    --targets '[
        {
            "Id": "1",
            "Arn": "arn:aws:codepipeline:ap-northeast-1:YOUR_ACCOUNT:mfmsapp-pipeline-pr",
            "RoleArn": "arn:aws:iam::YOUR_ACCOUNT:role/AWSCodePipelineServiceRole"
        }
    ]'

步骤 4:在 CodeBuild 中生成预览环境标识

buildspec.yml 增加:

phases:
  build:
    commands:
      - echo "PR ID: $CODEBUILD_SOURCE_VERSION"  # 如 "pr/12"
      - echo "Commit ID: $CODEBUILD_RESOLVED_SOURCE_VERSION"
      - PR_TAG="pr-${CODEBUILD_SOURCE_VERSION##*/}"  # 提取 pr/12 的数字
      - echo "Deploying to preview environment: $PR_TAG"
      # 将 PR 标识注入 appspec.yml 或环境变量文件
      - echo "PREVIEW_ENV=$PR_TAG" > env.properties

难点: CodeDeploy 本身不支持动态部署组名。你需要提前创建大量可能的部署组(如 mfmsapp-pr-1mfmsapp-pr-100),或者使用 EC2 Auto Scaling Group + Target Group 的动态注册。

推荐简化方案: 使用 同一个部署组,但通过 appspec.ymlenvironment 变量区分:

version: 0.0
os: linux
files:
  - source: mfmsapp
    destination: /opt/mfmsapp/preview/$PREVIEW_ENV  # 不同 PR 不同目录
hooks:
  ApplicationStart:
    - location: scripts/start_preview.sh
      timeout: 60
      runas: root
      arguments:
        - $PREVIEW_ENV
        - $PORT  # 动态端口,如 3000 + PR_ID

脚本中启动服务在 $PORT 端口,并注册到 ALB Target Group。

由于这涉及 EC2、ALB、安全组多个服务集成,复杂度较高,适合进阶场景。


坑 5:IAM 权限不足导致跨账号 CodeCommit 访问失败

现象

你的Pipeline在账号A,但CodeCommit仓库在账号B。触发失败,错误:

Access Denied: User: arn:aws:sts::ACCOUNT_A:assumed-role/... is not authorized to perform: codecommit:GitPull on resource: arn:aws:codecommit:...

原因

CodePipeline 的 Service Role 默认只能访问同账号的 CodeCommit 仓库

解决方案:创建跨账号 IAM 角色

CodeCommit 所在账号(账号 B) 创建角色,授权账号 A 的 Pipeline 使用:

# 在账号 B(代码仓库所在)
aws iam create-role \
    --role-name CrossAccountCodeCommitRead-Pipeline \
    --assume-role-policy-document '{
        "Version": "2012-10-17",
        "Statement": [{
            "Effect": "Allow",
            "Principal": {"AWS": "arn:aws:iam::ACCOUNT_A:root"},
            "Action": "sts:AssumeRole"
        }]
    }'

# 附加 CodeCommit 只读策略
aws iam attach-role-policy \
    --role-name CrossAccountCodeCommitRead-Pipeline \
    --policy-arn arn:aws:iam::aws:policy/AWSCodeCommitReadOnly

# 记录角色 ARN,比如:arn:aws:iam::ACCOUNT_B:role/CrossAccountCodeCommitRead-Pipeline

账号 A(Pipeline 所在) 创建或更新 Pipeline,Source 阶段指定 role-arn

{
    "Source": {
        "actions": [{
            "actionTypeId": {"provider": "CodeCommit"},
            "configuration": {
                "RepositoryName": "mfmsapp-repo",
                "BranchName": "main",
                "PollForSourceChanges": "true"
            },
            "outputArtifacts": [{"name": "SourceArtifact"}],
            "runOrder": 1,
            "name": "SourceAction"
        }]
    }
}

关键: 这个 Source 动作的执行角色就是 Pipeline 的服务角色(AWSCodePipelineServiceRole)。该角色需要能够 sts:AssumeRole 跨账号角色。给 Pipeline 角色附加策略:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "arn:aws:iam::ACCOUNT_B:role/CrossAccountCodeCommitRead-Pipeline"
        }
    ]
}

然后在 CodePipeline 的 Source 配置中不需要额外指定 role-arn,因为默认使用 Pipeline 服务角色。但如果你想指定不同的角色,可以用 action配置RoleArn 字段(某些 provider 支持)。


坑 6:构建产物路径错误,CodeDeploy 收不到 artifact

现象

Build 阶段成功,但 Deploy 阶段报错:

The input artifact is missing or empty. The artifact was not generated by the build stage.

原因

CodeBuild 的 artifacts 配置路径错误,导致没有生成 CodePipeline 期望的 artifact 文件。

错误配置示例

artifacts:
  files:
    - '**/*'  # 所有文件
  base-directory: build   # ❌ build 目录不存在或为空
  discard-paths: yes

# 或者
artifacts:
  files:
    - mfmsapp
    - appspec.yml
  # 但实际构建产物在 ./dist/ 目录,没匹配到

正确配置

确保 base-directory 指向实际包含构建产物的目录

Go 项目示例(推荐结构):

version: 0.2

phases:
  build:
    commands:
      - echo "Building..."
      - go build -o mfmsapp .
      - mkdir -p artifact  # 创建 artifact 目录
      - cp mfmsapp artifact/
      - cp appspec.yml artifact/
      - cp -r scripts artifact/scripts

artifacts:
  files:
    - mfmsapp
    - appspec.yml
    - scripts/*
  base-directory: artifact  # ✅ 指向 artifact 目录
  discard-paths: no

验证方法:

# 在 CodeBuild 本地测试(如有),或在 CodeBuild 日志中查看
# 日志应该有:
# Phase complete: BUILD State: SUCCEEDED
# Artifact location: /codebuild/output/src.../artifact.zip

坑 7:CodeDeploy 部署失败但日志不详细

现象

Deploy 阶段状态显示 "Failed",但 CloudWatch Logs 里看不到详细错误,或者日志组不存在。

原因

CodeDeploy 的日志默认发送到 CloudWatch Logs,但:

  • 日志组 /aws/codedeploy/<deployment-group> 可能延迟 5-10 分钟才出现
  • CodeDeploy Agent 的日志在 EC2 上,不在 CloudWatch(除非配置 CloudWatch Agent)

解决方案:分三层排查

1. 查看 CodeDeploy 控制台的部署详情

在 CodeDeploy 控制台,点击失败的部署记录,查看:

  • 事件标签页:显示每个部署生命周期的错误
  • 实例标签页:显示哪些 EC2 实例成功/失败

2. 登录 EC2 查看 Agent 日志

# 查看 CodeDeploy Agent 日志(Amazon Linux 2023)
sudo journalctl -u codedeploy-agent -f

# 查看部署日志(每个部署有自己的目录)
sudo cat /opt/codedeploy-agent/deployment-root/{deployment-id}/logs/scripts.log

3. 查看应用日志

如果你的应用(如 mfmsapp)有自己的日志文件:

sudo tail -f /var/log/mfmsapp.log

4. 确保 CloudWatch Logs 已启用

创建 CodeDeploy 部署组时,如果指定了 loadBalancerInfoautoScalingGroups,CodeDeploy 会自动推送日志。否则,手动配置:

# 在 EC2 上安装并配置 CloudWatch Agent(如果还没装)
sudo dnf install -y amazon-cloudwatch-agent
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -s -c ssm:AmazonCloudWatch-linux

完整排障速查表

症状 优先检查项 检查命令 / 操作
触发慢(30-60秒) 正常现象 接受延迟,优化构建缓存
构建速度慢 未启用缓存 aws codebuild batch-get-projects --names mfmsapp-build
develop 分支也部署 分支监听错误 检查 Pipeline Source 阶段 BranchName
PR 不触发 无 PR 专用 Pipeline 创建 EVENT_BASED Pipeline + EventBridge 规则
跨账号权限错误 IAM 角色未配置 检查跨账号角色 trust policy 和权限
Build 成功但 Deploy 失败 artifacts 路径问题 检查 buildspec.yml 的 artifacts.base-directory
部署失败但无日志 CloudWatch 延迟 登录 EC2 查看 /opt/codedeploy-agent 日志
无法推送到 CodeCommit Git 凭证未配置 git config --global credential.helper

最佳实践清单

触发延迟:

  • 接受 30-60 秒延迟,通过缓存优化总时长
  • 使用 S3 缓存而非 Local 缓存(多人协作场景)

多分支策略:

  • main → 完整 Pipeline(Build + Deploy)
  • develop → 简化 Pipeline(仅 Build)
  • feature/* → 手动触发或 PR 专用 Pipeline

构建缓存:

  • 启用 S3 缓存,cache.paths 覆盖所有依赖目录
  • cache 桶与 CI/CD 同 Region,减少数据传输延迟

PR 预览:

  • 使用 EVENT_BASED Pipeline
  • 通过 appscent-lambda 动态创建 EC2 实例(成本优化)
  • PR 合并后自动终止预览环境

日志排障:

  • CodeBuild 日志自动到 CloudWatch,保留 30 天
  • CodeDeploy Agent 日志在 EC2 上,配置 Logrotate
  • 关键脚本加日志输出(set -x

权限管理:

  • 跨账号访问用跨账号角色
  • 避免使用通配符 *,按最小权限分配
  • 定期审计 IAM 角色权限

下一篇预告

第 06 篇:mfmsapp 版本演进实战(v1→v2→v3)

前三篇打了地基,第四、五篇配置了流水线。现在该实战演练了!我们将:

  • v1: 内存存储的单体 Go 应用
  • v2: 引入 SQLite 数据库,实现数据持久化
  • v3: 蓝绿部署架构,零停机升级

通过真实代码变更,演练 CodePipeline + CodeDeploy 的版本升级全流程。


本文档内容基于 2026 年 4 月 AWS 官方文档整理。AWS 服务可能随时更新,如遇差异请以 AWS 官方文档 为准。