CICD之Jenkins与Kubernetes的串联

2,265 阅读6分钟

0 - 前言

之前,在上家公司的时候,搭建过基于gitlab-ci和Kubernetes方案的CICD,不过发现,目前国内主流的CICD,还是基于Jenkins的。所以,掌握基于Jenkins的CICD方案,还是很有必要的,毕竟不掌握devops技能的后端开发不是一个好架构啊。这一周,刚好工作上有这方面的安排,研究了下基于Jenkins和Kubernetes的CICD,趁热记录下,给未来的自己。

本文,我会从以下几个方面来叙述:

  • 整体架构图

    • 描述Jenkins,Jenkins Agent和Kubernetes的关系图,这样整体上对该方案有一个概览,避免了盲人摸象
  • Jenkins和Kubernetes的必要配置

    • 本文重点。之前部署的过程中,在这里卡了一个下午,所以我会:

      • step by step理清每一个配置项
      • 然后复盘下troubleshooting的过程
  • CICD实战

    • 用groovy编写pipeline,实现从代码clone-->Maven Build-->Docker Build-->Docker Push-->K8S Deploy

1 - 整体架构图

整体架构图 图1

  • Jenkins Server: 这个架构图里,Jenkins服务是单独部署的,而不是通过Kubernetes平台进行管理。当然,Jenkins服务本身也可以以pod的形式通过Kubernetes进行管理。
    • Configure Clouds: 这里主要是配置Kubernetes的相关参数和pod的模板文件(我的理解就是:用GUI的方式配置编写一个k8s部署服务的yaml文件)
      • Kubernetes(配置参数见第三章节)
    • http port:通过浏览器访问Jenkins Web平台的http端口,默认是8080
    • tcp port:默认情况下,基于JNLP的Jenkins代理通过TCP端口50000与Jenkins主站进行通信。
  • K8S CLuster
    • kube-apiserver: 提供Kubernetes资源管理的api,使得Jenkins可以通过提供的API对集群内部资源进行管理。需要注意的是,如果Jenkins是集群外部应用,则需要在Jenkins里配置一些鉴权参数,如k8s server certificate key, credentials等,才能合法的调用k8s提供的api接口进行集群内部应用的管理,此过程见图1中的线标0。
    • namespace: jenkins
      • jenkins agent pod: 在Kubernetes集群的一个namespace里的pod,作为一个agent,和Jenkins Server进行通信,目的之一是管理在Kubernetes集群里部署的服务,即CD(continue deployment)。
      • 该agent的管理是Jenkins Server通过k8s apiserver的接口,在namespace为jenkins的空间内创建的,见线标1。
      • 创建该agent pod的资源描述文件(即yaml文件),是在Configure Cloudsde Kubernetes里的pod template里配置的,包括但不限于:pod name, pod namespace, container name, host path volume,见线标2。
      • 该agent pod启动后,会根据配置文件的Jenkins URL或Jenkins tunnel的地址,连接Jenkins Server,见线标4。
      • agent pod会根据Jenkins Server里传递过来的pipeline脚本进行一系列的操作,如拉取代码仓库, 构建jar包,生产docker镜像文件,将镜像文件推送到镜像仓库,以及在k8s里生成业务服务的资源等等,见线标5。
    • namespace: conn-dev
      • business pod: 通过jenkins cicd部署的业务pod

2 - Jenkins和Kubernetes的必要配置

从整体架构图(图1)中可以看出,整个基于Jenkins和Kubernetes的CICD由Jenkins Server和K8S Cluster两大块组成。

在Jenkins Server中主要是需要配置Configure Clouds页面(Jenkins-->Manage Jenkins-->Manage Nodes and Clouds-->Configure Clouds)里的Kubernetes的相关配置(见appendix I. Jenkins上 configure clouds 配置截屏):

  • Name: 在Jenkins Clouds中唯一标识此Cloud实例的ID。

    Uniquely identifies this Cloud instance among other instances in Jenkins Clouds. This is expected to be short ID-like string that does not contain any character unsafe as variable name or URL path token.

  • Kubernetes URL: k8s集群的apiserver地址

    The URL of the Kubernetes API server. If not set the connection options will be autoconfigured from service account or kube config file.

  • Use Jenkins Proxy: 是否需要通过代理连接到apiserver上

    use Jenkins proxy settings to connect to kubernetes URL

  • Kubernetes server certificate key: Kubernetes的CA证书,可以通过cat /etc/kubernetes/pki/ca.crt找到。了解更多PKI证书相关,请点击

    X509 PEM encoded certificate

  • Disable https ceertificate check: 这个选项选中的话,Jenkins Server 和API Server的连接虽然依旧会走https通道,但是不会对ssl证书做验证。

    With this option enabled, communication with kubernetes API master will rely on https but will fully ignore ssl certificate verification. This is usefull for quick setup but does make your installation unsecured, so please consider twice. Alternatively, capture API server certificate and register it as Kubernetes server certificate key.

  • Kubernetes Namespace:

  • Credentials: 这里创建一个凭证,如下图,Kind选secret file,在File选择admin.conf上传(在Kubernetes集群master节点的/etc/kubernetes/admin.conf)。当然,也可以通过kubectl create secret的方式创建一个新的secret file给Jenkins Server使用 图2

  • WebSocket: 使用websocket协议连接 jenkins agent 和 jenkins server

    Use WebSocket to connect agents rather than the TCP port. See JEP-222 for background.

  • Direct Connection: 这个配置我理解就是,如果选中,那么用Jenkins tunnel地址连接,如果不选中,则用Jenkins URL连接

    With this option it is possible to connect directly to the TCP agent listener port. This skips the connect to an HTTP(S) port to retrieve the connection information. This is useful in scenarios where the master does not expose an HTTP(S) port, for example Jenkinsfile Runner. Here is an example how the connect on agent side would look like. Most parameters are provided automatically when you enable the Direct Connection option. Note1: In Direct Connection mode agents will not be able to reconnect to a restarted master if a Random 'TCP port for inbound agents' is configured! Note2: Direct Connection requires a jnlp-slave image with a version equal or higher than 3.35-5. Note3: Direct Connection does not work with the currently available jenkins/inbound-agent:windowsservercore-1809 image.

    -headless \
    -workDir <WORK_DIRECTORY> \
    -direct <MASTER_HOST:TCP_AGENT_LISTENER_PORT> \
    -protocols JNLP4-connect \
    -instanceIdentity <INSTANCE_IDENTITY> \
    <SECRET_STRING> <AGENT_NAME>
    
  • Jenkins URL: Jenkins Server Web服务的地址,基于HTTP协议

    The URL of the Jenkins Master server.

  • Jenkins tunnel: Jenkins 隧道地址,基于TCP协议

    Connect to the specified host and port, instead of connecting directly to Jenkins. Useful when connection to Jenkins needs to be tunneled. 我对这句话的理解是:Jenkins agent从Kubernetes集群内部连接Jenkins Server,除了可以通过Jenkins URL外,还能通过这个tunnel配置的主机+端口号。那么即使不配置这个参数,也是可以连接的。

  • Pod Template

    • Name: jenkins agent pod的名字,如jenkins-slave
    • Namespace: agent pod部署在哪个namespace下
    • Labels: agent pod的标签,如jenkins-slave。【注意】:pipeline里的node (${lable})要和此处的标签保持一致,如node("jenkins-slave")
    • Usage
      • Use this node as much as possible (默认):Jenkins可以自由使用此节点,也就是说只要该node节点还能构建,Jenkins就会一直使用它。
      • Only build jobs with label expressions matching this node:只会构建到符合的标签节点上
    • Pod tempalte to inherit from: 从另一个pod模板继承配置
    • Container Template
      • Name: 容器名字,这里一定要填写jnlp,不然启动失败
      • Docker image: 这里需要注意下,官方的jnlp镜像,不含kubectl工具,所以如果需要部署k8s服务的话,就会失败,所以我在官方镜像的基础上,加入了docker,kuberctl,maven等配置文件,来满足需要:registry.cn-shanghai.aliyuncs.com/arkmon/jnlp:jnlp6
      • Always pull image: 镜像拉取策略,选中表示,无论本地是否有该镜像,都会从服务器拉取最新的镜像
      • Working directory: agent的工作空间,保持默认即可,无需变动
      • Command to run: 保持为空,不要填任何值,之前参考另一篇文章时,这里填了参数,导致agent连不上server。排查了好久,发现这里多穿了不必要的参数,导致jnlp6镜像启动时的参数被这里填写的参数覆盖掉,从而连接不上server
      • Arguments to pass to the command: 保持为空,不要填任何值
      • Allocate pseudo-TTY: 选中分配一个TTY
      • Enviroment Variables: 该容器内的环境变量配置
    • Enviroment Variables: 全局环境变量配置
    • Volumes
      • Host Path Volumes: 宿主机文件夹挂载到容器里
        • /var/run/docker.sock: docker in docker
        • /root/.kube/: kubernetes相关配置文件
        • /etc/kubernetes/pki: pki相关证书
        • /root/.m2/repository:maven本地jar包仓库,加上这个挂载,可以加速maven build。不然,每次都需要在线拉取jar包,严重影响构建速度。

【注意】

  • 如果在Configure Clouds里找不到Kubernetes,那么,需要安装jenkins的Kubernetes插件:Manage Jenkins -> Manage Plugins -> Available -> Kubernetes plugin 勾选安装即可

3 - CICD实战

这个章节,主要是从Jenkins创建Item,编写Pipeline以及构建并验证三个方面,完成一次CICD的实战。

3.1 - 创建Item:

选择以哪种方式进行构建,这里选择Pipeline,点击ok 图3

3.2 - 编写Pipeline:

一次完整的CICD,包含:代码拉取(Clone)-->构建jar包(Maven Build)-- 单元测试(Unit Test)--> 静态代码检查(SonarQube Check)--> 构建镜像(Docker Build)--> 推送镜像到私有仓库(Docker Push)--> 部署k8s服务(Deploy)

图4

Pipeline示例:

node('jenkins-slave'){ // 选用node节点上标签为jenkins-slave的pod进行如下的pipeline处理

  stage('Clone') {
        echo "1.Clone Stage"
        git url: "http://192.168.30.62:10080/arkmon/weather.git"
  }

  stage('Maven Build') {
        echo "2.Test Stage"
        sh "mvn -B clean package -Dmaven.test.skip=true"
  }

  stage('Unit Test') {
        echo "3.Test Stage"
        // 需要进行单元测试,可以用
        mvn test
  }

  stage('SonarQube Check') {
        echo "4.SonarQube Check Stage"
  }

  stage('Docker Image Build&Push') {
        echo "5.Docker Build Stage"
        sh 'docker build -f Dockerfile -t weather:v0.0.1 .'
        sh 'docker tag weather:v0.0.1 192.168.30.171:8081/arkmon/weather:v0.0.1'
        sh 'docker login -u ${username} -p ${password} 192.168.30.171:8081' //这里使用明文存储
        sh 'docker push 192.168.30.171:8081/arkmon/weather:v0.0.1'
  }

  stage('Deploy') {
        echo "6. Deploy Stage"
        sh "kubectl apply -f k8s_deploy.yml -n conn-dev"
  }
}

3.3 - 构建并验证

可以看到,maven build特别快,因为将宿主机里的.m2/repository/的jar包挂载到了agent pod里,不需要再次在线拉取依赖jar包。

图5

Appendix

I. Jenkins上 configure clouds 配置截屏

图6

II. Jenkins Agent Pod YAML配置文件

apiVersion: "v1"
kind: "Pod"
metadata:
  labels:
    jenkins: "slave"
    jenkins/label-digest: "5059d2cd0054f9fe75d61f97723d98ab1a42d71a"
    jenkins/label: "jenkins-slave"
  name: "jenkins-slave-54rh9"
spec:
  containers:
  - env:
    - name: "JENKINS_SECRET"
      value: "********"
    - name: "JENKINS_AGENT_NAME"
      value: "jenkins-slave-54rh9"
    - name: "JENKINS_NAME"
      value: "jenkins-slave-54rh9"
    - name: "JENKINS_AGENT_WORKDIR"
      value: "/home/jenkins/agent"
    - name: "JENKINS_URL"
      value: "http://192.168.30.62:8088/"
    image: "192.168.30.171:8081/common/arkmon-jenkins:jnlp6"
    imagePullPolicy: "IfNotPresent"
    name: "jnlp"
    resources:
      limits: {}
      requests: {}
    tty: true
    volumeMounts:
    - mountPath: "/home/jenkins/.kube"
      name: "volume-1"
      readOnly: false
    - mountPath: "/etc/kubernetes/pki"
      name: "volume-2"
      readOnly: false
    - mountPath: "/var/run/docker.sock"
      name: "volume-0"
      readOnly: false
    - mountPath: "/root/.m2/repository"
      name: "volume-3"
      readOnly: false
    - mountPath: "/home/jenkins/agent"
      name: "workspace-volume"
      readOnly: false
    workingDir: "/home/jenkins/agent"
  hostNetwork: true
  nodeSelector:
    kubernetes.io/os: "linux"
  restartPolicy: "Never"
  volumes:
  - hostPath:
      path: "/var/run/docker.sock"
    name: "volume-0"
  - hostPath:
      path: "/etc/kubernetes/pki"
    name: "volume-2"
  - hostPath:
      path: "/root/.kube/"
    name: "volume-1"
  - emptyDir:
      medium: ""
    name: "workspace-volume"
  - hostPath:
      path: "/root/.m2/repository"
    name: "volume-3"

III. 文中出现的IP地址说明

  • 192.168.30.62:8088: Jenkins Web URL
  • 192.168.30.62:10080: Gitlab 代码仓库地址
  • 192.168.30.171:8081: Harbor docker镜像仓库地址

有问题,欢迎留言交流