赞
踩
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 <none> 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 创建有权限访问和控制(创建、更新、删除)集群资源的账户及其授权信息。
我的是在我的笔记本的桌面上创建了一份。
之后按照下图方式创建即可
需要选择 Secret file
选择准备好的配置文件
点击右侧的 连接测试, 成功连接后 在 凭据 下方返回当前集群的版本号。
最后点击 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。
点击 Pod Template details
假如我们只使用默认的镜像,配置信息如下:
接下来我们创建一个自由风格的任务,构建步骤仅仅执行一下命令 java -version
构建
当点击构建时,此次构建后经历如下当阶段。
此时 Pod 资源还没有被创建
此时 Pod 资源被创建,但是还没有准备好
Pod 已经准备好,正在构建中
这个过程有可能会很快,因此不一定能及时看到。
已经构建完成
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: <none>
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')
}
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。