Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细

文章目录

  • 一、插件介绍
  • 通用设置
    • 先决条件
    • 配置
      • 配置kubernetes环境
        • 1 Jenkins 运行于 Kubernets 内部的情况下
          • 1.1 配置 kubernetes 集群 URL 、名称空间 和 凭据
          • 1.2 配置 Jenkins URL 和 通道
        • 2 Jenkins 在 Kubernetes 集群的外部
          • 2.1 配置访问kubernetes 集群的凭证
          • 2.2 配置 kubernetes 集群URL和名称空间
        • 配置 Jenkins 和 jenkins 代理之间的连接信息
      • 配置 Pod 模板
    • 测试使用
  • 用法详解
    • 概述
    • 使用标签
    • Pipeline 支持
    • 多容器支持
    • 配置 pod 模板
    • 配置容器模板
    • 指定代理默认连接超时时间
    • 使用 yaml 定义 Pod Templates
    • Liveness Probe Usage 活性探针的使用
  • Inheritance 继承
    • 概述
    • 多Pod模板继承
    • 嵌套 Pod 模板
  • 声明式管道
    • 默认继承
  • 杂项
    • 从管道访问容器日志
        • 所需参数
        • 可选参数
  • 使用系统属性控制的功能
  • 约束
  • 故障排除
    • 删除状态不佳的 Pod
    • 使用多个容器时遇到 Pipeline sh 步骤被挂起
    • 使用带有自签名 HTTPS 证书的 Jenkins 控制器的 WebSockets
  • Docker 镜像
    • 运行 Docker 镜像
  • 在 Kubernetes 中运行
    • 使用 minikube 在本地运行
    • 在 Google 容器引擎 GKE 中运行
    • 自定义部署
      • 修改 CPU 和内存请求/限制(Kubernetes Resource API)
    • 建造
  • 相关项目
  • 声明式 Pipeline 的 yamlFile示例
  • pipeline maven 示例
  • selenium 示例

一、插件介绍

Kubernetes

Jenkins 的 Kubernetes 插件可以实现在 Kubernetes 集群中运行动态的 Agent, 并在动态 Agent 中去构建任务或运行 Pipeline 代码。

Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第1张图片
图片来源

该插件为每个启动的代理创建一个 Kubernetes Pod,并在每次构建后停止它。

代理会使用 JNLP(inbound agents)启动,因此 agents 会自动连接到 Jenkins 控制器(master)。为此,会自动注入一些环境变量:

JENKINS_URL : Jenkins 网页界面网址
JENKINS_SECRET : 认证的秘钥
JENKINS_AGENT_NAME : Jenkins 代理的名字
JENKINS_NAME :Jenkins 代理的名称(已弃用。仅用于向后兼容)

容器镜像:jenkins/inbound-agent 已经通过测试, 具体请查看Docker image source code.

Jenkins 控制器可以运行在 Kubernetes 外部,也可以运行在 Kubernetes 外部。

通用设置

先决条件

  • 正在运行的 Kubernetes 集群 1.14 或更高版本。对于 OpenShift 用户,这意味着 OpenShift Container Platform 4.x。
  • 安装了一个 Jenkins 实例
  • 已安装 Jenkins Kubernetes 插件

配置

填写Kubernetes插件配置。为此,您将打开 Jenkins UI 并导航到:
Manage Jenkins -> Manage Nodes and Clouds -> Configure Clouds -> Add a new cloud -> Kubernetes
Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第2张图片

配置kubernetes环境

这里分两种情况:
1 jenkins 部署在 kubernetes 集群内部
2 jenkins 部署在 kubernetes 集群外部
接下来分别说明两种情况的不同配置。

1 Jenkins 运行于 Kubernets 内部的情况下

1.1 配置 kubernetes 集群 URL 、名称空间 和 凭据

Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第3张图片

1.2 配置 Jenkins URL 和 通道
[root@master ~]# kubectl -n jenkins get service
NAME      TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)                          AGE
jenkins   NodePort   10.98.59.142           8080:32080/TCP,50000:32500/TCP   2d
[root@master ~]#

Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第4张图片

2 Jenkins 在 Kubernetes 集群的外部

2.1 配置访问kubernetes 集群的凭证

在Kubernetes外部运行Jenkins控制器时,需要将凭证设置为secret text。凭证的值将是您在代理将运行的集群中为Jenkins创建的服务帐户的令牌。

注意 如果您的Jenkins控制器在集群之外,并且使用自签名HTTPS证书,则需要一些 附加配置。

当我们的 Jenkins 在 Kubernetes 集群的外部,就需要配置认证信息。

支持的认证方式如下:

  • Secret File (kubeconfig file)
  • Secret text (Token-based authentication) (OpenShift)
  • Google Service Account from private key (GKE authentication)

如何创建凭据

我们的 Kubernetes 集群就是用 Secret File 方式即可。
这个方式需要在你访问 Jenskins UI 的浏览器所在的主机上保存一份访问 Kubernetes 集群的配置文件,就是我们平常使用 ~/.kube/config 文件就行。或者单独给你的 Jenkins 创建有权限访问和控制(创建、更新、删除)集群资源的账户及其授权信息。

我的是在我的笔记本的桌面上创建了一份。

Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第5张图片

之后按照下图方式创建即可

Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第6张图片

需要选择 Secret file
Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第7张图片
选择准备好的配置文件
Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第8张图片
Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第9张图片

2.2 配置 kubernetes 集群URL和名称空间

Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第10张图片
点击右侧的 连接测试, 成功连接后 在 凭据 下方返回当前集群的版本号。

最后点击 Save

在这里插入图片描述

配置 Jenkins 和 jenkins 代理之间的连接信息

代理和控制端的连接是使用 TCP 协议通信的,需要控制端打开 50000 端口。
打开方式如下:

Manage Jenkins -> Configure Global Security 或者 全局安全配置

**系统管理 -> 全局安全设置 **

Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第11张图片

保存设置后,就可以返回到 Configure Clouds(配置集群) 页面设置 Jenkins 的连接信息了,如下图:

  • Jenkins 部署在集群外部的情况下
    Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第12张图片
    最后点击 Apply 或者 应用

配置 Pod 模板

除了需要配置连接到 Kubernetes 集群的信息外,还需要在 Kubernetes Pod Template 部分,我们需要配置 Jenkins 代理的镜像 ,将用于启动 Pod。

Jenkins 默认情况下提供了一个镜像用于启动 Jenkins 代理,这个镜像不要覆盖,镜像中已经集成了 JDK 环境。假如还需要其他环境用于构建你的项目,可以向 Pod 模板中继续添加其他的镜像。也就是说一个模板中可以配置多个镜像。

节点管理->配置集群->Kubernetes->Kubernetes Pod Template部分
Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第13张图片
Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第14张图片

您需要指定以下内容(其余的配置由您决定):

  • Kubernetes Pod 模板名称 - 可以是任意的,并将显示为唯一生成的代理名称的前缀,这将在构建 Docker 映像期间自动运行
    Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第15张图片

  • Docker 映像名称将用作启动新 Jenkins 代理的参考,默认是 jenkins/inbound-agent:4.3-4

点击 Pod Template details
Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第16张图片

假如我们只使用默认的镜像,配置信息如下:

Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第17张图片

测试使用

接下来我们创建一个自由风格的任务,构建步骤仅仅执行一下命令 java -version

Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第18张图片
Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第19张图片
Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第20张图片

Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第21张图片
构建

当点击构建时,此次构建后经历如下当阶段。

  1. 此时 Pod 资源还没有被创建
    Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第22张图片

  2. 此时 Pod 资源被创建,但是还没有准备好
    Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第23张图片

  3. Pod 已经准备好,正在构建中
    这个过程有可能会很快,因此不一定能及时看到。
    Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第24张图片

  4. 已经构建完成
    Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第25张图片

用法详解

概述

Kubernetes 插件在 Kubernetes pod 中分配 Jenkins 代理。在这些 Pod 中,总是有一个特殊的容器 jnlp 在运行 Jenkins 代理。其他容器可以运行您选择的任意进程,并且可以在代理 pod 中的任何容器中动态运行命令。

使用标签

用户在设置页面定义的 Pod 模板中声明的一个标签。
freestyle 作业或 pipeline 作业使用 node('some-label') 选择一个pod 模板声明的标签时,Kubernetes Cloud 会分配一个新的 pod 来运行 Jenkins 代理。

Pipeline 支持

Agent 节点可以在 pipeline 中定义,然后使用,但是,默认构建命令的执行总是转到名称为 jnlp 的容器,这个 jnlp 的容器不需要明确定义。

请注意,POD_LABEL是一个新功能,可以在1.17.0或更高版本中自动的生成POD标记,Kubernetes插件的旧版本需要手动标记POD模板

这将在默认运行 Jenkins 代理的 jnlp 容器中执行命令。

podTemplate {
    node(POD_LABEL) {
        stage('Run shell') {
            sh 'echo hello world'
        }
    }
}

在示例目录中查找更多示例。

  • POD_CONTAINER

注意变量 POD_CONTAINER 包含了当前上下文中容器的名称。就是在 container 块中定义的容器名称。

podTemplate(containers: […]) {
  node(POD_LABEL) {
    stage('Run shell') {
      container('mycontainer') {
        sh "echo hello from $POD_CONTAINER" // displays 'hello from mycontainer'
      }
    }
  }
}

更多的示例请点击: examples dir.

The default jnlp agent image used can be customized by adding it to the template
使用的默认 jnlp 代理镜像可以通过将其添加到 template 中进行自定义。

containerTemplate(name: 'jnlp', image: 'jenkins/inbound-agent:4.3-4-alpine', args: '${computer.jnlpmac} ${computer.name}'),

或者使用 yaml 语法

apiVersion: v1
kind: Pod
spec:
  containers:
  - name: jnlp
    image: 'jenkins/inbound-agent:4.3-4-alpine'
    args: ['\$(JENKINS_SECRET)', '\$(JENKINS_NAME)']

实例:

podTemplate(containers: [
    containerTemplate(name: 'jnlp', image: 'jenkins/inbound-agent:4.11.2-2-alpine-jdk8', args: '${computer.jnlpmac} ${computer.name}')
  ]) {

    node(POD_LABEL) {
        stage('Get a Maven project') {
            container('jnlp') {
                stage('show version') {
                    sh 'java -version'
                }
            }
        }

    }
}

多容器支持

可以为代理pod定义多个容器,其中包含共享资源,比如挂载。每个容器中的端口都可以像在任何Kubernetes pod中一样通过使用“localhost”进行访问。

“container” 属性语句允许直接在每个容器中执行命令。

podTemplate(containers: [
    containerTemplate(name: 'maven', image: 'maven:3.8.1-jdk-8', command: 'sleep', args: '99d'),
    containerTemplate(name: 'golang', image: 'golang:1.16.5', command: 'sleep', args: '99d')
  ]) {

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

        stage('Get a Golang project') {
            git url: 'https://github.com/hashicorp/terraform.git', branch: 'main'
            container('golang') {
                stage('Build a Go project') {
                    sh '''
                    mkdir -p /go/src/github.com/hashicorp
                    ln -s `pwd` /go/src/github.com/hashicorp/terraform
                    cd /go/src/github.com/hashicorp/terraform && make
                    '''
                }
            }
        }

    }
}

配置 pod 模板

podTemplate 是用于创建代理的pod的模板。它可以通过用户界面配置,也可以通过 Pipeline 配置。无论哪种方式,它都提供对以下字段的访问:

  • cloud 在Jenkins设置中定义的云的名称. 默认是 kubernetes

  • name Pod 的名称

  • namespace Pod 的命名空间

  • label Pod 的 label , 可以设置为唯一值以避免在生成之间发生冲突,也可以忽略,并且在 setup 内部使用 POD_LABEL 进行定义。

  • yaml Pod 的 yaml 描述文件, 这种方式将会完全兼容 Pod 的 yaml 语法,就像在 Kubernetes 中定义一个 Pod 一样。

  • yamlMergeStrategy merge() or override(). 控制 yaml 的定义是重写还是与从pod templates 中用inheritFrom声明的 yaml 继承并合并。默认是重写: override().

  • containers pod 的容器模板部分

  • serviceAccount Pod 的 service account

  • nodeSelector Pod 的 node selector

  • nodeUsageMode NORMAL or EXCLUSIVE 的任何一个, 这将控制Jenkins是仅调度匹配标签表达式的作业,还是尽可能多地使用节点。

  • volumes 为可以被 Pod 中所有的容器挂载的 volume

  • envVars应用于所有容器的环境变量。

    • envVar一个环境变量,其值是内联定义的。
    • secretEnvVar一个环境变量,其值源自 Kubernetes 机密。
  • imagePullSecrets拉密名称列表,用于从私有 Docker 注册表拉取镜像。

  • annotations要应用于 pod 的注解。

  • inheritFrom从一个或多个荚模板继承名单*(更多详情如下)*。

  • slaveConnectTimeout代理在线的超时(以秒为单位*)(更多详细信息见下文)*。

  • podRetention控制保持代理 pod 的行为。可以是 ‘never()’、‘onFailure()’、‘always()’ 或 ‘default()’ - 如果为空,则默认为在activeDeadlineSeconds过去后删除 pod 。

  • activeDeadlineSeconds如果podRetention设置为never()onFailure(),则在超过此期限后删除 Pod。

  • idleMinutes允许 pod 保持活动状态以供重用,直到自上一步执行后经过配置的分钟数。仅在用户界面中定义 pod 模板时使用此选项。

  • showRawYaml启用或禁用原始 pod 清单的输出。默认为true

  • runAsUser用于运行 pod 中所有容器的用户 ID。

  • runAsGroup用于运行 Pod 中所有容器的组 ID。

  • hostNetwork使用主机网络。

  • workspaceVolume用于工作区的卷类型。

    • emptyDirWorkspaceVolume (默认):在主机上分配的空目录
    • dynamicPVC():动态管理的持久卷声明。它与 pod 同时被删除。
    • hostPathWorkspaceVolume() : 主机路径卷
    • nfsWorkspaceVolume() : nfs 卷
    • persistentVolumeClaimWorkspaceVolume() :按名称声明的现有持久卷。

配置容器模板

容器模板是 pod 的一部分。它们可以通过用户界面或管道进行配置,并允许您设置以下字段:

  • name容器的名称。
  • image容器的镜像。
  • envVars应用于容器的环境变量(补充和覆盖在 pod 级别设置的环境变量**)**。
    • envVar一个环境变量,其值是内联定义的。
    • secretEnvVar一个环境变量,其值源自 Kubernetes secret。
  • command容器将执行的命令。将覆盖 Docker entrypoint。典型值为sleep
  • args传递给命令的参数。典型值为99999999
  • ttyEnabled标记应启用 tty 的标志。
  • livenessProbe要添加到容器中的 exec 活性探针的参数(不支持 httpGet 活性探针)
  • 端口在容器上公开端口。
  • alwaysPullImage容器会在每次启动时拉取镜像。
  • runAsUser运行容器的用户 ID。
  • runAsGroup运行容器的组 ID。

指定代理默认连接超时时间

默认情况下,代理连接超时设置为 1000 秒。可以使用系统属性对其进行自定义。请参阅以下部分

使用 yaml 定义 Pod Templates

为了支持 Kubernetes Pod对象中的任何可能的值,我们可以传递一个 yaml 片段,该片段将用作模板的基础。如果在 YAML 之外设置了任何其他属性,则它们将优先。

podTemplate(yaml: '''
    apiVersion: v1
    kind: Pod
    metadata:
      labels: 
        some-label: some-label-value
    spec:
      containers:
      - name: busybox
        image: busybox
        command:
        - sleep
        args:
        - 99d
    ''') {
    node(POD_LABEL) {
      container('busybox') {
        echo POD_CONTAINER // displays 'busybox'
        sh 'hostname'
      }
    }
}

您可以使用 readFilereadTrusted 步骤从文件加载 yaml。另请注意,在声明性管道中可以使用 yamlFile

  • 脚本式 Pipeline 示例

pod.yaml

apiVersion: v1
kind: Pod
spec:
  containers:
  - name: maven
    image: maven:3.8.1-jdk-8
    command:
    - sleep
    args:
    - 99d
  - name: golang
    image: golang:1.16.5
    command:
    - sleep
    args:
    - 99d

Jenkinsfile

podTemplate(yaml: readTrusted('pod.yaml')) {
  node(POD_LABEL) {
    // ...
  }
}
  • 声明式 Pipeline 示例

KubernetesPod.yaml

metadata:
  labels:
    some-label: some-label-value
spec:
  containers:
  - name: jnlp
    env:
    - name: CONTAINER_ENV_VAR
      value: jnlp
  - name: maven
    image: maven:3.8.1-jdk-8
    command:
    - sleep
    args:
    - 99d
    env:
    - name: CONTAINER_ENV_VAR
      value: maven
  - name: busybox
    image: busybox
    command:
    - cat
    tty: true
    env:
    - name: CONTAINER_ENV_VAR
      value: busybox

Jenkinsfile

pipeline {
  agent {
    kubernetes {
      yamlFile './KubernetesPod.yaml'
    }
  }
  stages {
    stage('Run maven') {
      steps {
        sh 'set'
        sh "echo OUTSIDE_CONTAINER_ENV_VAR = ${CONTAINER_ENV_VAR}"
        container('maven') {
          sh 'echo MAVEN_CONTAINER_ENV_VAR = ${CONTAINER_ENV_VAR}'
          sh 'mvn -version'
        }
        container('busybox') {
          sh 'echo BUSYBOX_CONTAINER_ENV_VAR = ${CONTAINER_ENV_VAR}'
          sh '/bin/busybox'
        }
      }
    }
  }
}

Liveness Probe Usage 活性探针的使用

containerTemplate(name: 'busybox', image: 'busybox', command: 'sleep', args: '99d',
                  livenessProbe: containerLivenessProbe(execArgs: 'some --command', initialDelaySeconds: 30, timeoutSeconds: 1, failureThreshold: 3, periodSeconds: 10, successThreshold: 1)
)

关于更多的详情前查看 Kubernetes 官方文档的: Defining a liveness command

Inheritance 继承

概述

Pod 模板可能继承自现有模板,也可能不继承。这意味着 pod 模板将从它继承的模板继承 Node selectorService accountImage Pull SecretsContainer templatesvolume

yaml 根据 yamlMergeStrategy 的值进行合并。

Service accountNode selector 在被覆盖时,会完全替代在“父”上找到的任何可能的值。

Container templates 被添加到 podTemplate,其将在“父”模板去匹配一个和它有相同名称的 containerTemplate,从而继承父containerTemplate 的配置。如果未找到匹配的容器模板,则按原样添加 podTemplate

Volume 继承与 Container templates 完全一样。

Image Pull Secrets 将会被合并(使用在“父”和“当前”模板上定义的所有机密)。

在下面的示例中,我们将从我们之前创建的 pod 模板继承,并且只会覆盖 maven 的版本,以便它使用 jdk-11 代替原来的 jdk-8:

Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第26张图片

podTemplate(inheritFrom: 'mypod', containers: [
    containerTemplate(name: 'maven', image: 'maven:3.8.1-jdk-11')
  ]) {
  node(POD_LABEL) {}
}

或者用声明式管道

pipeline {
  agent {
    kubernetes {
      inheritFrom 'mypod'
      yaml '''
      spec:
        containers:
        - name: maven
          image: maven:3.8.1-jdk-11
'''
      …
    }
  }
  stages {
    …
  }
}

请注意,我们只需要指定不同的东西。
所以,commandarguments 没有指定,因为它们是继承的。
此外,golang容器将按照“父”模板中的定义自动被添加。

多Pod模板继承

字段inheritFrom可以引用单个 podTemplate 或多个由空格分隔。在后一种情况下,每个模板将按照它们出现在列表中的顺序进行处理*(后面的项目覆盖前面的项目)*。在任何情况下,如果未找到引用的模板,它将被忽略。

嵌套 Pod 模板

FieldinheritFrom提供了一种简单的方法来组合已预先配置的 podTemplates。在许多情况下,使用 groovy 直接在管道中定义和组合 podTemplates 会很有用。这是通过嵌套实现的。您可以将多个 pod 模板嵌套在一起以组成一个。

下面的示例组合了两个不同的 pod 模板,以创建一个具有 maven 和 docker 功能的模板。

podTemplate(containers: [containerTemplate(image: 'docker', name: 'docker', command: 'cat', ttyEnabled: true)]) {
    podTemplate(containers: [containerTemplate(image: 'maven', name: 'maven', command: 'cat', ttyEnabled: true)]) {
      node(POD_LABEL) { // gets a pod with both docker and maven}
    }
}

此功能对管道库开发人员来说非常有用,因为它允许您将 pod 模板包装到函数中,并让用户根据自己的需要嵌套这些函数。

例如,可以为其 podTemplate 创建函数并导入它们以供使用。说这是我们的文件src/com/foo/utils/PodTemplates.groovy

package com.foo.utils

public void dockerTemplate(body) {
  podTemplate(
        containers: [containerTemplate(name: 'docker', image: 'docker', command: 'sleep', args: '99d')],
        volumes: [hostPathVolume(hostPath: '/var/run/docker.sock', mountPath: '/var/run/docker.sock')]) {
    body.call()
}
}

public void mavenTemplate(body) {
  podTemplate(
        containers: [containerTemplate(name: 'maven', image: 'maven', command: 'sleep', args: '99d')],
        volumes: [secretVolume(secretName: 'maven-settings', mountPath: '/root/.m2'),
                  persistentVolumeClaim(claimName: 'maven-local-repo', mountPath: '/root/.m2repo')]) {
    body.call()
}
}

return this

然后,库的使用者可以通过将两者结合来表达对具有 docker 功能的 maven pod 的需求,但是,再一次,您将需要表达您希望在其中执行命令的特定容器。您不能省略该node语句。

请注意,这POD_LABEL将是最内层生成的标签,用于获取一个节点,该节点具有该节点上所有可用的外部 pod,如下例所示:

import com.foo.utils.PodTemplates

podTemplates = new PodTemplates()

podTemplates.dockerTemplate {
  podTemplates.mavenTemplate {
    node(POD_LABEL) {
      container('docker') {
        sh "echo hello from $POD_CONTAINER" // displays 'hello from docker'
      }
      container('maven') {
        sh "echo hello from $POD_CONTAINER" // displays 'hello from maven'
      }
     }
  }
}

在脚本管道中,有些情况下不需要通过嵌套声明进行这种隐式继承,或者首选其他显式继承。在这种情况下,用于inheritFrom ''删除任何继承,或inheritFrom 'otherParent'覆盖它。

声明式管道

声明式代理可以从 yaml 中定义

pipeline {
  agent {
    kubernetes {
      yaml '''
        apiVersion: v1
        kind: Pod
        metadata:
          labels:
            some-label: some-label-value
        spec:
          containers:
          - name: maven
            image: maven:alpine
            command:
            - cat
            tty: true
          - name: busybox
            image: busybox
            command:
            - cat
            tty: true
        '''
    }
  }
  stages {
    stage('Run maven') {
      steps {
        container('maven') {
          sh 'mvn -version'
        }
        container('busybox') {
          sh '/bin/busybox'
        }
      }
    }
  }
}

或使用yamlFile用于将 pod 模板保存在单独的KubernetesPod.yaml文件中

pipeline {
  agent {
    kubernetes {
      yamlFile 'KubernetesPod.yaml'
    }
  }
  stages {}
}

请注意,以前可以定义,containerTemplate但已被弃用,以支持 yaml 格式。

pipeline {
  agent {
    kubernetes {
      //cloud 'kubernetes'
      containerTemplate {
        name 'maven'
        image 'maven:3.8.1-jdk-8'
        command 'sleep'
        args '99d'
      }
    }
  }
  stages {}
}

默认情况下在容器内运行步骤。步骤将嵌套在隐式container(name) {...}块中,而不是在 jnlp 容器中执行。

pipeline {
  agent {
    kubernetes {
      defaultContainer 'maven'
      yamlFile 'KubernetesPod.yaml'
    }
  }

  stages {
    stage('Run maven') {
      steps {
        sh 'mvn -version'
      }
    }
  }
}

在自定义工作区中运行管道或单个阶段 - 除非明确说明,否则不需要。

pipeline {
  agent {
    kubernetes {
      customWorkspace 'some/other/path'
      defaultContainer 'maven'
      yamlFile 'KubernetesPod.yaml'
    }
  }

  stages {
    stage('Run maven') {
      steps {
        sh 'mvn -version'
        sh "echo Workspace dir is ${pwd()}"
      }
    }
  }
}

默认继承

与脚本化 k8s 模板不同,声明式模板不从父模板继承。由于在阶段级别声明的代理可以覆盖全局代理,因此隐式继承会导致混淆。

如有必要,您需要使用字段显式声明继承inheritFrom

在下面的例子中,nested-pod将只包含maven容器。

pipeline {
  agent {
    kubernetes {
      yaml '''
        spec:
        containers:
        - name: golang
            image: golang:1.16.5
            command:
            - sleep
            args:
            - 99d
        '''
    }
  }
  stages {
    stage('Run maven') {
        agent {
            kubernetes {
                yaml '''
                    spec:
                    containers:
                    - name: maven
                      image: maven:3.8.1-jdk-8
                      command:
                      - sleep
                      args:
                      - 99d
                    '''
            }
        }
      steps {
        …
      }
    }
  }
}

杂项

从管道访问容器日志

如果您使用containerTemplate来在后台运行某些服务(例如用于集成测试的数据库),您可能希望从管道访问其日志。这可以通过containerLog将请求容器的日志打印到构建日志的步骤来完成。

所需参数

  • 命名要从中获取日志的容器的名称,如podTemplate. 参数名称可以在简单用法中省略:
containerLog 'mongodb'

可选参数

  • returnLog返回,而不是将它打印到生成日志日志(默认:false
  • tailingLines只返回日志的最后 n 行(可选)
  • sinceSeconds只返回日志的最后 n 秒(可选)
  • limitBytes将输出限制为 n 个字节(从日志的开头开始,不准确)。

示例:

podTemplate(yaml: '''
              apiVersion: v1
              kind: Pod
              metadata:
                labels:
                  some-label: some-label-value
              spec:
                containers:
                - name: maven
                  image: maven:3.8.1-jdk-8
                  command:
                  - sleep
                  args:
                  - 99d
                  tty: true
                - name: mongo
                  image: mongo
''') {
  node(POD_LABEL) {
    stage('Integration Test') {
      try {
        container('maven') {
          sh 'nc -z localhost:27017 && echo "connected to mongo db"'
          // sh 'mvn -B clean failsafe:integration-test' // real integration test

          def mongoLog = containerLog(name: 'mongo', returnLog: true, tailingLines: 5, sinceSeconds: 20, limitBytes: 50000)
          assert mongoLog.contains('connection accepted from 127.0.0.1:')
          sh 'echo failing build; false'
        }
      } catch (Exception e) {
        containerLog 'mongo'
        throw e
      }
    }
  }
}

使用系统属性控制的功能

请阅读由系统属性页面控制的功能以了解如何在 Jenkins 中设置系统属性。

  • KUBERNETES_JENKINS_URL:代理使用的Jenkins URL。这旨在用于 OEM 集成。
  • io.jenkins.plugins.kubernetes.disableNoDelayProvisioning(自 1.19.1 起)是否禁用插件使用的无延迟配置策略(默认为false)。
  • jenkins.host.address :(用于单元测试)控制主机代理应该用来联系 Jenkins
  • org.csanchez.jenkins.plugins.kubernetes.PodTemplate.connectionTimeout: 在考虑 pod 调度失败之前等待的时间(以秒为单位1000)(默认为)
  • org.csanchez.jenkins.plugins.kubernetes.pipeline.ContainerExecDecorator.stdinBufferSize:发送到 Kubernetes exec api 的命令的 stdin 缓冲区大小(以字节为单位)。较低的值会导致执行命令的速度变慢。更高的值会消耗更多的内存(默认为16*1024
  • org.csanchez.jenkins.plugins.kubernetes.pipeline.ContainerExecDecorator.websocketConnectionTimeout: 等待containerstep使用的 websocket连接的时间(默认为30

约束

一个 pod 中可以定义多个容器。其中之一使用 name 自动创建jnlp,并使用 args 运行 Jenkins JNLP 代理服务,${computer.jnlpmac} ${computer.name}并将成为充当 Jenkins 代理的容器。

其他容器必须运行一个长时间运行的进程,所以容器不会退出。如果默认入口点或命令只是运行一些东西并退出,那么它应该被像catwith 之类的东西覆盖ttyEnabled: true

警告如果你想为代理提供你自己的 Docker 镜像,你必须命名容器为 jnlp,以便覆盖默认的它。否则将导致两个代理尝试同时连接到控制器。

故障排除

首先观察 Jenkins 代理 pod 是否启动。确保您位于正确的集群和命名空间中。

kubectl get -a pods --watch

如果它们处于与 不同的状态Running,则用于describe获取事件

kubectl describe pods/my-jenkins-agent

如果是Running,则用于logs获取日志输出

kubectl logs -f pods/my-jenkins-agent jnlp

如果 Pod 未启动或出现任何其他错误,请检查控制器端的日志。

有关更多详细信息,请为at级别配置一个新的Jenkins 日志记录器。org.csanchez.jenkins.plugins.kubernetes``ALL

要检查的消息来回发送到你可以设置一个新的Kubernetes API服务器的JSON詹金斯日志记录了okhttp3DEBUG水平。

删除状态不佳的 Pod

kubectl get pods -o name --selector=jenkins=slave --all-namespaces  | xargs -I {} kubectl delete {}

使用多个容器时遇到 Pipeline sh 步骤被挂起

要对此进行调试,您需要设置:

-Dorg.jenkinsci.plugins.durabletask.BourneShellScript.LAUNCH_DIAGNOSTICS=true系统属性,然后重新启动管道。您很可能会在控制台日志中看到以下内容:

sh: can't create /home/jenkins/agent/workspace/thejob@tmp/durable-e0b7cd27/jenkins-log.txt: Permission denied
sh: can't create /home/jenkins/agent/workspace/thejob@tmp/durable-e0b7cd27/jenkins-result.txt.tmp: Permission denied
mv: can't rename '/home/jenkins/agent/workspace/thejob@tmp/durable-e0b7cd27/jenkins-result.txt.tmp': No such file or directory
touch: /home/jenkins/agent/workspace/thejob@tmp/durable-e0b7cd27/jenkins-log.txt: Permission denied
touch: /home/jenkins/agent/workspace/thejob@tmp/durable-e0b7cd27/jenkins-log.txt: Permission denied
touch: /home/jenkins/agent/workspace/thejob@tmp/durable-e0b7cd27/jenkins-log.txt: Permission denied

通常,当jnlp容器中用户的 UID与另一个容器中的用户 UID 不同时,就会发生这种情况。您使用的所有容器都应该具有相同的用户 UID,这也可以通过设置securityContext来实现:

apiVersion: v1
kind: Pod
spec:
  securityContext:
    runAsUser: 1000 # default UID of jenkins user in agent image
  containers:
  - name: maven
    image: maven:3.8.1-jdk-8
    command:
    - cat
    tty: true

使用带有自签名 HTTPS 证书的 Jenkins 控制器的 WebSockets

使用 WebSockets 是在代理和集群外运行的 Jenkins 控制器之间建立连接的最简单且推荐的方法。但是,如果您的 Jenkins 控制器使用自签名证书配置了 HTTPS,您需要确保代理容器信任 CA。为此,您可以扩展 jenkins/inbound-agent 镜像并添加您的证书,如下所示:

FROM jenkins/inbound-agent

USER root

ADD cert.pem /tmp/cert.pem

RUN keytool -noprompt -storepass changeit \
  -keystore "$JAVA_HOME/jre/lib/security/cacerts" \
  -import -file /tmp/cert.pem -alias jenkinsMaster && \
  rm -f /tmp/cert.pem

USER jenkins

然后,jnlp像往常一样将其用作pod 模板的容器。无需指定命令或参数。

**注意:**当使用 WebSocket 模式时,-disableHttpsCertValidation-certjenkins/inbound-agent 上将变得不可用,这就是您必须扩展 docker image 的原因。

Docker 镜像

Jenkins 的 Docker 映像,已安装插件。基于官方镜像。

运行 Docker 镜像

docker run --rm --name jenkins -p 8080:8080 -p 50000:50000 -v /var/jenkins_home csanchez/jenkins-kubernetes

在 Kubernetes 中运行

示例配置将创建一个有状态集,运行 Jenkins 和持久卷,并使用服务帐户对 Kubernetes API 进行身份验证。

使用 minikube 在本地运行

可以使用minikube创建一个具有一个节点的本地测试集群

minikube start

您可能需要为主机安装的卷设置正确的权限

minikube ssh
sudo chown 1000:1000 /tmp/hostpath-provisioner/pvc-*

然后使用以下命令创建 Jenkins 命名空间、控制器和服务

kubectl create namespace kubernetes-plugin
kubectl config set-context $(kubectl config current-context) --namespace=kubernetes-plugin
kubectl create -f src/main/kubernetes/service-account.yml
kubectl create -f src/main/kubernetes/jenkins.yml

获取要连接的网址

minikube service jenkins --namespace kubernetes-plugin --url

在 Google 容器引擎 GKE 中运行

假设您创建了一个名为jenkinsthis的 Kubernetes 集群,那么如何在那里运行 Jenkins 和代理。

创建所有元素并设置默认命名空间

kubectl create namespace kubernetes-plugin
kubectl config set-context $(kubectl config current-context) --namespace=kubernetes-plugin
kubectl create -f src/main/kubernetes/service-account.yml
kubectl create -f src/main/kubernetes/jenkins.yml

连接到 Kubernetes 创建的网络负载均衡器的 ip,端口 80。获取 ip(在本例中为104.197.19.100kubectl describe services/jenkins(填充可能需要一点时间)

$ kubectl describe services/jenkins
Name:           jenkins
Namespace:      default
Labels:         
Selector:       name=jenkins
Type:           LoadBalancer
IP:         10.175.244.232
LoadBalancer Ingress:   104.197.19.100
Port:           http    80/TCP
NodePort:       http    30080/TCP
Endpoints:      10.172.1.5:8080
Port:           agent   50000/TCP
NodePort:       agent   32081/TCP
Endpoints:      10.172.1.5:50000
Session Affinity:   None
No events.

在 Kubernetes 1.4 移除源 ips 的 SNAT 之前,似乎需要配置 CSRF(在 Jenkins 2 中默认启用)以避免WARNING: No valid crumb was included in request错误。这可以通过在 Manage Jenkins -> Configure Global Security 下检查启用代理兼容性来完成

配置 Jenkins,Kubernetes在配置下添加云,将 Kubernetes URL 设置为容器引擎集群端点或简单的https://kubernetes.default.svc.cluster.local. 在凭据下,单击Add并选择Kubernetes Service Account,或者使用 Kubernetes API 用户名和密码。如果 kubernetes 集群配置为使用客户端证书进行身份验证,请选择“证书”作为凭据类型。

使用Kubernetes Service Account将导致插件使用安装在 Jenkins pod 内的默认令牌。有关更多信息,请参阅为 Pod 配置服务帐户。

Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第27张图片

您可能希望设置Jenkins URL为内部服务 IP,http://10.175.244.232在这种情况下,通过内部网络进行连接。

Container Cap为测试设置一个合理的数字,即 3。

添加图像

  • 码头工人形象: jenkins/inbound-agent
  • Jenkins 代理根目录: /home/jenkins/agent

Jenkins-Kubernetes插件实现使用Pod作为 Agent-超详细_第28张图片

现在可以使用了。

撕下来

kubectl delete namespace/kubernetes-plugin

自定义部署

修改 CPU 和内存请求/限制(Kubernetes Resource API)

修改./src/main/kubernetes/jenkins.yml具有所需限制的文件

resources:
  limits:
    cpu: 1
    memory: 1Gi
  requests:
    cpu: 0.5
    memory: 500Mi

注意:JVM 会使用内存requests作为堆限制(-Xmx)

建造

docker build -t csanchez/jenkins-kubernetes .

相关项目

  • Kubernetes 管道插件:管道扩展,为使用 Kubernetes pod、秘密和卷来执行构建提供本机支持
  • kubernetes-credentials:读取 Kubernetes 机密的凭据提供程序

声明式 Pipeline 的 yamlFile示例

pipeline {
  agent {
    kubernetes {
      // Pod Template 来自于一个已定义好的 YAML 文件
      yamlFile 'examples/declarative_from_yaml_file/KubernetesPod.yaml'
    }
  }
  stages {
    stage('Run maven') {
      steps {
        sh 'set'
        sh "echo OUTSIDE_CONTAINER_ENV_VAR = ${CONTAINER_ENV_VAR}"
        container('maven') {
          sh 'echo MAVEN_CONTAINER_ENV_VAR = ${CONTAINER_ENV_VAR}'
          sh 'mvn -version'
        }
        container('busybox') {
          sh 'echo BUSYBOX_CONTAINER_ENV_VAR = ${CONTAINER_ENV_VAR}'
          sh '/bin/busybox'
        }
      }
    }
  }
}

examples/declarative_from_yaml_file/KubernetesPod.yaml 文件内容如下:

metadata:
  labels:
    some-label: some-label-value
spec:
  containers:
  - name: jnlp
    env:
    - name: CONTAINER_ENV_VAR
      value: jnlp
  - name: maven
    image: maven:3.8.1-jdk-8
    command:
    - sleep
    args:
    - 99d
    env:
    - name: CONTAINER_ENV_VAR
      value: maven
  - name: busybox
    image: busybox
    command:
    - cat
    tty: true
    env:
    - name: CONTAINER_ENV_VAR
      value: busybox

pipeline maven 示例

*这个管道将执行一个简单的maven构建,使用持久卷声明来存储本地maven存储库

*需要使用maven-with-cache-pvc.yml中的定义提前创建PersistentVolumeClaim

*请注意,通常可写卷一次只能连接到一个Pod,因此无法执行使用此管道的两个并发作业。或者在第一次运行后更改readOnly:true

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: 
  name: maven-repo
  namespace: kubernetes-plugin
spec: 
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
podTemplate(containers: [
  containerTemplate(name: 'maven', image: 'maven:3.8.1-jdk-8', command: 'sleep', args: '99d')
  ], volumes: [
  persistentVolumeClaim(mountPath: '/root/.m2/repository', claimName: 'maven-repo', readOnly: false)
  ]) {

  node(POD_LABEL) {
    stage('Build a Maven project') {
      git 'https://github.com/jenkinsci/kubernetes-plugin.git'
      container('maven') {
        sh 'mvn -B -ntp clean package -DskipTests'
      }
    }
  }
}

selenium 示例

/**
 * This pipeline executes Selenium tests against Chrome and Firefox, all running in the same Pod but in separate containers
 * and in parallel
 */

podTemplate(yaml: '''
              apiVersion: v1
              kind: Pod
              spec:
                containers:
                - name: maven-firefox
                  image: maven:3.8.1-jdk-8
                  command:
                  - sleep
                  args: 
                  - 99d
                - name: maven-chrome
                  image: maven:3.8.1-jdk-8
                  command:
                  - sleep
                  args: 
                  - 99d
                - name: selenium-hub
                  image: selenium/hub:3.141.59
                - name: selenium-chrome
                  image: selenium/node-chrome:3.141.59
                  env:
                  - name: HUB_PORT_4444_TCP_ADDR
                    value: localhost
                  - name: HUB_PORT_4444_TCP_PORT
                    value: 4444
                  - name: DISPLAY
                    value: :99.0
                  - name: SE_OPTS
                    value: -port 5556
                - name: selenium-firefox
                  image: selenium/node-firefox:3.141.59
                  env:
                  - name: HUB_PORT_4444_TCP_ADDR
                    value: localhost
                  - name: HUB_PORT_4444_TCP_PORT
                    value: 4444
                  - name: DISPLAY
                    value: :98.0
                  - name: SE_OPTS
                    value: -port 5557
''') {

  node(POD_LABEL) {
    stage('Checkout') {
      git 'https://github.com/carlossg/selenium-example.git'
      parallel (
        firefox: {
          container('maven-firefox') {
            stage('Test firefox') {
              sh 'mvn -B clean test -Dselenium.browser=firefox -Dsurefire.rerunFailingTestsCount=5 -Dsleep=0'
            }
          }
        },
        chrome: {
          container('maven-chrome') {
            stage('Test chrome') {
              sh 'mvn -B clean test -Dselenium.browser=chrome -Dsurefire.rerunFailingTestsCount=5 -Dsleep=0'
            }
          }
        }
      )
    }

    stage('Logs') {
      containerLog('selenium-chrome')
      containerLog('selenium-firefox')
    }
  }
}

你可能感兴趣的:(Jenkins,jenkins,kubernetes,docker)