介绍

Jenkins是一个开源的持续集成和持续交付工具,它可以用来自动构建、测试和部署软件。传统jenkins-slave一主多从存在一些缺点:

  • 主Master发生单点故障,整个流程无法使用
  • 每个Slave配置环境不一样,完成不同语言编译打包等操作,维护不方便
  • 资源分配不均匀,有的Slave排队等待,有的Slave空闲
  • 资源有浪费,当Slave处于空闲状态时,也不会完全释放掉资源

Jenkins支持master-agent体系结构(许多build agents/构建代理根据master服务器调度来完成任务),使其具有高度的可伸缩性。Master的工作是安排构建作业,将作业分发给代理实际执行,监视这些代理并获得构建的结果。除此之外,master服务器还可以直接执行构建作业。 代理的任务是构建从master服务器发送过来的作业。作业可以配置在指定类型的代理商运行,如果没有特别需求,Jenkins就简单地选择下一个可用代理。 Jenkins的可伸缩性可以带来许多便利:

  • 服务高可用,并行运行多个构建方案
  • 动态伸缩,自动地挂载和移除代理,节约开销
  • 扩展性好,分配负载

jenkins-k8s

安装

Deployment

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
kubectl create namespace jenkins  # 以下都部署到jenkins下

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: jenkins
  namespace: jenkins
spec:
  template:
    metadata:
      labels:
        app: jenkins
    spec:
      terminationGracePeriodSeconds: 10
      serviceAccount: jenkins
      containers:
      - name: jenkins
        image: jenkins/jenkins:lts
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080
          name: web
          protocol: TCP
        - containerPort: 50000
          name: agent
          protocol: TCP
        resources:
          limits:
            cpu: 1000m
            memory: 1Gi
          requests:
            cpu: 500m
            memory: 512Mi
        livenessProbe:
          httpGet:
            path: /login
            port: 8080
          initialDelaySeconds: 60
          timeoutSeconds: 5
          failureThreshold: 12
        readinessProbe:
          httpGet:
            path: /login
            port: 8080
          initialDelaySeconds: 60
          timeoutSeconds: 5
          failureThreshold: 12
        volumeMounts:
        - name: jenkinshome
          subPath: jenkins
          mountPath: /var/jenkins_home  
        env:
        - name: LIMITS_MEMORY
          valueFrom:
            resourceFieldRef:
              resource: limits.memory
              divisor: 1Mi
        - name: JAVA_OPTS
          value: -Xmx$(LIMITS_MEMORY)m -XshowSettings:vm -Dhudson.slaves.NodeProvisioner.initialDelay=0 -Dhudson.slaves.NodeProvisioner.M
ARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85 -Duser.timezone=Asia/Shanghai
      securityContext:
        fsGroup: 1000
      volumes:
      - name: jenkinshome
        persistentVolumeClaim:
          claimName: jenkinspvc

---
apiVersion: v1
kind: Service
metadata:
  name: jenkins
  namespace: jenkins
  labels:
    app: jenkins
spec:
  selector:
    app: jenkins
  type: NodePort
  ports:
  - name: web
    port: 8080
    targetPort: web
    nodePort: 30002
  - name: agent
    port: 50000
    targetPort: agent

_容器的 /var/jenkinshome 目录挂载到了一个名为 jenkinspvc 的 PVC 对象上面,创建对应的PVC对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: v1
kind: PersistentVolume
metadata:
  name: jenkinspv
spec:
  capacity:
    storage: 20Gi
  accessModes:
  - ReadWriteMany
  persistentVolumeReclaimPolicy: Delete
  nfs:
    server: 192.168.1.230
    path: /nfs/k8s

---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: jenkinspvc
  namespace: jenkins
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 20Gi

给 jenkins 赋予权限,直接给予 sa 绑定一个 cluster-admin 的集群角色权限

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
apiVersion: v1
kind: ServiceAccount
metadata:
  name: jenkins
  namespace: jenkins

---

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: jenkins
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: jenkins
subjects:
  - kind: ServiceAccount
    name: jenkins
    namespace: jenkins

创建jenkins资源对象

1
2
3
4
5
6
7
8
9
kubectl create -f .

deployment.extensions/jenkins created
persistentvolume/jenkinspv created
persistentvolumeclaim/jenkinspvc created
serviceaccount/jenkins created
clusterrole.rbac.authorization.k8s.io/jenkins created
clusterrolebinding.rbac.authorization.k8s.io/jenkins created
service/jenkins created

创建完成之后,会有权限问题,查看日志

1
2
3
kubectl logs jenkins-7f56d657b9-f5bj9 -n jenkins
touch: cannot touch '/var/jenkins_home/copy_reference_file.log': Permission denied
Can not write to /var/jenkins_home/copy_reference_file.log. Wrong volume permissions?

这是因为默认的镜像使用的是 jenkins 这个用户,而我们通过 PVC 挂载到 nfs 服务器的共享数据目录下面却是 root 用户的,所以没有权限访问该目录,只需要在 nfs 共享数据目录下面把我们的目录权限重新分配下即可

1
chown -R 1000 /nfs/k8s/jenkins

在重新创建,就可以任意节点IP:30002正常访问jenkins服务了

jenkins-login

初始化密码可以在nfs共享目录/nfs/k8s/jenkins/secrets/initAdminPassword查看,也可以查看jenkins pod日志查看

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
kubectl logs -f jenkins-7f56d657b9-vk57z -n jenkins

...
Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:

c69048c47b7f43b4967bd01ab844274f

This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
...

然后继续选择推荐安装即可,完成安装后进入jenkins主界面

配置

接下来配置jenkins,让他动态得生成slave的pod 安装 kubernetes 插件 kubernetes-plugin

插件安装之后,进入系统设置,拖到最下方,新增一个云,选择kubernetes,然后配置 jenkins-config > 添加kuberentes URL 测试连接,如果出现 Connection test successful 的提示信息证明 Jenkins 已经可以和 Kubernetes 系统正常通信了,然后填写下方 jenkins URL 地址 ,如果连接失败,可能是权限问题,把创建的 jenkins 的 serviceAccount 对应的 secret 添加到这里的 Credentials 里面

配置 Pod Template,就是配置 Jenkins Slave 运行的 Pod 模板,Labels 非常重要,对于后面执行 Job 的时候需要用到该值。 pod > 默认运行的命令和命令参数是不为空的,如果不清空掉,后续在构建的时候会出现Slave pod连接不上,然后尝试100次连接之后销毁 Pod,然后会再创建一个 Slave Pod 继续尝试连接,无限循环

另外需要在下面挂载两个主机目录,一个是/var/run/docker.sock,该文件是用于 Pod 中的容器能够共享宿主机的 Docker,外一个目录下/root/.kube是为了让我们能够在 Pod 的容器中能够使用 kubectl 工具来访问我们的 Kubernetes 集群。 mountpath

另外还有几个参数需要注意,如下图中的Time in minutes to retain slave when idle,这个参数表示的意思是当处于空闲状态的时候保留 Slave Pod 多长时间,这个参数最好我们保存默认就行了,如果你设置过大的话,Job 任务执行完成后,对应的 Slave Pod 就不会立即被销毁删除 运行 Slave Pod 的时候出现了权限问题,因为 Jenkins Slave Pod 中没有配置权限,所以需要配置上 ServiceAccount,在 Slave Pod 配置的地方点击下面的高级,添加上对应的 ServiceAccount 即可 sa

测试

在 Jenkins 首页点击create new jobs,创建一个测试的任务,输入任务名称,然后我们选择 Freestyle project 类型的任务 注意在下面的 Label Expression 这里要填入前面我们配置的 Slave Pod 中的 Label,这两个地方必须保持一致 lable

然后输入测试脚本 shell

点击保存,立即构建,观察 jenkins slave pod的变化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
kubectl get po -n jenkins -w
NAME                       READY   STATUS    RESTARTS   AGE
jenkins-7f56d657b9-hzrcm   1/1     Running   1          18h
jnlp-cd9dv                 0/1     Pending   0          0s
jnlp-cd9dv                 0/1     Pending   0          0s
jnlp-cd9dv                 0/1     ContainerCreating   0          0s
jnlp-cd9dv                 0/1     ContainerCreating   0          1s
jnlp-cd9dv                 1/1     Running             0          2s
jnlp-cd9dv                 1/1     Terminating         0          6s
jnlp-cd9dv                 1/1     Terminating         0          7s

我们可以看到一个新的pod被创建了,jenkins slave pod slave-pod 任务构建完成,再去集群查看pod,发现已经没有 Slave pod了

1
2
3
kubectl get po -n jenkins -w
NAME                       READY   STATUS    RESTARTS   AGE
jenkins-7f56d657b9-hzrcm   1/1     Running   1          18h