DevOps全链路实战 | 第 9 天:CI 与 CD 联动:从代码提交到部署的全自动闭环

第 9/18 天

引言

K8s

在前八天的实践中,我们已经分别搭建了 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 指标采集,为这条全自动管道装上”眼睛”,实时监控从构建到部署的全链路健康状态。

系列大纲

  1. 全链路架构总览:从代码提交到灰度发布的完整管道设计
  2. K8s 集群与网络基础:kubeadm 离线部署与 Cilium 网络插件
  3. Harbor 私有镜像仓库部署与验证:Helm 安装与镜像推送
  4. GitLab CE 自托管部署:代码仓库与项目管理平台
  5. GitLab Runner 配置:K8s Executor 与 RBAC 权限设置
  6. GitLab CI 流水线搭建:.gitlab-ci.yml 与 Kaniko 无守护进程构建
  7. Harbor 镜像版本管理与 Tag 策略配置
  8. Argo CD 部署与 GitOps 配置仓库创建
  9. CI 与 CD 联动:从代码提交到部署的全自动闭环(今天)
  10. Prometheus 部署:kube-prometheus-stack 与 ServiceMonitor 指标采集
  11. Grafana 看板搭建:K8s 标准面板与自定义业务看板
  12. 告警规则与 Alertmanager:钉钉/邮件通知渠道配置
  13. Argo Rollouts 安装与基础概念:CRD 与 Rollout 资源定义
  14. 金丝雀发布实战:setWeight 流量分割与 pause 步骤
  15. AnalysisTemplate 指标分析与自动回滚机制
  16. 全链路端到端演示:代码提交→构建→灰度→监控→告警完整流程
  17. 生产化加固要点:Harbor/GitLab/Argo CD 高可用方案
  18. 供应链安全闭环:Trivy 扫描 + cosign 签名 + Kyverno 验签
微信二维码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像 - 恒星
欢迎您留下宝贵的见解!
提交
头像 - 恒星

昵称

取消
昵称表情代码图片快捷回复

    暂无评论内容