查看原文
其他

Jenkins Pipeline 流水线部署 Kubernetes 应用

点击关注 👉 DevOps技术栈 2022-11-10
原文链接:https://blog.k8s.li/jenkins-with-kubernetes.html
背景

虽然云原生时代有了 JenkinsXDroneTekton 这样的后起之秀,但 Jenkins 这样一个老牌的 CI/CD 工具仍是各大公司主流的使用方案。比如我司的私有云产品打包发布就是用这老家伙完成的。然而传统的 Jenkins Slave 一主多从方式会存在一些痛点,比如:

  • 每个 Slave 的配置环境不一样,来完成不同语言的编译打包等操作,但是这些差异化的配置导致管理起来非常不方便,维护起来也是比较费劲

  • 资源分配不均衡,有的 Slave 要运行的 job 出现排队等待,而有的 Slave 处于空闲状态

  • 资源有浪费,每台 Slave 可能是物理机或者虚拟机,当 Slave 处于空闲状态时,也不会完全释放掉资源。

正因为上面的 Jenkins slave 存在这些种种痛点,我们渴望一种更高效更可靠的方式来完成这个 CI/CD 流程,而 Docker 虚拟化容器技术能很好的解决这个痛点,又特别是在 Kubernetes 集群环境下面能够更好来解决上面的问题,下图是基于 Kubernetes 搭建 Jenkins slave 集群的简单示意图:

从图上可以看到 Jenkins Master 时以 docker-compose 的方式运行在一个节点上。Jenkins Slave 以 Pod 形式运行在 Kubernetes 集群的 Node 上,并且它不是一直处于运行状态,它会按照需求动态的创建并自动删除。这种方式的工作流程大致为:当 Jenkins Master 接受到 Build 请求时,会根据配置的 Label 动态创建一个运行在 Pod 中的 Jenkins Slave 并注册到 Master 上,当运行完 Job 后,这个 Slave 会被注销并且这个 Pod 也会自动删除,恢复到最初状态。

那么我们使用这种方式带来了以下好处:

  • 动态伸缩,合理使用资源,每次运行 Job 时,会自动创建一个 Jenkins Slave,Job 完成后,Slave 自动注销并删除容器,资源自动释放,而且 Kubernetes 会根据每个资源的使用情况,动态分配 Slave 到空闲的节点上创建,降低出现因某节点资源利用率高,还排队等待在该节点的情况。

  • 扩展性好,当 Kubernetes 集群的资源严重不足而导致 Job 排队等待时,可以很容易的添加一个 Kubernetes Node 到集群中,从而实现扩展。

上面的大半段复制粘贴自 基于 Jenkins 的 CI/CD (一) 🤐

kubernetes 集群


关于 kubernetes 集群部署,使用 kubeadm 部署是最为方便的了,可参考我很早之前写过的文章《使用 kubeadm 快速部署体验 K8s》,在这里只是简单介绍一下:

  • 使用 kubeadm 来创建一个单 master 节点的 kubernets 集群


1
root@jenkins:~ # kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=192.168.20.11


  • 集群成功部署完成之后会有如下提示:


1
2
3
4
5
Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config


  • 查看节点状态和 pod 都已经正常


1
2
3
4
5
6
7
8
9
10
11
12
root@jenkins:~ # kubectl get pod -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-f9fd979d6-9t6qp 1/1 Running 0 89s
kube-system coredns-f9fd979d6-hntm8 1/1 Running 0 89s
kube-system etcd-jenkins 1/1 Running 0 106s
kube-system kube-apiserver-jenkins 1/1 Running 0 106s
kube-system kube-controller-manager-jenkins 1/1 Running 0 106s
kube-system kube-proxy-8pzkz 1/1 Running 0 89s
kube-system kube-scheduler-jenkins 1/1 Running 0 106s
root@jenkins:~ # kubectl get node
NAME STATUS ROLES AGE VERSION
jenkins Ready master 119s v1.19.8


  • 去除 master 节点上的污点,允许其他的 pod 调度在 master 节点上,不然后面 Jenkins 所创建的 pod 将无法调度在该节点上。


1
kubectl taint nodes $(hostname) node-role.kubernetes.io/master:NoSchedule-


Jenkins master


至于 Jenkins master 的部署方式,个人建议使用 docker-compose 来部署。运行在 kubernetes 集群集群中也没什么毛病,可以参考 基于 Jenkins 的 CI/CD (一) 这篇博客。但从个人运维踩的坑来讲,还是将 Jenkins master 独立于 kubernetes 集群部署比较方便😂。

  • docker-compose.yaml


1
2
3
4
5
6
7
8
9
10
11
12
version: '3.6'
services:
jenkins:
image: jenkins/jenkins:2.263.4-lts-slim
container_name: jenkins
restart: always
volumes:
- ./jenkins_home:/var/jenkins_home
network_mode: host
user: root
environment:
- JAVA_OPTS=-Duser.timezone=Asia/Shanghai


  • 使用 docker-compose up 来启动,成功启动后会有如下提示,日志输出的密钥就是 admin 用户的默认密码,使用它来第一次登录 Jenkins。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
jenkins | 2021-03-06 02:22:31.741+0000 [id=41] INFO jenkins.install.SetupWizard#init:
jenkins |
jenkins | *************************************************************
jenkins | *************************************************************
jenkins | *************************************************************
jenkins |
jenkins | Jenkins initial setup is required. An admin user has been created and a password generated.
jenkins | Please use the following password to proceed to installation:
jenkins |
jenkins | 4c2361968cd94323acdde17f7603d8e1
jenkins |
jenkins | This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
jenkins |
jenkins | *************************************************************
jenkins | *************************************************************
jenkins | *************************************************
************


  • 登录上去之后,建议选择 选择插件来安装,尽可能少地安装插件,按需安装即可。


  • 在 Jenkins 的插件管理那里安装上 kubernetes 插件



  • 接下来开始配置 Jenkins 大叔如何与 kubernetes 船长手牵手🧑‍🤝‍🧑 :-)。配置 kubernets 的地方是在 系统管理 > 节点管理 > Configure Clouds。点击 Add a new cloud,来添加一个 kubernetes 集群。


  • 配置连接参数


参数说明
名称kubernetes也是后面 pod 模板中的 cloud 的值
凭据kubeconfig 凭据 id使用 kubeconfig 文件来连接集群
Kubernetes 地址默认即可
Use Jenkins Proxy默认即可
Kubernetes 服务证书 key默认即可
禁用 HTTPS 证书检查默认即可
Kubernetes 命名空间默认即可
WebSocket默认即可
Direct Connection默认即可
Jenkins 地址http://jenkins.k8s.li:8080Jenkins pod 连接 Jenkins master 的 URL
Jenkins 通道50000Jenkins JNLP 的端口,默认为 50000
Connection Timeout默认即可Jenkins 连接 kubernetes 超时时间
Read Timeout默认即可
容器数量默认即可Jenkins pod 创建的最大数量
Pod Labels默认即可Jenkins pod 的 lables
连接 Kubernetes API 的最大连接数默认即可
Seconds to wait for pod to be running默认即可等待 pod 正常 running 的时间


  • 在 Jenkins 的凭据那里添加上 kubeconfig 文件,凭据的类型选择为 Secret file,然后将上面使用 kubeadm 部署生成的 kubeconfig 上传到这里。


  • 点击连接测试,如果提示 Connected to Kubernetes v1.19.8 就说明已经成功连接上了 kubernetes 集群。


  • 关于 pod 模板

其实就是配置 Jenkins Slave 运行的 Pod 模板,个人不太建议使用插件中的模板去配置,推荐将 pod 的模板放在 Jenkinsfile 中,因为这些配置与我们的流水线紧密相关,把 pod 的配置存储在 Jenkins 的插件里实在是不太方便;不方便后续的迁移备份之类的工作;后续插件升级后这些配置也可能会丢失。因此建议将 pod 模板的配置直接定义在 Jenkinsfile 中,灵活性更高一些,不会受 Jenkins 插件升级的影响。总之用代码去管理这些 pod 配置维护成本将会少很多。

Jenkinsfile


  • 流水线 Jenkinsfile,下面是一个简单的任务,用于构建 webp-server-go 项目的 docker 镜像。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Kubernetes pod template to run.
def JOB_NAME = "${env.JOB_NAME}"
def BUILD_NUMBER = "${env.BUILD_NUMBER}"
def POD_NAME = "jenkins-${JOB_NAME}-${BUILD_NUMBER}"
podTemplate(
# 这里定义 pod 模版
)
{ node(POD_NAME) {
container(JOB_NAME) {
stage("Build image") {
sh """#!/bin/bash
git clone https://github.com/webp-sh/webp_server_go /build
cd /build
docker build -t webps:0.3.2-rc.1 .
"""
}
}
}
}


  • pod 模版如下,将模板的内容复制粘贴到上面的 Jenkinsfile 中。在容器中构建镜像,我们使用 dind 的方案:将 pod 所在宿主机的 docker sock 文件挂载到 pod 的容器内,pod 容器内只要安装好 docker-cli 工具就可以像宿主机那样直接使用 docker 了。


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
podTemplate(
cloud: "kubernetes",
namespace: "default",
name: POD_NAME,
label: POD_NAME,
yaml: """
apiVersion: v1
kind: Pod
spec:
containers:
- name: ${JOB_NAME}
image: "debian:buster-docker"
imagePullPolicy: IfNotPresent
tty: true
volumeMounts:
- name: dockersock
mountPath: /var/run/docker.sock
- name: jnlp
args: ["\$(JENKINS_SECRET)", "\$(JENKINS_NAME)"]
image: "jenkins/inbound-agent:4.3-4-alpine"
imagePullPolicy: IfNotPresent
volumes:
- name: dockersock
hostPath:
path: /var/run/docker.sock
""",
)


  • 构建 debian:buster-docker 镜像,使用它来在 pod 的容器内构建 docker 镜像,使用的 Dockerfile 如下:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FROM debian:buster
RUN apt update \
&& apt install -y --no-install-recommends \
vim \
curl \
git \
make \
ca-certificates \
gnupg \
&& rm -rf /var/lib/apt/lists/*
RUN curl -fsSL "https://download.docker.com/linux/debian/gpg" | apt-key add -qq - >/dev/null \
&& echo "deb [arch=amd64] https://download.docker.com/linux/debian buster stable" > /etc/apt/sources.list.d/docker.list \
&& apt update -qq \
&& apt-get install -y -qq --no-install-recommends docker-ce-cli \
&& rm -rf /var/lib/apt/lists/*


定义好 jenkinsfile 文件并且构建好 pod 模板中的镜像后,接下来我们开始使用它来创建流水线任务。

流水线


  • 在 Jenkins 上新建一个任务,选择任务的类型为 流水线



  • 将定义好的 Jenkinsfile 内容复制粘贴到流水线定义 Pipeline script 中并点击保存。在新建好的 Job 页面点击 立即构建 来运行流水线任务。


  • 在 kubernetes 集群的机器上使用 kubectl 命令查看 pod 是否正常 Running


1
2
3
root@jenkins:~ # kubectl get pod
NAME READY STATUS RESTARTS AGE
jenkins-webps-9-bs78x-5x204 2/2 Running 0 66s


  • Job 正常运行并且状态为绿色表明该 job 已经成功执行了。



  • 在 kubernetes 集群机器上查看 docker 镜像是否构建成功


1
2
root@jenkins:~ # docker images | grep webps
webps 0.3.2-rc.1 f68f496c0444 20 minutes ago 13.7MB


踩坑


  • pod 无法正常 Running


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
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline
[Pipeline] podTemplate
[Pipeline] {
[Pipeline] node
Created Pod: kubernetes default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r][Scheduled] Successfully assigned default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r to jenkins
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r][Pulling] Pulling image "debian:buster"
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r][Pulled] Successfully pulled image "debian:buster" in 2.210576896s
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r][Created] Created container debian
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r][Started] Started container debian
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r][Pulling] Pulling image "jenkins/inbound-agent:4.3-4-alpine"
Still waiting to schedule task
‘debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r’ is offline
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r][Pulled] Successfully pulled image "jenkins/inbound-agent:4.3-4-alpine" in 3.168311973s
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r][Created] Created container jnlp
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r][Started] Started container jnlp
Created Pod: kubernetes default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-qdw4m
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-qdw4m][Scheduled] Successfully assigned default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-qdw4m to jenkins
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-qdw4m][Pulled] Container image "debian:buster" already present on machine
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-qdw4m][Created] Created container debian
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-qdw4m][Started] Started container debian
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-qdw4m][Pulled] Container image "jenkins/inbound-agent:4.3-4-alpine" already present on machine
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-qdw4m][Created] Created container jnlp
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkm
zq-qdw4m][Started] Started container jnlp


这是因为 Jenkins pod 中的 jnlp 容器无法连接 Jenkins master。可以检查一下 Jenkins master 上 系统管理 > 节点管理 > Configure Clouds 中 Jenkins 地址 和 Jenkins 通道 这两个参数是否配置正确。

结束


到此为止,我们就完成了让 Jenkins 大叔与 kubernetes 船长手牵手🧑‍🤝‍🧑啦!上面使用了一个简单的例子来展示了如何将 Jenkins 的 Job 任务运行在 kubernetes 集群上,但在实际工作中遇到的情形可能比这要复杂一些,流水线需要配置的参数也要多一些。

- END -

 推荐阅读 

Kubernetes 企业容器云平台运维实战 快、狠、准!系统有效的排查运维类故障
OpenStack 与 Kubernetes 的共存Nginx 常用配置清单最强整理!常用正则表达式速查手册
运维的工作边界,这次真的搞明白了!七年老运维实战中的 Shell 开发经验总结
面试数十家Linux运维工程师,总结了这些面试题(含答案)
快速入门 Ansible 自动化运维工具 | 16张图
12年资深运维老司机的成长感悟



点亮,服务器三年不宕机

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存