01. CI/CD - Jenkins 安装说明(War / Kubernetes)
CI/CD
日常运维中常常听到 CI/CD 这个词,它其实包含整个研发生命周期的三个阶段:
- CI,Continuous integration,持续集成
- CD,Continuous delivery,持续交付
- CD,Continuous deployment,持续部署
大致的流程图如下:
而对于 Kubernete 的 CI/CD
工具目前也有很多,比如 Jenkins
、Gitlab CI
以及 drone
等等,日常运维中遇到最多的就是 Jenkins。
Jenkins
Jenkins 是由 Java 开发的一个可扩展的持续集成引擎,前身为 Hudson
。官网地址为:
由于是 Java 开发,那么 Jenkins 的运行就会依赖于 JDK 运行环境。在官方文档中有提到:
Jenkins requires Java 11 or 17 since Jenkins 2.357 and LTS 2.361.1
注意自己配置的时候选对 JDK 版本。安装 Jenkins 的方式也有很多,常见的有:
- Docker(一般不会单独选择它)
- Kubernetes(在 Kubernetes 环境中可以选择,好处在于高可用)
- Linux rpm(安装简单,但是不方便定制化统一管理)
- Java tomcat(War 或者 jar,用户自定义程度高,管理维护也方便)
- 其它
可以根据自己的需求进行安装,但是需要注意:如果公司项目比较多,构建也很频繁,需要配置较大内存(或者多个 Agent)和较大磁盘空间(会存代码依赖包,可能很大)。
官网中也有提到关于配置方面的内容:
Minimum hardware requirements:
- 256 MB of RAM
- 1 GB of drive space (although 10 GB is a recommended minimum if running Jenkins as a Docker container)
Recommended hardware configuration for a small team:
- 4 GB+ of RAM
- 50 GB+ of drive space
The amount of memory Jenkins needs is largely dependent on many factors, which is why the RAM allotted for it can range from 200 MB for a small installation to 70+ GB for a single and massive Jenkins controller. However, you should be able to estimate the RAM required based on your project build needs.
Each build node connection will take 2-3 threads, which equals about 2 MB or more of memory. You will also need to factor in CPU overhead for Jenkins if there are a lot of users who will be accessing the Jenkins user interface.
接下来将会通过两种方式进行安装。
Jenkins Server(War / 推荐使用)
安装包可以去官网下载,本文使用的是最新稳定版 2.387.1
:
同时也需要 JDK 11 以上版本,这些都可以去华为镜像站下载。
Jenkins:
JDK:
如果想要使用 Tomcat 运行 war,可以看看官网的要求:
Jenkins requires Servlet API 4.0 (Jakarta EE 8) with
javax.servlet
imports.Tomcat 9 is based on Servlet API 4.0 (Jakarta EE 8), which is the version of the servlet API required by Jenkins.
安装 JDK:
# 下载安装包
wget https://repo.huaweicloud.com/java/jdk/11.0.2+9/jdk-11.0.2_linux-x64_bin.tar.gz
# 解压安装
tar -zxf jdk-11.0.2_linux-x64_bin.tar.gz
mkdir -p /opt/service/jdk
mv jdk-11.0.2 /opt/service/jdk/
由于这里 Java 没有配置环境变量,后续需要使用绝对路径来执行 java 命令。
这也是在 Jenkins 环境中比较推荐的,因为可能会存在多个 JDK 版本共存的情况,不建议设置环境变量。
安装 Jenkins:
# 下载安装包
wget https://repo.huaweicloud.com/jenkins/war-stable/2.387.1/jenkins.war
# 安装依赖
yum install fontconfig
# 解压安装
mkdir -p /opt/service/jenkins/{logs,data,backup,bin,server,agent}
mv jenkins.war /opt/service/jenkins/server/
配置启动脚本,相关参数可以在官方文档中看到:
https://www.jenkins.io/doc/book/installing/initial-settings/
其它 Jenkins 本身的启动参数:
常用的参数包含以下:
- 数据目录:
-DHUDSON_HOME
- 时区设置:
-Dorg.apache.commons.jelly.tags.fmt.timeZone=Asia/Shanghai
脚本内容如下:
cat > /opt/service/jenkins/bin/start_server.sh << EOF
#!/bin/bash
##############################################################
# 说明:Jenkins 启动脚本
##############################################################
##############################################################
# JDK 相关
##############################################################
# 安装目录
JAVA_HOME="/opt/service/jdk/jdk-11.0.2"
# 启动命令
JAVA="\${JAVA_HOME}/bin/java"
##############################################################
# Jenkins 相关
##############################################################
# 安装目录
JENKINS_BASE_PATH="/opt/service/jenkins"
# 数据目录
JENKINS_HOME="\${JENKINS_BASE_PATH}/data"
# 日志目录
JENKINS_LOG="\${JENKINS_BASE_PATH}/logs"
# Server 目录
JENKINS_SERVER="\${JENKINS_BASE_PATH}/server"
# Agent 目录
JENKINS_AGENT="\${JENKINS_BASE_PATH}/agent"
##############################################################
# 启动参数
##############################################################
\${JAVA} -DHUDSON_HOME="\${JENKINS_HOME}" \\
-Dorg.apache.commons.jelly.tags.fmt.timeZone=Asia/Shanghai \\
-jar "\${JENKINS_SERVER}/jenkins.war" > \${JENKINS_LOG}/jenkins-server.log 2>&1 &
EOF
修改权限并启动:
chmod 755 /opt/service/jenkins/bin/start_server.sh
sh /opt/service/jenkins/bin/start_server.sh
启动之后在日子中会看到:
这里就是第一次登录的密码,使用安装服务器的 8080
端口则可以访问到 Jenkins。
初始化配置
启动完成之后就能进行初始化配置了:
- 使用刚刚日志中的密码解锁 Jenkins:
- 插件安装,可以后续自己安装,推荐的很多用不到,到时候用一个装一个:
有些时候因为网络的原因,这个页面面能会提示 离线
,不需要管它,跳过插件安装
即可。
只需要安装中文本地插件,用于汉化即可:
安装如图所示:
- 创建超级管理员用户:
- 配置 Jenkins 访问地址:
如果有域名就写域名,后续接口访问这些会在页面显示这个地址,如果不修改也没有关系,后面可以通过在系统设置中设置:
- 安装完成:
- 修改 Jenkins 保存密码的方式
Manage Jenkins
:
全局安全设置:
使用 Jenkins 自己的数据库:
一般安装配置完成使用就是这个,如果不是,那么下次登录的时候就会有问题。
- 中文设置:
设置 Locale:
设置为 zh_CN
并忽略浏览器语言。但是 Jenkins 这个汉化比较随意,一般都不彻底。
Jenkins Agent(Jar / 推荐使用)
打开 Jenkins 的管理 Jenkins 菜单,会有这样一个提示:
Jenkins 不推荐直接在 Server 上进行 build,建议按照 agent 来执行构建任务。
这里推荐的也是 agent,毕竟一台机器的性能有限,如果需求很大,可以通过多个 agent 分担压力。
创建 Agent 节点:
添加节点:
详细信息配置:
完成之后可以在左边看到:
查看节点状态:
发现因为启动的时候并没有配置 Agent 通信的节点,所有默认只监听了 Web 访问端口。没有用于 Agent 连接的端口。
可以点击 配置链接
进行端口设置(实际就是在全局安全设置下面):
这里自定义了一个 12580
端口。
再次查看节点状态,可以看到已经不一样了:
用过 Kubernetes 的就知道,这个有点像节点加入集群的方法,可以选择第二种安装 agent 的方式。
同时还提供了 agent 的下载地址,此时只需要在一台新的机器上执行相关命令即可,由于我这里测试只有一台机器,所以还是当前机器:
cd /opt/service/jenkins/agent/
echo ae9baf90e272d7c969f4f264573374eba3ca42666806c45980a071c02b7c8013 > secret-file
curl -sO http://192.168.2.100:8080/jnlpJars/agent.jar
添加启动脚本:
cat > /opt/service/jenkins/bin/start_agent.sh << EOF
#!/bin/bash
##############################################################
# 说明:Jenkins Agent 启动脚本
##############################################################
##############################################################
# JDK 相关
##############################################################
# 安装目录
JAVA_HOME="/opt/service/jdk/jdk-11.0.2"
# 启动命令
JAVA="\${JAVA_HOME}/bin/java"
##############################################################
# Jenkins 相关
##############################################################
# 安装目录
JENKINS_BASE_PATH="/opt/service/jenkins"
# 日志目录
JENKINS_LOG="\${JENKINS_BASE_PATH}/logs"
# Agent 目录
JENKINS_AGENT="\${JENKINS_BASE_PATH}/agent"
##############################################################
# 启动参数
##############################################################
cd ${JENKINS_AGENT}
\${JAVA} -jar agent.jar \\
-jnlpUrl http://192.168.2.100:8080/manage/computer/%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%2D01/jenkins-agent.jnlp \\
-secret @secret-file \\
-workDir "/devops/jenkins" > \${JENKINS_LOG}/jenkins-agent.log 2>&1 &
EOF
启动 agent:
chmod 755 /opt/service/jenkins/bin/start_agent.sh
sh /opt/service/jenkins/bin/start_agent.sh
启动完成之后刷新页面:
Jenkins Server(Kubernetes)
Kubernetes 里面运行 Jenkins,最核心的就是数据持久化。
- 创建一个 PV:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-longhorn-jenkins
namespace: devops
spec:
storageClassName: longhorn
resources:
requests:
storage: 1Gi
accessModes:
- ReadWriteOnce
- 创建 RBAC 授权:
apiVersion: v1
kind: ServiceAccount
metadata:
name: sa-jenkins
namespace: devops
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cr-jenkins
rules:
- apiGroups: ["extensions", "apps"]
resources: ["deployments", "ingresses"]
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", "events"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: crb-jenkins
namespace: devops
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cr-jenkins
subjects:
- kind: ServiceAccount
name: sa-jenkins
namespace: devops
为了安全起见,没有设置 cluster-admin
的集群角色权限,而是使用自定义的。
- 创建 Jenkins Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: jenkins
namespace: devops
spec:
selector:
matchLabels:
app.kubernetes.io/name: jenkins
app.kubernetes.io/version: "2.397"
template:
metadata:
labels:
app.kubernetes.io/name: jenkins
app.kubernetes.io/version: "2.397"
spec:
serviceAccount: sa-jenkins
volumes:
- name: v-jenkins
persistentVolumeClaim:
claimName: pvc-longhorn-jenkins
initContainers:
- name: fix-permissions
image: busybox:1.34.1
command: ["sh", "-c", "chown -R 1000:1000 /var/jenkins_home"]
securityContext:
privileged: true
volumeMounts:
- name: v-jenkins
mountPath: /var/jenkins_home
containers:
- name: jenkins
image: jenkins/jenkins:2.397
env:
- name: JAVA_OPTS
value: -Dhudson.model.DownloadService.noSignatureCheck=true
ports:
- containerPort: 8080
name: web
- containerPort: 50000
name: agent
resources:
requests:
cpu: 1000m
memory: 1024Mi
limits:
cpu: 1000m
memory: 1024Mi
readinessProbe:
httpGet:
path: /login
port: 8080
initialDelaySeconds: 60
volumeMounts:
- name: v-jenkins
mountPath: /var/jenkins_home
由于 jenkens 会对 update-center.json
做签名校验安全检查,需要先提前关闭,否则下面更改插件源可能会失败。
通过配置环境变量 JAVA_OPTS=-Dhudson.model.DownloadService.noSignatureCheck=true
即可。
- 创建 Service:
apiVersion: v1
kind: Service
metadata:
name: svc-jenkins
namespace: devops
spec:
selector:
app.kubernetes.io/name: jenkins
app.kubernetes.io/version: "2.397"
ports:
- name: web
port: 8080
targetPort: 8080
- name: agent
port: 50000
targetPort: 50000
- 创建 Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: jenkins-ingress
namespace: devops
spec:
ingressClassName: nginx
rules:
- host: jenkins.k8s.io
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: svc-jenkins
port:
number: 8080
本地配置 ingress 节点的 host 解析即可。
完成之后通过查看 Pod 日志或者进入 Pod 中查看初始化密码,初始化不走跟 war 包启动的方式一样。
Jenkins Slave(Kubernetes)
在传统的 Server / Agent 架构中,存在以下一些痛点:
- Master 单点故障,整个 Jenkins 集群就不可用了。
- 可以通过给不同 Agent 配置不同的打包环境用于处理特定的任务,但是不好管理,增加了维护成本。
- 资源分配不均,忙的忙死,闲的闲死。
- 在没有任务构建的时候,服务会一直占用着系统资源,造成资源浪费。
为了解决这些痛点,在 Kubernetes 中采用了新的架构来实现 CI/CD:
流程大致为:当 Jenkins Master 接受到 Build 请求时,会根据配置的 Label 动态创建一个运行在 Pod 中的 Jenkins Slave 并注册到 Master 上,当运行完 Job 后,这个 Slave 会被注销并且这个 Pod 也会自动删除,恢复到最初状态。
该方案的好处在于:
- 服务高可用,不再有单点故障的风险。
- 动态伸缩,合理的利用资源。
- 扩展性好,能够尽可能的减少多任务排队情况,提高效率。
配置方法:
- 安装 Kubernetes 插件:
安装完成后重启 Jenkins。
设置集群:
设置 Kubernetes:
配置 Kubernetes 信息:
配置 Jenkins 信息:
配置 Pod 信息:
注意标签不要有特殊符号,可能会识别失败。
配置容器信息:
特别说明:
官方的容器其实只是一个模板,需要根据自己的实际情况,打包环境,在它的基础上生成新的容器。
对于一家公司而言,开发语言可能会有很多种,这意味着 agent 的镜像可能会有多个,Pod 也就有多个。通过对不同的 Pod 设置不同的标签,可以在执行任务的时候指定使用某个 Pod。
配置 Pod SA:
Jenkins Slave(Kubernetes 测试)
创建一个自由风格的任务:
执行 Shell:
执行构建:
可以看到新建了一个 agent 来执行构建。
也能看到新建的 Pod:
调整时区
默认安装的 Jenkins 的时区不对,需要进行配置:
执行配置:
System.setProperty('org.apache.commons.jelly.tags.fmt.timeZone', 'Asia/Shanghai')
如图所示:
此时时间就正常了!