环境版本
本次实验使用的是k8s集群1.23.1版本,容器运行时使用的是docker
k8s集群服务器为两台
控制节点:k8s-master
工作节点:k8s-worker
jenkins版本:
jenkins/jenkins:2.394
rpm包版本的源地址:https://mirrors.tuna.tsinghua.edu.cn/jenkins/redhat/
一、安装Jenkins
1.1 安装nfs
安装Jenkins之前还需要准备一下环境,在k8s集群的任意节点上安装一下nfs
#首先在集群中的每个节点上安装nfs服务
[root@k8s-master ~]# yum install nfs-utils -y
[root@k8s-master ~]# systemctl enable nfs --now
#选择一个节点当作jenkins的数据存储节点,这里选择的是控制节点
#创建数据共享目录
[root@k8s-master ~]# mkdir /data/test-1 -p
[root@k8s-master ~]# cat /etc/exports //编辑该配置文件,写入下列内容
/data/test-1 *(rw,no_root_squash)
#使配置生效
[root@k8s-master ~]# exportfs -arv
exporting *:/data/test-1
1.2 创建pv、pvc、sa做rbac授权
#创建名称空间
[root@k8s-master ~]# kubectl create ns jenkins
namespace/jenkins created
#创建pv
[root@k8s-master ~]# cat pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: jenkins-pv
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteMany
nfs:
server: 192.168.57.131 #写nfs共享存储的节点IP及共享路径
path: /data/test-1
#创建
[root@k8s-master ~]# kubectl apply -f pv.yaml
persistentvolume/jenkins-pv created
[root@k8s-master ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
jenkins-pv 5Gi RWX Retain Available 10s
#创建pvc
[root@k8s-master ~]# cat pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: jenkins-pvc
namespace: jenkins
spec:
resources:
requests:
storage: 5Gi
accessModes:
- ReadWriteMany
#创建
[root@k8s-master ~]# kubectl apply -f pvc.yaml
persistentvolumeclaim/jenkins-pvc created
[root@k8s-master ~]# kubectl get pvc -n jenkins
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
jenkins-pvc Bound jenkins-pv 5Gi RWX 12s
[root@k8s-master ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
jenkins-pv 5Gi RWX Retain Bound jenkins/jenkins-pvc 3m37s
#创建sa账号并做RBAC授权
[root@k8s-master ~]# kubectl create sa jenkins-sa -n jenkins
serviceaccount/jenkins-sa created
[root@k8s-master ~]# kubectl create clusterrolebinding jenkins-sa-clusterrolebinding -n jenkins --clusterrole=cluster-admin --serviceaccount=jenkins:jenkins-sa
clusterrolebinding.rbac.authorization.k8s.io/jenkins-sa-clusterrolebinding created
[root@k8s-master ~]# kubectl get sa -n jenkins
NAME SECRETS AGE
jenkins-sa 1 97s
1.3 在k8s中安装Jenkins
安装之前需要准备一下镜像
1.3.1 准备及构建镜像
#工作节点下载镜像
[root@k8s-worker1 ~]# docker pull jenkins/jenkins:2.421 //k8s创建jenkins时使用的镜像
[root@k8s-worker1 ~]# docker pull jenkins/jnlp-slave:4.13.3-1-jdk11 //创建从节点Jenkins使用的镜像,用来构建dockerfile
#PS:jenkins是主从的,jenkins:2.421 是k8s创建jenkins时使用的镜像,创建Jenkins时会在k8s中创建一个从节点,该节点以pod形式运行
#下面使用jenkins/jnlp-slave:4.13.3-1-jdk11构建的新镜像是让Jenkins从节点使用的。
#工作节点使用dockerfile构建镜像
[root@k8s-worker1 ~]# mkdir /docker
[root@k8s-worker1 ~]# cd /docker/
[root@k8s-worker1 docker]# vim dockerfile //加入如下内容
FROM jenkins/jnlp-slave:4.13.3-1-jdk11
USER root
# 安装Docker
RUN apt-get update && apt-get install -y docker.io
# 将当前用户加入docker用户组
RUN usermod -aG docker jenkins
# 下载 kubectl
RUN curl -LO https://dl.k8s.io/release/stable.txt
RUN curl -LO https://dl.k8s.io/release/$(cat stable.txt)/bin/linux/amd64/kubectl
RUN chmod +x kubectl
RUN mv kubectl /usr/local/bin/
# 设置 Docker host
ENV DOCKER_HOST=unix:///var/run/docker.sock
#构建镜像
[root@k8s-worker1 docker]# docker build -t jenkins-slave-latest:v1.1 .
[root@k8s-worker1 docker]# docker images | egrep -i "v1.1|tag"
REPOSITORY TAG IMAGE ID CREATED SIZE
jenkins-slave-latest v1.1 083a87059bea 2 minutes ago 1.09GB
1.3.2 使用deployment控制器创建Jenkins
[root@k8s-master ~]# cat jenkins-deploy.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
name: jenkins-deploy
namespace: jenkins
spec:
replicas: 1
selector:
matchLabels:
app: jenkins
template:
metadata:
labels:
app: jenkins
spec:
serviceAccount: jenkins-sa
containers:
- name: jenkins
image: jenkins/jenkins:2.421
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: #这里下面pvc做成的卷挂在到了容器中;
- name: jenkins-volume #还需要将pvc中使用的共享存储路径赋权限;
subPath: jenkins-home #否则会报如下途中的错误。
mountPath: /var/jenkins_home ##这里对应挂载的就是pvc的路径,pvc绑定了pv,pv使用的是/data/test-1这个共享路径,创建jenkins之后,使用的密钥就从对应物理机共存存储中获得
volumes:
- name: jenkins-volume
persistentVolumeClaim:
claimName: jenkins-pvc
#给共享路径赋权
[root@k8s-master ~]# chown -R 1000.1000 /data/test-1/
#创建Jenkins pod
[root@k8s-master ~]# kubectl apply -f jenkins-deploy.yaml
deployment.apps/jenkins-deploy created
[root@k8s-master ~]# kubectl get pod -n jenkins
NAME READY STATUS RESTARTS AGE
jenkins-deploy-57b7dd4555-2x5tn 1/1 Running 0 5m41s
1.3.3 创建service
[root@k8s-master ~]# cat jenkins-service.yaml
apiVersion: v1
kind: Service
metadata:
name: jenkins-service
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
#创建
[root@k8s-master ~]# kubectl apply -f jenkins-service.yaml
service/jenkins-service created
[root@k8s-master ~]# kubectl get svc -n jenkins
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
jenkins-service NodePort 10.96.72.59 <none> 8080:30002/TCP,50000:30511/TCP 6s
#浏览器访问k8s集群任意节点加宿主机端口30002进行访问,如下图:
1.3.4 登入Jenkins
#获取登入jenkins的密钥
'/var/jenkins_home/secrets/initialAdminPassword' //图中提示的密钥获取地址路径为对应挂载的共享存储路径
[root@k8s-master ~]# ls /data/test-1/
jenkins-home
[root@k8s-master ~]# ls /data/test-1/jenkins-home/
config.xml jenkins.telemetry.Correlator.xml nodeMonitors.xml secret.key updates war
copy_reference_file.log jobs nodes secret.key.not-so-secret userContent
hudson.model.UpdateCenter.xml logs plugins secrets users
[root@k8s-master ~]# ls /data/test-1/jenkins-home/secrets/
initialAdminPassword jenkins.model.Jenkins.crumbSalt master.key
[root@k8s-master ~]# cat /data/test-1/jenkins-home/secrets/initialAdminPassword
e6e57d199f3941fca2ee4d11e2d98a20 //将获取的密码输入浏览器jenkins中并解锁登入
#点击安装推荐的插件,安装插件完成之后创建用户 如下图所示 (安装耗时较久,取决于网络速度)
1.3.5 安装kubernetes插件
依次点击:
Manage Jnekins--->插件管理--->可选插件--->搜索blueocean------>按照如下图片点击安装
安装完成之后Jenkins就可以连接配置k8s了
如下图二安装成功之后继续安装blueocean插件
1.3.6 安装blueocean插件
安装kubernetes完成之后,点击左上角的Available plugins 继续搜索blueocean插件进行安装,如下图:
1.3.7 重启Jenkins刷新插件
安装完成kubernetes及blueocean插件之后,浏览器访问 192.168.57.131:30002/restart 进行重启,如下图:
二、 配置Jenkins
2.1 配置Jenkins连接k8s
依次点击:系统管理--->Clouds--->如下图:
apiserver的地址及端口查询如下
Jenkins的pod前端service的域名地址查询如下
service的名称+所在名称空间+默认域名后缀(.svc.cluster.local)+端口
即:jenkins-service.jenkins.svc.cluster.local:8080
填写k8s控制节点的apiserver的地址
填写Jenkins的pod前端service的域名地址
2.2 配置pod模板
配置pod模板,使用Jenkins构建流水线任务时,他会在Jenkins指定的k8s集群中帮助创建一个Jenkins从节点,该从节点也是以pod形式运行的,该从节点pod是基于下方所配置的pod模板创建的。
2.3 pod模板中挂载卷
如下挂载卷方式使用物理机的目录挂载卷方式,
将宿主机的docker.sock挂载到容器中,
Jenkins从节点就可以使用docker命令或者使用docker构建镜像。
[root@k8s-master ~]# scp -r /root/.kube/ k8s-worker1:/root/
#将控制节点的/root/.kube 拷贝到Jenkins所调度到的工作节点上,
#然后下方图片将新增一个挂在卷,将宿主机工作节点上的/root/.kube 挂载到容器中的/home/jenkins/.kube路径下
#也就是说Jenkins 所在哪个节点上,该节点就要有控制节点的/root/.kube目录
2.4 配置Harbor 凭据
Huabor 配置安装方式点我
返回主页依次点击:系统管理--->凭据管理--->如下图点全局--->添加凭据:
PS:harbor的地址最好与k8s集群在同网段,要达到能够互相通信的目的。
2.4.1 创建Harbor项目
登入Harbor的UI界面进行创建一个测试项目
PS:1、还需要将Harbor的地址加入到k8s集群节点服务器的/etc/docker/daemon.json文件中
2、将Harbor的地址及主机名称加入到k8s集群节点服务器的/etc/hosts文件中
三、通过Jenkins部署应用流水线形式发布到各个环境中
基于GitHub与dockeHub构建镜像并通过主从Jenkins以流水线的形式发布应用到每个名称空间中
3.1 创建名称空间
创建名称空间,将应用发布到不同的名称空间中
[root@k8s-master ~]# kubectl create ns test-1
namespace/test-1 created
[root@k8s-master ~]# kubectl create ns test-2
namespace/test-2 created
[root@k8s-master ~]# kubectl create ns test-3
namespace/test-3 created
3.2 Jenkins创建流水线任务
返回Jenkins主页依次点击:
如下图
新建任务--->自定义一个名称--->点击流水线
点击确定之后下拉找到脚本,粘贴如下脚本内容
根据下方脚本内容中的注释进行修改后在粘贴进行构建流水线任务
PS:下方步骤构建失败的话,将脚本中的中文注释删除并再次尝试构建。
//这里的test001是上方配置pod模板时填写的标签列表名称,根据该pod模板中指定的镜像创建一个从节点Jenkins的pod,并将下方的GitHub仓库中的地址进行克隆到丛节点Jenkins的pod中
node('test001') {
stage('Clone') {
// 输出当前执行阶段
echo "1.Clone Stage"
// 指定 Git 仓库克隆代码,(将该链接Fork到自己的gethub中,并将下方指定的链接地址更换为自己github的,并修改链接中下方提到的三个yaml文件中的镜像名称及名称空间)
git url: "https://github.com/cyfang666/jenkins-sample"
script {
// 获取当前提交的短哈希值
build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
}
}
stage('Test') {
// 输出测试阶段,实际的测试逻辑可以在此添加
echo "2.Test Stage"
}
stage('Build') {
// 输出构建 Docker 镜像阶段
echo "3.Build Docker Image Stage"
// 使用获取的 build_tag 标签构建 Docker 镜像
// 192.168.57.136/test/jenkins-demo镜像名称前的192.168.57.136/test是dockerhub的主机名称及在UI界面创建的test项目
sh "docker build -t 192.168.57.136/test/jenkins-demo:${build_tag} ."
}
stage('Push') {
// 输出推送 Docker 镜像阶段
echo "4.Push Docker Image Stage"
// 使用 Jenkins Credentials 插件获取 DockerHub 凭证,下面的harbor是配置凭证时填写的ID,通过该id获取配置的凭证中指定的harbor的用户名及密码,用于登入harbor
withCredentials([usernamePassword(credentialsId: 'harbor', passwordVariable: 'dockerHubPassword', usernameVariable: 'dockerHubUser')]) {
// 登录到 Docker Hub
sh "echo ${dockerHubPassword} | docker login 192.168.57.136 -u ${dockerHubUser} --password-stdin"
// 推送构建的 Docker 镜像到 Docker Hu
sh "docker push 192.168.57.136/test/jenkins-demo:${build_tag}"
}
}
// 下方用到了三个yaml文件,分别是k8s-dev-harbor.yaml、k8s-qa-harbor.yaml、k8s-prod-harbor.yaml
// 该三个yaml文件需要登入上方指定的github的连接中:https://github.com/cyfang666/jenkins-sample
// 登入连接中找到这三个yaml文件并将文件中使用的镜像名称修改为与本脚本中的镜像名称一样(192.168.57.136/test/jenkins-demo:<BUILD_TAG>)
// 还有yaml中的pod、service的名称空间也需要修改,分别修改k8s-dev-harbor.yaml在test-1、k8s-qa-harbor.yaml在test-2、k8s-prod-harbor.yaml在test-3
// 名称空间可以保持一致,但是镜像名称因为使用的是自己的用户,所以需要将该链接Fork到自己的github上,并修改镜像名称
stage('Deploy to test-1') {
echo "5. Deploy test-1" // 输出部署到test-1环境阶段
// 替换 k8s-dev.yaml 中的占位符 <BUILD_TAG> 和 <BRANCH_NAME>
sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s-dev-harbor.yaml"
sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s-dev-harbor.yaml"
// 执行 Kubernetes 部署命令
sh "kubectl apply -f k8s-dev-harbor.yaml --validate=false"
}
// 提示用户是否要推广到test-2 环境(这里要选择yes/no)
stage('Promote to test-2') {
def userInput = input(
id: 'userInput',
message: 'Promote to test-2?',
parameters: [
[
$class: 'ChoiceParameterDefinition',
choices: "YES\nNO",
name: 'Env'
]
]
)
echo "This is a deploy step to ${userInput}"
// 如果选择 YES,则进行如下操作
if (userInput == "YES") {
// 替换 k8s-qa.yaml 中的占位符
sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s-qa-harbor.yaml"
sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s-qa-harbor.yaml"
// 执行 Kubernetes 部署命令
sh "kubectl apply -f k8s-qa-harbor.yaml --validate=false"
// 等待 6 秒后获取 test-2 环境的 Pods 状态
sh "sleep 6"
sh "kubectl get pods -n test-2"
} else {
// 如果选择 NO,则不进行操作
exit
}
}
// 提示用户是否要推广到test-3环境
stage('Promote to test-3') {
def userInput = input(
id: 'userInput',
message: 'Promote to test-3?',
parameters: [
[
$class: 'ChoiceParameterDefinition',
choices: "YES\nNO",
name: 'Env'
]
]
)
// 选择yes则进行如下操作
echo "This is a deploy step to ${userInput}"
if (userInput == "YES") {
sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s-prod-harbor.yaml"
sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s-prod-harbor.yaml"
sh "cat k8s-prod-harbor.yaml"
sh "kubectl apply -f k8s-prod-harbor.yaml --record --validate=false"
}
}
}
系统下查看创建的从Jenkins pod,待整个流水线任务完成之后,该从Jenkins pod就会自动被删除,留下一个主Jenkins pod。
确认物理机映射端口。UI界面访问验证
四、更新、回滚
4.1 更新
将流水线任务中的Pipeline脚本中定义的GitHub地址中的主页内容进行修改。
访问如下地址,进行修改,如下图
https://上方脚本中github的地址并+后边的后缀/blob/master/static/index.html
如:https://github.com/cyfang666/jenkins-sample/blob/master/static/index.html
重新运行之后查看pod状态,可看到刚刚运行60S左右,代表已更新完成
观察运行时间,新的已将旧的替换掉了。
UI访问验证
4.2 回滚
将上面更新的pod回滚到第一个版本
新建一个流水线任务, 如下图
node('test001') {
stage('git clone') {
// 同样将如下GitHub的链接fock到自己的GitHub并将链接修改为自己的
git url: "https://github.com/cyfang666/jenkins-rollout"
sh "ls -al"
sh "pwd"
}
stage('select env') {
def envInput = input(
id: 'envInput',
message: 'Choose a deploy environment',
parameters: [
[
$class: 'ChoiceParameterDefinition',
// 定义选择回滚的名称空间描述,将该空间赋给envInput变量,将回滚该选择名称空间的pod
choices: "test-1\ntest-2\ntest-3",
name: 'Env'
]
]
)
echo "This is a deploy step to ${envInput}"
// 如下脚本内容获取自定义的版本数字
sh "sed -i 's/<namespace>/${envInput}/' getVersion.sh"
// 如下脚本内容定义回滚命令
sh "sed -i 's/<namespace>/${envInput}/' rollout.sh"
sh "bash getVersion.sh"
}
stage('select version') {
env.WORKSPACE = pwd()
def version = readFile "${env.WORKSPACE}/version.csv"
println version
// 定义选择回滚版本号
def userInput = input(id: 'userInput',
message: '选择回滚版本',
parameters: [
[
$class: 'ChoiceParameterDefinition',
choices: "$version\n",
name: 'Version'
]
]
)
sh "sed -i 's/<version>/${userInput}/' rollout.sh"
}
stage('rollout deploy') {
sh "bash rollout.sh"
}
}
系统下验证 选择的回滚名称空间下的pod 选择的是回滚test-1空间
根据运行时间确认已更新完成
浏览器验证回滚完成