Kubernetes
Jenkins 的 Kubernetes 插件可以实现在 Kubernetes 集群中运行动态的 Agent, 并在动态 Agent 中去构建任务或运行 Pipeline 代码。
该插件为每个启动的代理创建一个 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插件配置。为此,您将打开 Jenkins UI 并导航到:
Manage Jenkins -> Manage Nodes and Clouds -> Configure Clouds -> Add a new cloud -> Kubernetes
这里分两种情况:
1 jenkins 部署在 kubernetes 集群内部
2 jenkins 部署在 kubernetes 集群外部
接下来分别说明两种情况的不同配置。
[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 ~]#
在Kubernetes外部运行Jenkins控制器时,需要将凭证设置为secret text。凭证的值将是您在代理将运行的集群中为Jenkins创建的服务帐户的令牌。
注意 如果您的Jenkins控制器在集群之外,并且使用自签名HTTPS证书,则需要一些 附加配置。
当我们的 Jenkins 在 Kubernetes 集群的外部,就需要配置认证信息。
支持的认证方式如下:
如何创建凭据
我们的 Kubernetes 集群就是用 Secret File 方式即可。
这个方式需要在你访问 Jenskins UI 的浏览器所在的主机上保存一份访问 Kubernetes 集群的配置文件,就是我们平常使用 ~/.kube/config
文件就行。或者单独给你的 Jenkins 创建有权限访问和控制(创建、更新、删除)集群资源的账户及其授权信息。
我的是在我的笔记本的桌面上创建了一份。
之后按照下图方式创建即可
点击右侧的 连接测试, 成功连接后 在 凭据 下方返回当前集群的版本号。
最后点击 Save
代理和控制端的连接是使用 TCP 协议通信的,需要控制端打开 50000 端口。
打开方式如下:
Manage Jenkins -> Configure Global Security 或者 全局安全配置
**系统管理 -> 全局安全设置 **
保存设置后,就可以返回到 Configure Clouds(配置集群) 页面设置 Jenkins 的连接信息了,如下图:
除了需要配置连接到 Kubernetes 集群的信息外,还需要在 Kubernetes Pod Template 部分,我们需要配置 Jenkins 代理的镜像 ,将用于启动 Pod。
Jenkins 默认情况下提供了一个镜像用于启动 Jenkins 代理,这个镜像不要覆盖,镜像中已经集成了 JDK 环境。假如还需要其他环境用于构建你的项目,可以向 Pod 模板中继续添加其他的镜像。也就是说一个模板中可以配置多个镜像。
在节点管理->配置集群->Kubernetes->Kubernetes Pod Template部分
您需要指定以下内容(其余的配置由您决定):
Kubernetes Pod 模板名称 - 可以是任意的,并将显示为唯一生成的代理名称的前缀,这将在构建 Docker 映像期间自动运行
Docker 映像名称将用作启动新 Jenkins 代理的参考,默认是 jenkins/inbound-agent:4.3-4。
假如我们只使用默认的镜像,配置信息如下:
接下来我们创建一个自由风格的任务,构建步骤仅仅执行一下命令 java -version
当点击构建时,此次构建后经历如下当阶段。
Kubernetes 插件在 Kubernetes pod 中分配 Jenkins 代理。在这些 Pod 中,总是有一个特殊的容器 jnlp 在运行 Jenkins 代理。其他容器可以运行您选择的任意进程,并且可以在代理 pod 中的任何容器中动态运行命令。
用户在设置页面定义的 Pod 模板中声明的一个标签。
当 freestyle 作业或 pipeline 作业使用 node('some-label')
选择一个pod 模板声明的标签时,Kubernetes Cloud 会分配一个新的 pod 来运行 Jenkins 代理。
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
包含了当前上下文中容器的名称。就是在 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
'''
}
}
}
}
}
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应用于所有容器的环境变量。
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 的一部分。它们可以通过用户界面或管道进行配置,并允许您设置以下字段:
sleep
。99999999
。默认情况下,代理连接超时设置为 1000 秒。可以使用系统属性对其进行自定义。请参阅以下部分
为了支持 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'
}
}
}
您可以使用 readFile 或 readTrusted 步骤从文件加载 yaml。另请注意,在声明性管道中可以使用 yamlFile 。
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) {
// ...
}
}
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'
}
}
}
}
}
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
Pod 模板可能继承自现有模板,也可能不继承。这意味着 pod 模板将从它继承的模板继承 Node selector 、Service account 、Image Pull Secrets 、Container templates、 volume。
yaml 根据 yamlMergeStrategy
的值进行合并。
Service account 和 Node selector 在被覆盖时,会完全替代在“父”上找到的任何可能的值。
Container templates 被添加到 podTemplate,其将在“父”模板去匹配一个和它有相同名称的 containerTemplate,从而继承父containerTemplate 的配置。如果未找到匹配的容器模板,则按原样添加 podTemplate。
Volume 继承与 Container templates 完全一样。
Image Pull Secrets 将会被合并(使用在“父”和“当前”模板上定义的所有机密)。
在下面的示例中,我们将从我们之前创建的 pod 模板继承,并且只会覆盖 maven
的版本,以便它使用 jdk-11 代替原来的 jdk-8:
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 {
…
}
}
请注意,我们只需要指定不同的东西。
所以,command
和 arguments
没有指定,因为它们是继承的。
此外,golang容器将按照“父”模板中的定义自动被添加。
字段inheritFrom
可以引用单个 podTemplate 或多个由空格分隔。在后一种情况下,每个模板将按照它们出现在列表中的顺序进行处理*(后面的项目覆盖前面的项目)*。在任何情况下,如果未找到引用的模板,它将被忽略。
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'
false
)示例:
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
:(用于单元测试)控制主机代理应该用来联系 Jenkinsorg.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
: 等待container
step使用的 websocket连接的时间(默认为30
)一个 pod 中可以定义多个容器。其中之一使用 name 自动创建jnlp
,并使用 args 运行 Jenkins JNLP 代理服务,${computer.jnlpmac} ${computer.name}
并将成为充当 Jenkins 代理的容器。
其他容器必须运行一个长时间运行的进程,所以容器不会退出。如果默认入口点或命令只是运行一些东西并退出,那么它应该被像cat
with 之类的东西覆盖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詹金斯日志记录了okhttp3
在DEBUG
水平。
kubectl get pods -o name --selector=jenkins=slave --all-namespaces | xargs -I {} kubectl delete {}
要对此进行调试,您需要设置:
-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
使用 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
和-cert
在jenkins/inbound-agent
上将变得不可用,这就是您必须扩展 docker image 的原因。
Jenkins 的 Docker 映像,已安装插件。基于官方镜像。
docker run --rm --name jenkins -p 8080:8080 -p 50000:50000 -v /var/jenkins_home csanchez/jenkins-kubernetes
示例配置将创建一个有状态集,运行 Jenkins 和持久卷,并使用服务帐户对 Kubernetes API 进行身份验证。
可以使用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
假设您创建了一个名为jenkins
this的 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.100
)kubectl 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 URL
为内部服务 IP,http://10.175.244.232
在这种情况下,通过内部网络进行连接。
Container Cap
为测试设置一个合理的数字,即 3。
添加图像
jenkins/inbound-agent
/home/jenkins/agent
现在可以使用了。
撕下来
kubectl delete namespace/kubernetes-plugin
修改./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 .
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
*这个管道将执行一个简单的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'
}
}
}
}
/**
* 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')
}
}
}