一、遇到的真实坑:CI推完镜像,服务却没更新
做DevOps的同学大概率碰过这种糟心事:开发同学把代码改好,CI流水线自动打包成新镜像推到镜像仓库,按说后面ArgoCD会自动拉新配置更新服务,结果刷新服务一看——啥变化都没有!翻日志才发现,要么是新镜像的签名校验失败,要么是旧配置比新配置先到ArgoCD,导致ArgoCD直接跳过了这次更新。
我之前帮一家做电商的客户调这个链路时,他们的大促场景下这个问题特别突出:大促前一周每天要更3次服务,经常出现“明明CI显示成功,线上服务却没更新”的情况,后来才发现是链路的顺序和校验逻辑出了问题。
二、从代码到ArgoCD的完整事件链路拆解
要解决问题,得先把每一步的流程理清楚,不能只盯着ArgoCD的配置。我把整个链路拆成了5个核心环节,每个环节的顺序和逻辑都不能乱。
2.1 代码提交触发CI流水线
首先是开发同学把代码推到Git仓库,比如Gitea或者GitLab,然后CI流水线开始跑。CI流水线一般会做编译、单元测试、打包镜像、推镜像到镜像仓库(比如Harbor)这几步。
这里要注意,CI流水线是串行执行的,每一步都要等上一步成功才会继续。比如只有镜像推到Harbor成功了,才会去更新配置仓库里的镜像标签。
2.2 镜像推送成功后更新配置仓库
镜像推完之后,CI流水线会修改配置仓库里的k8s配置文件,把原来的旧镜像标签改成新的。比如原来的标签是v1.0.0,现在改成v1.0.1。
这个步骤特别关键,必须保证镜像真的推成功了才改标签。我之前见过有团队的CI流水线是先改标签再推镜像,结果推镜像失败了,配置仓库却已经更新了,导致ArgoCD拉到新配置,但是找不到镜像,服务更新失败。
2.3 配置仓库变更触发Webhook到ArgoCD
配置仓库改完之后,会通过Webhook给ArgoCD发一个通知,告诉ArgoCD“配置变了,快来拉新配置”。
这里的Webhook有个坑:Webhook是异步触发的,也就是说配置仓库改完之后,给ArgoCD发通知的动作是后台跑的,不保证通知一定会成功,也不保证通知的顺序。比如你先改了v1.0.1的标签,又改了v1.0.2的标签,结果Webhook先把v1.0.2的通知发给ArgoCD,再把v1.0.1的通知发给ArgoCD,ArgoCD就会先拉v1.0.2的配置,再拉v1.0.1的配置,最后更新的是旧配置。
2.4 ArgoCD拉取配置并校验镜像
ArgoCD收到Webhook通知后,会去拉配置仓库的最新配置,然后解析配置里的镜像标签,去镜像仓库拉取镜像并校验签名。
这里的校验逻辑是:ArgoCD会验证镜像的签名是否和配置仓库里的签名一致,如果不一致,就会拒绝更新。如果校验失败,或者拉配置的时候拉到了旧版本的配置,ArgoCD就会跳过这次更新。
2.5 ArgoCD同步配置到K8s集群
如果校验成功,ArgoCD就会把配置同步到K8s集群,更新服务的镜像。
三、核心问题分析:为什么会跳过更新?
我把常见的问题分成了两类,每类都有具体的场景和原因。
3.1 签名校验失败的原因
签名校验失败一般有三种情况: 第一种是镜像推送失败,比如Harbor的磁盘满了,或者网络波动,导致镜像没推上去,但是CI流水线却认为推成功了,改了配置仓库的标签。结果ArgoCD拉配置的时候,镜像不存在,校验失败。 第二种是配置仓库的签名和镜像的签名不一致。比如开发同学手动改了配置仓库里的签名,或者CI流水线在改标签的时候,签名更新失败了。 第三种是ArgoCD的签名校验配置有问题。比如ArgoCD用的是旧的公钥,而镜像用的是新的私钥签名,导致校验不通过。
3.2 事件顺序颠倒的原因
事件顺序颠倒的核心原因是Webhook的异步触发。比如:
- 配置仓库先改了v1.0.1的标签,然后Webhook发通知给ArgoCD,但是网络波动,通知延迟了。
- 然后开发同学又改了代码,CI流水线跑了新的版本v1.0.2,改了配置仓库的标签,这次Webhook的通知很快就到了ArgoCD。
- ArgoCD先拉了v1.0.2的配置,更新了服务。
- 过了一会儿,v1.0.1的Webhook通知到了ArgoCD,ArgoCD又拉了v1.0.1的配置,发现和当前集群的配置(v1.0.2)不一致,但是ArgoCD默认会认为新的通知是最新的,所以会回滚到v1.0.1,或者直接跳过这次更新。
四、解决方案:配置可靠的重试和顺序控制
针对上面的问题,我总结了一套完整的解决方案,包括链路优化、重试配置、校验逻辑优化三个部分。
4.1 链路优化:保证每一步的顺序
首先要保证CI流水线的每一步都是串行执行的,必须等上一步成功才会继续。比如:
- 第一步:编译、单元测试。
- 第二步:打包镜像、推镜像到Harbor,并且验证镜像是否真的存在(比如用curl请求Harbor的API,检查镜像是否存在)。
- 第三步:更新配置仓库的镜像标签和签名。
- 第四步:触发Webhook到ArgoCD。
这里我给大家一个具体的CI流水线示例,用的是GitLab CI,因为很多团队都在用。
# GitLab CI配置,所有步骤串行执行,每步都做验证
stages:
- build_and_test
- push_image
- update_config
- trigger_webhook
# 第一步:编译和单元测试
build_and_test:
stage: build_and_test
image: maven:3.8.5-openjdk-11
script:
- mvn clean package -DskipTests
- mvn test
only:
- main
# 第二步:打包并推镜像到Harbor,验证镜像存在
push_image:
stage: push_image
image: docker:24.0.6
services:
- docker:dind
script:
# 登录Harbor
- docker login -u $HARBOR_USER -p $HARBOR_PASSWORD $HARBOR_URL
# 打包镜像,标签用当前Git提交的哈希值,保证唯一
- IMAGE_TAG=$CI_COMMIT_SHORT_SHA
- docker build -t $HARBOR_URL/$PROJECT_NAME/$APP_NAME:$IMAGE_TAG .
# 推镜像
- docker push $HARBOR_URL/$PROJECT_NAME/$APP_NAME:$IMAGE_TAG
# 验证镜像是否存在:用Harbor的API检查镜像
- |
curl -f -s -o /dev/null -H "Authorization: Basic $(echo -n $HARBOR_USER:$HARBOR_PASSWORD | base64)" $HARBOR_URL/api/v2.0/projects/$PROJECT_NAME/repositories/$APP_NAME/artifacts/$IMAGE_TAG
if [ $? -ne 0 ]; then
echo "镜像推送失败,不存在"
exit 1
fi
only:
- main
# 第三步:更新配置仓库的镜像标签和签名
update_config:
stage: update_config
image: alpine/git:latest
before_script:
# 配置Git的身份信息
- git config --global user.email "ci@example.com"
- git config --global user.name "CI Bot"
# 克隆配置仓库
- git clone $CONFIG_REPO_URL config-repo
script:
- cd config-repo
# 替换配置文件里的旧镜像标签为新标签
- IMAGE_TAG=$CI_COMMIT_SHORT_SHA
- sed -i "s|image: $HARBOR_URL/$PROJECT_NAME/$APP_NAME:.*|image: $HARBOR_URL/$PROJECT_NAME/$APP_NAME:$IMAGE_TAG|" deployment.yaml
# 这里可以加签名更新的逻辑,比如用cosign给镜像签名,然后把签名写到配置文件里
# 比如:cosign sign $HARBOR_URL/$PROJECT_NAME/$APP_NAME:$IMAGE_TAG
# 然后把签名写到deployment.yaml的annotation里
# 提交变更到配置仓库
- git add deployment.yaml
- git commit -m "Update $APP_NAME image to $IMAGE_TAG"
- git push
only:
- main
# 第四步:触发Webhook到ArgoCD
trigger_webhook:
stage: trigger_webhook
image: alpine:latest
script:
# 调用ArgoCD的Webhook API,通知ArgoCD配置仓库有变更
- |
curl -f -s -o /dev/null -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $ARGOCD_TOKEN" $ARGOCD_URL/api/webhook/git
if [ $? -ne 0 ]; then
echo "Webhook触发失败"
exit 1
fi
only:
- main
这个CI流水线的核心是每一步都做验证,比如推完镜像之后会用API检查镜像是否存在,Webhook触发失败会直接退出流水线,保证不会出现“配置更新了,镜像不存在”的情况。
4.2 重试配置:保证Webhook和校验的可靠性
针对Webhook触发失败和校验失败的情况,我们需要配置重试机制。
4.2.1 Webhook的重试配置
ArgoCD的Webhook支持配置重试,我们可以在ArgoCD的配置里设置重试的次数和间隔。比如我们可以设置最多重试3次,每次间隔5分钟。
# ArgoCD的应用配置,配置Webhook的重试
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: https://git.example.com/config-repo.git
targetRevision: main
path: my-app
destination:
server: https://kubernetes.default.svc
namespace: my-app
# 配置同步策略,包括重试
syncPolicy:
automated:
prune: true
selfHeal: true
# 重试配置:失败后重试3次,每次间隔5分钟
retry:
limit: 3
backoff:
duration: 5m
factor: 2
maxDuration: 1h
这里的retry配置是说,如果同步失败,ArgoCD会每隔5分钟重试一次,最多重试3次。backoff的factor是2,意思是如果第一次重试失败,第二次的间隔会变成10分钟,第三次变成20分钟,最大不超过1小时。
4.2.2 镜像校验的重试配置
如果镜像校验失败,我们也可以配置重试。比如我们可以在ArgoCD的配置里设置校验失败后,隔一段时间再校验一次。
另外,我们还可以在配置仓库里加一个校验脚本,每次更新配置之前,先验证镜像的签名是否正确。比如:
# 校验脚本,验证镜像签名
#!/bin/bash
# 镜像地址
IMAGE=$1
# 公钥路径
PUBLIC_KEY=$2
# 验证签名
cosign verify --key $PUBLIC_KEY $IMAGE
if [ $? -ne 0 ]; then
echo "镜像签名验证失败"
exit 1
fi
然后把这个脚本加到CI流水线的update_config步骤里,在提交配置之前先运行这个脚本,验证镜像的签名是否正确。
4.3 顺序控制:保证Webhook的通知顺序
针对Webhook的顺序问题,我们可以用两种方法解决:
4.3.1 用Git的提交哈希作为版本号
我们可以把Git的提交哈希作为镜像的标签和配置仓库的版本号。比如每次提交代码,都会生成一个唯一的提交哈希,这个哈希是按时间顺序排列的,新的提交哈希比旧的大。
ArgoCD在拉配置的时候,会比较配置仓库的提交哈希和当前集群的配置的提交哈希,如果新的提交哈希比旧的大,就会更新,否则就会跳过。这样就可以保证ArgoCD只会更新到最新的版本,不会被旧的Webhook通知影响。
4.3.2 配置ArgoCD的同步策略为“仅同步最新版本”
我们可以在ArgoCD的配置里设置,只有当配置仓库的版本比当前集群的版本新的时候,才会同步。比如:
# ArgoCD的应用配置,仅同步最新版本
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: https://git.example.com/config-repo.git
targetRevision: main
path: my-app
destination:
server: https://kubernetes.default.svc
namespace: my-app
syncPolicy:
automated:
prune: true
selfHeal: true
# 配置同步的条件:只有当配置仓库的版本比当前集群的版本新的时候才同步
syncOptions:
- CreateNamespace=true
- Validate=true
# 仅同步最新版本
- AllowEmpty=false
另外,我们还可以在配置仓库的配置文件里加一个annotation,记录当前的版本号,ArgoCD在同步的时候会比较这个版本号,只有新的版本号才会同步。
五、应用场景、优缺点和注意事项
5.1 应用场景
这套解决方案适用于所有用CI/CD流水线的团队,特别是以下场景:
- 大促、活动前频繁更新服务的团队,需要保证每次更新都能成功。
- 对服务可用性要求高的团队,不能因为更新失败导致服务中断。
- 用ArgoCD做持续部署的团队,经常遇到更新跳过的问题。
5.2 技术优缺点
优点:
- 可靠性高:每一步都做验证,配置了重试机制,保证更新不会因为网络波动、镜像推送失败等问题跳过。
- 顺序控制:用Git的提交哈希作为版本号,保证ArgoCD只会更新到最新的版本,不会被旧的Webhook通知影响。
- 可维护性高:链路清晰,每一步的逻辑都很明确,出问题的时候很容易排查。
缺点:
- 配置复杂:需要配置CI流水线、ArgoCD的同步策略、重试机制等,对新手来说有一定的学习成本。
- 增加了流水线的时间:每一步都做验证,比如验证镜像是否存在、验证签名等,会增加CI流水线的执行时间。
- 对Git仓库的依赖高:如果Git仓库出问题,整个链路都会受影响。
5.3 注意事项
- 要保证CI流水线的每一步都是串行执行的,不能并行。
- 要定期更新ArgoCD的公钥和镜像的私钥,保证签名校验的安全性。
- 要监控ArgoCD的同步状态,及时发现同步失败的情况。
- 要定期备份配置仓库和镜像仓库,防止数据丢失。
六、总结
从代码提交到ArgoCD同步的整个链路,任何一个环节出问题都会导致更新被跳过。要解决这个问题,需要从链路优化、重试配置、顺序控制三个方面入手,保证每一步的顺序和可靠性。
首先要优化CI流水线,保证每一步都做验证,必须等上一步成功才会继续;然后要配置ArgoCD的重试机制,保证Webhook触发失败和校验失败后能自动重试;最后要控制Webhook的通知顺序,保证ArgoCD只会更新到最新的版本。
这套方案虽然配置复杂,但是能大大提高更新的可靠性,适合对服务可用性要求高的团队。
评论
围绕“CI流水线推送新镜像后,ArgoCD依赖Webhook自动触发同步,但签名校验失败或事件顺序颠倒往往促使更新被跳过,需要梳理从代码仓库到ArgoCD的完整事件链路并配置可靠的重试保障”参与讨论