AWS CI/CD 实战系列 05:CodeCommit 模式避坑指南 —— 触发延迟、缓存配置、多分支策略与 PR 预览
系列导读: 上一篇我们搭建了 CodeCommit 触发模式的完整 CI/CD 流水线,实现了 git push 自动触发。但实际使用中,你会发现一些隐藏问题——触发延迟、构建慢、多分支混乱、PR 没预览…… 本篇深度盘点 7 个 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 启动
每个环节都有延迟:
- CodeCommit 写入后事件生成:~5-10 秒
- EventBridge 事件路由:~10-20 秒(默认轮询间隔)
- 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 项目间共享,适合多分支场景。
- 创建 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"
- 更新 CodeBuild 项目:
aws codebuild update-project \
--name mfmsapp-build \
--cache type=S3,location="codebuild-cache"
- 在
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分支后,依旧触发生产环境部署! - 或者,
main和develop共用同一个部署组,导致测试代码跑到生产。
原因
你只创建了一个 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 只监听
main或develop分支的 push) - 或者触发了,但不知道 PR 对应的 commit ID,无法标识环境
原因
CodeCommit 的 PR 事件不会自动触发现有 Pipeline。你需要:
- 创建专门监听 PR 事件的 Pipeline(
trigger-type: EVENT_BASED) - 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-1 到 mfmsapp-pr-100),或者使用 EC2 Auto Scaling Group + Target Group 的动态注册。
推荐简化方案: 使用 同一个部署组,但通过 appspec.yml 的 environment 变量区分:
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 部署组时,如果指定了 loadBalancerInfo 或 autoScalingGroups,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 官方文档 为准。