基于Jenkins持续集成与部署项目在Kubernetes上

构建部署工具及镜像作用介绍

Github仓库地址:https://github.com/Beatrueman/Jenkins-CICD.git

工具

Jenkins:通过Pipeline实现CI/CD流

Sonar:进行代码检查

draft:根据项目自动生成Dockerfile与Helm Chart

Buildah:打包、推送镜像

Helm:部署Chart到Kubernetes集群

镜像

beatrueman/builder:1.0:整合了draftbuildah,负责镜像构建与镜像推送

beatrueman/deployer:1.0:整合了Helm,用于部署Chart到Kubernetes

sonarsource/sonar-scanner-cli:latest:用于执行代码检查

jenkins/inbound-agent:3206.vb_15dcf73f6a_9-2:它是 Jenkins Pipeline 中的一种代理机制,允许在 Jenkins 中动态创建代理节点以执行特定的构建任务。

整体流程

image-20240607222125043

  1. 开发人员推送代码到Git仓库,自动触发Jenkins CI/CD流
  2. SonarQube进行代码检查
  3. 查找Dockerfile,如果没有则通过Draft自动生成Dockerfile和Helm Chart
  4. 使用buildah进行镜像打包与镜像推送到Harbor仓库
  5. 使用Helm将Chart部署在Kubernetes集群上,并把打包好的chart包推送至Harbor

参数

参数化构建

变量名表示值可选项
HARBOR_REGISTRYHarbor仓库名
PROJECT_NAME项目名称必须小写
ENTRYPOINT项目入口文件(仅用于Python)app.py或main.py
PORT项目暴露入口
IMAGE_NAME镜像名称
TAG镜像标签
SONAR_PROJECT_NAMEsonar代码检查项目名称

准备

插件下载

插件管理中搜索并下载以下插件

Kubermetes:Kubernetes版本4238.v41b_3ef14a_5d8

SonarQube Scanner for Jenkins:SonarQube Scanner for Jenkins版本

添加凭据

image-20240607235051117

  1. SonarQube凭据保存的内容为在SonarQube中生成的全局令牌
  2. Harbor-Secret凭据保存Harbor的用户名和密码
  3. kubeconfig保存最后一步生成的kubeconfig

Kubernetes集群连接

以使用Kubernerts部署的Jenkins为例(部署方法请自行查询)

image-20240607233606595

1.在系统管理 >> Clouds中新增一个cloud

2.主要填入以下配置

  • 名称
  • Kubernetes地址:https://<your_ip>:6443
  • Kubernetes命名空间(需要与jenkins部署在同一个命名空间)
  • Jenkins地址:Jenkins在K8s部署,填入http://ClusterIP:Port (http://10.96.3.38:8080)。若不在K8s部署,需要将/root/.kube/configbase64编码后保存为凭据,然后再填入jenkins的暴露地址。
  • Jenkins通道:填入10.96.1.180:50000,注意一定不要加http

将cloud名称填入cloud ""

image-20240608001827248

20240607-234551

image-20240607234620509

可以点击连接测试检查是否可以连接集群

image-20240607234655491

SonarQube准备

1.手工新建一个项目

2.新建一个全局令牌

image-20240607232105268

3.将该令牌生成的token添加进Jenkins的全局凭据中

4.在系统配置中,填入Sonar的服务地址与凭据

20240607-235555

使用受限的kubeconfig

感谢@zhangxinhui02
使用该工具kubeconfig-generator,生成一个受限制的kubeconfig

  1. 新建一个命名空间,用于最终项目的部署
  2. kubeconfig-generator.py中,指定NAMESPACECLUSTER_SERVERSA_NAME

image-20240607235927663

3.将生成的kubeconfig下载,以secret file形式添加进Jnekins凭据

4.因为此时的config是受限的,需要生成一个rolebinding,用于jenkins命名空间下的default用户控制test命名空间下的一些操作

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: jenkins-rolebinding
  namespace: test # 控制test命名空间
subjects:
- kind: ServiceAccount
  name: default
  namespace: jenkins # 这里假设Jenkins服务账户位于jenkins命名空间
roleRef:
  kind: ClusterRole
  name: edit # 或者你可以定义一个自定义的Role,只包含所需的最小权限
  apiGroup: rbac.authorization.k8s.io

完整Jenkinsfile

pipeline {
    agent {
        kubernetes {
            cloud "Redrock-Cloud"
            yaml """
apiVersion: v1
kind: Pod
metadata:
  labels:
    component: jenkins-agent
spec:
  containers:
    - name: sonar-scanner
      image: sonarsource/sonar-scanner-cli:latest
      command:
        - cat
      tty: true
    - name: builder
      image: beatrueman/builder:1.0
      securityContext:
        privileged: true
      command:
        - cat
      tty: true
    - name: deployer
      image: beatrueman/deployer:1.0
      command:
        - cat
      tty: true
    - name: jnlp
      image: jenkins/inbound-agent:3206.vb_15dcf73f6a_9-2
      resources:
        limits:
          memory: "1Gi"
          cpu: "200m"
"""
        }
    }

    // environment {
    //     HARBOR_REGISTRY = ''
    //     PROJECT_NAME = 'myapp' // 项目名称,必须小写
    //     ENTRYPOINT = 'main.py' // 项目入口文件,app.py或main.py
    //     PORT = '6666' // 项目暴露的端口
    //     IMAGE_NAME = 'yicloud' // 镜像名称
    //     TAG = 'v1' // 镜像标签
    //     SONAR_PROJECT_NAME = 'Python' // sonar项目名称
    // }

    stages {
        stage('git clone') {
            steps {
                echo "1.正在克隆代码......"
                git url: ""
            }
        }

        stage('SonarQube code checking') {
            steps {
                container('sonar-scanner') {
                    echo '2.正在进行代码检查......'
                    echo '代码检查结果请在面板查看!'
                    withSonarQubeEnv('SonarQube') {
                        sh '''sonar-scanner \
                            -Dsonar.projectKey=${SONAR_PROJECT_NAME} \
                            -Dsonar.projectName=${SONAR_PROJECT_NAME} \
                            -Dsonar.sources="/home/jenkins/agent/workspace/${JOB_NAME}" \
                            -Dsonar.projectVersion=1.0 \
                            -Dsonar.sourceEncoding=UTF-8
                        '''
                    }
                }
            }
        }

        stage('Image build') {
            steps {
                script {
                    echo '3.正在制作镜像......'
                    try {
                        def result = sh(script: "ls -al | grep Dockerfile", returnStatus: true)
                        if (result == 0) {
                            echo "找到 Dockerfile,开始构建镜像"
                            container('builder') {
                                sh '''
                                    draft create -a myapp --variable PORT=${PORT} \
                                        --variable APPNAME=${PROJECT_NAME} \
                                        --variable SERVICEPORT=8088 \
                                        --variable NAMESPACE=test \
                                        --variable IMAGENAME=${HARBOR_REGISTRY}/library/${IMAGE_NAME} \
                                        --variable IMAGETAG=${TAG} \
                                        --variable ENTRYPOINT=${ENTRYPOINT} \
                                        --variable VERSION=3 \
                                        --deploy-type helm \
                                        --deployment-only
                                    rm "/home/jenkins/agent/workspace/${JOB_NAME}/charts/templates/namespace.yaml"
                                    buildah bud -t ${HARBOR_REGISTRY}/library/${IMAGE_NAME}:${TAG} .
                                '''
                            }
                        } else {
                            echo "未找到 Dockerfile,将生成 Dockerfile"
                            container('builder') {
                                def jobNameLower = "${JOB_NAME}".toLowerCase()
                                sh '''
                                    draft create -a myapp --variable PORT=${PORT} \
                                        --variable APPNAME=${PROJECT_NAME} \
                                        --variable SERVICEPORT=8088 \
                                        --variable NAMESPACE=test \
                                        --variable IMAGENAME=${HARBOR_REGISTRY}/library/${IMAGE_NAME} \
                                        --variable IMAGETAG=${TAG} \
                                        --variable ENTRYPOINT=${ENTRYPOINT} \
                                        --variable VERSION=3 \
                                        --deploy-type helm
                                    rm "/home/jenkins/agent/workspace/${JOB_NAME}/charts/templates/namespace.yaml"
                                    buildah bud -t ${HARBOR_REGISTRY}/library/${IMAGE_NAME}:${TAG} .
                                '''
                            }
                        }
                    } catch (err) {
                        echo "查找 Dockerfile 发生错误: ${err}"
                    }
                }
            }
        }

        stage('Image push') {
            steps {
                echo "3. Pushing image"
                container('builder') {
                    script {
                        withCredentials([usernamePassword(credentialsId: 'Redrock-Harbor-Secret', passwordVariable: 'passwd', usernameVariable: 'username')]) {
                            sh "buildah login -u ${username} -p ${passwd} ${env.HARBOR_REGISTRY}"
                            sh "buildah images"
                            sh "buildah push ${HARBOR_REGISTRY}/library/${IMAGE_NAME}:${TAG}"
                        }
                    }
                }
            }
        }
        
        stage('Deploy to Kubernetes') {
            steps {
                echo "5.即将把服务部署在Kubernetes集群上......"
                container('deployer') {
                    script {
                        // 使用受限制的kubeconfig
                        def kubeConfigCreds = credentials('kubeconfig')
                        // 写入临时kubeconfig文件
                        sh 'echo "${kubeConfigCreds}" | base64 --decode > /tmp/kubeconfig.yaml'
                        sh 'ls'

			            // 部署并获取返回码,如果成功部署则打包chart并上传至Harbor仓库
                        def chartYamlContent = readFile "charts/Chart.yaml" // 读取Chart.yaml
                        def chartYaml = readYaml text: chartYamlContent
                        def chartVersion = chartYaml.version // 获取Chart的version
                        def packageName = "${PROJECT_NAME}-${chartVersion}.tgz" // 打包后的Chart名
                        
                        def helmDeployResult = sh(script: "helm install ${PROJECT_NAME} -n test --kubeconfig /tmp/kubeconfig.yaml charts", returnStatus: true)
			                if(helmDeployResult == 0) {
			                    echo '部署成功!'
			                    echo '正在打包Chart,并上传至Harbor仓库'
			                    
			                    sh "helm package charts"
			                    withCredentials([usernamePassword(credentialsId: 'Redrock-Harbor-Secret', passwordVariable: 'passwd', usernameVariable: 'username')]) {
			                        sh "helm registry login ${env.HARBOR_REGISTRY} -u ${username} -p ${passwd}"
			                        sh "helm push ${packageName} oci://${HARBOR_REGISTRY}/library"
			                    }
			                    
			                } else {
			                    error "Chart部署失败,Helm返回码: ${helmDeployResult}"
			                }
			                
                    }
                }
            }
        }
    }
}

image-20240608002622612

安全警示

根据buildah image v1.34: Error: open /usr/lib/containers/storage/overlay-images/images.lock: permission denied · Issue #5332 · containers/buildah (github.com)

builder容器不得不开启privileged,否则无法进行正常的打包与推送镜像。

原因与解决可参考:https://opensource.com/article/19/3/tips-tricks-rootless-buildah

image-20240608004704783

  • 24
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值