之前写过一篇《Jenkins On Mesos—Jenkins上Mesos Plugin的使用》的博客,说的是Jenkins通过Mesos Plugin来实现slave节点的动态扩展和收缩。如果使用docker的人,不知道kubernetes的话,总是显得有些尴尬,所以最近自己也开始在测试环境使用目前火热的Kubernetes 1.8版(之前是在用Marathon+Mesos那一套)。Marathon、Mesos有的功能,Kubernetes当然也都有。这里就记录一下Kubernetes上实现slave节点动态增减的Kubernetes Plugin配置的一点实践。
首先,需要有一套k8s集群环境。之前已经部好了,这里就不说k8s的搭建,只说Jenkins插件。我的k8s集群是配置了SSL加密证书的,所以Jenkins Server要想能与k8s集群的apiserver通信,需要先通过权限认证。k8s里面有个Service Account的概念,配置使用Service Account来实现给Jenkins Server的授权。
Service Account:
相对于kubectl访问apiserver时用的user account,service account方案是为了给Pod中的process访问k8s API提供的一种身份标识。简单的说就是,通过service account可以实现给Pod中的进程授权访问k8s API。
下面是配置Jenkins Server用到的yaml配置文件:
jenkins-rbac.yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
k8s-app: jenkins
name: jenkins-admin
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: jenkins-admin
labels:
k8s-app: jenkins
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: jenkins-admin
namespace: kube-system
说明:这里是创建了一个名为jenkins-admin的ServiceAccount,直接继承了cluster-admin的权限。还可以根据自己实际情况,创建指定权限的ClusterRole,比如(仅供参考):
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
labels:
k8s-app: jenkins
name: jenkins
rules:
- apiGroups: ["", "extensions", "apps"]
resources:
- nodes
- nodes/proxy
- endpoints
- secrets
- pods
- deployments
- services
verbs: ["get", "list", "watch"]
jenkins.yaml
apiVersion: apps/v1beta2 # for versions before 1.8.0 use apps/v1beta1
kind: Deployment
metadata:
name: jenkins
namespace: kube-system
labels:
k8s-app: jenkins
spec:
replicas: 1
selector:
matchLabels:
k8s-app: jenkins
template:
metadata:
labels:
k8s-app: jenkins
spec:
containers:
- name: jenkins
image: my.example.com/library/centos7.4-ssh-maven-jenkins:2.19
imagePullPolicy: Always
volumeMounts:
- name: jenkins-home
mountPath: /var/jenkins_home
ports:
- containerPort: 8080
- containerPort: 50000
volumes:
- name: jenkins-home
hostPath:
path: /var/www/webapps/jenkins
nodeSelector:
jenkins: "true"
serviceAccount: "jenkins-admin"
注: 这里的Jenkins镜像我是用的自己定制的,可以替换成自己需要的镜像。serviceAccount指定上面创建的jenkins-admin账号。
jenkins-service.yaml
jenkins-service.yaml
---
kind: Service
apiVersion: v1
metadata:
labels:
k8s-app: jenkins
name: jenkins
namespace: kube-system
annotations:
prometheus.io/scrape: 'true'
spec:
ports:
- port: 8080
name: jenkins
nodePort: 31888
targetPort: 8080
- port: 50000
name: jenkins-agent
nodePort: 50000
targetPort: 50000
type: NodePort
selector:
k8s-app: jenkins
说明:Service配置暴露两个端口,一个是Jenkins Server的访问端口,这里nodePort方式指定的是31888;另一个是Jenkins Agent通信用的端口,默认是50000,如果不暴露的话,Jenkins slave节点是无法和Jenkins Server建了连接的。注:nodePort方式默认的端口范围是30000~32767,不包含50000端口的,可以参考这里进行修改。
用上面的yaml配置在k8s上将Jenkins Server运行之后,在Jenkins的【插件管理】里面搜索Kubernetes plugin并安装。
接下来,在【系统管理】-【系统设置】-【新增一个云】-【Kubernetes】配置k8s的插件。
参考上图的说明,进行配置。三个标记到的地方,是需要配置的必须信息,可以根据自己的情况进行配置。要注意的是,这里的Name字段配的名字,后面在配置pipeline的Jenkins任务时,是需要用到的(假设这里使用的名字叫Kubernetes)。然后点【Test Connection】,如果前面的Service Account配置的没问题的话,就会提示“Connection successful”,否则,会有访问apiserver的403权限报错。
到这里,最基本的配置其实就可以了。比如,用pipeline方式创建一个如下的Jenkins构建任务:
pipeline scripts的内容如下:
/* cloud字段指定系统设置里配置的Kubernetes云的名字,本例用的是:Kubernetes */
podTemplate(label: 'mypod', cloud: 'Kubernetes') {
node('mypod') {
stage('Run shell') {
sh 'echo hello world'
}
}
}
这里是一个简单的示例,此插件的更多pipeline写法可以参考: https://github.com/jenkinsci/kubernetes-plugin
点击Jenkins任务构建,观察会自动生成作为slave节点的Pod:
运行成功的Jenkins任务输出日志里,可以看到运行的slave节点名:
以上,就完成了我们希望通过Kubernetes Plugin来完成Jenkins Slave节点动态创建和回收的目的。
上面虽然已经可以实现Jenkins Slave节点的动态创建和回收了,但是使用的是默认的slave镜像:jenkinsci/jnlp-slave。在Jenkins触发任务构建的时候,kubernetest plugin会先去公共的docker仓库获取这个镜像,然后运行容器作为slave节点。实际使用中,我们往往希望用自己预装了一些软件的镜像来做slave节点,来完成我们需要的构建任务。这里就可以参考jenkinsci/jnlp-slave(https://github.com/jenkinsci/docker-jnlp-slave)的制作,来做自己的slave节点镜像。
Jenkins Server和slave节点直接有几种连接方式:ssh连接和jnlp连接。Kubernetes plugin插件用的是jnlp方式。这种方式是通过运行slave.jar,指定Jenkins Server的url参数和secret token参数,来建立连接。
jenkinsci/jnlp-slave镜像是以jenkinsci/slave (https://github.com/jenkinsci/docker-slave)为基础镜像制作的。参考这两个镜像的Dockerfile,做一个自己的:
FROM my.example.com/library/centos7.4-ssh-docker-maven:latest
ENV HOME /home/jenkins
ARG AGENT_WORKDIR=/home/jenkins/agent
COPY slave.jar /usr/share/jenkins/slave.jar
COPY jenkins-slave /usr/local/bin/jenkins-slave
RUN groupadd -g 1000 jenkins \
&& useradd -c "Jenkins user" -d $HOME -u 1000 -g 1000 -m jenkins \
&& chmod 755 /usr/share/jenkins \
&& chmod 644 /usr/share/jenkins/slave.jar
USER jenkins
ENV AGENT_WORKDIR=${AGENT_WORKDIR}
RUN mkdir /home/jenkins/.jenkins && mkdir -p ${AGENT_WORKDIR}
VOLUME /home/jenkins/.jenkins
VOLUME ${AGENT_WORKDIR}
WORKDIR /home/jenkins
ENTRYPOINT ["jenkins-slave"]
说明:这里的基础镜像是用的自己之前制作的基于centos的镜像(默认的jnlp-slave镜像是基于ubuntu系做的)。slave.jar这个包,可以在自己Jenkins Server的访问地址后添加/jnlpJars/slave.jar路径来获取到(如:http://your-jenkins-server/jnlpJars/slave.jar)。jenkins-slave这个脚本,可以在 https://github.com/jenkinsci/docker-jnlp-slave/blob/master/jenkins-slave 下载到。
使用docker build命令将上面的Dockerfile制作成镜像之后,上传到自己的docker私有仓库来供k8s获取就可以了。
要想使用自己的slave节点镜像,在配置pipeline脚本的时候,就需要参数指明了。
podTemplate(label: 'mypod-1', cloud: 'Kubernetes', containers: [
containerTemplate(
name: 'jnlp',
image: 'my.example.com/library/centos7.4-ssh-docker-maven-jenkins-slave:2.19',
alwaysPullImage: true,
args: '${computer.jnlpmac} ${computer.name}'),
],
volumes: [
hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
],)
{
node('mypod-1') {
stage('Task-1') {
stage('show release version') {
sh 'cat /etc/redhat-release '
}
}
}
}
说明: containerTemplate的name属性必须叫jnlp,才能用images指定的镜像替换默认的jenkinsci/jnlp-slave镜像。此外,还要args参数传递两个jenkins-slave运行需要的参数。
如果不使用pipeline类型任务的话,要想使用kubernetes plugin的云构建任务,还需要回到【系统设置】-【云】-【Kubernetes】-【Add Pod Template】里面继续配置
这里有两点需要注意:Labels和Containers下的Name字段的名字配置。Labels名在配置非pipeline任务时,用来指定任务运行的节点。
Name名则必须叫jnlp,才能用指定的Docker image代替默认的jenkinsci/jnlp-slave镜像,否则,你配的不叫jnlp的容器会被运行,但是Kubernetes plugin还是会用默认的jenkinsci/jnlp-slave镜像与Jenkins Server建立连接。