第 9/18 天
引言

在前八天的实践中,我们已经分别搭建了 Kubernetes 集群、Harbor 镜像仓库、GitLab 代码平台、GitLab Runner 以及 Argo CD GitOps 引擎。这些组件各自独立运行时已经具备强大能力,但 DevOps 的精髓在于联动——只有将 CI(持续集成)与 CD(持续交付)串联成一条无人值守的管道,才能真正实现”提交代码即部署”的终极目标。今天,我们将把 GitLab CI 流水线与 Argo CD GitOps 引擎打通,构建一个从代码提交到生产部署的全自动闭环,让每一次 git push 都能自动触发构建、测试、镜像推送、清单更新和集群部署的完整链路。
核心概念
CI 与 CD 的职责边界
在深入实战之前,先厘清 CI 与 CD 各自的职责边界,这是设计联动架构的基础:
- CI(持续集成):负责代码编译、单元测试、安全扫描和容器镜像构建。核心产出是可部署的镜像制品,存储在 Harbor 中。
- CD(持续交付):负责将镜像部署到目标环境。在 GitOps 模式下,CD 的核心是维护一份声明式的 Kubernetes 清单仓库,Argo CD 持续监听这份清单并与集群实际状态对齐。
两者的衔接点是镜像 Tag:CI 构建出镜像后,需要将新的 Tag 写入 CD 仓库的 Deployment 清单中,Argo CD 检测到清单变更后自动同步部署。
双仓库模型
联动架构的核心设计是双仓库模型(Two-Repository Model),将应用代码与部署清单彻底分离:
# 仓库一:应用代码仓库(app-repo)
app-repo/
├── src/ # 应用源码
├── Dockerfile # 构建定义
├── .gitlab-ci.yml # CI 流水线定义
└── tests/ # 测试用例
# 仓库二:部署清单仓库(deploy-repo / GitOps repo)
deploy-repo/
├── base/ # Kustomize 基础清单
│ ├── deployment.yaml
│ ├── service.yaml
│ └── kustomization.yaml
├── overlays/
│ ├── dev/ # 开发环境覆盖
│ │ ├── kustomization.yaml
│ │ └── patch-image.yaml
│ └── prod/ # 生产环境覆盖
│ ├── kustomization.yaml
│ └── patch-image.yaml
└── .argocd/ # Argo CD Application 定义
└── app.yaml
这种分离带来三大好处:第一,开发人员只需关注代码仓库,无需触碰 Kubernetes YAML;第二,运维人员可以在部署仓库中独立管理环境差异和配置变更;第三,部署历史完全由 Git 版本控制,任何变更都可审计、可回滚。
实战步骤
第一步:准备部署清单仓库
首先创建 deploy-repo 并编写基础 Kubernetes 清单。以一个简单的 Go Web 服务为例:
# deploy-repo/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app
namespace: production
labels:
app: demo-app
spec:
replicas: 3
selector:
matchLabels:
app: demo-app
template:
metadata:
labels:
app: demo-app
spec:
containers:
– name: demo-app
image: harbor.stellardata.top/library/demo-app:PLACEHOLDER
ports:
– containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
其中 PLACEHOLDER 是占位符,CI 流水线在部署阶段会将其替换为实际的镜像 Tag。接下来定义 Kustomize 基础配置和 Argo CD Application:
# deploy-repo/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
– deployment.yaml
– service.yaml
—
# deploy-repo/.argocd/app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: demo-app
namespace: argocd
spec:
destination:
namespace: production
server: https://kubernetes.default.svc
source:
repoURL: https://gitlab.stellardata.top/devops/deploy-repo.git
targetRevision: main
path: overlays/dev
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
– CreateNamespace=true
syncPolicy.automated 配置是自动闭环的关键:prune: true 表示会删除 Git 中已移除的资源,selfHeal: true 表示当有人手动修改集群资源时 Argo CD 会自动纠正回 Git 中的声明状态。将此 Application 应用到集群:
kubectl apply -f deploy-repo/.argocd/app.yaml -n argocd
# 验证 Application 状态
kubectl get application demo-app -n argocd
# NAME SYNC STATUS HEALTH STATUS
# demo-app Synced Healthy
第二步:配置 CI 流水线的部署阶段
GitLab CI 流水线在前几天已经配置了 build 和 test 阶段,现在需要新增一个 deploy 阶段,负责将新镜像 Tag 更新到 deploy-repo 中。这是打通 CI 与 CD 的核心环节:
# app-repo/.gitlab-ci.yml 完整流水线
stages:
– build
– test
– deploy
variables:
HARBOR_URL: "harbor.stellardata.top"
IMAGE_NAME: "${HARBOR_URL}/library/demo-app"
IMAGE_TAG: "${CI_COMMIT_SHORT_SHA}"
DEPLOY_REPO: "git@gitlab.stellardata.top:devops/deploy-repo.git"
# 构建阶段:Kaniko 无守护进程构建
build:image:
stage: build
image:
name: gcr.io/kaniko-project/executor:latest
entrypoint: [""]
script:
– /kaniko/executor
–context "${CI_PROJECT_DIR}"
–dockerfile "${CI_PROJECT_DIR}/Dockerfile"
–destination "${IMAGE_NAME}:${IMAGE_TAG}"
–destination "${IMAGE_NAME}:latest"
–cache=true
rules:
– if: '$CI_COMMIT_BRANCH == "main"'
# 测试阶段
test:unit:
stage: test
image: golang:1.22-alpine
script:
– go test ./… -v –cover
rules:
– if: '$CI_COMMIT_BRANCH == "main"'
# 部署阶段:更新 GitOps 仓库中的镜像 Tag
deploy:manifest:
stage: deploy
image: alpine/git:latest
before_script:
– eval $(ssh-agent -s)
– echo "$DEPLOY_SSH_KEY" | tr -d 'r' | ssh-add –
– git config –global user.email "ci-bot@stellardata.top"
– git config –global user.name "GitLab CI Bot"
script:
– git clone "${DEPLOY_REPO}" /tmp/deploy-repo
– cd /tmp/deploy-repo
# 替换镜像 Tag
– sed -i "s|harbor.stellardata.top/library/demo-app:.*|harbor.stellardata.top/library/demo-app:${IMAGE_TAG}|" overlays/dev/patch-image.yaml
– git add -A
– "git commit -m "chore: update demo-app image to ${IMAGE_TAG}""
– git push origin main
rules:
– if: '$CI_COMMIT_BRANCH == "main"'
这里有几个关键点:DEPLOY_SSH_KEY 是一个预先在 GitLab CI/CD Variables 中配置的 SSH 私钥,该密钥对应的公钥需要添加到 deploy-repo 项目的 Deploy Keys 中(只读权限即可,但 CI Bot 需要 push 权限,所以应使用具有写权限的 Deploy Key)。IMAGE_TAG 使用 CI_COMMIT_SHORT_SHA(Git 短哈希),确保每次提交的镜像 Tag 唯一且可追溯。
第三步:编写镜像 Tag 更新脚本
为了使 Tag 更新更健壮(避免 sed 对多行 YAML 的误伤),推荐使用专门的 YAML 处理工具或 Python 脚本。以下是一个基于 Python 的更新脚本,它精确修改 Kustomize patch 中的 image 字段:
#!/usr/bin/env python3
# update_manifest.py – 更新 GitOps 仓库中的镜像 Tag
import sys
import yaml
import subprocess
def update_image_tag(repo_path, new_tag):
patch_file = f"{repo_path}/overlays/dev/patch-image.yaml"
with open(patch_file, 'r') as f:
docs = list(yaml.safe_load_all(f))
for doc in docs:
if doc and doc.get('kind') == 'Kustomization':
for img in doc.get('images', []):
if img.get('name') == 'harbor.stellardata.top/library/demo-app':
img['newTag'] = new_tag
print(f"Updated image tag to {new_tag}")
with open(patch_file, 'w') as f:
yaml.safe_dump_all(docs, f, default_flow_style=False)
return True
if __name__ == '__main__':
repo = sys.argv[1] if len(sys.argv) > 1 else '/tmp/deploy-repo'
tag = subprocess.check_output(['printenv', 'IMAGE_TAG']).decode().strip()
update_image_tag(repo, tag)
对应的 Kustomize overlay patch 文件格式如下,脚本会精确修改 newTag 字段:
# deploy-repo/overlays/dev/patch-image.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
images:
– name: harbor.stellardata.top/library/demo-app
newTag: a1b2c3d # CI 自动更新此字段
第四步:触发 Argo CD 即时同步
Argo CD 默认每 3 分钟轮询一次 Git 仓库,在生产环境中这个延迟是可以接受的。但如果需要更快的反馈,可以配置 GitLab Webhook 推送通知到 Argo CD,触发即时同步:
# 获取 Argo CD 的 webhook 端口(默认在 argocd-server 上)
kubectl get svc argocd-server -n argocd
# 配置 GitLab 项目的 Webhook
# URL: https://argocd.stellardata.top/api/webhook
# Content-Type: application/json
# Trigger: Push events, branch main
# 验证 Argo CD webhook 是否正常
curl -X POST https://argocd.stellardata.top/api/webhook
-H "Content-Type: application/json"
-d '{"repository": {"url": "https://gitlab.stellardata.top/devops/deploy-repo.git"}}'
# 正常返回:{}(空 JSON 表示接收成功)
配置 Webhook 后,deploy-repo 每次被 CI Bot 推送新提交,GitLab 会立即通知 Argo CD,Argo CD 在数秒内即可启动同步流程,无需等待 3 分钟轮询周期。
第五步:端到端验证闭环
现在来验证完整的闭环。模拟一次代码提交,观察全链路行为:
# 1. 开发者修改代码并提交
cd app-repo
echo "version 1.0.0" >> src/version.txt
git add -A && git commit -m "feat: bump version to 1.0.0"
git push origin main
# 2. 观察 GitLab CI 流水线执行
glab ci view # 或在 GitLab Web UI 查看流水线状态
# build:image -> test:unit -> deploy:manifest 依次通过
# 3. 检查 deploy-repo 是否被自动更新
cd /tmp/deploy-repo && git pull
git log –oneline -3
# a1b2c3d chore: update demo-app image to a1b2c3d4
# 7e8f9a0 chore: update demo-app image to 7e8f9a0b
# 4. 观察 Argo CD 同步事件
argocd app get demo-app –refresh
# Expected: SYNC STATUS: Synced, HEALTH STATUS: Healthy
# 5. 验证集群中的 Pod 已更新
kubectl get pods -n production -l app=demo-app
# NAME READY STATUS RESTARTS AGE
# demo-app-7d6f5c4-x2k9m 1/1 Running 0 45s
# demo-app-7d6f5c4-p8n3q 1/1 Running 0 45s
# demo-app-7d6f5c4-r1t5w 1/1 Running 0 45s
kubectl get deployment demo-app -n production -o jsonpath='{.spec.template.spec.containers[0].image}'
# harbor.stellardata.top/library/demo-app:a1b2c3d4
从 git push 到新 Pod 运行,全链路在 5 分钟内自动完成,无需任何人工干预。这就是 GitOps + CI 联动的威力。
常见问题
问题一:CI Bot 推送 deploy-repo 时报权限错误
现象:deploy:manifest 阶段报 git push 权限被拒绝(Permission denied (publickey))。
解决:确认三点——第一,GitLab CI Variable DEPLOY_SSH_KEY 使用了正确的 SSH 私钥,且变量类型设为 File(而非 Variable),这样 GitLab 会将其写入临时文件,避免换行符问题;第二,对应的公钥已添加到 deploy-repo 项目的 Settings → Repository → Deploy Keys,并勾选了 “Write access”;第三,如果使用 HTTPS 方式,应使用 Project Access Token 而非个人 SSH Key,在 .gitlab-ci.yml 中通过 CI Job Token 或预配置的 CI/CD Variables 认证。
问题二:Argo CD 同步后应用状态一直是 Progressing
现象:Argo CD 显示 SYNC 为 Synced 但 HEALTH 为 Progressing,长时间不转为 Healthy。
解决:通常是 readinessProbe 配置不当或镜像拉取失败。首先检查 Pod 事件:kubectl describe pod <pod-name> -n production,确认没有 ImagePullBackOff。如果镜像拉取正常,检查 readinessProbe 路径是否正确——应用必须实现 /healthz 端点并返回 HTTP 200。还可以通过 kubectl logs <pod-name> -n production 查看应用启动日志,确认服务已监听指定端口。
问题三:GitOps 仓库的 CI Bot 提交过多导致历史膨胀
现象:每次 git push 都会在 deploy-repo 产生一条 commit,长期运行后 Git 历史极其庞大,git clone 变慢。
解决:两种方案。短期方案是定期使用 git filter-repo 或 git gc --aggressive 压缩历史。长期方案是采用 Argo CD Image Updater(argocd-image-updater),它不再让 CI 直接修改 Git 仓库,而是 Argo CD 侧自动监听 Harbor 镜像 Tag 变化并更新 Application 的 image 字段,这样 deploy-repo 中只保留人工审批的环境配置变更,CI Bot 无需写入权限,Git 历史保持干净。
总结
今天的实战打通了 GitLab CI 与 Argo CD 之间的最后一公里,构建了一个完整的从代码提交到集群部署的全自动闭环管道。核心设计要点回顾:采用双仓库模型分离应用代码与部署清单;CI 的 deploy 阶段自动更新 GitOps 仓库中的镜像 Tag;Argo CD 的 syncPolicy.automated 实现声明式自动同步;Webhook 将同步延迟从分钟级压缩到秒级。这条管道建立后,开发团队只需关注代码质量,部署过程完全自动化、可审计、可回滚——这正是 GitOps 理念的最佳实践。不过,当前的部署是全量替换式的,缺少灰度和回滚的能力。接下来的章节将引入可观测性和渐进式交付来补全这些能力。
下期预告
明天我们将进入可观测性专题,发布 第 10 天:Prometheus 部署:kube-prometheus-stack 与 ServiceMonitor 指标采集,为这条全自动管道装上”眼睛”,实时监控从构建到部署的全链路健康状态。
系列大纲
- 全链路架构总览:从代码提交到灰度发布的完整管道设计
- K8s 集群与网络基础:kubeadm 离线部署与 Cilium 网络插件
- Harbor 私有镜像仓库部署与验证:Helm 安装与镜像推送
- GitLab CE 自托管部署:代码仓库与项目管理平台
- GitLab Runner 配置:K8s Executor 与 RBAC 权限设置
- GitLab CI 流水线搭建:.gitlab-ci.yml 与 Kaniko 无守护进程构建
- Harbor 镜像版本管理与 Tag 策略配置
- Argo CD 部署与 GitOps 配置仓库创建
- CI 与 CD 联动:从代码提交到部署的全自动闭环(今天)
- Prometheus 部署:kube-prometheus-stack 与 ServiceMonitor 指标采集
- Grafana 看板搭建:K8s 标准面板与自定义业务看板
- 告警规则与 Alertmanager:钉钉/邮件通知渠道配置
- Argo Rollouts 安装与基础概念:CRD 与 Rollout 资源定义
- 金丝雀发布实战:setWeight 流量分割与 pause 步骤
- AnalysisTemplate 指标分析与自动回滚机制
- 全链路端到端演示:代码提交→构建→灰度→监控→告警完整流程
- 生产化加固要点:Harbor/GitLab/Argo CD 高可用方案
- 供应链安全闭环:Trivy 扫描 + cosign 签名 + Kyverno 验签

















暂无评论内容