GitOps之Jenkins 构建镜像自动更新 Helm 并触发 ArgoCD 自动同步

前言

在云原生越来越普及的今天,GitOps 已经成为企业部署应用的标准方式。开发只需要关心代码,运维只需要维护配置,所有变更都通过 Git 管理,安全、可追溯、可回滚。

最近我从零搭建了一套 项目完整 CI/CD 流程

  • 代码提交 → Jenkins 自动构建镜像
  • 自动推送到 Harbor 私有仓库
  • 自动更新 Helm 的 values.yaml
  • ArgoCD 自动同步到 Kubernetes

全程无人工介入 ,真正实现 代码即部署。本篇把完整步骤、踩坑、优化全部记录下来,方便自己回顾,也给大家参考。

接上文,一个完整的CI/CD流程的实现。

ArgoCD 与 GitOps:K8S 原生持续部署的实操指南_argocd 设置中文-CSDN博客

一、GitOps的设计思想

二、架构实现

操作系统均为Ubuntu24.04LTS/25.10版本。

|---------|-----------|---------|
| 服务名称 | 角色 | 版本 |
| Jenkins | CI | 2.541.2 |
| Harbor | 容器镜像持久化存储 | 2.13.1 |
| Gitee | 代码仓库 | / |
| K8S | 容器集群化管理工具 | 1.35.0 |
| ArgoCD | CD | 3.3.4 |

2.1 Gitee配置

创建存放代码与K8S资源清单仓库

2.2 Jenkins配置

2.1.1 密钥管理

Jenkins需要提前配置好Harbor、代码仓库、K8S资源清单仓库的凭证。

选择username with password 即可

2.1.2 任务创建

2.1.3 任务配置

描述一下你的这个任务

使用参数化构建,为后续镜像的自定义标签使用

源码管理,连接好git代码仓库,账号密码凭证之前创建好的,直接选择即可。

2.1.4 WebHook钩子

这个WebHook钩子目前没办法使用,因为Gitee是公有仓库,连接不到自己测试环境的内网地址,但是配置是一样的,主要触发的核心动作是push.

2.1.5 流水线或者SHELL变量定义

2.1.6 Build Steps

可以选择shell脚本,也可以shell脚本转Jenkins的pipeline。

bash 复制代码
#!/bin/bash
set -e  # 出错立即退出,避免无效执行

# ====================== 定义日志函数 ======================
info() {
    echo -e "\033[32m[INFO] $(date +'%Y-%m-%d %H:%M:%S') $1\033[0m"
}
error() {
    echo -e "\033[31m[ERROR] $(date +'%Y-%m-%d %H:%M:%S') $1\033[0m"
    exit 1
}

# ====================== 1. 参数化变量 + 空值检查 =======================
IMAGE_TAG="${IMAGE_VERSION}"  
ENV_TYPE="${ENV_TYPE}"

# 核心变量空值检查
[ -z "${IMAGE_TAG}" ] && error "IMAGE_VERSION参数为空,请传入镜像版本号"
[ -z "${ENV_TYPE}" ] && error "ENV_TYPE参数为空,请传入环境标识(prod/test)"
[ -z "${GIT_USER}" ] && error "GIT_USER参数为空,请配置Gitee用户名"
[ -z "${GIT_PWD}" ] && error "GIT_PWD参数为空,请配置Gitee密码/令牌"
[ -z "${HARBOR_USER}" ] && error "HARBOR_USER参数为空,请配置Harbor用户名"
[ -z "${HARBOR_PWD}" ] && error "HARBOR_PWD参数为空,请配置Harbor密码"

# Argocd repo 配置
ARGOCD_GIT_REPO="https://${GIT_USER}:${GIT_PWD}@gitee.com/cao-facan/argocd.git"
ARGOCD_GIT_BRANCH="master"
ARGOCD_TMP_DIR="/tmp/jenkins-argocd-tmp-$(date +%Y%m%d%H%M%S)"

# 生成最终镜像标签
GIT_COMMIT_MSG=$(git log -1 --pretty=format:%s | tr -d " " | cut -c1-15 || echo "nogitmsg")
TIME_STAMP=$(date +%Y%m%d%H%M)
FINAL_TAG="${TIME_STAMP}-${IMAGE_TAG}-${GIT_COMMIT_MSG}-${ENV_TYPE}"

# 镜像仓库地址
HARBOR_REPO="harbor.online.com/cicd/myweb"

# ====================== 2. 构建并推送镜像 =======================
echo "======================"
echo "开始构建镜像:${HARBOR_REPO}:${FINAL_TAG}"
echo "======================"
# 登录 Harbor 并推送镜像
echo "${HARBOR_PWD}" | docker login harbor.online.com -u "${HARBOR_USER}" --password-stdin
pwd
docker build -t ${HARBOR_REPO}:${FINAL_TAG} .
docker push ${HARBOR_REPO}:${FINAL_TAG}
docker logout harbor.online.com
echo "✅ 镜像构建推送完成!"
echo "👉 镜像标签:${FINAL_TAG}"

# ====================== 3. 修改argocd仓库values.yaml(修复Git顺序)=====================
info "===== 开始修改argocd仓库的values.yaml ====="
info "克隆argocd仓库:${ARGOCD_GIT_REPO}"

# 清理旧临时目录,创建新目录
rm -rf ${ARGOCD_TMP_DIR}
mkdir -p ${ARGOCD_TMP_DIR} || error "创建argocd临时目录失败"

# 克隆argocd仓库(带重试)
for i in {1..2}; do
    if git clone -b ${ARGOCD_GIT_BRANCH} ${ARGOCD_GIT_REPO} ${ARGOCD_TMP_DIR}; then
        break
    fi
    info "Git克隆失败,第${i}次重试..."
    sleep 3
    if [ ${i} -eq 2 ]; then
        error "argocd仓库克隆失败,终止流程"
    fi
done

# 进入argocd临时目录(git clone已自动初始化仓库,无需git init)
cd ${ARGOCD_TMP_DIR} || error "进入argocd临时目录失败"
pwd

# ====================== 关键修复1:先拉取最新代码(此时工作区干净)=====================
info "拉取argocd仓库最新代码"
git pull origin ${ARGOCD_GIT_BRANCH} || error "拉取argocd仓库最新代码失败"

# 确定要修改的values文件(兼容分环境/单环境)
VALUES_FILE="values-${ENV_TYPE}.yaml"
if [ ! -f ${VALUES_FILE} ]; then
    info "未找到${VALUES_FILE},使用默认values.yaml"
    VALUES_FILE="values.yaml"
fi
[ ! -f ${VALUES_FILE} ] && error "argocd仓库中未找到${VALUES_FILE}文件"

# ====================== 关键:用sed替换image.tag的值 ======================
info "修改${VALUES_FILE}的image.tag为:${FINAL_TAG}"
# 适配你的yaml格式(2个空格缩进)
sed -i -e "/^  image:$/!{n;/^  tag:/s/\".*\"/\"${FINAL_TAG}\"/}" ${VALUES_FILE}

# 验证修改是否成功
MODIFIED_TAG=$(grep -E '^  tag: ' ${VALUES_FILE} | awk -F'"' '{print $2}')
if [ "${MODIFIED_TAG}" != "${FINAL_TAG}" ]; then
    error "sed修改tag失败!当前tag值:${MODIFIED_TAG},期望:${FINAL_TAG}"
else
    info "✅ tag修改验证成功:${MODIFIED_TAG}"
fi

# ====================== 关键修复2:修改后再提交+推送 ======================
info "提交修改到argocd仓库"
git config --global user.name "jenkins-auto"
git config --global user.email "jenkins@auto.com"

# 暂存修改
git add ${VALUES_FILE}

# 检查是否有变更(避免无变更时提交失败)
if git diff --cached --quiet; then
    info "values.yaml无变更,无需提交"
else
    git commit -m "auto update ${ENV_TYPE} image tag: ${FINAL_TAG}" || error "提交修改失败"
    # 推送修改(无需rebase,因为已先拉取最新代码)
    git push origin ${ARGOCD_GIT_BRANCH} || error "推送argocd仓库修改失败"
fi

# 清理临时目录
rm -rf ${ARGOCD_TMP_DIR}

info "✅ 全流程执行完成!"
info "👉 argocd仓库${VALUES_FILE}的image.tag已更新为:${FINAL_TAG}"
info "👉 ArgoCD将自动同步新镜像到${ENV_TYPE}环境"

exit 0

2.1.7 Dockerfile文件模板

bash 复制代码
[root@cfc /var/lib/jenkins/workspace/prod]# cat Dockerfile 
FROM nginx:alpine

COPY . /usr/share/nginx/html/

EXPOSE 80

2.3 K8S配置

2.3.1 HELM资源创建

bash 复制代码
[root@k8s-master ~]# helm create myweb
bash 复制代码
[root@k8s-master ~/myweb]# cat values.yaml 
# 副本数配置
replicaCount: 3

# 镜像配置
image:
  repository: harbor.online.com/cicd/myweb
  pullPolicy: IfNotPresent
  tag: "202603181022--addDockerfilefi-prod"

# 服务配置
service:
  type: ClusterIP
  port: 80

# Gateway API 相关配置
gateway:
  enabled: true
  className: envoy
  port: 80
httpRoute:
  enabled: true
  hostname: www.myweb.com
  path: /
  serviceName: myweb-service
  servicePort: 80
[root@k8s-master ~/myweb]# 
bash 复制代码
[root@k8s-master ~/myweb]# rm -rf templates/*
bash 复制代码
[root@k8s-master ~/myweb/templates]# cat gateway-httproute.yaml  
{{- if .Values.gateway.enabled }}
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: gateway
  namespace: myweb
spec:
  gatewayClassName: {{ .Values.gateway.className }}
  listeners:
  - name: http
    port: {{ .Values.gateway.port }}
    protocol: HTTP
    allowedRoutes:
      namespaces:
        from: Same
---
{{- end }}
{{- if .Values.httpRoute.enabled }}
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: myweb
  namespace: myweb
spec:
  parentRefs:
  - name: gateway
  hostnames:
  - {{ .Values.httpRoute.hostname }}
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: {{ .Values.httpRoute.path }}
    backendRefs:
    - name: {{ .Values.httpRoute.serviceName }}
      namespace: myweb
      port: {{ .Values.httpRoute.servicePort }}
{{- end }}
[root@k8s-master ~/myweb/templates]# 
bash 复制代码
[root@k8s-master ~/myweb/templates]# cat myweb-deploy.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myweb
  namespace: myweb
  labels:
    app: myweb
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: myweb
  template:
    metadata:
      labels:
        app: myweb
    spec:
      containers:
      - name: myweb
        image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
        ports:
        - containerPort: 80
        resources:
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "200m"
      imagePullSecrets:
      - name: harbor-auth
---
apiVersion: v1
kind: Service
metadata:
  name: myweb-service
  namespace: myweb
spec:
  selector:
    app: myweb
  ports:
  - protocol: TCP
    port: {{ .Values.service.port }}
    targetPort: 80
  type: {{ .Values.service.type }}

然后推送到我们对应存放资源清单的的Gitee仓库即可。

2.3.2 创建Harbor连接密钥

bash 复制代码
kubectl create secret docker-registry harbor-auth   -n myweb   --docker-server=harbor.online.com   --docker-username=admin   --docker-password=1   --docker-email=2XX2@qq.com

2.4 ArgoCD配置

2.4.1 SYNC

初次的话保证自己配置的ArgoCD的资源清单仓库没问题的话,可以SYNC一次,看一下资源创建是否正常,是否可以正常连接到Harbor.

访问www.myweb.com测试.

没有问题。

2.4.2 记得开启自动同步

syncPolicy:

automated:

prune: true

selfHeal: true

三、架构执行

3.1 确认环境

在最开始创建好Jenkins与git的连接之后,可以简单跑一个流程看是否可以将代码仓库下的代码给拉取下来。

bash 复制代码
[root@cfc /var/lib/jenkins/workspace/prod]# cd /var/lib/jenkins/workspace/
[root@cfc /var/lib/jenkins/workspace]# ll
total 20
drwxr-xr-x 2 root    root    4096 Mar 17 14:37  dockerbuild/
drwxr-xr-x 6 jenkins jenkins 4096 Mar 18 09:22  Myproject/
drwxr-xr-x 2 jenkins jenkins 4096 Mar 17 17:59 'Myproject@tmp'/
drwxr-xr-x 6 jenkins jenkins 4096 Mar 18 14:26  prod/
drwxr-xr-x 2 jenkins jenkins 4096 Mar 18 14:34 'prod@tmp'/
bash 复制代码
[root@cfc /var/lib/jenkins/workspace]# ll prod
total 220
-rw-r--r-- 1 jenkins jenkins 16458 Mar 18 13:58 about.html
-rw-r--r-- 1 jenkins jenkins 20149 Mar 18 13:58 album.html
-rw-r--r-- 1 jenkins jenkins 19662 Mar 18 13:58 article_detail.html
-rw-r--r-- 1 jenkins jenkins 18767 Mar 18 13:58 article.html
-rw-r--r-- 1 jenkins jenkins 18913 Mar 18 13:58 comment.html
-rw-r--r-- 1 jenkins jenkins 16465 Mar 18 13:58 contact.html
drwxr-xr-x 2 jenkins jenkins  4096 Mar 18 13:58 css/
-rw-r--r-- 1 jenkins jenkins    60 Mar 18 13:58 Dockerfile
drwxr-xr-x 5 jenkins jenkins  4096 Mar 18 13:58 images/
-rw-r--r-- 1 jenkins jenkins 29675 Mar 18 14:26 index.html
drwxr-xr-x 2 jenkins jenkins  4096 Mar 18 13:58 js/
-rw-r--r-- 1 jenkins jenkins 24893 Mar 18 13:58 product_detail.html
-rw-r--r-- 1 jenkins jenkins 20672 Mar 18 13:58 product.html

3.2 模拟开发写代码以及推送

直接修改index.html的内容吧,这里我添加了一些内容

bash 复制代码
[root@cfc /var/lib/jenkins/workspace/prod]# cat index.html 
<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">

<head>
......
</head>

<body>
    
    <header>
            <h1>GitOps-prod,ArgoCD自动同步!</h1>  
            <h2>GitOps-prod,这是我的博客测试!!!</h2>  
bash 复制代码
[root@cfc /var/lib/jenkins/workspace/prod]# git add index.html 
[root@cfc /var/lib/jenkins/workspace/prod]# git commit -m "AddH2" 
[detached HEAD b9e8ef3] AddH2
 1 file changed, 1 insertion(+)
[root@cfc /var/lib/jenkins/workspace/prod]# git push origin master
Username for 'https://gitee.com': cao-facan
Password for 'https://cao-facan@gitee.com': 
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 2 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 362 bytes | 362.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0), pack-reused 0
remote: Powered by GITEE.COM [1.1.23]
remote: Set trace flag 3848b698
To https://gitee.com/cao-facan/prod.git
   9bd3b41..b9e8ef3  master -> master

3.3 Jenkins执行build

3.4 查看Harbor镜像

3.5 查看ArgoCD

bash 复制代码
[root@k8s-master ~]# kubectl get po -n myweb
NAMESPACE              NAME                                                READY   STATUS    RESTARTS        AGE
myweb                  myweb-6cbb89b958-7lf7w                              1/1     Running   0               3h23m
myweb                  myweb-6cbb89b958-8zbf5                              1/1     Running   0               3h22m
myweb                  myweb-6cbb89b958-rp9q7                              1/1     Running   0               3h23m

同步之后,自己会创建新Pod和删除老Pod

bash 复制代码
[root@k8s-master ~]# kubectl get po -n myweb
NAME                     READY   STATUS    RESTARTS   AGE
myweb-7cb6b87c46-kl79s   1/1     Running   0          82s
myweb-7cb6b87c46-t2zd9   1/1     Running   0          86s
myweb-7cb6b87c46-wqr2d   1/1     Running   0          84s

3.6 访问测试

没问题!

四、常见问题解决

4.1 Harbor自建证书对接报错X509

bash 复制代码
cp /etc/docker/certs.d/harbor.online.com/ca.crt /usr/local/share/ca-certificates/harbor-ca.crt

# 然后更新系统的CA库
update-ca-certificates

4.2 containerd对接Harbor

bash 复制代码
mkdir -p /etc/containerd/certs.d/harbor.online.com
cp /usr/local/harbor/certs/ca.crt /etc/containerd/certs.d/harbor.online.com/ca.crt
[root@cfc /usr/local/harbor/certs]# cp -a harbor.online.com.cert  harbor.online.com.key /etc/containerd/certs.d/harbor.online.com/
 
# 
cat > /etc/containerd/certs.d/harbor.online.com/hosts.toml <<EOF
server = "https://harbor.online.com"
[host."https://harbor.online.com"]
capabilities = ["pull", "resolve", "push"]
ca = "/etc/containerd/certs.d/harbor.online.com/ca.crt"
EOF
systemctl restart containerd
 
[root@cfc /usr/local/harbor/certs]# ll /etc/containerd/certs.d/harbor.online.com/
total 16
-rw-r--r-- 1 root root 2061 Nov 25 14:37 ca.crt
-rw-r--r-- 1 root root 2167 Nov 25 13:44 harbor.online.com.cert
-rw------- 1 root root 3268 Nov 25 13:43 harbor.online.com.key
-rw-r--r-- 1 root root  172 Nov 25 15:32 hosts.toml
bash 复制代码
[root@cfc /usr/local/harbor/certs]# containerd config dump | grep -A 5 "registry]"
    [plugins.'io.containerd.cri.v1.images'.registry]
      config_path = '/etc/containerd/certs.d'
 
    [plugins.'io.containerd.cri.v1.images'.image_decryption]
      key_model = 'node'

4.3 git报错 fatal: detected dubious ownership

Git 安全机制:仓库所有者(jenkins)和执行用户(root)不一致

bash 复制代码
git config --global --add safe.directory 仓库路径

4.4 Jenkins 推送 Git 失败

本地分支未关联远程、凭证未配置

bash 复制代码
绑定 GIT_USER/GIT_PWD 凭证 + 分支关联

4.5 ArgoCD UI 勾选自动同步后保存就取消

Application 是声明式配置,UI 修改会被 Git/YAML 覆盖

bash 复制代码
在 Application YAML 中添加 syncPolicy.automated 配置

4.6 自动同步不触发

轮询间隔太长(默认 3 分钟)

bash 复制代码
ArgoCD 自动同步的检测间隔,官方明确由 argocd-cm ConfigMap 中的 timeout.reconciliation 字段控制,默认值 180s(3 分钟)

[root@k8s-master ~]# kubectl edit configmap argocd-cm -n argocd
kind: ConfigMap
metadata:
  creationTimestamp: "2026-03-17T03:21:17Z"
  labels:
    app.kubernetes.io/name: argocd-cm
    app.kubernetes.io/part-of: argocd
  name: argocd-cm
  namespace: argocd
  resourceVersion: "25751"
  uid: b92b74b3-d278-4c3e-8617-de491f9eee85
# 新增这部分 data 节点,配置轮询间隔
data:
  # 官方标准字段:设置自动同步检测间隔为30秒(默认180秒)
  timeout.reconciliation: 30s

五、总结

本文完整记录了从 Jenkins 自动化构建、镜像推送,到 ArgoCD 实现 GitOps 自动同步的全流程落地实践。过程中解决了 Jenkins 构建时 Git 分支游离、权限异常、Webhook 内网限制,以及 ArgoCD 自动同步不触发、同步策略失效等高频问题。通过优化 Jenkins 脚本、配置 Gitee 触发机制、调整 ArgoCD 全局轮询间隔,实现了代码提交到应用发布的全链路自动化。

未来将进一步完善通知机制、构建缓存与权限管控,持续提升流水线稳定性与发布效率,为团队规模化、标准化的云原生部署打下坚实基础。

相关推荐
一殊酒2 小时前
【Docker】实战用例:前后端分离项目多容器Docker化设计
运维·docker·容器
大傻^2 小时前
Spring AI 2.0 生产部署指南:从 1.x 迁移、性能调优与云原生实践
人工智能·spring·云原生·springai
熠速2 小时前
CI/CD功能介绍
运维·ci/cd
李长渊哦2 小时前
Nginx 反向代理实战:解决 IPv6 报错与跨网段访问指南
运维·nginx
信创工程师-小杨2 小时前
银河麒麟SP3如何离线部署二进制docker
运维·docker·容器
小疙瘩2 小时前
本文记录Windows11安装Docker(Docker Desktop)的详细步骤
运维·docker·容器
lpruoyu2 小时前
【云原生】可观测性系统—Prometheus—EFK
云原生·prometheus
沐伊~2 小时前
LINUX基础篇(Ubuntu):
linux·运维·服务器
艾莉丝努力练剑2 小时前
System V IPC底层原理详解
linux·运维·服务器·网络·c++·人工智能·学习