kubernetes ci/cd(一)

基于jenkins的CI/CD安装

        jenkins一个流行的持续集成/发布工具,在Kubernetes使用,持续构建与发布是我们日常工作中必不可少的一个步骤,目前大多公司都采用 Jenkins 集群来搭建符合需求的 CI/CD 流程,然而传统的 Jenkins Slave 一主多从方式会存在一些痛点,比如:主 Master 发生单点故障时,整个流程都不可用了;每个 Slave 的配置环境不一样,来完成不同语言的编译打包等操作,但是这些差异化的配置导致管理起来非常不方便,维护起来也是比较费劲;资源分配不均衡,有的 Slave 要运行的 job 出现排队等待,而有的 Slave 处于空闲状态;最后资源有浪费,每台 Slave 可能是实体机或者 VM,当 Slave 处于空闲状态时,也不会完全释放掉资源。

        提到基于Kubernete的CI/CD,可以使用的工具有很多,比如Jenkins、Gitlab CI已经新兴的drone之类的,我们这里会使用大家最为熟悉的Jenins来做CI/CD的工具。

  • 优点:
    • Jenkins 安装完成了,接下来我们不用急着就去使用,我们要了解下在 Kubernetes 环境下面使用 Jenkins 有什么好处。都知道持续构建与发布是我们日常工作中必不可少的一个步骤,目前大多公司都采用 Jenkins 集群来搭建符合需求的 CI/CD 流程,然而传统的 Jenkins Slave 一主多从方式会存在一些痛点,比如:
      • E 主 Master 发生单点故障时,整个流程都不可用了。
      • E 每个 Slave 的配置环境不一样,来完成不同语言的编译打包等操作,但是这些差异化的配置导致管理起来非常不方便,维护起来也是比较费劲。
      • E 资源分配不均衡,有的 Slave 要运行的 job 出现排队等待,而有的 Slave 处于空闲状态。
      • E 资源有浪费,每台 Slave 可能是物理机或者虚拟机,当 Slave 处于空闲状态时,也不会完全释放掉资源。
    • 正因为这些种种痛点,我们渴望一种更高效更可靠的方式来完成这个 CI/CD 流程,而 Docker 虚拟化容器技术能很好的解决这个痛点,又特别是在 Kubernetes 集群环境下面能够更好来解决上面的问题,下图是基于 Kubernetes 搭建 Jenkins 集群的简单示意图
      img

        可以看到 Jenkins Master 和 Jenkins Slave 以 Pod 形式运行在 Kubernetes 集群的 Node 上,Master 运行在其中一个节点,并且将其配置数据存储到一个 Volume 上去,Slave 运行在各个节点上,并且它不是一直处于运行状态,它会按照需求动态的创建并自动删除。

  • 这种方式的工作流程大致为
    • 当 Jenkins Master 接受到 Build 请求时,会根据配置的 Label 动态创建一个运行在 Pod 中的 Jenkins Slave 并注册到 Master 上,当运行完 Job 后,这个 Slave 会被注销并且这个 Pod 也会自动删除,恢复到最初状态。那么使用这种方式带来了哪些好处呢?
    • E 服务高可用,当 Jenkins Master 出现故障时,Kubernetes 会自动创建一个新的 Jenkins Master 容器,并且将 Volume 分配给新创建的容器,保证数据不丢失,从而达到集群服务高可用。
    • E 动态伸缩,合理使用资源,每次运行 Job 时,会自动创建一个 Jenkins Slave,Job 完成后,Slave 自动注销并删除容器,资源自动释放,而且 Kubernetes 会根据每个资源的使用情况,动态分配 Slave 到空闲的节点上创建,降低出现因某节点资源利用率高,还排队等待在该节点的情况。
    • E 扩展性好,当 Kubernetes 集群的资源严重不足而导致 Job 排队等待时,可以很容易的添加一个 Kubernetes Node 到集群中,从而实现扩展。

1、安装jenkins

1.1、新建一个 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
#cat  jenkins-deployment.yaml
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: jenkins2
namespace: kube-ops
spec:
template:
metadata:
labels:
app: jenkins2
spec:
terminationGracePeriodSeconds: 10
serviceAccountName: jenkins2
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: jenkins2
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.MARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85 -Duser.timezone=Asia/Shanghai
securityContext:
fsGroup: 1000
volumes:
- name: jenkinshome
persistentVolumeClaim:
claimName: opspvc

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

    对象资源都放置在一个名为 kube-ops 的 namespace 下面,所以我们需要添加创建一个 namespace,namespace 请参考namspace章节的具体介绍

1
# kubectl create namespace kube-ops

    这里使用一个名为 jenkins/jenkins:lts 的官方镜像,这是 jenkins 官方的 Docker 镜像,然后也有一些环境变量,当然我们也可以根据自己的需求来定制一个镜像,比如我们可以将一些插件打包在自定义的镜像当中,可以参考文档。我们这里使用默认的官方镜像就行,另外一个还需要注意的是我们将容器的 /var/jenkins_home 目录挂载到了一个名为 opspvc 的 PVC 对象上面,所以我们同样还得提前创建一个对应的 PVC 对象,当然我们也可以使用我们前面的 StorageClass 对象来自动创建:(jenkins-pvc.yaml)

1.2 Jenkins StorageClass 创建

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
# cat jenkins-pvc.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: opspv
namespace: kube-ops
spec:
capacity:
storage: 20Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Delete
nfs:
server: 172.21.16.231
path: /data/jenkins

---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: opspvc
namespace: kube-ops
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 20Gi
  • 创建pvc对象
    1
    # kubectl create -f jenkins-pvc.yaml

    另外这里还需要使用到一个拥有相关权限的 serviceAccount:jenkins2,我们这里只是给jenkins 赋予了一些必要的权限,当然如果你对 serviceAccount 的权限不是很熟悉的话,我们给这个 sa 绑定一个 cluster-admin 的集群角色权限也是可以的,当然这样具有一定的安全风险:(jenkins-rbac.yaml)

1.3 Jenkins serviceAccount

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
# cat jenkins-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins2
namespace: kube-ops

---

kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: jenkins2
namespace: kube-ops
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: RoleBinding
metadata:
name: jenkins2
namespace: kube-ops
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: jenkins2
subjects:
- kind: ServiceAccount
name: jenkins2
  • 创建 rbac 相关的资源对象
    1
    # kubectl create -f jenkins-rbac.yaml

    这里通过 ingress的形式来访问Jenkins 的 web 服务,Jenkins 服务端口为8080,50000 端口为agent,这个端口主要是用于 Jenkins 的 master 和 slave 之间通信使用的。(jenkins-ingress.yaml)

1.4 Jenkins 对外提供访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# cat jenkins-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: jenkins-ingress
namespace: kube-ops
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: ci.xxlaila.cn
http:
paths:
- backend:
serviceName: jenkins2
servicePort: 8080
  • 创建 Jenkins 服务
    1
    # kubectl create -f jenkins-deployment.yaml

创建完成后docke回去拉去镜像,需要等待一会,我们可以通过命令来进行查看jenkins是否部署成功

1
2
3
# kubectl get pods -n kube-ops
NAME READY STATUS RESTARTS AGE
jenkins2-84f476cbb-vz4b2 1/1 Running 0 2d19h

部署完成以后我么可以通过在jenkins-ingress.yaml里面绑定过的域名进行访问,然后进行安装配置:
img

初始化的密码我们可以在 jenkins 的容器的日志中进行查看,也可以直接在 nfs 的共享数据目录中查看

$ cat /data/jenkins/jenkins2/secrets/initialAdminPassword

完成配置,就可以到jenkins的界面,就和我们在vm下安装的jenkins没有任何的区别。
img

2 配置jenkins

接下来我们需要来配置 Jenkins,让他能够动态的生成 Slave 的 Pod,安装jenkins的插件清单

Kubernetes This plugin integrates Jenkins with Kubernetes
2.1 Kubernetes和Jenkins的结合
    点击 系统管理(Manage Jenkins) —> 系统配置(Configure System) —> (拖到最下方)Add a new cloud —> 选择 Kubernetes,然后填写 Kubernetes 和 Jenkins 配置信息。

img

注意 namespace,我们这里填 kube-ops,然后点击Test Connection,如果出现 Connection test successful 的提示信息证明Jenkins 已经可以和 Kubernetes 系统正常通信了,然后下方的 Jenkins URL 地址:http://jenkins2.kube-ops.svc.cluster.local:8080,这里的格式为服务名.namespace.svc.cluster.local:8080,根据上面创建的jenkins的服务名填写,我这里是之前创建的名为jenkins,如果是用上面我们创建的就应该是jenkins2

2.2、配置 Pod Template

    配置 Jenkins Slave 运行的 Pod 模板,命名空间我们同样是用kube-ops,Labels 这里也非常重要,对于后面执行 Job 的时候需要用到该值,然后我们这里使用的是 cnych/jenkins:jnlp 这个镜像,这个镜像是在官方的 jnlp 镜像基础上定制的,加入了 kubectl 等一些实用的工具。
img
img

    另外需要注意我们这里需要在下面挂载一个主机目录,一个是 /var/run/docker.sock,该文件是用于 Pod 中的容器能够共享宿主机的 Docker,这就是说的 docker in docker 的方式,Docker 二进制文件我们已经打包到上面的镜像中了。如果在slave agent中想要访问kubernetes 集群中其他资源,我们还需要绑定之前创建的Service Account 账号:jenkins2

img
    另外还有几个参数需要注意,上图有一个pod寿命代理的空闲存活时间(分),意思是当处于空闲状态的时候保留 Slave Pod多长时间,这个参数最好我们保存默认就行了,如果你设置过大的话,Job 任务执行完成后,对应的 Slave Pod 就不会立即被销毁删除。到这里我们的 Kubernetes Plugin插件就算配置完成了

2.3 Jenkins 测试

    Kubernetes 插件的配置工作完成了,接下来我们就来添加一个 Job 任务,看是否能够在 Slave Pod 中执行,任务执行完成后看 Pod 是否会被销毁在 Jenkins 首页点击create new jobs,创建一个测试的任务,输入任务名称,然后我们选择 Freestyle project 类型的任务
    新建一个job为simple-test,增加一个shell模块,shell模块里面增加简单的echo来测试slave的动态部署:

1
2
3
4
5
echo "测试 Kubernetes 动态生成 jenkins slave"
echo "==============docker in docker==========="
docker info
echo "=============kubectl============="
kubectl get pods -n kube-system

img

现在我们直接在页面点击做成的 Build now 触发构建即可,然后观察 Kubernetes 集群中 Pod 的变化

1
# kubectl get pods -n kube-ops

Kubernetes 界面也会出现jenkins agent的进行pod的进行部署。部署完成后随及删除pod。

img
img

3、Jenkins错误解决

第一次学习安装jenkins踩了很多坑,但是同时也学习了很多的,下面是在k8s上安装jenkins遇到的一些错误:

  • 打开jenkins页面的时候提示dns不能解析,洁面如下图:

img

  • 查看jenkins的日志提示

img

    导致的问题有https、网络连接不通畅,这里我们需要吧https修改为http,需要修改jenkins的配置文件。然后再重新建立jenkins的pod。进入jenkins的目录修改hudson.model.UpdateCenter.xml文件

1
2
3
4
5
6
7
$ cat hudson.model.UpdateCenter.xml
<?xml version='1.1' encoding='UTF-8'?>
<sites>
<site>
<id>default</id>
<url>http://updates.jenkins.io/update-center.json</url>
</site>

    在做k8s的时候一定要用证书,不然后期在做各种服务的时候都会遇到错误,因为docker默认去私有registory要https,kuber-api要https。当然没有使用https都可以换成http,在次重新部署jenkins以后提示系列信息。访问目录没有权限。

img

    进入nfs目录,需要修改下目录权限, 因为当映射本地数据卷时,/home/docker/jenkins目录的拥有者为root用户,而容器中jenkins user的uid为1000

$ sudo chown -R 1000:1000 /data/jenkins

这里吧https解决了还是遇到提示网络不通。下图

img

这里是dns的不能解析的问题,以下排错思路:登陆jenkins的容器里面查看路由是否正确

img

然后在确认容器是否可以联通外网,还是dns不能解析
img

这里ping 114没有问题,ping域名不能解析,说明是dns解析有问题。接着我们在查看容器的dns配置

img

    这里是dns问题。这里不阐述dns,参考第二章k8s dns,jenkins 在执行编译的时候提示: ‘Jenkins’ doesn’t have label ‘jnlp-agent’,在系统配置配置里面进行测试连接k8s 的api提示如下错误

img

  • 添加jenkins的secret认证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# kubectl get secret  -n kube-ops
NAME TYPE DATA AGE
default-token-4gzkv kubernetes.io/service-account-token 3 13d
jenkins2-token-mjnw4 kubernetes.io/service-account-token 3 14m
prometheus-token-84p87 kubernetes.io/service-account-token 3 13d
# kubectl describe secret jenkins2-token-mjnw4 -n kube-ops
Name: jenkins2-token-mjnw4
Namespace: kube-ops
Labels: <none>
Annotations: kubernetes.io/service-account.name: jenkins2
kubernetes.io/service-account.uid: ffced652-2f6c-11e9-98a4-fa163e14c5bd

Type: kubernetes.io/service-account-token

Data
====
ca.crt: 1025 bytes
namespace: 8 bytes
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLW9wcyIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJqZW5raW5zMi10b2tlbi1tam53NCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJqZW5raW5zMiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImZmY2VkNjUyLTJmNmMtMTFlOS05OGE0LWZhMTYzZTE0YzViZCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLW9wczpqZW5raW5zMiJ9.PlPvO_AST4Q6tJJ2i2zGFfufFN1xjWLlHZ5ipTK0aU5CdR49OAropPQhQ0TjLRWf4Z66h847g28OCABmxO1cSG_-8UpwVsohFROTCOjx9Ka3KACmaIkw9Bvihm_lPQlaLykdyXxVDrfI6TobtG0Y5KnKPFj8CjkIFPk5ewTKpOm5pDKVDKu4W_4uOhSnISfLVUvHp8A_ojK_JCVnBBr0Py3UeuEF8vjJES0_yKNxPUtXQq-vkWEZecnAC_x5sfFJTA5aB18sEnxCaeMzgUxzi4IflNxxyVjdZrbq0UdS8llmfnGg5Ur7Zf-lu2ajdOlRdQp6VRPMcQmQaWoHUuoevg

img
img

坚持原创技术分享,您的支持将鼓励我继续创作!
0%