Jenkins on Kubernetes中的Pipeline语法以及自定义Slave的使用方式
Posted 嗡汤圆
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jenkins on Kubernetes中的Pipeline语法以及自定义Slave的使用方式相关的知识,希望对你有一定的参考价值。
从单体应用,到Spring Cloud,再到Kubernetes,我们的平台正在一点一点追赶别人的脚步。但是由于网络隔离等外部因素影响,CI/CD一直是实施比较困难的环节。最近稍微空闲一些,准备一鼓作气做一次整体代码迁移,打通代码仓库和应用部署环境。在技术选型方面,jenkins的入门门槛最低,同时由于我们已经将应用部署在Kubernetes了,因此选择了集群方式的Jenkins部署。主要部署参考文档: 初试 Jenkins 使用 Kubernetes Plugin 完成持续构建与发布
下面我主要对Jenkins部署过程中的关键点、Jenkins使用过程中Pipeline和slave的定制方面分享一下心得。
1 Jenkins在Kubernetes上的部署
首先最好单独新建一个CICD namespace以便将Jenkins和生产应用独立开。
当Jenkins以集群方式部署时候,我们将Master以Deployment的方式部署在Kubernetes中。要点如下:
1.1 建立ServiceAccount
建立ServiceAccount以分配权限
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app: jenkins
name: jenkins-admin
namespace: cicd
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: jenkins-admin
labels:
app: jenkins
subjects:
- kind: ServiceAccount
name: jenkins-admin
namespace: cicd
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
1.2 配置Volume
由于Jenkins Master需要保存一些配置和插件,因此需要一个可以持久化存储的Volume并挂载在容器内,我们采用比较容易部署的nfs方式。
具体方法参考: CentOS 6.8 NFS 文件共享设置的方法
- 找一台磁盘较大的服务器作为文件服务器,并安装nfs服务(nfs-utils, rpcbind)
- 启动nfs服务
- 在集群节点上安装(nfs-utils)以便任意节点均支持nfs挂载
然后,我们在Jenkins的Deployment声明文件中挂载nfs即可 (即yaml中的volumes和volumeMounts)
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: jenkins
namespace: cicd
labels:
app: jenkins
spec:
replicas: 1
template:
metadata:
labels:
app: jenkins
spec:
serviceAccountName: jenkins-admin
containers:
- name: jenkins
image: jenkins/jenkins
imagePullPolicy: Always
volumeMounts:
- name: jenkins-home
mountPath: /var/jenkins_home
readOnly: false
- name: maven-repository
mountPath: /opt/maven/repository
readOnly: false
- name: docker
mountPath: /usr/bin/docker
- name: docker-sock
mountPath: /var/run/docker.sock
ports:
- containerPort: 8080
- containerPort: 50000
volumes:
- name: jenkins-home
nfs:
path: /path/of/your/jenkins_home
server: ip-of-your-nfs-server
- name: maven-repository
nfs:
path: /path/of/your/jenkins_repository
server: ip-of-your-nfs-server
- name: docker
hostPath:
path: /usr/bin/docker
- name: docker-sock
hostPath:
path: /var/run/docker.sock
1.3 将Jenkins暴露出来
内部服务我们声明一个jenkins的ClusterIp,对外用户访问则简单点用NodePort形式暴露即可,这里我们使用32003端口。
apiVersion: v1
kind: Service
metadata:
labels:
app: jenkins
name: jenkins
namespace: cicd
annotations:
prometheus.io/scrape: 'true'
spec:
ports:
- name: jenkins-web
port: 8080
targetPort: 8080
- name: jenkins-agent
port: 50000
targetPort: 50000
selector:
app: jenkins
---
apiVersion: v1
kind: Service
metadata:
name: jenkins-exposed
namespace: cicd
labels:
app: jenkins
spec:
type: NodePort
ports:
- port: 8080
nodePort: 32003
selector:
app: jenkins
1.4 初始化Jenkins
部署上去后,我们打开 nodeip:32003就可以看到jenkins的初始化界面了,按照正常操作,安装必要插件即可。
安装完毕后,我们进入系统配置,在cloud中添加kubernetes,由于Jenkins就运行在kubernetes中,因此Kubernetes地址直接填写内部api-server地址 https://kubernetes.default
即可。这里填写default是因为我们的Jenkins运行在CICD命名空间中,而api-server在default命名空间中。
2 Pipeline的使用
首先先把kubernetes, pipeline等插件安装上,参考的仍然是文章开头的那个博客。
2.1 pipeline雏形
pipeline语法参见: pipeline
我们新建一个任务,项目类型中选择Pipeline即可进入配置页面,最下边的Pipeline选项卡就是填写脚本的地方。
我们一般根据项目需要将构建过程分为几个阶段(Stage),每个阶段都有不同的步骤(Step)。从实践中可以知道,step是可以简化不写的,在最终log时候,每步操作命令都会变成step。形式如下
stage('stage-1')
sh 'echo "step 1.1"'
sh 'echo "step 1.2"'
stage('stage-2')
sh 'echo "step 2.1"'
比如我们的项目分为 checkout, maven build, docker build, docker push, deploy 5个阶段,我们一次写上5个stage即可。
2.2 自定义Slave
如果不自行声明,默认Jenkins创建的slave Pod 仅包含一个名为jnlp
的容器,这个容器支持基本的echo, cp, mv, ls, svn, git的操作,可以用来检出代码。
注意: 这里必须包含一个名字叫jnlp的容器,如果用户自定义容器不叫jnlp,则Jenkins创建的Pod会包含两个容器,一个是用户自定义容器,一个是Jnlp。
但是,如果我们需要进行maven操作, docker操作,则基本的jnlp容器就不知吃了,这个时候,我们可以在Slave Pod中添加各个功能对应的基础容器接口。容器可以自己基于基础镜像编写Dockerfile来构建,也可以在Dockerhub上搜索可以直接使用的镜像。
容器Slave的自定义方式,使用podTemplate创建Slave Pod。 如果对Kubernetes熟悉,这里的声明方式和Pod的yaml声明结构几乎一致。参考: kubernetes-plugin
比如我这里需要svn检出、maven构建、docker构建操作,我们就引入maven和docker基础镜像即可。总体pipeline结构变为
podTemplate()
stage()
stage()
然后根据我们的实际情况填写参数:
def label = "pipeline-worker-$PROJECT"
podTemplate(label: label, cloud: 'my-kubernetes-cloud',
containers: [
containerTemplate(name: 'jnlp',
ttyEnabled: true,
image: 'jenkins/jnlp-slave:alpine', imagePullPolicy: 'Always'),
containerTemplate(name: 'jnlp-mvn',
ttyEnabled: true,
command: 'cat',
image: 'jenkins/jnlp-slave-maven:v1.5', imagePullPolicy: 'Always'),
containerTemplate(name: 'jnlp-docker',
ttyEnabled: true,
command: 'cat',
image: 'jenkins/jnlp-slave-docker:v1.6', imagePullPolicy: 'Always')
],
volumes: [
hostPathVolume(hostPath: '/var/run/docker.sock', mountPath:'/var/run/docker.sock'),
hostPathVolume(hostPath: '/etc/docker/daemon.json', mountPath:'/etc/docker/daemon.json')
]
)
stage()
stage()
这里各个容器的image我们隐藏了私服的具体细节,用户根据实际需要选择即可。另外podTemplate的参数中cloud就是我们在初始化Kubernetes时候填写的kubernetes cloud名称。
由于我们用了docker,采用docker in docker方式运行(即在容器中运行docker命令)执行我们构建步骤中的docker build和docker push,因此我们将容器中的docker和宿主机的docker的sock以及daemon文件进行连接,通过hostPathVolume的方式挂载在容器中即可。
2.3 代码生成器
对于svn检出、docker login私服的操作,如果我们直接把密码写在pipeline中很不安全,我们可以借助Jenkins的Pipeline Syntax帮助我们生成安全的检出代码和docker操作代码,这样我们的用户名口令就会保存在Jenkins的Credentials中。
1) 检出SVN的代码
- 根据提示操作并单机Pipeline Syntax,选择checkout: Check out from version control
- 填写svn地址,添加Credentials,并选择好Credentials
- 点击Generate Pipeline Script
- 将生成的代码拷贝回pipeline对应的stage中即可
checkout([$class: 'SubversionSCM', additionalCredentials: [], excludedCommitMessages: '', excludedRegions: '', excludedRevprop: '', excludedUsers: '', filterChangelog: false, ignoreDirPropChanges: false, includedRegions: '', locations: [[cancelProcessOnExternalsFail: true, credentialsId: '588162ba-56c0-4c9d-8cf6-e7c12d437a44', depthOption: 'infinity', ignoreExternalsOption: true, local: '.', remote: 'svn://xx.xxx.xx.xx/your/svn/repo/path']], quietOperation: true, workspaceUpdater: [$class: 'UpdateUpdater']])
2) 用Credentials隐藏pipeline中的用户名和口令
比如docker登录私服的时候我们需要docker login -U xxx -P xxx,这时候我们就可以通过Cerdentials隐藏用户名密码
- Pipeline Syntax 中选择 withCredentials: Bind credentials to variables
- 选择Add, Username and password(separated)
- 填入用户名和密码
- 点击Generate Pipeline Script
- 将需要用户名口令的操作写在生成的代码中,并使用变量
$USERNAME
和$PASSWORD
代替原来的明文即可,如下演示了如何进行 docker login操作。
withCredentials([usernamePassword(credentialsId: 'd23f551a-1e55-4bea-a9fb-5098a9ac291d', passwordVariable: 'PASSWORD', usernameVariable: 'USERNAME')])
sh "docker login $DOCKER_URL -u $USERNAME -p $PASSWORD"
2.4 不同阶段切换不同的功能容器
上文中我们引入了 jnlp, jnlp-mvn, jnlp-docker三个容器共同组成了Slave Pod, 现在就可以在不同的操作中使用不同的容器进行构建了。用container()
包住stage内部的代码即可
stage('svn checkout')
container('jnlp')
// do svn check out
stage('maven build')
container('jnlp-mvn')
// do maven build stuff
stage('docker build')
container('jnlp-docker')
// do docker build, docker push stuff
2.5 pipeline外部变量
在默认pipeline中,点击“build”,pipeline就会开始工作,但是缺少必要的外部交互,比如在build前需要用户进行一些参数输入的话,就需要外部参数的支持。
在pipeline的设置页面中勾选"参数化构建过程",就可以添加外部变量了,可以选择:文本、选项、布尔值等等不同形式的变量类型。对应的在脚本中使用"$"引用即可。
此时 “build"按钮会变为"Build With Parameters”。点击后会先让用户设置参数,然后才开始构建。
效果
构建结构会分阶段展示出来
以上是关于Jenkins on Kubernetes中的Pipeline语法以及自定义Slave的使用方式的主要内容,如果未能解决你的问题,请参考以下文章
从 kubernetes 插件 / jenkins 中的 gcr 拉取 docker 图像的问题
kubernetes 基于jenkins spinnaker的ci/cd实践一增加制品镜像扫描
kubernetes 基于jenkins spinnaker的ci/cd实践一增加制品镜像扫描