0、前言
首先,我們考慮個問題,為何需要jenkins slave?其實在生產環境中,如果用單master,除非你單機器的配置特別高並且構建次數不多情況下,可以不考慮使用slave,但是,在構建次數上百次並且jenkins master運行在kubernetes環境中,借助kubernetes的靈活性,強烈推薦使用slave,master負責自動創建Slave Pod,然后將任務推送給Slave Pod,任務執行完畢后,Slave Pod會自動被回收/銷毀。
創建slave流程圖:
1、Jenkins部署
jenkins交付進kubernetes
1.准備鏡像文件
$ docker pull jenkins/jenkins:2.204.1
$ docker tag a3f949e5ebfd harbor.od.com/infra/jenkins:v2.204.1
$ docker push harbor.od.com/infra/jenkins:v2.204.1
2.資源配置文件
- rbac
$ vi /data/k8s-yaml/jenkins_slave/rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins
namespace: infra
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: jenkins
rules:
- apiGroups: ["extensions", "apps"]
resources: ["deployments"]
verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
- apiGroups: [""]
resources: ["services"]
verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["create","delete","get","list","patch","update","watch"]
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create","delete","get","list","patch","update","watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get","list","watch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: jenkins
namespace: infra
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: jenkins
subjects:
- kind: ServiceAccount
name: jenkins
namespace: infra
Note:Jenkins會自動創建slave pod,所以需要給jenkins綁定Kubernetes的RBAC權限
- Deployment
$ vi /data/k8s-yaml/jenkins_slave/dp.yaml
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: jenkins
namespace: infra
labels:
name: jenkins
spec:
replicas: 1
selector:
matchLabels:
name: jenkins
template:
metadata:
labels:
app: jenkins
name: jenkins
spec:
serviceAccount: jenkins
volumes:
- name: data
nfs:
server: hdss7-200.host.com
path: /data/nfs-volume/jenkins_home
containers:
- name: jenkins
image: harbor.od.com/infra/jenkins:v2.204.1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
name: web
protocol: TCP
- containerPort: 50000
name: agent
protocol: TCP
env:
- name: JAVA_OPTS
value: "-Xms1G -Xmx1G -XX:PermSize=512m -XX:MaxPermSize=1024m -Duser.timezone=Asia/Shanghai"
- name: TRY_UPGRADE_IF_NO_MARKER
value: "true"
volumeMounts:
- name: data
mountPath: /var/jenkins_home
imagePullSecrets:
- name: harbor
securityContext:
runAsUser: 0
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
revisionHistoryLimit: 7
progressDeadlineSeconds: 600
- Service
$ vi /data/k8s-yaml/jenkins_slave/svc.yaml
kind: Service
apiVersion: v1
metadata:
name: jenkins
namespace: infra
spec:
ports:
- name: web
port: 80
targetPort: 8080
protocol: TCP
- name: agent
port: 50000
targetPort: 50000
protocol: TCP
selector:
app: jenkins
- Ingress
$ vi /data/k8s-yaml/jenkins_slave/ingress.yaml
kind: Ingress
apiVersion: extensions/v1beta1
metadata:
name: jenkins
namespace: infra
spec:
rules:
- host: jenkins.od.com
http:
paths:
- path: /
backend:
serviceName: jenkins
servicePort: 80
Note:這里的Service對外暴露了80以及50000端口,80作為Jenkins Server Web端口,50000位創建的Jenkins Salve和Master建立通信連接的默認端口,如果該端口不暴露的話,Slave就無法和Master建立連接。
3.應用資源配置清單
$ kubectl apply -f http://k8s-yaml.od.com/jenkins_slave/rbac.yaml
$ kubectl apply -f http://k8s-yaml.od.com/jenkins_slave/dp.yaml
$ kubectl apply -f http://k8s-yaml.od.com/jenkins_slave/svc.yaml
$ kubectl apply -f http://k8s-yaml.od.com/jenkins_slave/ingress.yaml
2、配置jenkins動態slave
初始化jenkins后,需要安裝kubernetes 插件。
1.安裝插件完成之后,點擊 Manage Jenkins —> Configure System —> (拖到最下方)Add a new cloud —> 選擇 Kubernetes,然后填寫 Kubernetes 和 Jenkins 配置信息
填寫kubernetes集群內部訪問地址:https://kubernetes.default.svc.cluster.local,點擊Test Connection,如果出現 Connection test successful 的提示信息證明 Jenkins 已經可以和 Kubernetes 系統正常通信了,然后下方的 Jenkins URL 地址:http://jenkins.infra.svc.cluster.local:80
為何我這里使用k8s svc的名稱使用方式,為了方便通過DNS記錄能夠解析成該Service的Cluster IP。
2.創建Pipeline動態構建測試
創建一個類型為Pipeline類型Job命名為test-slave,然后在Pipeline腳本填寫下面一個簡單的測試腳本
def label = "jenkins-slave-${UUID.randomUUID().toString()}"
podTemplate(label: label, cloud: 'kubernetes') {
node(label) {
stage('Run shell') {
sh 'sleep 10s'
sh 'echo hello world.'
}
}
}
3.點擊構建流水線
可以看到在jenkins的namespaces下自動創建了對應的agent pod 相當於就是一個jenkins 的node 當任務執行完成這個pod會自動退出這個pod默認會去pull一個jenkins/jnlp-slave:x.xx-xx-alpine
的鏡像
[root@hdss7-21 ~]# kubectl get pods -n infra
NAME READY STATUS RESTARTS AGE
jenkins-77b9c47874-qjgfd 1/1 Running 1 13h
jenkins-slave-c07daa7b-31ef-41ea-825e-05c9c721edad-sb7h6-lpgwv 1/1 Running 0 18s
3、dubbo服務構建
我們在構建dubbo服務的時候,需要編譯dubbo后制作鏡像並推送到harbor,這時候就要用到maven和docker命令,所以需要自己去構建基礎鏡像,然后在pipeline里調用
注意:這里並不實現如何將dubbo服務交付到k8s,而是演示實現動態jenkins創建slave去構建dubbo服務
3.1、制作dubbo鏡像底包
底包需要有通用性,所有的dubbo微服務制作鏡像時都會引用這個底包來制作,該底包主要實現一些功能,例如jmx監控(jmx_javaagent),和dubbo微服務的啟動腳本,以及依賴的jdk環境
1.准備鏡像(jdk環境)
$ docker pull stanleyws/jre8:8u112
$ docker tag stanleyws/jre8:8u112 harbor.od.com/public/jre:8u112
$ docker push harbor.od.com/public/jre:8u112
2.自定義Dockerfile
- /data/dockerfile/jre8/Dockerfile
FROM harbor.od.com/public/jre:8u112
RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&\
echo 'Asia/Shanghai' >/etc/timezone
ADD config.yml /opt/prom/config.yml
ADD jmx_javaagent-0.3.1.jar /opt/prom/
WORKDIR /opt/project_dir
ADD entrypoint.sh /entrypoint.sh
CMD ["/entrypoint.sh"]
- config.yml(這是jmx_agent讀取的配置文件)
---
rules:
- pattern: '.*'
- jmx_javaagent-0.3.1.jar
# 采集jvm監控數據的jar包
$ wget https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.3.1/jmx_prometheus_javaagent-0.3.1.jar
- entrypoint.sh(不要忘了給該文件執行權限)
#!/bin/sh
M_OPTS="-Duser.timezone=Asia/Shanghai -javaagent:/opt/prom/jmx_javaagent-0.3.1.jar=$(hostname -i):${M_PORT:-"12346"}:/opt/prom/config.yml"
C_OPTS=${C_OPTS}
JAR_BALL=${JAR_BALL}
exec java -jar ${M_OPTS} ${C_OPTS} ${JAR_BALL}
按照上面的java啟動選項解釋
-agentpath:<pathname>[=<options>] // 加載java代理
-Duser.timezone:<timezone> // 指定時區
C_OPTS=${C_OPTS} // 額外的啟動參數,默認為空,可在k8s資源配置清單中添加額外參數
JAR_BALL=${JAR_BALL} // 啟動的jar包名字,在k8s資源配置清單中指定
3.制作dubbo服務docker底包
$ ls -l
total 372
-rw-r--r-- 1 root root 405 Jan 16 15:26 Dockerfile
-rw-r--r-- 1 root root 41 Jan 16 15:28 config.yaml
-rwxr-xr-x 1 root root 234 Jan 16 15:37 entrypoint.sh
-rw-r--r-- 1 root root 367417 May 10 2018 jmx_prometheus_javaagent-0.3.1.jar
$ docker build . -t harbor.od.com/base/jre8:8u112
$ docker push harbor.od.com/base/jre8:8u112
3.2、制作slave基礎鏡像
我們現在有個dubbo項目,需要用到maven構建項目,然后通過Docker打包成鏡像並推送到Harbor,所以需要用到兩個鏡像,Maven以及Docker鏡像。
3.2.1、Maven鏡像
該鏡像主要用於構建java應用,這里就選擇:maven:v3.3.9-jdk8
准備鏡像文件(推送到本地倉庫)
$ docker pull maven:3.3.9-jdk-8-alpine
$ docker tag dd9d4e1cd9db harbor.od.com/public/maven:v3.3.9-jdk8
$ docker push harbor.od.com/public/maven:v3.3.9-jdk8
3.2.2、Docker鏡像
該鏡像主要用於將dubbo項目打包成鏡像並推送到harbor,但需要定制化一下鏡像,需要將一台已經實現docker login 登錄到harbor倉庫所生成的配置文件,路徑為:/root/.docker/config.json
,與原始Docker鏡像一起打包生成新的Docker鏡像並推送到本地倉庫。
1.准備鏡像文件
$ docker pull docker:19.03
$ docker tag e036013d6d10 harbor.od.com/public/docker:v19.03
$ docker push harbor.od.com/public/docker:v19.03
2.Dockerfile
- vim /data/dockerfile/docker/Dockerfile
FROM harbor.od.com/public/docker:v19.03
USER root
ADD config.json /root/.docker/config.json
3.將/root/.docker/config.json
文件拷貝到Dockerfile目錄下
{
"auths": {
"harbor.od.com": {
"auth": "YWRtaW46SGFyYm9yMTIzNDU="
}
},
"HttpHeaders": {
"User-Agent": "Docker-Client/19.03.6 (linux)"
}
}
需要通過該文件才能訪問到harbor倉庫
4.制作並推送鏡像(推送到本地倉庫)
$ docker build ./ -t harbor.od.com/public/docker:v19.03
$ docker push harbor.od.com/public/docker:v19.03
3.3、添加git key
我們這里使用到了Jenkins的Git插件來拉取代碼,所以需要先創建一堆密鑰,然后將公鑰添加到git倉庫,再將私鑰添加到jenkins憑證,如下:
3.4、創建dubbo流水線
1.添加參數化構建
-
The String Parameter:app_name
Describe:項目名稱,例如dubbo-service
-
String Parameter:image_name
Describe:docker鏡像名稱,格式:<倉庫名>/<鏡像名> 例如:app/dubbo-demo-service
-
String Parameter:git_repo
Describe:項目所在的git中央倉庫的地址:如https://gitee.com/jasonminghao/dubbo-demo-service.git
-
String Parameter:git_ver
Describe:項目所在git中央倉庫對應項目的分支或者版本號,例如master分支:*/master,commit ID:903b4e6
-
String Parameter:image_ver
Describe:鏡像版本和git_ver一致即可,但是不要有任何的特殊符號
-
String Parameter:add_tag
Describe:docker鏡像標簽的一部分,日期時間戳,例如:20200121_1734
-
String Parameter:target_dir
Default:./target
Describe:編譯項目的目錄,產生jar/war包所在的目錄
-
String Parameter:mvn_cmd
Default:mvn clean package -Dmaven.test.skip=true
Describe:執行mvn編譯所用的命令
-
Choice Parameter:base_image
Choice Value: base/jre8:8u112
Describe:項目使用的docker底包鏡像
2.pipeline
podTemplate(cloud:'kubernetes',containers: [
containerTemplate(
name: 'maven',
image: 'harbor.od.com/public/maven:v3.3.9-jdk8',
ttyEnabled: true,
command: 'cat'),
containerTemplate(
name: 'docker',
ttyEnabled: true,
image: 'harbor.od.com/public/docker:v19.03'),
],
volumes: [
nfsVolume(mountPath: '/root/.m2', readOnly: false, serverAddress: 'hdss7-200.host.com', serverPath: '/data/nfs-volume/maven_repo/'),
hostPathVolume(hostPath: '/run/docker.sock', mountPath: '/run/docker.sock')
]
){
node(POD_LABEL) {
stage('Get a Maven project') {
// 從git倉庫拉取代碼
checkout([$class: 'GitSCM', branches: [[name: "${params.git_ver}"]], browser: [$class: 'GitLab', repoUrl: ''], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'git', name: 'git', refspec: 'refs/changes/*:refs/changes/*', url: "${params.git_repo}"]]])
container('maven') {
stage('Build a Maven project') {
// 執行maven構建
sh "${params.mvn_cmd}"
}
}
}
stage('Docker build') {
container('docker') {
stage('create dir') {
// /tmp目錄創建一個臨時用於構建鏡像的工作目錄,將jar包移動到該目錄
sh "mkdir /tmp/${params.app_name}"
sh "cd ${params.target_dir} && mkdir /tmp/${params.app_name}/project_dir && mv *.jar /tmp/${params.app_name}/project_dir"
}
stage('docker build image') {
// 動態生成Dockerfile,構建鏡像並推送到harbor
sh "cd /tmp/${params.app_name}/ && ls -lha"
sh """
echo "FROM harbor.od.com/${params.base_image}" >/tmp/${params.app_name}/Dockerfile
echo "ADD ./project_dir /opt/project_dir" >>/tmp/${params.app_name}/Dockerfile
"""
sh "cd /tmp/${params.app_name}/ && pwd && docker build ./ -t harbor.od.com/${params.image_name}:${params.git_ver}_${params.add_tag} && docker push harbor.od.com/${params.image_name}:${params.git_ver}_${params.add_tag} "
}
}
}
}
}
podTemplate只適用於pipeline
3.5、執行流水線構建
1.填寫對應參數
2.查看infra
名稱空間下的Pod
[root@hdss7-200 harbor]# kubectl get pods -n infra
NAME READY STATUS RESTARTS AGE
apollo-portal-57bc86966d-4tr6w 1/1 Running 8 37h
dubbo-demo-slave-16-trktm-8x2d7-bw5dr 3/3 Running 0 53s
dubbo-monitor-555c94f4b7-85plg 1/1 Running 32 7d14h
jenkins-75fbb46546-f5ltc 1/1 Running 6 18h
可以看到jenkins slave會以在jenkins創建的項目名為命名來創建pod
3.構建結果