1. 传统架构与K8S 架构的区别

(1). 传统Master/Slave架构,Master收到Job后,将请求转发到Slave节点处理。Slave节点数固定,Slave节点未能自动申缩容。

Jenkins Master/Slave架构

(2). K8S中Jenkins Master/Slave架构,Master收到Job后,会自动创建Slave节点处理此Job,根据客户端的Job自动申缩容。

Jenkins的kubernetes-plugin在执行构建时会在kubernetes集群中自动创建一个Pod,并在Pod内部创建一个名为jnlp的容器,该容器会连接Jenkins并运行Agent程序,形成一个Jenkins的Master和Slave架构,然后Slave会执行构建脚本进行构建。如果要在集群集群内部进行部署操作可以使用kubectl执行命令,要解决kubectl的安装和权限分配问题。

因为默认的jnlp容器可以执行的命令比较少,所以要实现执行kubectl命令,就要自定义构建Docker Image,因为一个Pod内部可以运行多个容器,所以可以用自定义的Docker容器实现上述目的(下放实现)。

K8S中Jenkins Master/Slave架构

2. jenkins插件连接K8S配置

需要先安装pipeline插件, 建议不在 pipeline UI上配置pod创建模版,免得以后每来一个项目都要创建,管理不方便,建议使用pipeline统一 配置。
Kubernetes插件介绍:https://github.com/jenkinsci/kubernetes-plugin
配置路径 “系统管理” —> “系统设置” —> “云(翻到最后)” —> “Kubernetes”
新版已放到 “节点管理” -> “configureClouds” (> v2.343)
在这里插入图片描述

img

  1. Name 处默认为 kubernetes,也可以修改为其他名称,如果这里修改了,下边在执行 Job 时指定 podTemplate() 参数 cloud 为其对应名称,否则会找不到,cloud 默认值取:kubernetes
  2. Kubernetes URL 处我填写了 https://kubernetes.default 这里我填写了 Kubernetes Service 对应的 DNS 记录,通过该 DNS 记录可以解析成该 Service 的 Cluster IP,注意:也可以填写 https://kubernetes.default.svc.cluster.local 完整 DNS 记录,因为它要符合 <svc_name>.<namespace_name>.svc.cluster.local 的命名方式,或者直接填写外部 Kubernetes 的地址 https://<ClusterIP>:<Ports>
  3. Jenkins URL 处我填写了 http://jenkins.default,跟上边类似,也是使用 Jenkins Service 对应的 DNS 记录,不过要指定为 8080 端口,因为我们设置暴漏 8080 端口。同时也可以用 http://<ClusterIP>:<Node_Port> 方式,例如我这里可以填 http://192.168.99.100:30645 也是没有问题的,这里的 30645 就是对外暴漏的 NodePort。
    注意 命名空间

3.测试并验证

通过 Kubernetes 安装 Jenkins Master 完毕并且已经配置好了连接,接下来,我们可以配置 Job 测试一下是否会根据配置的 Label 动态创建一个运行在 Docker Container 中的 Jenkins Slave 并注册到 Master 上,而且运行完 Job 后,Slave 会被注销并且 Docker Container 也会自动删除吧!

4.pipeline 类型支持

创建一个 Pipeline 类型 Job 并命名为 my-k8s-jenkins-pipeline,然后在 Pipeline 脚本处填写一个简单的测试脚本如下:

def label = "mypod-${UUID.randomUUID().toString()}"
podTemplate(label: label, cloud: 'kubernetes')
{
    node(label) {
        stage('Run shell') {
            sh 'sleep 30s'
            sh 'echo hello world.'
        }
    }
}

node(label ) 中的 label :匹配 node 配置 为 label的节点。

podTemplate(label) 中的 label: 定义 节点 node 运行 podTemplate 的 label ,也就是 节点node的 label

可以看出 podTemplatenode 配置并没有相关,只需要label 相关联

执行构建,此时去构建队列里面,可以看到有一个构建任务,暂时还没有执行中的构建,因为还没有初始化好,稍等一会,就会看到 Master 和 jenkins-slave-jbs4z-xs2r8 已经创建完毕,在等一会,就会发现 jenkins-slave-jbs4z-xs2r8 已经注册到 Master 中,并开始执行 Job,点击该 Slave 节点,我们可以看到通过标签 mypod-b538c04c-7c19-4b98-88f6-9e5bca6fc9ba 关联,该 Label 就是我们定义的标签格式生成的,Job 执行完毕后,jenkins-slave 会自动注销,我们通过 kubectl 命令行,可以看到整个自动创建和删除过程。

image-20211209173812924

image-20211209173823909

image-20211209173839496

image-20211209173902319

5. Container Group 类型支持

创建一个 Pipeline 类型 Job 并命名为 my-k8s-jenkins-container,然后在 Pipeline 脚本处填写一个简单的测试脚本如下:

def label = "mypod-${UUID.randomUUID().toString()}"
podTemplate(label: label, cloud: 'kubernetes', containers: [
    containerTemplate(name: 'maven', image: 'maven:3.3.9-jdk-8-alpine', ttyEnabled: true, command: 'cat'),
  ]) {

    node(label) {
        stage('Get a Maven Project') {
            git 'https://github.com/jenkinsci/kubernetes-plugin.git'
            container('maven') {
                stage('Build a Maven project') {
                    sh 'mvn -B clean install'
                }
            }
        }
    }
}

注意:这里我们使用的 containers 定义了一个 containerTemplate 模板,指定名称为 maven 和使用的 Image,下边在执行 Stage 时,使用 container('maven'){...} 就可以指定在该容器模板里边执行相关操作了。比如,该示例会在 jenkins-slave 中执行 git clone 操作,然后进入到 maven 容器内执行 mvn -B clean install 编译操作。这种操作的好处就是,我们只需要根据代码类型分别制作好对应的编译环境镜像,通过指定不同的 container 来分别完成对应代码类型的编译操作。模板详细的各个参数配置可以参照 Pod and container template configuration

执行构建,跟上边 Pipeline 类似,也会新建 jenkins-slave 并注册到 master,不同的是,它会在 Kubernetes 中启动我们配置的 maven 容器模板,来执行相关命令。

Console Output

image-20211209174602015

生成的 yaml 文件
apiVersion: "v1"
kind: "Pod"
metadata:
  annotations:
    buildUrl: "http://jenkins.ops.svc.cluster.local/job/maven/7/"
    runUrl: "job/maven/7/"
  labels:
    jenkins: "slave"
    jenkins/label-digest: "42a877c388ee17d414289d422ca60f5fd36ac08f"
    jenkins/label: "mypod-75997681-42e1-4ed6-bd02-8ac5df191d4f"
  name: "mypod-75997681-42e1-4ed6-bd02-8ac5df191d4f-h8pqj-bnj15"
spec:
  containers:
  - command:
    - "cat"
    image: "maven:3.3.9-jdk-8-alpine"
    imagePullPolicy: "IfNotPresent"
    name: "maven"
    resources:
      limits: {}
      requests: {}
    tty: true
    volumeMounts:
    - mountPath: "/home/jenkins/agent"
      name: "workspace-volume"
      readOnly: false
  - env:
    - name: "JENKINS_SECRET"
      value: "********"
    - name: "JENKINS_AGENT_NAME"
      value: "mypod-75997681-42e1-4ed6-bd02-8ac5df191d4f-h8pqj-bnj15"
    - name: "JENKINS_NAME"
      value: "mypod-75997681-42e1-4ed6-bd02-8ac5df191d4f-h8pqj-bnj15"
    - name: "JENKINS_AGENT_WORKDIR"
      value: "/home/jenkins/agent"
    - name: "JENKINS_URL"
      value: "http://jenkins.ops.svc.cluster.local/"
    image: "jenkins/inbound-agent:4.11-1-jdk11"
    name: "jnlp"
    resources:
      limits: {}
      requests:
        memory: "256Mi"
        cpu: "100m"
    volumeMounts:
    - mountPath: "/home/jenkins/agent"
      name: "workspace-volume"
      readOnly: false
  nodeSelector:
    kubernetes.io/os: "linux"
  restartPolicy: "Never"
  volumes:
  - emptyDir:
      medium: ""
    name: "workspace-volume"

可以看到一个pod下面有两个 docker启动了。一个是默认的jnlp(jenkins slave agent)另外一个是我们配置的 maven。jnlp 负责与jenkins master 通讯,maven 负责打包。

6.非 Pipeline 类型支持

jenkins 中除了使用 Pipeline 方式运行 Job 外,通常我们也会使用普通类型 Job,如果也要想使用kubernetes plugin 来构建任务,那么就需要点击 “系统管理” —> “系统设置” —> “云” —> “Kubernetes” —> “Add Pod Template” 进行配置 “Kubernetes Pod Template” 信息。

image-20211209175013918

注意:这里的 Labels 名在配置非 pipeline 类型 Job 时,用来指定任务运行的节点。Containers 下的 Name 字段的名字,这里要注意的是,如果 Name 配置为 jnlp,那么 Kubernetes 会用下边指定的 Docker Image 代替默认的 jenkinsci/jnlp-slave 镜像,否则,Kubernetes plugin 还是会用默认的 jenkinsci/jnlp-slave 镜像与 Jenkins Server 建立连接,即使我们指定其他 Docker Image。这里我随便配置为 jnlp-slave,意思就是使用默认的 jenkinsci/jnlp-slave 镜像来运行,因为我们暂时还没制作可以替代默认镜像的镜像。

新建一个自由风格的 Job 名称为 my-k8s-jenkins-simple,配置 “Restrict where this project can be run” 勾选,在 “Label Expression” 后边输出我们上边创建模板是指定的 Labels 名称 jnlp-agent,意思是指定该 Job 匹配 jnlp-agent 标签的 Slave 上运行。

7. 配置自定义 jenkins-slave 镜像

通过 kubernetest plugin 默认提供的镜像 jenkinsci/jnlp-slave 可以完成一些基本的操作,它是基于 openjdk:8-jdk 镜像来扩展的,但是对于我们来说这个镜像功能过于简单,比如我们想执行 Maven 编译或者其他命令时,就有问题了,那么可以通过制作自己的镜像来预安装一些软件,既能实现 jenkins-slave 功能,又可以完成自己个性化需求,那就比较不错了。如果我们从头开始制作镜像的话,会稍微麻烦些,不过可以参考 jenkinsci/jnlp-slave 和 jenkinsci/docker-slave 这两个官方镜像来做,注意:jenkinsci/jnlp-slave 镜像是基于 jenkinsci/docker-slave 来做的。这里我简单演示下,基于 jenkinsci/jnlp-slave:latest 镜像,在其基础上做扩展,安装 Maven 到镜像内,然后运行验证是否可行吧。

创建一个 Pipeline 类型 Job 并命名为 my-k8s-jenkins-container-custom,然后在 Pipeline 脚本处填写一个简单的测试脚本如下:

def label = "mypod-${UUID.randomUUID().toString()}"
podTemplate(label: label, cloud: 'kubernetes',containers: [
    containerTemplate(
        name: 'jnlp', 
        image: 'andanyoung/jenkins-slave-maven:latest', 
        alwaysPullImage: false, 
        args: '${computer.jnlpmac} ${computer.name}'),
  ]) {

    node(label) {
        stage('stage1') {
            stage('Show Maven version') {
                sh 'mvn -version'
                sh 'sleep 60s'
            }
        }
    }
}

说明一下:这里 containerTemplate 的name 属性必须叫 jnlp ,Kubernetes 才能用自定义 images 指定的镜像替换默认的 jenkinsci/jnlp-slave 镜像。此外,args 参数传递两个 jenkins-slave 运行需要的参数。还有一点就是这里并不需要指定 container('jnlp'){...} 了,因为它被 Kubernetes 指定了要被执行的容器,所以直接执行 Stage 就可以了。

最后,贴一下我自定义的预安装了 Maven 的 Jenkins-slave 镜像的 Dockerfile,当然大家可以基于此预安装一些其他软件,来完成日常持续构建与发布工作吧。

当然也可以直接使用我的 andanyoung/jenkins-slave-maven 镜像

# Dockerfile
FROM jenkins/jnlp-slave:latest

LABEL Author  andanyang

LABEL Description "This is a extend image base from jenkins/jnlp-slave which install maven in it."

# 切换到 root 账户进行操作
USER root
VOLUME [ "/home/jenkins/.m2" ]

ARG MAVEN_VERSION=3.8.2
# 安装 maven https://archive.apache.org/dist/maven/maven-3/3.8.2/binaries/apache-maven-3.8.2-bin.tar.gz
RUN curl -OL https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz && \
  tar -zxf ./apache-maven-${MAVEN_VERSION}-bin.tar.gz && \
  mv apache-maven-${MAVEN_VERSION} /usr/local && \
  rm -f apache-maven-${MAVEN_VERSION}-bin.tar.gz && \
  ln -s /usr/local/apache-maven-${MAVEN_VERSION}/bin/mvn /usr/bin/mvn && \
  ln -s /usr/local/apache-maven-${MAVEN_VERSION} /usr/local/apache-maven

USER jenkins

## docker build --build-arg MAVEN_VERSION=3.8.2  -t andanyoung/jenkins-slave-maven:3.8.2 .
## docker push andanyoung/jenkins-slave-maven:3.8.2

3. 使用 Pod Templates

为了方便配置一个Pod Templates,在配置kubernetes连接内容的下面,这里的模板只是模板(与类一样使用时还要实例化过程),名称和标签列表不要以为是Pod的name和label,这里的名称和标签列表只是Jenkins查找选择模板时使用的,Jenkins自动创建Pod的name是项目名称+随机字母的组合,所以我们填写jnlp-maven,命名空间填写ops(创建命令:kubectl create namespace ops),Pod内添加一个容器名称是 jnlp (替换掉默认的自动创建jnlp容器),Docker镜像填写:andanyoung/jenkins-slave-maven,也可以是私服地址(搭建Docker私有仓库),下面增加m2maven仓库的持久化 PVC 配置 用户保存 maven 的 setting.xml 配置文件和 本地仓库,保存回到系统管理页面。

image-20211210143840002

image-20211210143943888

image-20211210145350369

image-20211210144037858

image-20211210151144580

如果使用的是私服需要设置 Image pull Secret(生成 Image pull Secret https://andyoung.blog.csdn.net/article/details/120313644 )

  • setting.xml 文件示例(将其放入pvc-maven-m2中)
<?xml version="1.0" encoding="UTF-8"?>

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
  <pluginGroups>
  </pluginGroups>

  <proxies>
  </proxies>

  <servers>
  </servers>

  <mirrors>
    <mirror>
      <id>central</id>
      <mirrorOf>central</mirrorOf>
      <name>aliyun maven</name>
      <url>https://maven.aliyun.com/repository/public</url>
    </mirror>
  </mirrors>

  <profiles>
  </profiles>

</settings>

测试运行

podTemplate (inheritFrom: "jnlp-maven"){
    node(POD_LABEL) {
        container('jnlp'){
            stage('Run shell') {
                sh 'echo hello world'
                sh 'mvn -version'
            }
        }
    }
}
  • podTemplate:用Pod模版实例化一个Pod配置并在kubernetes内自动创建
  • inheritFrom:意思是创建的Pod配置继承自jnlp-maven模版
  • POD_LABEL:自动创建Pod的label
  • container:选择哪个容器执行脚本(默认就是 jnlp, container('jnlp'){..} 最外层可以不写)

执行构建结果:

[Pipeline] Start of Pipeline
[Pipeline] podTemplate
[Pipeline] {
[Pipeline] node
Created Pod: kubernetes ops/maven-13-pbvhr-xbdq3-wb3j8
[Normal][ops/maven-13-pbvhr-xbdq3-wb3j8][Scheduled] Successfully assigned ops/maven-13-pbvhr-xbdq3-wb3j8 to a3
[Normal][ops/maven-13-pbvhr-xbdq3-wb3j8][Pulled] Container image "andanyoung/jenkins-slave-maven" already present on machine
[Normal][ops/maven-13-pbvhr-xbdq3-wb3j8][Created] Created container jnlp
[Normal][ops/maven-13-pbvhr-xbdq3-wb3j8][Started] Started container jnlp
Agent maven-13-pbvhr-xbdq3-wb3j8 is provisioned from template maven_13-pbvhr-xbdq3
---
apiVersion: "v1"
kind: "Pod"
metadata:
  annotations:
    buildUrl: "http://jenkins.ops.svc.cluster.local/job/maven/13/"
    runUrl: "job/maven/13/"
  labels:
    jenkins: "slave"
    jenkins/label-digest: "a3166022e7be1cbf48366e0c0ffece53242411af"
    jenkins/label: "maven_13-pbvhr"
  name: "maven-13-pbvhr-xbdq3-wb3j8"
spec:
  containers:
  - env:
    - name: "JENKINS_SECRET"
      value: "********"
    - name: "JENKINS_AGENT_NAME"
      value: "maven-13-pbvhr-xbdq3-wb3j8"
    - name: "JENKINS_NAME"
      value: "maven-13-pbvhr-xbdq3-wb3j8"
    - name: "JENKINS_AGENT_WORKDIR"
      value: "/home/jenkins/agent"
    - name: "JENKINS_URL"
      value: "http://jenkins.ops.svc.cluster.local/"
    image: "andanyoung/jenkins-slave-maven"
    imagePullPolicy: "IfNotPresent"
    name: "jnlp"
    resources:
      limits:
        memory: "1Gi"
        cpu: "1"
      requests: {}
    tty: false
    volumeMounts:
    - mountPath: "/home/jenkins/.m2"
      name: "volume-0"
      readOnly: false
    - mountPath: "/home/jenkins/agent"
      name: "workspace-volume"
      readOnly: false
    workingDir: "/home/jenkins/agent"
  hostNetwork: false
  nodeSelector:
    kubernetes.io/os: "linux"
  restartPolicy: "Never"
  volumes:
  - name: "volume-0"
    persistentVolumeClaim:
      claimName: "pvc-maven-m2"
      readOnly: false
  - emptyDir:
      medium: ""
    name: "workspace-volume"

Running on maven-13-pbvhr-xbdq3-wb3j8 in /home/jenkins/agent/workspace/maven
[Pipeline] {
[Pipeline] container
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Run shell)
[Pipeline] sh
+ echo hello world
hello world
[Pipeline] sh
+ mvn -version
Apache Maven 3.8.4 (9b656c72d54e5bacbed989b64718c159fe39b537)
Maven home: /usr/local/apache-maven-3.8.4
Java version: 1.8.0_292, vendor: AdoptOpenJDK, runtime: /opt/java/openjdk/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "3.10.0-1160.15.2.el7.x86_64", arch: "amd64", family: "unix"
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // container
[Pipeline] }
[Pipeline] // node
[Pipeline] }
[Pipeline] // podTemplate
[Pipeline] End of Pipeline
Finished: SUCCESS

4. Pod内使用kubectl、Docker命令

如果再springboot 项目中没有用dockerfile-maven-plugin 插件则需要手动使用 docker 命令打包并push到私服

4.1 docker 命令

添加两个主机挂载盘

image-20211210153140004

或者直接用命令方式

hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
hostPathVolume(mountPath: '/usr/bin/docker', hostPath: '/usr/bin/docker'),
hostPathVolume(mountPath: '/etc/localtime', hostPath: '/etc/localtime')
修改pipline,运行测试
podTemplate (inheritFrom: "jnlp-maven"){
    node(POD_LABEL) {
        container('jnlp'){
            stage('Run shell') {
                sh 'echo hello world'
                sh 'mvn -version'
                sh 'docker info'
            }
        }
    }
}

这时候会报 权限不足错误,因为 宿主机docker使用 root运行的,pipline pod 使用 jenkins(1000) 运行的

ERROR: Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/info": dial unix /var/run/docker.sock: connect: 

注意:我K8S集群使用root运行的所以权限很高,你如果使用其他账号运行的K8S集群,会遇到/var/run/docker.sock没有访问权限的问题,因为Docker必须是root权限运行,解决办法是:

# Docker 服务重启要重新执行
chmod 777 /var/run/docker.sock
# 另一种方法是使用用户组

4.2 Pod内使用Kubectl命令

jnlp-maven 的 Pod Templates 添加 容器 kubectl。其中 andanyoung/kubectl是我自己封装的轻量级kubectl命令库原理很简单,参考https://hub.docker.com/r/andanyoung/kubectl

image-20211210154233166

新增PVC(用于保存kubectl的配置)

image-20211210154316578

修改pipline,运行测试

再 jnlp 上运行 mvn,再 jnlp-kubectl 运行 kubectl 命令

podTemplate (inheritFrom: "jnlp-maven"){
    node(POD_LABEL) {
            stage('Run shell') {
                sh 'echo hello world'
                sh 'mvn -version'
            }
            
        container('jnlp-kubectl'){
            stage('Run kubectl') {
                sh 'kubectl config view'
            }
        }
    
    }
}
运行结果
[Pipeline] Start of Pipeline
[Pipeline] podTemplate
[Pipeline] {
[Pipeline] node
Created Pod: kubernetes ops/maven-47-spd2w-p9t8w-94194
[Normal][ops/maven-47-spd2w-p9t8w-94194][Scheduled] Successfully assigned ops/maven-47-spd2w-p9t8w-94194 to a3
[Normal][ops/maven-47-spd2w-p9t8w-94194][Pulled] Container image "andanyoung/kubectl" already present on machine
[Normal][ops/maven-47-spd2w-p9t8w-94194][Created] Created container jnlp-kubectl
[Normal][ops/maven-47-spd2w-p9t8w-94194][Started] Started container jnlp-kubectl
[Normal][ops/maven-47-spd2w-p9t8w-94194][Pulled] Container image "andanyoung/jenkins-slave-maven" already present on machine
[Normal][ops/maven-47-spd2w-p9t8w-94194][Created] Created container jnlp
[Normal][ops/maven-47-spd2w-p9t8w-94194][Started] Started container jnlp
Agent maven-47-spd2w-p9t8w-94194 is provisioned from template maven_47-spd2w-p9t8w
---
apiVersion: "v1"
kind: "Pod"
metadata:
  annotations:
    buildUrl: "http://jenkins.ops.svc.cluster.local/job/maven/47/"
    runUrl: "job/maven/47/"
  labels:
    jenkins: "slave"
    jenkins/label-digest: "9d6c53e7f3a6bb33281553a37c902ca1c01f8881"
    jenkins/label: "maven_47-spd2w"
  name: "maven-47-spd2w-p9t8w-94194"
spec:
  containers:
  - image: "andanyoung/kubectl"
    imagePullPolicy: "IfNotPresent"
    name: "jnlp-kubectl"
    resources:
      limits: {}
      requests: {}
    tty: true
    volumeMounts:
    - mountPath: "/home/jenkins/.m2"
      name: "volume-0"
      readOnly: false
    - mountPath: "/var/run/docker.sock"
      name: "volume-1"
      readOnly: false
    - mountPath: "/usr/bin/docker"
      name: "volume-2"
      readOnly: false
    - mountPath: "/home/jenkins/agent"
      name: "workspace-volume"
      readOnly: false
    workingDir: "/home/jenkins/agent"
  - env:
    - name: "JENKINS_SECRET"
      value: "********"
    - name: "JENKINS_AGENT_NAME"
      value: "maven-47-spd2w-p9t8w-94194"
    - name: "JENKINS_NAME"
      value: "maven-47-spd2w-p9t8w-94194"
    - name: "JENKINS_AGENT_WORKDIR"
      value: "/home/jenkins/agent"
    - name: "JENKINS_URL"
      value: "http://jenkins.ops.svc.cluster.local/"
    image: "andanyoung/jenkins-slave-maven"
    imagePullPolicy: "IfNotPresent"
    name: "jnlp"
    resources:
      limits:
        memory: "1Gi"
        cpu: "1"
      requests: {}
    tty: true
    volumeMounts:
    - mountPath: "/home/jenkins/.m2"
      name: "volume-0"
      readOnly: false
    - mountPath: "/var/run/docker.sock"
      name: "volume-1"
      readOnly: false
    - mountPath: "/usr/bin/docker"
      name: "volume-2"
      readOnly: false
    - mountPath: "/home/jenkins/agent"
      name: "workspace-volume"
      readOnly: false
    workingDir: "/home/jenkins/agent"
  hostNetwork: false
  imagePullSecrets:
  - name: ""
  nodeSelector:
    kubernetes.io/os: "linux"
  restartPolicy: "Never"
  volumes:
  - name: "volume-0"
    persistentVolumeClaim:
      claimName: "pvc-maven-m2"
      readOnly: false
  - hostPath:
      path: "/usr/bin/docker"
    name: "volume-2"
  - hostPath:
      path: "/var/run/docker.sock"
    name: "volume-1"
  - emptyDir:
      medium: ""
    name: "workspace-volume"

Running on maven-47-spd2w-p9t8w-94194 in /home/jenkins/agent/workspace/maven
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Run shell)
[Pipeline] sh
+ echo hello world
hello world
[Pipeline] sh
+ mvn -version
Apache Maven 3.8.4 (9b656c72d54e5bacbed989b64718c159fe39b537)
Maven home: /usr/local/apache-maven-3.8.4
Java version: 1.8.0_292, vendor: AdoptOpenJDK, runtime: /opt/java/openjdk/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "3.10.0-1160.15.2.el7.x86_64", arch: "amd64", family: "unix"
[Pipeline] }
[Pipeline] // stage
[Pipeline] container
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Run kubectl)
[Pipeline] sh
+ kubectl config view
apiVersion: v1
clusters: null
contexts: null
current-context: ""
kind: Config
preferences: {}
users: null
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // container
[Pipeline] }
[Pipeline] // node
[Pipeline] }
[Pipeline] // podTemplate
[Pipeline] End of Pipeline
Finished: SUCCESS

当然你不能运行kubectl get pod 命令,因为现在的用户为 default 用户。没有权限

+ kubectl get pod
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:ops:default" cannot list resource "pods" in API group "" in the namespace "ops"

5. 为Jenkins创建kubernetes集群账号和证书

参考:k8s 带你一步步 创建用户账号(User Account)

接下来我们需要创建一个 名为 jenkins 的 User Account 账号并赋予相应权限

在Master上执行:

# 进入集群CA证书所在目录
cd /etc/kubernetes/pki

# 执行创建证书命令
(umask 077;openssl genrsa -out jenkins.key 2048)

# O=组织信息,CN=用户名
openssl req -new -key jenkins.key -out jenkins.csr -subj "/O=kubernetes/CN=jenkins"

# 签署证书
openssl  x509 -req -in jenkins.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out jenkins.crt -days 3650

生成kubectl配置文件

生成配置文件位置 /root/jenkins.conf

master url --server=https://10.31.90.200:8443

# 1. 创建集群配置
kubectl config set-cluster k8s --server=https://10.31.90.200:8443 --certificate-authority=ca.crt --embed-certs=true --kubeconfig=/root/jenkins.conf
Cluster "k8s" set.

# 创建jenkins用户信息
kubectl config set-credentials jenkins --client-certificate=jenkins.crt --client-key=jenkins.key --embed-certs=true --kubeconfig=/root/jenkins.conf

# 创建context配置 设置上下文信息,jenkins用户与集群建立关系
kubectl config set-context jenkins@default-cluster --cluster=default-cluster --user=jenkins --kubeconfig=/root/jenkins.conf 

## 切换context
kubectl config use-context default-context --kubeconfig=/root/jenkins.conf

# 查看结果
kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://192.168.122.3:6443
  name: default-cluster
contexts:
- context:
    cluster: default-cluster
    user: jenkins
  name: jenkins@default-cluster
current-context: jenkins@default-cluster
kind: Config
preferences: {}
users:
- name: jenkins
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED

最后生成 /root/jenkins.conf 文件

最后将该文件放在 配置的 PVC(pvc-kube-config) 下面,再次运行测试

为 jenkins 用户添加权限

切换刚创建的上下文(切换用户)

# 切换用户
kubectl config use-context jenkins@default-cluster

# 测试
kubectl get pod

# 没有权限
Error from server (Forbidden): pods is forbidden: User "jenkins" cannot list resource "pods" in API group "" in the namespace "default"

目前新账号没有分配权限无法使用,创建dev-test命名空间,并创建管理该命名空间下pod资源的角色,然后绑定到jenkins账户:

创建角色:
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: jenkins-role
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["create","delete","get","list","patch","update","watch"]
- apiGroups: [""]
  resources: ["pods/exec"]
  verbs: ["create","delete","get","list","patch","update","watch"]
- apiGroups: [""]
  resources: ["pods/log"]
  verbs: ["get","list","watch"]
- apiGroups: [""]
  resources: ["events"]
  verbs: ["watch"]
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get"]
绑定账号与角色:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: jenkins-role-bind
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: jenkins-role
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: jenkins

注意:Role和RoleBinding的命名空间都是dev-test权限才生效,否则是不会生效的,账号jenkins此时拥有对dev-test命名空间pod的管理权限。

测试权限:

# 在Node节点执行
[root@centos7-k8s-node1 ~]# kubectl apply -f ndsutils.yaml -n dev-test
pod/dnsutils created
[root@centos7-k8s-node1 ~]# kubectl get pod -n dev-test
NAME       READY   STATUS    RESTARTS   AGE
dnsutils   1/1     Running   0          11s
[root@centos7-k8s-node1 ~]# kubectl get pod -n default
Error from server (Forbidden): pods is forbidden: User "jenkins" cannot list resource "pods" in API group "" in the namespace "default"
测试运行(增删改查,自行认证吧)
podTemplate (inheritFrom: "jnlp-maven"){
    node(POD_LABEL) {
            stage('Run shell') {
                sh 'echo hello world'
                sh 'mvn -version'
            }
            
        container('jnlp-kubectl'){
            stage('Run kubectl') {
                sh 'kubectl get pod -n dev-test'
            }
        }
    }
}

运行结果

+ kubectl get pod
NAME                         READY   STATUS    RESTARTS   AGE
jenkins-0                    1/1     Running   0          24h
maven-49-gwdm5-101pq-0spr9   2/2     Running   0          10s

为jenkins账号赋予集群角色

Role 只能拥有当前namespace 的权限,而ClusterRole 没有namespace的限制,没有命名空间的概念,集群角色是在所有命名空间有效。

创建集群角色,此集群角色只有查看Pod的权限:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cluster-reader
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get","list","watch"]

绑定账号和集群角色:

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: cluster-reader-jenkins
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-reader
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: jenkins

在Node节点上执行:

[root@centos7-k8s-node1 yaml]# kubectl get pod -A
NAMESPACE              NAME                                         READY   STATUS    RESTARTS   AGE
dev-test               dnsutils                                     1/1     Running   0          7m17s
kube-system            coredns-7ff77c879f-ck49p                     1/1     Running   9          5d3h
kube-system            coredns-7ff77c879f-d2xfc                     1/1     Running   10         5d3h
kube-system            etcd-centos7-k8s-master                      1/1     Running   11         5d3h
kube-system            kube-apiserver-centos7-k8s-master            1/1     Running   6          4d1h
kube-system            kube-controller-manager-centos7-k8s-master   1/1     Running   12         5d3h
kube-system            kube-flannel-ds-amd64-52vcn                  1/1     Running   9          5d3h
kube-system            kube-flannel-ds-amd64-vtw58                  1/1     Running   12         5d3h
kube-system            kube-flannel-ds-amd64-xm8d5                  1/1     Running   10         5d3h
kube-system            kube-proxy-l8875                             1/1     Running   18         5d3h
kube-system            kube-proxy-p5fdr                             1/1     Running   9          5d3h
kube-system            kube-proxy-pdvz2                             1/1     Running   16         5d3h
kube-system            kube-scheduler-centos7-k8s-master            1/1     Running   10         5d3h
kube-system            metrics-server-7f6d95d688-vjsbj              1/1     Running   7          4d1h
kubernetes-dashboard   dashboard-metrics-scraper-6b4884c9d5-8hblg   1/1     Running   7          4d1h
kubernetes-dashboard   kubernetes-dashboard-7b544877d5-tz4xj        1/1     Running   7          4d1h
nginx-ingress          coffee-5f56ff9788-2745d                      1/1     Running   6          4d1h
nginx-ingress          coffee-5f56ff9788-c8jlx                      1/1     Running   6          4d1h
nginx-ingress          nginx-ingress-hjqzc                          1/1     Running   6          4d1h
nginx-ingress          nginx-ingress-jfh6h                          1/1     Running   6          4d1h
nginx-ingress          tea-69c99ff568-cpp2k                         1/1     Running   6          4d1h
nginx-ingress          tea-69c99ff568-rmnr2                         1/1     Running   6          4d1h

PS: 用户账号有了(User Account),权限也可以配置了。那k8s 发布pod(重启、更新版本等)不是可以实现了吗?请看下回分析。

Logo

开源、云原生的融合云平台

更多推荐