03 使用Kubernetes作为工作节点

使用Kubernetes集群作为工作节点

在前面那个简单的pipeline中实际并没有做什么东西,同时它的工作任务也是运行在Jenkins主节点的,现在,给Jenkins添加一个Kubernetes集群作为worker节点,当有任务开始执行的时候,让他在集群中自动创建一个pod,然后再这个pod内完成所有工作,最后任务执行结束,销毁这个pod.

添加Kubernetes作为工作节点

首先安装Kubernetes插件.
系统管理 -> 节点管理
03 使用Kubernetes作为工作节点_第1张图片
然后配置集群apiserver等地址

03 使用Kubernetes作为工作节点_第2张图片
03 使用Kubernetes作为工作节点_第3张图片
03 使用Kubernetes作为工作节点_第4张图片
03 使用Kubernetes作为工作节点_第5张图片

开始使用

首先先来一个比较简单的操作,比如拉取代码.

首先,Git新建项目,然后上传一个名为cpu.py的文件,内容:

from flask import Flask
app = Flask(__name__)

@app.route("/")
def main():
    number = 0
    for i in range(0,100000000):
        number+=i
    return {"key":number}

if __name__ == '__main__':
    app.run(host='0.0.0.0',port="8000")

然后Jenkins添加对应的密钥,在01文章中给gitlab添加了一个拉取代码的密钥,现在Jenkins也需要添加一下.

查看密钥

cat ~/.ssh/id_rsa

03 使用Kubernetes作为工作节点_第6张图片
03 使用Kubernetes作为工作节点_第7张图片
03 使用Kubernetes作为工作节点_第8张图片
03 使用Kubernetes作为工作节点_第9张图片
保存后自动返回,填写git地址等.
03 使用Kubernetes作为工作节点_第10张图片
然后点击"生成流水线脚本",替换下面代码中的git branch ....等内容.

实际直接复制新建凭据的ID替换掉,然后修改对应拉取代码的地址即可

podTemplate(
  cloud: 'kubernetes'
) {
  node(POD_LABEL) {
    stage('pull code') {
      // 克隆代码
      git branch: 'main', credentialsId: 'd17b1091-fa2d-4310-8a4d-0b1d7f823ea9', url: 'ssh://git@20.88.9.34:222/my_group/one_project.git'
      // 打印信息
      echo "The first stage end"
    }
  }
}

手动执行:
03 使用Kubernetes作为工作节点_第11张图片
第一步成功.

模拟编译和构建镜像

获取到了代码,接下来就是如何对代码进行编译,然后打包推送仓库的问题了.

Python代码可以用pyinstaller命令将其编译为一个二进制文件,通过二进制文件可以再不同的主机运行并且不需要Python环境

没有找到Jenkins有对应支持pipeline的插件,自己制作一个镜像,里面包含这个命令即可.

FROM python:3.6.15-slim
RUN apt-get update -y
RUN apt-get install binutils -y
RUN /usr/local/bin/python -m pip install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple
RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pyinstaller
ADD start.sh start.sh
CMD ["sleep","99d"]
docker build -t 528909316/jenkins:pyinstaller_v1 .
# 推送到远程仓库
docker push 528909316/jenkins:pyinstaller_v1

接下来,就是如何让Jenkins的slave节点在这个容器中运行了.

podTemplate(
  cloud: 'kubernetes',
  containers: [
  	// 定义一个pod模板
    containerTemplate(name: 'build', image: '528909316/jenkins:pyinstaller_v1', command: "sleep 99d", ttyEnabled: true)]
) {
  node(POD_LABEL) {
  	// 步骤1: 拉取代码
    stage('pull code') {
      // 克隆代码
      git branch: 'main', credentialsId: 'd17b1091-fa2d-4310-8a4d-0b1d7f823ea9', url: 'ssh://git@20.88.9.34:222/my_group/one_project.git'
      echo "The first stage end"
    }
    // 步骤2: 编译代码
    stage('build code') {
      container('build') {
        stage('Build a python project') {
          // 编译为文件
          sh '''
          pyinstaller -F cpu.py
          '''
        }
      }
      echo "The second stage end."
    }
  }
}

如上的代码中定义了一个pod模板.
默认Jenkins会为slave创建一个pod,然后在里面启动一个容器来运行所有的工作任务.pod中多个容器的存储是共享的,所以这里的容器模板实际上是在这个pod中新增了一个容器,这个容器具有 pyinstaller命令.当主容器拉取到代码后,会执行步骤2的编译过程.

继续开始添加下一个过程: 制作镜像推送到远程仓库

同样新增一个具有docker命令的容器,不同的是这个容器如果想使用docker命令需要先启动服务,通过/var/run/docker.sock文件和服务通信完成.容器中没有办法再启动一个docker,但是主机上(Kubernetes容器运行时使用的docker)是有这个文件的,所以将主机文件挂载到pod中,让容器中的程序直接和主机docker进程通信.

涉及知识点: 一切皆文件

制作一个包含docker命令的镜像

FROM alpine:3.14
RUN echo 'http://mirrors.aliyun.com/alpine/v3.8/main/' >/etc/apk/repositories;echo 'http://mirrors.aliyun.com/alpine/v3.8/community/'>>/etc/apk/repositories
RUN apk add docker
CMD ["sleep","99d"]

执行命令

docker build -t 528909316/jenkins:rundocker_v1 .

推送到仓库,接下来还要再gitlab中也添加一个Dockerfile,来定义制作这个镜像的过程.

名称自然是Dockerfile,内容:

FROM python:3.6.15-slim
RUN /usr/local/bin/python -m pip install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple
RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple flask
ADD cpu.py cpu.py
CMD ["python","cpu.py"]

继续修改pipeline文件

podTemplate(
  cloud: 'kubernetes',
  containers: [
  	// 容器模板1 : 编译镜像
    containerTemplate(name: 'build', image: '528909316/jenkins:pyinstaller_v1', command: "sleep 99d", ttyEnabled: true),
    // 容器模板2: 制作docker镜像
    containerTemplate(name: 'docker', image: '528909316/jenkins:rundocker_v1', command: "sleep 99d", ttyEnabled: true)],
  // 挂载卷: hostPathVolume为主机目录或文件,将主机的/var/run/docker.sock文件挂载到pod的/var/run/docker.sock
  volumes: [hostPathVolume(hostPath: '/var/run/docker.sock', mountPath: '/var/run/docker.sock')]
) {
  node(POD_LABEL) {
    stage('pull code') {
      // 克隆代码
      git branch: 'main', credentialsId: 'd17b1091-fa2d-4310-8a4d-0b1d7f823ea9', url: 'ssh://git@20.88.9.34:222/my_group/one_project.git'
      echo "The first stage end"
    }
    stage('build code') {
      container('build') {
        stage('Build a python project') {
          // 编译为文件
          sh '''
          pyinstaller -F cpu.py
          '''
        }
      }
      echo "The second stage end."
    }
    // 步骤3 :构建docker镜像
    stage("build image") {
      container("docker") {
        stage("build image") {
        	// build 镜像
          sh "docker build -t 528909316/jenkins:taskcpu_v1 ."
          // 登陆docker仓库
          sh "docker login -u 528909 -p '**********'"
          // 推送镜像
          sh "docker push 528909316/jenkins:taskcpu_v1"
        }
      }
      echo "push end."
    }
  }
}

部署到k8s

拉取代码,编译,制作镜像都完成了,接下来只剩下一件事情: 部署应用到k8s

在此选择了使用Kubernetes Continuous Deploy组件.

官方地址
注: 不知为何我安装了最新的2.1.2版本发布一直失败.直到我将版本换为了1.0.0才成功,参考文档:Kubernetes Continuous Deploy插件的使用,文章发布于2019-9,里面提到该插件1.0.0版本比较稳定,所以我也就降低了版本.

既然是插件自然使用"流水线语法"了,选择组件填写内容,添加一个kubeconfig类型的文件(内容就是Kubernetes主节点的/etc/kubernetes/admin.conf文件内容),然后重新修改pipeline增加一个流水线步骤:

发布过程中还要用到一个Kubernetes的编排文件,继续在仓库添加一个名为deploy.yaml的文件,内容:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: python-cpu
spec:
  selector:
    matchLabels:
      run: python-cpu
  replicas: 1
  template:
    metadata:
      labels:
        run: python-cpu
    spec:
      containers:
      - name: python-cpu
        image: 528909316/version:run_cpu1
        ports:
        - containerPort: 8000
        resources:
          limits:
            cpu: 500m
          requests:
            cpu: 200m
---
apiVersion: v1
kind: Service
metadata:
  name: python-cpu
  labels:
    run: python-cpu
spec:
  ports:
  - port: 8000
  selector:
    run: python-cpu
podTemplate(
  cloud: 'kubernetes',
  containers: [
    containerTemplate(name: 'jenkins', image: 'jenkins/jnlp-slave:4.9-1-alpine', command: "sleep 99d", ttyEnabled: true),
    containerTemplate(name: 'build', image: '528909316/jenkins:pyinstaller_v1', command: "sleep 99d", ttyEnabled: true),
    containerTemplate(name: 'docker', image: '528909316/jenkins:rundocker_v1', command: "sleep 99d", ttyEnabled: true)],
  volumes: [hostPathVolume(hostPath: '/var/run/docker.sock', mountPath: '/var/run/docker.sock')]
) {
  node(POD_LABEL) {
    stage('pull code') {
      // 克隆代码
      git branch: 'main', credentialsId: 'd17b1091-fa2d-4310-8a4d-0b1d7f823ea9', url: 'ssh://git@20.88.9.34:222/my_group/one_project.git'
      echo "The first stage end"
    }
    stage('build code') {
      container('build') {
        stage('Build a python project') {
          // 编译为文件
          sh '''
          pyinstaller -F cpu.py
          '''
        }
      }
      echo "The second stage end."
    }
    stage("build image") {
      container("docker") {
        stage("build image") {
          sh "docker build -t 528909316/jenkins:taskcpu_v1 ."
          sh "docker login -u 528909 -p '**********'"
          sh "docker push 528909316/jenkins:taskcpu_v1"
        }
      }
      echo "push end."
    }
    stage("deploy image") {
      kubernetesDeploy configs: 'deploy.yml', kubeConfig: [path: ''], kubeconfigId: 'ae92d8dc-053e-409e-ae1b-f6e3f3bbb9f4', secretName: '', ssh: [sshCredentialsId: '*', sshServer: ''], textCredentials: [certificateAuthorityData: '', clientCertificateData: '', clientKeyData: '', serverUrl: 'https://']
    }
  }
}

手动执行,到Kubernetes集群中查看默认名称空间下查看,pod被创建了!

[root@master ~]# kubectl get pod 
NAME                                 READY   STATUS    RESTARTS   AGE
centos-deployment-76dc4d95c9-s2gvf   1/1     Running   0          10h
python-cpu-cc4cfb466-dkrpd           1/1     Running   0          60s

到了这一步可以确定,流程已经没有问题了,接下来就是继续完善流水线文件了,首先先把发布的deploy.yml文件中的镜像版本改对,然后继续修改对应的dockerfile将打包好的文件加入到容器中,最后运行这个新制作的镜像!

最终的Dockerfile
FROM python:3.6.15-slim
ADD dist/cpu cpu
CMD ["./cpu"]
最终的Jenkins.groovy(pipeline)
podTemplate(
  cloud: 'kubernetes',
  containers: [
  //流水线执行过程中的环境 默认会创建一个jenkins-slave容器作为pod的主容器
  // 新增一个叫做 build的容器,使用528909316/jenkins:pyinstaller_v1这个镜像,具有命令pyinstaller
    containerTemplate(name: 'build', image: '528909316/jenkins:pyinstaller_v1', command: "sleep 99d", ttyEnabled: true),
    // 新增一个叫做 docker 的容器,具有命令 docker
    containerTemplate(name: 'docker', image: '528909316/jenkins:rundocker_v1', command: "sleep 99d", ttyEnabled: true)],
  volumes: [hostPathVolume(hostPath: '/var/run/docker.sock', mountPath: '/var/run/docker.sock')]
) {
  node(POD_LABEL) {
    stage('pull code') {
      // 克隆代码
      git branch: 'main', credentialsId: 'd17b1091-fa2d-4310-8a4d-0b1d7f823ea9', url: 'ssh://git@20.88.9.34:222/my_group/one_project.git'
      echo "The first stage end"
    }
    stage('build code') {
      container('build') {
        stage('Build a python project') {
          // 编译为文件 首先更新当前编译环境的pip 然后使用清华源安装flask组件(pyinstaller时当前主机中必须有文件中import的所有包,否则会报错)
          // 最后pyinstaller 制作一个叫做cpu的二进制文件,放在 ./dist/目录下
          sh '''
          python -m pip install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple
          pip install -i https://pypi.tuna.tsinghua.edu.cn/simple flask
          pyinstaller -F cpu.py
          '''
        }
      }
      echo "The second stage end."
    }
    stage("build image") {
      container("docker") {
        stage("build image") {
        // 首先docker build 制作一个镜像 然后login登陆一下
          sh "docker build -t 528909316/jenkins:taskcpu_v1 ."
          sh "docker login -u 528909316 -p 'MyNewPass4!'"
          // 最后推送镜像
          sh "docker push 528909316/jenkins:taskcpu_v1"
        }
      }
      echo "push end."
    }
    stage("deploy image") {
    // 部署的k8s集群 kubeconfigId为添加的配置在Jenkins的id的名字 创建时没有填写ID的话 Jenkins为为其生成一串随机字符
      kubernetesDeploy configs: 'deploy.yml', kubeConfig: [path: ''], kubeconfigId: 'ae92d8dc-053e-409e-ae1b-f6e3f3bbb9f4', secretName: '', ssh: [sshCredentialsId: '*', sshServer: ''], textCredentials: [certificateAuthorityData: '', clientCertificateData: '', clientKeyData: '', serverUrl: 'https://']
    }
  }
}
最终的deploy.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: python-cpu
spec:
  selector:
    matchLabels:
      run: python-cpu
  replicas: 3
  template:
    metadata:
      labels:
        run: python-cpu
    spec:
      containers:
      - name: python-cpu
        imagePullPolicy: Always
        image: 528909316/jenkins:taskcpu_v1
        ports:
        - containerPort: 8000
        resources:
          limits:
            cpu: 500m
          requests:
            cpu: 200m
---
apiVersion: v1
kind: Service
metadata:
  name: python-cpu
  labels:
    run: python-cpu
spec:
  ports:
  - port: 8000
  selector:
    run: python-cpu
  type: LoadBalancer

参考文献
Pipeline Syntax

问题

提示:

Started by user user
[Pipeline] Start of Pipeline
[Pipeline] podTemplate
[Pipeline] {
[Pipeline] node
ERROR: Unable to create pod kubernetes jenkins/cluster-one-project-10-stvzm-fl2nv-r8s82.
Failure executing: POST at: https://kubernetes.default.svc.cluster.local/api/v1/namespaces/jenkins/pods. Message: Forbidden!Configured service account doesn’t have access. Service account may have been revoked. pods is forbidden: User “system:serviceaccount:jenkins:default” cannot create resource “pods” in API group “” in the namespace “jenkins”.
[Pipeline] // node
[Pipeline] }
[Pipeline] // podTemplate
[Pipeline] End of Pipeline
Queue task was cancelled
Finished: ABORTED

参考

https://blog.csdn.net/zzb7728317/article/details/106282557/

你可能感兴趣的