1. 背景介绍

传统的主机方式部署的 Jenkins 集群在复杂场景中会经常碰到如下问题,如:

  • 主 Master 发生单点故障时,会导致整个流程不可用;
  • Slave 的配置环境不一样,需要重复安装和配置维护麻烦,没办法定制差异化的配置管理;
  • 资源分配不均衡,有的 Slave 要运行的 job 出现排队等待,而有的 Slave 处于空闲状态;
  • 资源有浪费,每台 Slave 可能是实体机或者 VM,当 Slave 处于空闲状态时,也不会完全释放掉资源;

基于k8s编排和Docker容器技术,可以很好的解决上面问题。Jenkins Master 和 Jenkins Slave 以 Docker Container 形式运行在 Kubernetes 集群的 Node 上,Master 运行在其中一个节点,Slave 运行在各个节点上,并且它不是一直处于运行状态,它会按照需求动态的创建并自动删除。最终的方式和传统的是一样的,也是基于jenkins-agent.jar,通过jnlp进行通信,只不过jenkins 的 kubernetes-plugin 插件帮我们把这些操作做完了。通过这样的方式,在需要 Build 的时候创建 Slave Pod 动态伸缩,而且pod可以基于模板定制环境,Build结束后就销毁,合理利用计算资源。

在这里插入图片描述

2. k8s中进行jenkins安装

网上很多,可以通过helm或者自己定制配置文件。

3. 插件安装

Pipeline相关,文档语法:
https://www.jenkins.io/zh/doc/book/pipeline/syntax/

Kubernetes Plugin,主要用于创建Slave Pod 并分配给Slave相关流水线工作内容,相关文档:
https://github.com/jenkinsci/kubernetes-plugin
https://www.jenkins.io/doc/pipeline/steps/kubernetes/

Kubernetes Continuous Deploy Plugin,主要用于容器内发布k8s相关资源,相关文档:
https://www.jenkins.io/doc/pipeline/steps/kubernetes-cd/#kubernetesdeploy-deploy-to-kubernetes

4. Jenkins 配置 Kubernetes Plugin

插件安装完毕后,点击 “系统管理” —> “系统设置” —> “Cloud” 进入云集群配置页面 选择 Kubernetes
在这里插入图片描述
填写kubernetes 和 Jenkins 配置
在这里插入图片描述

  1. kubernetes apiserver地址,k8s默认安装后是这个
  2. 点击连接测试,如果连接成功会显示 Connected to Kubernetes 失败会有其他提示
  3. jenkins的http服务地址,也就是用来登录的那个地址
  4. Jenkins 通道 jnlp 通信用的。如下图,如果 jenkins 在 k8s 中 service 是 8080 和 50000端口同时暴露的,就不需要额外填写。如果分两个service暴露8080和50000端口(或者端口有改动),则需要填写对应的ip:host
    在这里插入图片描述

需要注意一点:在使用的时候Pipeline的Pod启动的时候会有Node-Selectors: kubernetes.io/os=linux,所以需要在集群node上面添加kubernetes.io/os=linux的label

5. pipeline 方式

参数化配置

在这里插入图片描述
配置的参数可以在pipeline script 中使用 ${params.key} 来使用,如上面就是 ${params.APP_NAME}

流水线脚本,可以参考上面提供的插件文档

def label = "test-pipline"
def HARBOR_HOST = "test.harbor.com"
podTemplate(label: label, cloud: 'kubernetes', imagePullSecrets: ['test-harbor'],
    containers: [
        containerTemplate(name: 'node', image: 'node:14.16.0-alpine3.10', ttyEnabled: true, command: 'cat'),
        containerTemplate(name: 'maven', image: 'test.harbor.com/maven:3.6.3-jdk-8', ttyEnabled: true, command: 'cat'),
        containerTemplate(name: 'docker', image: 'docker:20.10.5-git', , ttyEnabled: true, command: 'cat'),
    ], volumes: [
        hostPathVolume(hostPath: '/var/run/docker.sock', mountPath:'/var/run/docker.sock'),
        persistentVolumeClaim(claimName: 'jenkins-build-pvc', mountPath: '/root/repo')
    ], workspaceVolume: persistentVolumeClaimWorkspaceVolume(claimName: 'jenkins-workspace-pvc')
    ) {
    node(label) {
        stage('Git Clone') {
            git branch: 'master', credentialsId: 'gitlab', url: 'https://test.gitlab.com/devil/test.git'
        }
        stage('Node Build') {
            container('node'){
                sh """
                cd vue 
                npm install
                npm run build
                """
            }
        }
        stage('Maven Build') {
            container('maven'){
                sh "mvn package -Dmaven.test.skip=true"
            }
            
        }
        stage('Docker Build') {
            container('docker'){
                withCredentials([usernamePassword(credentialsId: "harbor", passwordVariable: 'HARBOR_PWD', usernameVariable: 'HARBOR_USERNAME')]) {
                    sh "docker login " + HARBOR_HOST + " -u ${HARBOR_USERNAME} -p ${HARBOR_PWD}"
                    sh "docker build -t " + HARBOR_HOST + "/${params.IMAGE_NAME}:${params.IMAGE_TAG} ."
                    sh "docker push " + HARBOR_HOST + "/${params.IMAGE_NAME}:${params.IMAGE_TAG}"      
                }
            }
        }
        stage('Deploy') {
            sh "sed -e 's#{IMAGE_URL}#" + HARBOR_HOST + "/${params.IMAGE_NAME}#g;s#{IMAGE_TAG}#${params.IMAGE_TAG}#g;s#{APP_NAME}#${params.APP_NAME}#g;s#{REPLICAS_NUM}#${params.REPLICAS_NUM}#g;s#{HEAP_SIZE}#${params.HEAP_SIZE}#g;s#{DIRECT_MEMORY_SIZE}#${params.DIRECT_MEMORY_SIZE}#g' k8s-deployment-tpl.yaml > k8s-deployment.yml"
            sh "cat k8s-deployment.yml"
            kubernetesDeploy(configs: 'k8s-deployment.yml', kubeconfigId: "k8s-client")
        }
    }
}

容器模板相关

  • podTemplate:也就是为这个流水线这次作业而创建的pod的模板,可以通过yaml方式,参考文档
  • label:pod的名称,在流水线执行过程中,可以在 jenkins 的namespce看到创建了对应的pod
  • cloud:对应的cloud,也就是步骤4中配置的
  • containers:这个pod中使用了哪些容器
  • imagePullSecrets:由于例子中 containers 使用了私有仓库,所以需要docker登录认证,这里需要在jenkins master pod 的namespace创建k8s用于访问私有仓库的secret类型资源,imagePullSecrets数组中对应 k8s 中 secret 名称。通过命令创建
 kubectl create secret docker-registry -n jenkins harbor --docker-username=*** --docker-password=**** --docker-email=aaa@gg.com --docker-server=host
  • container(‘node’):使用node这个容器,并执行对应操作
  • credentialsId:jenkins 的 凭证管理中对应的凭证 id
  • kubernetesDeploy:参考上文 Kubernetes Continuous Deploy Plugin 相关文档
  • kubeconfigId:jenkins 的 凭证管理中创建的k8s config类型凭证,用于k8s集群访问,通过kubeadmin生成

数据挂载相关

  • volumes:需要挂载的文件,这里主要用于maven下载的jar包不重复下载
  • workspaceVolume:挂载slave的jenkins工作目录,这里使用pvc的方式,需要在k8s创建pvc

kubeconfig类型凭证,系统管理-> manager Credentials 创建凭证,credentialsId同理,基于需要用Username/Password或者其它类型
在这里插入图片描述

上面pipeline最后Deploy指定了项目根目录下k8s-deployment-tpl.yaml,用自定义的构建参数进行文本替换,生成k8s-deployment.yml用于k8s发布插件发布。k8s-deployment-tpl.yaml 模板内容如下:

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: {APP_NAME}
spec:
  replicas: {REPLICAS_NUM}
  template:
    metadata:
      name: {APP_NAME}
      labels:
        app: {APP_NAME}
    spec:
      containers:
      - image: {IMAGE_URL}:{IMAGE_TAG}
        name: {APP_NAME}
        ports:
        - containerPort: 80
        volumeMounts:
        - name: config
          mountPath: /root/config.yaml
          subPath: config.yaml
      volumes:
      - name: config
        configMap:
          name: test-config
          items:
          - key: config.yaml
            path: config.yaml
---
apiVersion: v1
kind: Service
metadata:
  name: {APP_NAME}
spec:
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: {APP_NAME}

6. 非Pipeline

创建 pod 模板
在这里插入图片描述
在这里插入图片描述
这里的配置其实和上面pipeline中podTemplate相似,创建完pod模板后,构建一个自由风格的软件项目,在标签中选择刚刚创建的模板,这样就会自动按模板要求进行操作。
在这里插入图片描述

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐