一、部署方式对比

1.1 原有部署方式(Jenkins + Yaml/Helm + Kubernetes)

基于 Yaml|Helm 做全部应用的管理,但是仅支持底层 CLI 模式,缺乏界面,而且各应用的配置不相同, Charts 维护成本高。

Day17-ArgoCD-图60

1.2 现有部署方式(Jenkins + ArgoCD + Kubernetes)

Day17-ArgoCD-图61

1、研发通过git客户端将代码推送到GitLab

2、通过Gitlab的webhook通知并触发jenkins相关流水线任务

3、jenkins使用Dockerfile构建镜像

4、jenkins将构建完成的镜像发送到私有镜像仓库Harbor

5、将相关的yaml文件上传到GitLab

6、GitLab通过webHook触发argo进行部署

7、k8s集群通过yaml文件指定的镜像通过Harbor进行拉取

利用 Gitops 思想,去触发 Argocd 部署。CI 部分基于 Jenkins 更加高效畅快,CD 部分基于 ArgoCD 提升部署+管控效率!

二、基于 Jenkins + ArgoCD + Kubernetes 智能化流程

2.1 Jenkins Pipeline

改造来自 CICD 章节的:KUBERNETES_APPS_DEPLOYMENT

//定义git相关数据

def git_address = "http://gitlab.example.com/demoteam/${GIT_PROJECT_NAME}.git" 

def git_auth = "2e1860e9-8891-4a18-95aa-72bbefc0fda6"

//构建版本的名称

def tag = "latest"

//Harbor私服地址

def harbor_url = "harbor.example.com"

//Harbor的项目名称

def harbor_project_name = "demo"

//Harbor的凭证

def harbor_auth = "8b75c989-f6f9-46bb-a706-3630a21149d6"

//启动时间

def start = new Date().format('yyyy-MM-dd HH:mm:ss')

//创建一个Pod的模板,label为jenkins-slave

podTemplate(label: 'jenkins-slave-app-argocd', cloud: 'kubernetes', nodeSelector: 'jenkins-jnlp=yes',  containers: [

    containerTemplate(

        name: 'jnlp',

        image: "harbor.example.com/library/jenkins-slave-maven:jdk-11-2",

        ttyEnabled: true

        ),

    containerTemplate( 

        name: 'docker',

        image: "harbor.example.com/library/docker:stable",

        ttyEnabled: true,

        command: 'cat'

        ),

    ],

    volumes: [

        hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),

    ],

)

{

node("jenkins-slave-app-argocd"){

    // 第一步

    stage('Pull'){

        checkout([$class: 'GitSCM', branches: [[name: "${branch}"]], extensions: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_address}"]]])

    }

    stage('BuildDescription') {

        // 自定义设置构建历史显示的名称和描述信息 

        // 不同的部署方式设置构建历史显示的名称和描述信息方式不一样,根据自己的部署方式自行百度找到设置方法

        script {

        //设置buildName

          wrap([$class: 'BuildUser']) {

           //修改Description

            buildDescription "${BUILD_USER} > ${project_name} > ${branch}"

           }

         }

     }

    // 第二步

    stage('Build&Tag&Push&Deploy'){

        //把选择的项目信息转为数组

        def selectedProjects = "${project_name}".split(',')

        for(int i=0;i<selectedProjects.size();i++){

            //取出每个项目的名称

            def currentProjectName = selectedProjects[i];

            //定义镜像名称

            def imageName = "${currentProjectName}:${tag}"

            //定义newTag

            def newTag = sh(returnStdout: true,script: 'echo `date +"%Y%m%d%H%M"_``git describe --tags --always`').trim()

            //编译,构建本地镜像

            //sh "sed -i 's#ACTIVEPROFILE#${springProfilesActive}#g' Dockerfile"

            if ( "${GIT_PROJECT_NAME}" == "java_kubernetes" ) {

                    sh "mvn clean package -Dmaven.test.skip=true"

            }

            container('docker') {

                //镜像编译

                sh "docker build -t ${imageName} ."

                //给镜像打标签

                sh "docker tag ${imageName} ${harbor_url}/${harbor_project_name}/${currentProjectName}:${newTag}"

                //登录Harbor,并上传镜像

                withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')])

                {

                    //登录

                    sh "docker login -u ${username} -p ${password} ${harbor_url}"

                    //上传镜像

                    sh "docker push ${harbor_url}/${harbor_project_name}/${currentProjectName}:${newTag}"

                }

            //删除本地镜像

            sh "docker rmi -f ${imageName}" 

            sh "docker rmi -f ${harbor_url}/${harbor_project_name}/${currentProjectName}:${newTag}"

            }

            //修改最新的镜像

            sh """

                sed -i 's@image: .*@image: harbor.example.com/demo/spring-java-demo:${newTag}@' yaml/deployment.yaml

                cat yaml/deployment.yaml

            """

            //打tag并推送

            withCredentials([usernamePassword(credentialsId: "${git_auth}", passwordVariable: 'GIT_PASSWORD', usernameVariable: 'GIT_USERNAME')])

            {

                sh '''

                    git config user.email "zhdya@zhdya.cn"

                    git config user.name "root"

                    git add yaml/deployment.yaml

                    git commit -m "Update deployment version ${BUILD_NUMBER}"

                    git push http://$GIT_USERNAME:$GIT_PASSWORD@gitlab.example.com/demoteam/${GIT_PROJECT_NAME}.git HEAD:main

                '''

            }

        }

    }

}

}

2.2 项目 Gitlab 配置 webhook

2.2.1 新建项目

1、依次点击【项目】-【创建空白项目】

image-20250425085608119

2、定义项目名称为JAVA Kubernetes Argocd,取消勾选【使用自述文件初始化仓库】

image-20250425085722418

2.2.2 配置webhook

1、进入管理中心中,依次点击【设置】-【网络】-【出站请求】,勾选【允许来自 webhooks 和集成对本地网络的请求】

image-20250425101035910

2、进入JAVA Kubernetes Argocd这个项目里面,依次点击【设置】-【webhook】,定义如下内容:

  • Url:https://argocd.example.com/api/webhook

  • Secret token:<gitlab-webhook-secret>

  • 按需勾选:push event

image-20250425101207241

3、添加完成webhook后,点击【测试】-【推送事件】,正常回显如下:

Hook executed successfully: HTTP 200

image-20250425101348781

2.3 创建 Application

2.3.1 开始创建

调整gitlab的ingress配置文件,允许上传大文件

# 添加下面内容

[root@master01 4]# vim gitlab-ing.yaml 

...

...

    nginx.ingress.kubernetes.io/enable-cors: 'true'

    nginx.ingress.kubernetes.io/proxy-body-size: 20G

# 完整配置文件

[root@master01 4]# cat gitlab-ing.yaml 

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

  annotations:

    nginx.ingress.kubernetes.io/enable-cors: 'true'

    nginx.ingress.kubernetes.io/proxy-body-size: 20G

  name: gitlab-ing

  namespace: devops

spec:

  ingressClassName: nginx

  rules:

  - host: gitlab.example.com

    http:

      paths:

      - backend:

          service:

            name: gitlab

            port:

              number: 80

        path: /

        pathType: Prefix

# 应用        

[root@master01 4]# kaf gitlab-ing.yaml 

上传代码文件到gitlab

# 准备代码文件放到java_kubernetes_argocd目录下

[root@master01 ~]# cd /root/17/argo-rollouts

[root@master01 argo-rollouts]# mkdir java_kubernetes_argocd

# 上传代码文件到gitlab

## 初始化

[root@master01 ~]# cd /root/17/argo-rollouts/java_kubernetes_argocd

[root@master01 java_kubernetes_argocd]# git init

## 添加远端仓库

[root@master01 java_kubernetes_argocd]# git remote add origin http://gitlab.example.com/demoteam/java-kubernetes-argocd.git

## 验证查看

[root@master01 java_kubernetes_argocd]# git remote -v

origin  http://gitlab.example.com/demoteam/java-kubernetes-argocd.git (fetch)

origin  http://gitlab.example.com/demoteam/java-kubernetes-argocd.git (push)

## 添加到暂存区

[root@master01 java_kubernetes_argocd]# git add .

## 提交到本地仓库

[root@master01 java_kubernetes_argocd]# git commit -m "first for java_kubernetes_argocd"

## 切换到main分支

[root@master01 java_kubernetes_argocd]# git branch -M main

## 上传到main分支

[root@master01 java_kubernetes_argocd]# git push -uf origin main

Username for 'http://gitlab.example.com': root

Password for 'http://root@gitlab.example.com': 

<gitlab-password>

...

...

# 编写zhdya-java-demo.yaml文件

[root@master01 ~]# cd /root/17/argo-rollouts

[root@master01 argo-rollouts]#

cat <<EOF >> zhdya-java-demo.yaml

apiVersion: argoproj.io/v1alpha1

kind: Application

metadata:

  name: zhdya-java-demo

  namespace: argocd

spec:

  destination:

    name: ''

    namespace: default

    server: 'https://kubernetes.default.svc'

  source:

    path: yaml

    repoURL: 'http://gitlab.example.com/demoteam/java_kubernetes_argocd.git'

    targetRevision: main

  sources: []

  project: default

  syncPolicy:

    syncOptions:

    - CreateNamespace=true

    automated:

      selfHeal: true

      prune: true

EOF

# 验证

[root@master01 java_kubernetes_argocd]# ll

total 4

-rw-r--r-- 1 root root 482 Apr 25 12:48 zhdya-java-demo.yaml

2.3.2 遇到问题-413报错

报错信息

# 提示error: RPC failed; result=22, HTTP code = 413

[root@master01 java_kubernetes_argocd]# git push -uf origin main

Username for 'http://gitlab.example.com': root

Password for 'http://root@gitlab.example.com': 

Counting objects: 45, done.

Delta compression using up to 8 threads.

Compressing objects: 100% (28/28), done.

error: RPC failed; result=22, HTTP code = 413

fatal: The remote end hung up unexpectedly

Writing objects: 100% (45/45), 15.15 MiB | 0 bytes/s, done.

Total 45 (delta 1), reused 0 (delta 0)

fatal: The remote end hung up unexpectedly

Everything up-to-date

报错解决方法

# 添加下面内容

[root@master01 4]# vim gitlab-ing.yaml 

...

...

    nginx.ingress.kubernetes.io/enable-cors: 'true'

    nginx.ingress.kubernetes.io/proxy-body-size: 20G

# 完整配置文件

[root@master01 4]# vim gitlab-ing.yaml 

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

  annotations:

    nginx.ingress.kubernetes.io/enable-cors: 'true'

    nginx.ingress.kubernetes.io/proxy-body-size: 20G

  name: gitlab-ing

  namespace: devops

spec:

  ingressClassName: nginx

  rules:

  - host: gitlab.example.com

    http:

      paths:

      - backend:

          service:

            name: gitlab

            port:

              number: 80

        path: /

        pathType: Prefix

# 重新应用

[root@master01 4]# kaf gitlab-ing.yaml 

2.4 Argocd 添加应用 repo 仓库

# 先使用密码登录

[root@master01 17]# argocd login argocd.example.com

Username: admin

Password: <new-admin-password>

# 添加repo源(添加前要保证对面项目不为空)

[root@master01 ~]# argocd repo add http://gitlab.example.com/demoteam/java-kubernetes-argocd.git --username root --password <gitlab-password> --insecure-skip-server-verification

2.5 应用

目前只是生成一个空的任务,任务名叫zhdya-java-demo

[root@master01 ~]# cd /root/17/argo-rollouts

[root@master01 argo-rollouts]# kubectl apply -f zhdya-java-demo.yaml

image-20250425193916014

三、Jenkins + ArgoCD 验证

涉及到的底层 deployment 控制器文件:

apiVersion: v1

kind: Service

metadata:

  name: spring-java-demo

spec:

  type: ClusterIP

  selector:

    app: spring-java-demo

  ports:

  - name: web

    port: 8080

    protocol: TCP

    targetPort: 8080

---

apiVersion: apps/v1

kind: Deployment

metadata:

  name: spring-java-demo

spec:

  replicas: 1

  selector:

    matchLabels:

      app: spring-java-demo

  template:

    metadata:

      labels:

        app: spring-java-demo

    spec:

      containers:

      - name: spring-java-demo

        image: harbor.example.com/demo/spring-java-demo:202409091002_8998f8a

        resources:

          limits:

            memory: 0.5

            cpu: 500

          requests:

            memory: 0.5

            cpu: 500

        ports:

          - containerPort: 8080

            name: web

        livenessProbe:

          httpGet:

            port: web

            path: /apptwo

          timeoutSeconds: 2       # 表示容器必须在2s内做出相应反馈给probe,否则视为探测失败

          periodSeconds: 30       # 探测周期,每30s探测一次

        readinessProbe:

          tcpSocket:

            port: web

          initialDelaySeconds: 10 # 容器启动后10s开始探测

---

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

  name: java-api-ingress

spec:

  ingressClassName: nginx

  rules:

  - host: java-argo.example.com

    http:

      paths:

      - backend:

          service:

            name: spring-java-demo

            port:

              number: 8080

        path: /

        pathType: Prefix

Jenkins 执行编译后的镜像:

Day17-ArgoCD-图62

Gitlab 成功 webhook 推送:

Day17-ArgoCD-图63

开始自动更新:

Day17-ArgoCD-图64

验证镜像数据:

Day17-ArgoCD-图65

四、改造基于金丝雀的自动化发布

4.1 创建 Rollouts

修改如上应用的 deployment 为 rollouts 资源:

cat <<EOF >> deployment.yaml

apiVersion: argoproj.io/v1alpha1

kind: Rollout

metadata:

  name: spring-java-canary-rollouts-analysis

  namespace: demo

spec:

  replicas: 3

  strategy:

    canary:

      analysis:

        templates:

        - templateName: success-rate-java  # 使用的 AnalysisTemplates

        startingStep: 2  # step 的索引,从第 2 个 step 开始分析(40%),第 1 个是 20% 初始的时候

        args:  # 传入 AnalysisTemplates 的参数

        - name: ingress

          value: spring-java-stable-ing

      canaryService: spring-java-canary

      stableService: spring-java-stable

      trafficRouting:

        nginx:

          stableIngress: spring-java-stable-ing

      # 发布的节奏

      steps:

      - setWeight: 20

      - pause: {}         # 需要手动确认通过

      - setWeight: 40

      - pause: {duration: 20s}

      - setWeight: 60

      - pause: {duration: 20s}

      - setWeight: 80

      - pause: {duration: 20s}

  revisionHistoryLimit: 2

  selector:

    matchLabels:

      app: spring-java-demo

  template:

    metadata:

      labels:

        app: spring-java-demo

    spec:

      containers:

      - name: spring-java-demo

        image: harbor.example.com/demo/spring-java-demo:202409111530_3fa1a9a

        ports:

        - name: http

          containerPort: 8080

          protocol: TCP

        livenessProbe:

          httpGet:

            port: http

            path: /apptwo

          timeoutSeconds: 2       # 表示容器必须在2s内做出相应反馈给probe,否则视为探测失败

          periodSeconds: 30       # 探测周期,每30s探测一次

        readinessProbe:

          tcpSocket:

            port: http

          initialDelaySeconds: 10 # 容器启动后10s开始探测

EOF

4.2 创建应用的 stable/canary SVC

cat <<EOF >> svc.yaml

apiVersion: v1

kind: Service

metadata:

  name: spring-java-canary

  namespace: demo

spec:

  ports:

  - port: 8080

    targetPort: http

    protocol: TCP

    name: http

  selector:

    app: spring-java-demo

---

apiVersion: v1

kind: Service

metadata:

  name: spring-java-stable

  namespace: demo

spec:

  ports:

  - port: 8080

    targetPort: http

    protocol: TCP

    name: http

  selector:

    app: spring-java-demo

EOF

4.3 创建应用的 ingress 路由

cat <<EOF >> ing.yaml

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

  name: spring-java-stable-ing

  namespace: demo

spec:

  ingressClassName: nginx

  rules:

  - host: java-argo.example.com

    http:

      paths:

      - backend:

          service:

            name: spring-java-stable

            port:

              number: 8080

        path: /

        pathType: Prefix

EOF

4.4 创建自动分析 AnalysisTemplate 模板

cat <<EOF >> analysis-success.yaml

apiVersion: argoproj.io/v1alpha1

kind: AnalysisTemplate

metadata:

  name: success-rate-java

  namespace: demo

spec:

  args:

  - name: ingress

  metrics:

  - name: success-rate

    initialDelay: 3s  # 延迟 3s 后启动

    interval: 3s  # 查询指标的频率

    failureLimit: 2  # 2 次不满足 successCondition 则视为失败

    successCondition: result[0] > 0.90  # 成功条件:测量值为空(指标还没采集到)或者大于 90%

    provider:

      prometheus:

        address: http://prometheus.monitor.svc:9090  # Prometheus 地址

        query: >+  # 查询语句

          sum(

            rate(nginx_ingress_controller_requests{ingress="{{args.ingress}}",status!~"[4-5].*"}[60s]))

            /

            sum(rate(nginx_ingress_controller_requests{ingress="{{args.ingress}}"}[60s])

          )

EOF

五、整体联调验证

5.1 应用迭代成功上线

测试访问:

# for i in {1..5000}; do curl java-argo.example.com/appone; done

Jenkins 发布:

Day17-ArgoCD-图71

Gitlab 验证:

Day17-ArgoCD-图66

webhook 验证:

Day17-ArgoCD-图67

Argocd UI 验证:

Day17-ArgoCD-图68

Argo Rollouts 验证:

Day17-ArgoCD-图69

成功上线:

Day17-ArgoCD-图70

5.2 应用迭代失败回滚

参照视频中描述的步骤来实现即可!