Gitops自动化部署实践全流程

Jenkins + Kubernetes 自动化部署实践全流程笔记


✅ 1. Jenkins 安装与环境准备

📦 1.1 安装 OpenJDK 21(Adoptium 版本)
# 添加 Adoptium 仓库
sudo tee /etc/yum.repos.d/adoptium.repo <<EOF
[Adoptium]
name=Adoptium Temurin
baseurl=https://packages.adoptium.net/artifactory/rpm/centos/7/x86_64
enabled=1
gpgcheck=1
gpgkey=https://packages.adoptium.net/artifactory/api/gpg/key/public
EOF

# 安装 OpenJDK 21
sudo yum install -y temurin-21-jdk

# 设置默认 Java
sudo alternatives --install /usr/bin/java java /usr/lib/jvm/temurin-21-jdk/bin/java 1091
sudo alternatives --install /usr/bin/javac javac /usr/lib/jvm/temurin-21-jdk/bin/javac 1091

# 验证版本
java -version

输出应类似:

openjdk version "21.0.7"
OpenJDK Runtime Environment Temurin-21.0.7+6
📦 1.2 安装 Jenkins(本地包安装方式)
# 安装 RPM 包(需事先下载 Jenkins RPM 文件)
sudo yum localinstall -y jenkins-2.479.1-1.1.noarch.rpm

# 如提示缺少依赖
sudo yum install -y daemon fontconfig
🚀 1.3 启动 Jenkins
sudo systemctl daemon-reexec
sudo systemctl enable jenkins
sudo systemctl start jenkins
sudo systemctl status jenkins

# 获取初始密码
sudo cat /var/lib/jenkins/secrets/initialAdminPassword

访问:http://<服务器IP>:8080 进入 Jenkins 初始化界面。


✅ 2. Jenkins 配置 GitLab SSH 拉取权限

🔐 2.1 生成专用 SSH 密钥对
ssh-keygen -t rsa -b 4096 -C "jenkins-gitlab" -f /root/.ssh/id_rsa_jenkins

生成:

私钥:/root/.ssh/id_rsa_jenkins
公钥:/root/.ssh/id_rsa_jenkins.pub

🔑 2.2 GitLab 中添加 SSH Key

登录 GitLab → 用户头像 → Preferences → SSH Keys → 粘贴公钥内容

cat /root/.ssh/id_rsa_jenkins.pub

🔗 2.3 Jenkins 中添加凭据

路径:Jenkins → 系统管理 → 凭据 → 全局域 → 添加凭据:

类型:SSH Username with private key

Username:git

Private Key:选择 Enter directly,粘贴私钥内容:

cat /root/.ssh/id_rsa_jenkins

ID:gitlab-ssh


✅ 3. Jenkins 配置 Pipeline 自动部署项目

📁 项目目录结构(GitLab 仓库)
k8s-deploy-pipeline/
├── jenkins/
│   └── Jenkinsfile
├── manifests/
│   └── nginx/
│       ├── configmap.yaml
│       └── deployment.yaml
└── scripts/
    └── healthcheck.sh
📜 Jenkinsfile 内容
pipeline {
  agent any

  parameters {
    string(name: 'IMAGE_TAG', defaultValue: 'stable', description: '部署镜像标签')
    string(name: 'NAMESPACE', defaultValue: 'default', description: '部署命名空间')
    string(name: 'DEPLOYMENT', defaultValue: 'nginx-service', description: 'K8s Deployment 名')
    string(name: 'CHECK_PATH', defaultValue: '/health', description: '探针路径')
  }

  environment {
    KUBECONFIG = '/root/.kube/config'
    TIMEOUT = 30
  }

  stages {
    stage('Apply Kubernetes Deployment') {
      steps {
        sh '''
          kubectl apply -f manifests/nginx/configmap.yaml -n ${params.NAMESPACE}
          sed 's/{
           {IMAGE_TAG}}/'"${params.IMAGE_TAG}"'/g' manifests/nginx/deployment.yaml | kubectl apply -n ${params.NAMESPACE} -f -
        '''
      }
    }

    stage('Health Check') {
      steps {
        script {
          def result = sh(
            script: "bash scripts/healthcheck.sh ${params.NAMESPACE} ${params.DEPLOYMENT} ${params.CHECK_PATH} ${env.TIMEOUT}",
            returnStatus: true
          )
          if (result != 0) {
            error("健康检查失败,准备回滚")
          }
        }
      }
    }

    stage('Success') {
      steps {
        echo "部署成功!服务状态健康。"
      }
    }
  }

  post {
    failure {
      echo "自动回滚中..."
      sh "kubectl rollout undo deployment/${params.DEPLOYMENT} -n ${params.NAMESPACE}"
    }
  }
}
🧪 healthcheck.sh
#!/bin/bash
NAMESPACE=$1
DEPLOYMENT=$2
CHECK_PATH=$3
TIMEOUT=${4:-30}

POD=$(kubectl get pod -n $NAMESPACE -l app=$DEPLOYMENT -o jsonpath='{.items[0].metadata.name}')

if [[ -z "$POD" ]]; then
  echo "[ERROR] 未找到 Pod"
  exit 1
fi

STATUS=$(kubectl exec -n $NAMESPACE $POD -- curl -s -o /dev/null -w "%{http_code}" http://localhost:80$CHECK_PATH)

if [[ "$STATUS" == "200" ]]; then
  echo "[INFO] 健康检查通过"
  exit 0
else
  echo "[ERROR] 健康检查失败,返回码:$STATUS"
  exit 1
fi
📦 manifests/nginx/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-config
data:
  default.conf: |
    server {
        listen 80;
        location / {
            return 200 'nginx ok';
        }
        location /health {
            return 200 'healthy';
        }
    }
📦 manifests/nginx/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-service
  template:
    metadata:
      labels:
        app: nginx-service
    spec:
      containers:
      - name: nginx
        image: nginx:{
           {IMAGE_TAG}}
        ports:
        - containerPort: 80
        volumeMounts:
        - name: nginx-config
          mountPath: /etc/nginx/conf.d/default.conf
          subPath: default.conf
      volumes:
      - name: nginx-config
        configMap:
          name: nginx-config

✅ Jenkins 创建 Pipeline 项目流程

打开 Jenkins → 新建任务 → 输入名称:k8s-deploy-pipeline
类型选择:Pipeline
勾选参数化构建流程,添加参数:

名称 默认值
IMAGE_TAG stable
NAMESPACE default
DEPLOYMENT nginx-service
CHECK_PATH /health

Pipeline → 定义方式选择:Pipeline script from SCM

SCM 类型:Git
仓库地址:<git@git.****.com>:system-ops/k8s-deploy-pipeline.git
凭据:选择 gitlab-ssh
分支:*/master
脚本路径:jenkins/Jenkinsfile

一、Kubernetes 基础信息确认

项目 示例值 说明
K8s 版本 v1.23.6 与 kubectl 版本需兼容
安装方式 kubeadm 决定权限与网络插件配置
Jenkins 安装位置 独立虚拟机 (172.16.3.80) 决定 kubeconfig 获取方式
网络插件 Calico 与服务连通性相关
是否启用 RBAC 决定是否需要绑定角色权限
期望访问方式 KubeConfig 文件 后续也可切换为 ServiceAccount

获取集群信息命令

kubectl version --short
kubectl api-versions | grep rbac
kubectl get pods -n kube-system
kubectl auth can-i '*' '*' --all-namespaces
kubectl config current-context

二、配置 Jenkins 虚拟机访问 Kubernetes

1. 拷贝 kubeconfig 文件

# 假设你在 k8s-master 上:
scp /etc/kubernetes/admin.conf root@172.16.3.80:/root/.kube/config

2. 安装 kubectl

# 添加 kubernetes yum 源
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
EOF

# 安装指定版本
yum install -y kubectl-1.23.6

3. 给 Jenkins 用户权限

mkdir -p /var/lib/jenkins/.kube
cp /root/.kube/config /var/lib/jenkins/.kube/config
chown -R jenkins:jenkins /var/lib/jenkins/.kube
chmod 600 /var/lib/jenkins/.kube/config

4. Jenkinsfile 设置

environment {
  KUBECONFIG = '/var/lib/jenkins/.kube/config'
}

三、Jenkins 安装 Docker 权限问题

报错:

permission denied while trying to connect to the Docker daemon socket

解决方案:

usermod -aG docker jenkins
systemctl restart jenkins

四、Harbor 镜像仓库配置

内容 示例
Harbor 地址 172.16.3.252 内网 IP
是否启用 HTTPS 使用 HTTP 配置 insecure registry
项目名 k8s-test Jenkins 推送目标
镜像命名 nginx-demo:test 示例

Docker insecure registry 设置

cat > /etc/docker/daemon.json <<EOF
{
  "insecure-registries": ["172.16.3.252"]
}
EOF
systemctl daemon-reexec
systemctl restart docker

五、Jenkins 项目目录结构建议

pipeline {
    agent any

    environment {
        REGISTRY = '172.16.3.252'
        IMAGE_NAME = 'k8s-test/nginx-demo'
        IMAGE_TAG = 'dev'
        DEPLOY_NAMESPACE = 'jenkins-dev'
        DEPLOYMENT_NAME = 'nginx-service'
    }

    stages {
        stage('阶段一:拉取代码') {
            steps {
                checkout scm
            }
        }

        stage('阶段二:构建并推送镜像') {
            steps {
                script {
                    echo '🔧 正在构建镜像...'
                    sh "docker build -t ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} ."

                    echo '🚀 正在推送镜像到 Harbor...'
                    withCredentials([usernamePassword(credentialsId: 'harbor-cred', usernameVariable: 'HARBOR_USER', passwordVariable: 'HARBOR_PASS')]) {
                        sh """
                            echo $HARBOR_PASS | docker login ${REGISTRY} -u $HARBOR_USER --password-stdin
                            docker push ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}
                            docker logout ${REGISTRY}
                        """
                    }
                }
            }
        }

        stage('阶段三:渲染并应用 K8S 配置') {
            steps {
                script {
                    echo '📦 正在渲染并应用 Kubernetes 配置...'
                    sh """
                        sed -e 's|{
           {REGISTRY}}|${REGISTRY}|g' 
                            -e 's|{
           {IMAGE_NAME}}|${IMAGE_NAME}|g' 
                            -e 's|{
           {IMAGE_TAG}}|${IMAGE_TAG}|g' 
                            manifests/nginx/deployment.yaml > /tmp/rendered.yaml

                        kubectl apply -f /tmp/rendered.yaml -n ${DEPLOY_NAMESPACE}
                    """
                }
            }
        }

        stage('阶段四:健康检查') {
            steps {
                script {
                    echo '🩺 正在检查 Deployment 状态...'
                    def rollout = sh (
                        script: "kubectl rollout status deployment/${DEPLOYMENT_NAME} -n ${DEPLOY_NAMESPACE} --timeout=60s",
                        returnStatus: true
                    )
                    if (rollout != 0) {
                        error "❌ Rollout 失败"
                    }

                    def statuses = sh(
                        script: "kubectl get pods -l app=${DEPLOYMENT_NAME} -n ${DEPLOY_NAMESPACE} -o jsonpath='{.items[*].status.phase}'",
                        returnStdout: true
                    ).trim().split()

                    def failedPods = []
                    for (status in statuses) {
                        if (status != "Running") {
                            failedPods << status
                        }
                    }

                    if (failedPods) {
                        echo "❌ 检测到非 Running 状态的 Pod: ${failedPods.join(', ')}"
                        sh "kubectl get pods -l app=${DEPLOYMENT_NAME} -n ${DEPLOY_NAMESPACE}"
                        error "❌ 健康检查未通过,终止流程。"
                    }
                }
            }
        }

        stage('阶段五:构建成功') {
            steps {
                echo '✅ 部署成功!'
            }
        }
    }

    post {
        failure {
            echo '❌ 部署失败,尝试回滚...'
            script {
                def revisions = sh(script: "kubectl rollout history deployment/${DEPLOYMENT_NAME} -n ${DEPLOY_NAMESPACE} | grep Revision || true", returnStdout: true).trim()
                if (revisions.contains("Revision")) {
                    sh "kubectl rollout undo deployment/${DEPLOYMENT_NAME} -n ${DEPLOY_NAMESPACE}"
                    echo '🔙 已执行回滚操作'
                } else {
                    echo '⚠️ 没有可用的历史版本,跳过回滚'
                }
            }
        }
    }
}


常见报错与排查

1. Jenkins 构建时 docker 权限不足

permission denied while trying to connect to the Docker daemon socket

解决: Jenkins 用户加入 docker 组

2. kubectl 权限不足

open /root/.kube/config: permission denied

解决: 使用 Jenkins 用户专属 config,并修改权限为 jenkins:jenkins

3. Harbor 登录失败(443 被拒)

解决: 添加 insecure-registries 设置后重启 docker

4. 拉取镜像失败(443 被拒)

解决: 创建私有专属秘钥

[root@redis-test ~]# kubectl create secret docker-registry harbor-secret 
>   --docker-server=172.16.3.252 
>   --docker-username=admin 
>   --docker-password=Harbor12345 
>   --docker-email=devops@yourcompany.com 
>   -n jenkins-dev

=====================================

六、多分支自动部署

一、项目目标

构建一个支持多分支(dev/test/release-*/master)自动部署的 CI/CD 流水线,包含:

Jenkins 拉取 GitLab 指定分支代码
构建 Docker 镜像并推送到 Harbor 私有仓库
渲染 Kubernetes 部署模板并自动部署
部署健康检查与失败自动回滚


📁 二、项目目录结构

k8s-deploy-pipeline/
├── Dockerfile                  # 镜像构建文件
├── jenkins/
│   └── Jenkinsfile             # Jenkins 流水线脚本
├── manifests/
│   └── nginx/
│       └── deployment.yaml     # K8S Deployment 模板
├── scripts/
│   └── healthcheck.sh          # 健康检查脚本(可选)
└── app/                        # 应用源码目录

⚙️ 三、关键配置文件

1. jenkins/Jenkinsfile
pipeline {
  agent any

  parameters {
    choice(
      name: 'BRANCH',
      choices: ['dev', 'test', 'master', 'release-xxx'],
      description: '请选择要部署的分支'
    )
  }

  environment {
    REGISTRY = '172.16.3.252'
    IMAGE_NAME = 'k8s-test/nginx-demo'
    BUILD_TAG = ''
    K8S_NAMESPACE = ''
    RENDERED_YAML = "${env.WORKSPACE}/rendered.yaml"
  }

  stages {

    stage('阶段一:初始化环境变量') {
      steps {
        script {
          if (params.BRANCH == 'dev') {
            BUILD_TAG = "dev-${env.BUILD_NUMBER}"
            K8S_NAMESPACE = 'jenkins-dev'
          } else if (params.BRANCH == 'test') {
            BUILD_TAG = "test-${env.BUILD_NUMBER}"
            K8S_NAMESPACE = 'jenkins-test'
          } else if (params.BRANCH == 'master' || params.BRANCH.startsWith('release-')) {
            BUILD_TAG = params.BRANCH
            K8S_NAMESPACE = 'jenkins-prod'
          } else {
            error "❌ 非法分支:${params.BRANCH},请使用 dev/test/master/release-*"
          }

          echo "🌿 当前分支: ${params.BRANCH},部署到命名空间: ${K8S_NAMESPACE},镜像 Tag: ${BUILD_TAG}"

          // 拉取对应分支的代码
          checkout([$class: 'GitSCM',
            branches: [[name: "origin/${params.BRANCH}"]],
            userRemoteConfigs: [[
              url: 'git@git.****.com:system-ops/k8s-deploy-pipeline.git',
              credentialsId: 'gitlab-ssh-key'
            ]]
          ])
        }
      }
    }

    stage('阶段二:构建并推送镜像') {
      steps {
        echo '🔧 正在构建镜像...'
        sh "docker build -t ${REGISTRY}/${IMAGE_NAME}:${BUILD_TAG} ."
        withCredentials([usernamePassword(credentialsId: 'harbor-cred', usernameVariable: 'USER', passwordVariable: 'PASS')]) {
          sh """
            echo $PASS | docker login ${REGISTRY} -u $USER --password-stdin
            docker push ${REGISTRY}/${IMAGE_NAME}:${BUILD_TAG}
            docker logout ${REGISTRY}
          """
        }
      }
    }

    stage('阶段三:渲染并部署 K8S 配置') {
      steps {
        script {
          def rawYaml = readFile('manifests/nginx/deployment.yaml')
          def rendered = rawYaml
            .replaceAll('\{\{REGISTRY\}\}', REGISTRY)
            .replaceAll('\{\{IMAGE_NAME\}\}', IMAGE_NAME)
            .replaceAll('\{\{IMAGE_TAG\}\}', BUILD_TAG)
          writeFile file: RENDERED_YAML, text: rendered
          sh "kubectl apply -f ${RENDERED_YAML} -n ${K8S_NAMESPACE}"
        }
      }
    }

    stage('阶段四:健康检查') {
      steps {
        echo '🩺 正在检查 Deployment 状态...'
        sh "kubectl rollout status deployment/nginx-service -n ${K8S_NAMESPACE} --timeout=60s"
      }
    }
  }

  post {
    success {
      echo "✅ 部署成功: ${IMAGE_NAME}:${BUILD_TAG} 到 ${K8S_NAMESPACE}"
    }
    failure {
      echo '❌ 部署失败,尝试回滚...'
      script {
        def history = sh(returnStdout: true, script: "kubectl rollout history deployment/nginx-service -n ${K8S_NAMESPACE} | grep Revision || true").trim()
        if (history) {
          sh "kubectl rollout undo deployment/nginx-service -n ${K8S_NAMESPACE}"
          echo '🔁 已回滚上一个版本'
        } else {
          echo '⚠️ 没有可回滚版本'
        }
      }
    }
  }
}

2. manifests/nginx/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-service
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-service
  template:
    metadata:
      labels:
        app: nginx-service
    spec:
      containers:
        - name: nginx
          image: {
           {REGISTRY}}/{
           {IMAGE_NAME}}:{
           {IMAGE_TAG}}
          ports:
            - containerPort: 80
3. Jenkins 参数设置

参数名称:BRANCH
类型:字符串(或下拉列表)
默认值:dev
描述:指定部署分支,如 dev/test/release-x/master


** 四、常见问题及解决方案**

问题 原因 解决方法
InvalidImageName ${BUILD_TAG} 未替换 确保 replaceAll 正确生效
credentials 不能在 env 中使用 Groovy 限制 移入 withCredentials 中
K8S_NAMESPACE 未定义报错 post 中变量未初始化 使用全局变量方式定义
没有分支选择参数 没添加 BRANCH 参数 Jenkins 添加参数即可

** 五、环境准备与命令**

kubectl create ns jenkins-dev
kubectl create ns jenkins-test
kubectl create ns jenkins-prod

Harbor 项目 k8s-test 手动创建并设置允许推送。

Jenkins 中添加:

GitLab 凭据 ID:gitlab-ssh-key
Harbor 凭据 ID:harbor-cred

七、 基于 Argo CD 实现 nginx-demo 自动部署

项目背景

目标:基于 ArgoCD 实现 GitOps 发布流程,部署测试应用 nginx-demo。
环境:已有 Jenkins 环境,命名空间为 jenkins-dev,Git 仓库为 <git@git.****.com>:system-ops/k8s-deploy-manifests.git。
镜像仓库:使用本地 Harbor(镜像前缀 172.16.3.252/k8s-test/),通过代理拉取并打 tag 推送。


** Git 仓库结构**

k8s-deploy-manifests/
├── applications/
│   └── nginx-demo.yaml      # ArgoCD Application 配置
└── nginx-demo/
    ├── deployment.yaml      # Deployment 配置
    ├── service.yaml         # Service 配置
    └── kustomization.yaml   # kustomize 文件

** 部署流程详解**

1️⃣ 安装 Argo CD 到 K8S 集群
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

💡 注意修改 yaml 中所有镜像为本地 Harbor 镜像,并提前推送。

2️⃣ 修改并推送镜像到本地 Harbor
docker tag ghcr.kubesre.xyz/dexidp/dex:v2.41.1 172.16.3.252/k8s-test/dex:v2.41.1
docker tag quay.kubesre.xyz/argoproj/argocd:v3.0.6 172.16.3.252/k8s-test/argocd:v3.0.6
docker tag dhub.kubesre.xyz/redis:7.2.7-alpine 172.16.3.252/k8s-test/redis:7.2.7-alpine
docker push ...
3️⃣ 访问 Argo CD UI 并登录
kubectl get svc argocd-server -n argocd
# 查看 NodePort 端口
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
# 初始用户名:admin,密码为上面命令输出

** 编写 nginx-demo 应用配置**

deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-demo
  template:
    metadata:
      labels:
        app: nginx-demo
    spec:
      containers:
      - name: nginx
        image: 172.16.3.252/k8s-test/nginx-demo:20250710
        ports:
        - containerPort: 80
service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-demo
spec:
  selector:
    app: nginx-demo
  ports:
    - port: 80
      targetPort: 80
kustomization.yaml
resources:
  - deployment.yaml
  - service.yaml
applications/nginx-demo.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: nginx-demo
  namespace: argocd
spec:
  project: default
  source:
    repoURL: git@git.****com:system-ops/k8s-deploy-manifests.git
    targetRevision: master
    path: nginx-demo
  destination:
    server: https://kubernetes.default.svc
    namespace: jenkins-dev
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

遇到的问题与解决方案

问题描述 解决方法
ArgoCD 拉取 Git 报错:SSH agent not specified 改用 HTTPS 拉取,并在 UI 中添加 Git 凭据
Git 仓库子模块导致路径错误 移除 submodule,重构为扁平结构
service.yaml 文件未识别 文件名有尾部空格,mv 去除空格后重新提交
仓库推送被拒绝 关闭 master 分支保护,或使用新分支
Manifest 缓存路径识别失败 逐步排查并上传正确 YAML 文件,确保本地同步提交

验证结果

kubectl -n jenkins-dev get deployment
kubectl -n jenkins-dev get svc

输出结果:

NAME         READY   UP-TO-DATE   AVAILABLE   AGE
nginx-demo   1/1     1            1           ✅

八、Jenkins + GitLab + Harbor + ArgoCD 自动化部署流程

环境:Kubernetes(命名空间:jenkins-dev),Jenkins,GitLab,Harbor,ArgoCD


一、项目结构说明

1.1 Git 仓库结构(k8s-deploy-manifests)

k8s-deploy-manifests/
├── applications/
│   └── nginx-demo.yaml             # ArgoCD 应用配置
├── nginx-demo/
│   ├── deployment.yaml             # Deployment 模板文件,镜像 tag 会被 Jenkins 更新
│   ├── service.yaml                # 暴露 Service
│   └── kustomization.yaml          # 用于 Kustomize 管理资源

1.2 业务代码仓库结构(nginx-demo)

nginx-demo/
├── Dockerfile                     # 构建 Nginx 镜像
├── Jenkinsfile                    # Jenkins CI/CD 流水线定义
└── index.html                     # 示例静态页面

二、配置文件详解

2.1 Jenkinsfile
pipeline {
    agent any

    environment {
        // 镜像仓库地址和 Tag 构造(按时间戳)
        IMAGE_TAG = "build-${new Date().format('yyyyMMdd-HHmm')}"
        IMAGE_REPO = "172.16.3.252/k8s-test/nginx-demo"
    }

    stages {

        stage('拉取项目代码') {
            steps {
                git(
                    url: 'git@git.****.com:system-ops/nginx-demo.git',
                    credentialsId: 'gitlab-ssh-key',
                    branch: 'master'
                )
            }
        }

        stage('构建镜像并推送至 Harbor') {
            steps {
                withCredentials([usernamePassword(credentialsId: 'harbor-cred', usernameVariable: 'HARBOR_USER', passwordVariable: 'HARBOR_PASS')]) {
                    sh """
                        echo '开始构建镜像: ${IMAGE_REPO}:${IMAGE_TAG}'
                        docker login 172.16.3.252 -u $HARBOR_USER -p $HARBOR_PASS
                        docker build -t ${IMAGE_REPO}:${IMAGE_TAG} .
                        docker push ${IMAGE_REPO}:${IMAGE_TAG}
                    """
                }
            }
        }

        stage('更新 deployment.yaml 中镜像 tag') {
            steps {
                sshagent(credentials: ['gitlab-ssh-key']) {
                    sh '''
                        set -e

                        # 清理已有目录,防止多次构建冲突
                        if [ -d "k8s-deploy-manifests" ]; then
                            echo "检测到已有 k8s-deploy-manifests 目录,正在删除..."
                            rm -rf k8s-deploy-manifests
                        fi

                        # 克隆 YAML 仓库
                        git clone git@git.****com:system-ops/k8s-deploy-manifests.git

                        # 切换到对应服务目录
                        cd k8s-deploy-manifests/nginx-demo

                        echo "将镜像地址替换为: ${IMAGE_REPO}:${IMAGE_TAG}"
                        sed -i "s|image: .*|image: ${IMAGE_REPO}:${IMAGE_TAG}|" deployment.yaml

                        # 配置提交信息并推送
                        git config user.name "jenkins"
                        git config user.email "jenkins@****.com"
                        git add deployment.yaml
                        git commit -m "auto: update nginx-demo image to ${IMAGE_TAG}"
                        git push origin master
                    '''
                }
            }
        }

        stage('部署阶段(ArgoCD 自动同步)') {
            steps {
                echo '无需手动部署,ArgoCD 将自动同步新镜像版本。'
            }
        }
    }

    post {
        failure {
            echo '❌ 流水线执行失败,请检查日志输出以定位问题。'
        }
    }
}
2.2 ArgoCD Application(nginx-demo.yaml)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: nginx-demo
  namespace: argocd
spec:
  project: default
  source:
    repoURL: git@git.****.com:system-ops/k8s-deploy-manifests.git
    targetRevision: HEAD
    path: nginx-demo
  destination:
    server: https://kubernetes.default.svc
    namespace: jenkins-dev
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

三、实际操作流程

创建两个 Git 仓库(代码仓库 nginx-demo,部署仓库 k8s-deploy-manifests)

初始化 nginx-demo 目录结构,写好 Jenkinsfile 和 Dockerfile

Jenkins 配置两个凭据:

gitlab-ssh-key(用于拉代码 + 推送 deployment.yaml 更新)
harbor-cred(用于推送镜像)

ArgoCD 添加并同步 nginx-demo.yaml 应用配置

Jenkins 成功执行流水线后会:

构建 + 推送镜像
更新 deployment.yaml 中镜像 tag
触发 ArgoCD 自动同步


四、遇到的问题及解决方法

4.1 Harbor 推送失败(unauthorized)

原因:凭据未配置或 ID 错误
解决:使用 harbor-cred 正确绑定用户名密码并注入登录脚本

4.2 Git 无法 push(权限拒绝)

原因:gitlab-ssh-key 无写权限
解决:确保对应 SSH Key 的用户对目标仓库拥有 Maintainer 权限

4.3 ArgoCD 未同步更新

原因:ArgoCD Application 中路径或 repoURL 配置错误
解决:确认 path 填写的是 k8s-deploy-manifests 中具体服务目录(如 nginx-demo)

**九、**GitOps 实践总结多环境部署

一、目标

实现多环境(dev/test/prod)下:

Jenkins 自动构建 + 推送镜像
自动更新对应 YAML 配置并提交 Git 仓库
通过 ArgoCD 实现自动部署与状态可视化


二、目录结构规范

GitOps 配置仓库目录结构:

k8s-deploy-manifests/
├── jenkins-dev/nginx-demo/
│   ├── deployment.yaml
│   ├── service.yaml
│   └── kustomization.yaml
├── jenkins-test/nginx-demo/
│   ├── deployment.yaml
│   ├── service.yaml
│   └── kustomization.yaml
├── jenkins-prod/nginx-demo/
│   ├── deployment.yaml
│   ├── service.yaml
│   └── kustomization.yaml
├── nginx-demo/              # 最早的单环境目录,已废弃
└── applications/           # 可作为 ArgoCD App of Apps 管理入口

✅ 每个环境单独管理,Jenkins 脚本按变量 BRANCH_ENV 自动切换路径。


三、Jenkinsfile 配置

** 支持内容:**

选择 dev/test/prod 环境
镜像构建 tag 自动带上时间戳(build-20250714-1415)
自动推送至 Harbor(私有仓库)
自动替换镜像字段并提交至配置仓库(含 commit message)
ArgoCD 自动同步,无需手动操作

参数化示例:
pipeline {
    agent any

    parameters {
        // ✅ 选择部署环境:dev/test/prod
        choice(name: 'BRANCH_ENV', choices: ['dev', 'test', 'prod'], description: '部署目标环境')
    }

    environment {
        IMAGE_TAG = "build-${new Date().format('yyyyMMdd-HHmm')}"
        IMAGE_REPO = "172.16.3.252/k8s-test/nginx-demo"
    }

    stages {
        stage('拉取代码') {
            steps {
                git url: 'git@git.***.com:system-ops/nginx-demo.git',
                    credentialsId: 'gitlab-ssh-key',
                    branch: 'master'
            }
        }

        stage('构建镜像并推送 Harbor') {
            steps {
                withCredentials([usernamePassword(credentialsId: 'harbor-cred', usernameVariable: 'HARBOR_USER', passwordVariable: 'HARBOR_PASS')]) {
                    sh """
                        docker login 172.16.3.252 -u $HARBOR_USER -p $HARBOR_PASS
                        docker build -t ${IMAGE_REPO}:${IMAGE_TAG} .
                        docker push ${IMAGE_REPO}:${IMAGE_TAG}
                    """
                }
            }
        }

        stage('更新部署配置') {
            steps {
                sshagent(credentials: ['gitlab-ssh-key']) {
                    sh """
                        rm -rf k8s-deploy-manifests
                        git clone git@git.*****.com:system-ops/k8s-deploy-manifests.git
                        cd k8s-deploy-manifests/jenkins-${BRANCH_ENV}/nginx-demo
                        sed -i "s|image: .*|image: ${IMAGE_REPO}:${IMAGE_TAG}|" deployment.yaml
                        git config user.name "jenkins"
                        git config user.email "jenkins@****.com"
                        git add deployment.yaml
                        git commit -m "auto: update nginx-demo image to ${IMAGE_TAG} for ${BRANCH_ENV}"
                        git push origin master
                    """
                }
            }
        }

        stage('ArgoCD 自动部署') {
            steps {
                echo "无需手动部署,ArgoCD 将自动同步 jenkins-${params.BRANCH_ENV} 环境的最新配置。"
            }
        }
    }

    post {
        failure {
            echo "❌ 构建失败,请检查日志并考虑手动回滚镜像 tag。"
        }
    }
}

四、ArgoCD 应用配置(已部署)

应用名 Namespace Git Path 自动同步 状态
nginx-demo-dev jenkins-dev jenkins-dev/nginx-demo ✅Synced
nginx-demo-test jenkins-test jenkins-test/nginx-demo ✅Synced
nginx-demo-prod jenkins-prod jenkins-prod/nginx-demo ✅Synced

五、错误记录与排查过程

时间 问题描述 原因分析 解决方式
构建失败 cd: k8s-deploy-manifests/jenkins-/nginx-demo: No such file or directory jenkins-${BRANCH_ENV} 拼接出错,参数未传或路径命名不规范(多了空格) 清理 Git 仓库路径中的空格字符;本地手动确认目录名称无误
拉取路径错误 nginx-demo 直接作为 root path,非多环境模式结构 原 ArgoCD 只部署了 nginx-demo,未区分环境 后续改为 jenkins-dev/nginx-demo 等多环境路径,创建新的 ArgoCD 应用
Git 提交失败 Git clone 的配置仓库目录结构误判 Jenkins 运行时默认进入工作目录,未处理多级子目录 cd 路径拼接异常 加入 set -e 严格执行;先 clone 后 cd k8s-deploy-manifests/… 补全路径

十、 Kubernetes 多环境 GitOps 最佳实践之kuustomize-demo

项目:nginx-kustomize-demo 多环境 GitOps 部署

所用技术: Jenkins + Harbor + ArgoCD + Kubernetes + Git + Kustomize


一、文件结构总览

k8s-deploy-kustomize/
├── base
│   ├── deployment.yaml
│   ├── service.yaml
│   └── kustomization.yaml 使用 images + newTag 管理
├── overlays
│   ├── jenkins-dev
│   │   ├── kustomization.yaml 使用 kustomize edit set image 更新
│   │   ├── service-patch.yaml patch 修改 Service 信息
│   │   └── ingress.yaml (optional)
│   ├── jenkins-test
│   └── jenkins-prod

二、base/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - deployment.yaml
  - service.yaml

images:
  - name: 172.16.3.252/k8s-test/nginx-kustomize-demo
    newName: 172.16.3.252/k8s-test/nginx-kustomize-demo
    newTag: latest  # 初始化 tag ,待 overlays 重写

三、overlays/jenkins-dev/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: jenkins-dev  # 指定环境命名空间

resources:
  - ../../base

patches:
  - path: service-patch.yaml
    target:
      kind: Service
      name: nginx-kustomize-demo  # 对 base 中 service.yaml 修改

service-patch.yaml

apiVersion: v1
kind: Service
metadata:
  name: nginx-kustomize-demo
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: 80

四、Jenkinsfile (images + newTag 配合 kustomize edit set image)

pipeline {
    agent any

    parameters {
        choice(name: 'BRANCH_ENV', choices: ['dev', 'test', 'prod'], description: '部署目标环境')
    }

    environment {
        IMAGE_REPO = "172.16.3.252/k8s-test/nginx-kustomize-demo"
        IMAGE_TAG = "build-${new Date().format('yyyyMMdd-HHmm')}"
        GIT_REPO = "git@git.****.com:system-ops/k8s-deploy-kustomize.git"
    }

    stages {
        stage('拉取 NGINX 项目代码') {
            steps {
                git(
                    url: 'git@git.***.com:system-ops/nginx-kustomize-demo.git',
                    credentialsId: 'gitlab-ssh-key',
                    branch: 'master'
                )
            }
        }

        stage('构建镜像并推送 Harbor') {
            steps {
                withCredentials([usernamePassword(credentialsId: 'harbor-cred', usernameVariable: 'HARBOR_USER', passwordVariable: 'HARBOR_PASS')]) {
                    sh """
                        echo '🚧 构建镜像: ${IMAGE_REPO}:${IMAGE_TAG}'
                        docker login 172.16.3.252 -u $HARBOR_USER -p $HARBOR_PASS
                        docker build -t ${IMAGE_REPO}:${IMAGE_TAG} .
                        docker push ${IMAGE_REPO}:${IMAGE_TAG}
                    """
                }
            }
        }

        stage('更新 Kustomize 镜像配置') {
            steps {
                sshagent(credentials: ['gitlab-ssh-key']) {
                    sh """
                        echo '🔄 克隆部署配置仓库'
                        rm -rf k8s-deploy-kustomize
                        git clone ${GIT_REPO}
                        cd k8s-deploy-kustomize/overlays/jenkins-${BRANCH_ENV}

                        echo '📝 设置镜像 tag 为 ${IMAGE_TAG}'
                        kustomize edit set image ${IMAGE_REPO}=${IMAGE_REPO}:${IMAGE_TAG}

                        echo '🚀 提交变更'
                        git config user.name "jenkins"
                        git config user.email "jenkins@****.com"
                        git add kustomization.yaml

                        if ! git diff --cached --quiet; then
                            git commit -m "auto: update jenkins-${BRANCH_ENV} image to ${IMAGE_TAG}"
                            git push origin master
                        else
                            echo '✅ 无需提交,镜像 tag 未变化'
                        fi
                    """
                }
            }
        }

        stage('提示部署进度') {
            steps {
                echo "✅ 镜像 ${IMAGE_REPO}:${IMAGE_TAG} 已构建并更新配置,等待 ArgoCD 自动同步。"
            }
        }
    }

    post {
        failure {
            echo "❌ 构建失败,请检查日志并回滚上一个版本镜像 tag。"
        }
    }
}

五、错误纪录 & 故障解决

错误环节 错误信息 原因 解决策略
镜像拉取失败 nginx:stable 拉取失败 base deployment.yaml 中 image 默认镜像 改为使用私有 Harbor 镜像,通过 Kustomize images 控制
ArgoCD 拉取失败 deployment-patch.yaml: no such file or directory patch 配置了不存在的文件 改为直接在 base 使用 images + newTag 管理镜像
Jenkins 执行失败 kustomize: command not found Jenkins 中未安装 kustomize 工具 通过 yum 安装或配置 kustomize 二进制文件路径
Git push 失败 rejected (fetch first) 本地未先 pull 先执行 git pull –rebase 再 push

六、ArgoCD 应用状态总览

应用 Namespace Path Sync Status
nginx-kustomize-demo-dev jenkins-dev overlays/jenkins-dev ✅ Synced
nginx-kustomize-demo-test jenkins-test overlays/jenkins-test ✅ Synced
nginx-kustomize-demo-prod jenkins-prod overlays/jenkins-prod ✅ Synced
© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容