前言:本文是构建企业级CICD的最后一篇文章,以实战为导向,讲解Jenkins 通过Pipeline流水线如何实现在kubernetes系统中做到持续集成持续部署的,请关注!
七、Jenkins在K8s中持续部署
7.1、构建镜像推送到Harbor
上面我们运行了一个拉取代码的示例,这里基于上面的Pipeline构建环境,实现代码的拉取–>构建镜像–>推送镜像三大发布操作
7.1.1、Jenkinsfile
首先来看Pipeline定义的流水线代码:
// 公共
def registry = “172.16.194.130”
// 项目
def project = “library”
def app_name = “java-demo”
def image_name = “ r e g i s t r y / {registry}/ registry/{project}/ a p p n a m e : {app_name}: appname:{Branch}-${BUILD_NUMBER}”
def git_address = “[email protected]:/home/git/app.git”
// 认证
def docker_registry_auth = “9df0c22e-9cce-4f54-b7d4-ede2a7755fc7” // Jenkins配置harbor凭证的ID
def git_auth = “a3672571-97ed-4088-b407-43a9c54d3f5a” // Jenkins配置Git凭证的ID
// 生成Jenkins-slave Pod的模板
podTemplate(label: ‘jenkins-slave’, cloud: ‘kubernetes’, containers: [
containerTemplate(
name: ‘jnlp’,
image: “${registry}/library/jenkins-slave-jdk:1.8”
),
],
volumes: [ // jenkins-slave镜像没有docker环境,因此需要将宿主机上的docker环境挂载到Pod中(Jenkins-slave需要使用docker push来推送镜像)
hostPathVolume(mountPath: ‘/var/run/docker.sock’, hostPath: ‘/var/run/docker.sock’),
hostPathVolume(mountPath: ‘/usr/bin/docker’, hostPath: ‘/usr/bin/docker’)
],
)
// 脚本式的Pipeline声明
{
node(“jenkins-slave”){
// 第一步
stage(‘拉取代码’){
checkout([ c l a s s : ′ G i t S C M ′ , b r a n c h e s : [ [ n a m e : ′ class: 'GitSCM', branches: [[name: ' class:′GitSCM′,branches:[[name:′{Branch}’]], userRemoteConfigs: [[credentialsId: “ g i t a u t h " , u r l : " {git_auth}", url: " gitauth",url:"{git_address}”]]])
}
// 第二步
stage(‘代码编译’){
sh “mvn clean package -Dmaven.test.skip=true” // 编译的目的就是生成一个可部署的java包
}
// 第三步
stage(‘构建镜像’){
withCredentials([usernamePassword(credentialsId: “${docker_registry_auth}”, passwordVariable: ‘password’, usernameVariable: ‘username’)]) {
sh “”"
echo ’
FROM nicksors/tomcat
RUN rm -rf /usr/local/tomcat/webapps/*
ADD target/*.war /usr/local/tomcat/webapps/ROOT.war
’ > Dockerfile
docker build -t ${image_name} .
docker login -u u s e r n a m e − p ′ {username} -p ' username−p′{password}’ ${registry}
docker push ${image_name}
“”"
}
}
}
}
代码字段说明:(前面说过的以及在上脚本有注释的这里不再说明)
def project # 定义Harbor项镜像库名称,一般都是私有项目镜像库;
app_name # 本次构建的应用名称,会显示在Harbor上的名称;
image_name # 提交到Harbor的镜像名称,’ B r a n c h ’ 参 数 需 要 在 J o b 里 定 义 , , {Branch}’参数需要在Job里定义,, Branch’参数需要在Job里定义,,{BUILD_NUMBER}参数是每次编译的版本号(Jenkins内置变量,会自动获取)
git_address # Git项目地址
7.1.2、定义Branch参数
在Job里定义’${Branch}’参数:(点击test项目–>配置–>勾选参数化构建过程–>选择字符参数,填写指定Branch变量的值为master)
7.1.3、编译、构建、推送镜像
将上面的Jenkinsfile粘贴到流水线脚本内容里,覆盖原有的脚本内容:
注意:左下角有一个“流水线语法”,可以根据流水线语法来生成Pipeline脚本内容。
构建成功后,Harbor仓库应多一个java-demo镜像,这就是所谓的镜像交付了,接下来就是把这个镜像通过Jenkins Pipeline部署到K8s中,即可完成整套CI/CD。
7.2、将镜像持续部署到k8s集群
7.2.1、安装Kubernetes Continuous Deploy插件
Kubernetes Continuous Deploy插件:用于将资源部署到Kubernetes中
插件介绍:https://plugins.jenkins.io/kubernetes-cd
支持以下资源类型:(这些资源都可以通过该插件部署到k8s集群中)
Deployment
Replica Set
Daemon Set
Pod
Job
Service
Ingress
Secret
输入kubernetes搜索,安装:
在这里可以定义你需要部署的资源,自动生成流水线脚本。
需要注意的是,在这里配置部署资源,有两个必要的条件:
Kuberconfig:连接k8s api需要kubeconfig认证,
Config Files:部署资源的文件名
那这两个文件都还没有,那接下来进生成这两个必要的条件吧。
7.2.2、K8s生成config文件
在K8s集群里生成一个拥有管理员权限的config文件
[root@k8s-master-128 k8s-cret]# wget https://raw.githubusercontent.com/rootsongjc/kubernetes-handbook/master/tools/create-user/create-user.sh
[root@k8s-master-128 k8s-cret]# vim create-user.sh
#!/bin/bash
KUBE_APISERVER=$1
USER=KaTeX parse error: Undefined control sequence: \n at position 55: …ver>
if [[ $KUBE_APISERVER == “” ]]; then
echo -e $USAGE
exit 1
fi
if [[ $USER == “” ]];then
echo -e $USAGE
exit 1
fi
function createCSR(){
cat>$CSR<
“CN”: “USER”,
“hosts”: [],
“key”: {
“algo”: “rsa”,
“size”: 2048
},
“names”: [
{
“C”: “CN”,
“ST”: “BeiJing”,
“L”: “BeiJing”,
“O”: “k8s”,
“OU”: “System”
}
]
}
EOF
sed -i “s/USER/$USER/g” $CSR
}
function ifExist(){
if [ ! -f “$SSL_PATH/ 1 " ] ; t h e n e c h o " 1" ]; then echo " 1"];thenecho"SSL_PATH/$1 not found.”
exit 1
fi
}
for f in ${SSL_FILES[@]};
do
echo “Check if ssl file $f exist…”
ifExist $f
echo “OK”
done
echo “Create CSR file…”
createCSR
echo “$CSR created”
echo “Create user’s certificates and keys…”
cd $SSL_PATH
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes $CSR| cfssljson -bare $USER
cd -
kubectl config set-cluster kubernetes
–certificate-authority=KaTeX parse error: Undefined control sequence: \ at position 19: …L_PATH}/ca.pem \̲ ̲--embed-certs=t…{KUBE_APISERVER}
–kubeconfig=${USER}.kubeconfig
kubectl config set-credentials KaTeX parse error: Undefined control sequence: \ at position 6: USER \̲ ̲--client-certif…SSL_PATH/KaTeX parse error: Undefined control sequence: \ at position 12: {USER}.pem \̲ ̲--client-key=SSL_PATH/KaTeX parse error: Undefined control sequence: \ at position 16: {USER}-key.pem \̲ ̲--embed-certs=t…{USER}.kubeconfig
kubectl config set-context kubernetes
–cluster=kubernetes
–user=KaTeX parse error: Undefined control sequence: \ at position 6: USER \̲ ̲--kubeconfig={USER}.kubeconfig
kubectl config use-context kubernetes --kubeconfig=${USER}.kubeconfig
kubectl create clusterrolebinding U S E R − a d m i n − b i n d i n g − − c l u s t e r r o l e = c l u s t e r − a d m i n − − u s e r = {USER}-admin-binding --clusterrole=cluster-admin --user= USER−admin−binding−−clusterrole=cluster−admin−−user=USER
kubectl config get-contexts
echo “Congratulations!”
echo “Your kubeconfig file is ${USER}.kubeconfig”
[root@k8s-master-128 k8s-cret]# chmod +x create-user.sh
[root@k8s-master-128 k8s-cret]# sh create-user.sh https://172.16.194.128:6443 nicksors
执行完成后,当前目录下会生成一个文件,配置测试使用:
[root@k8s-master-128 k8s-cret]# cp nicksors.kubeconfig /root/.kube/config
[root@k8s-master-128 k8s-cret]# kubectl get deploy -A
NAMESPACE NAME READY UP-TO-DATE AVAILABLE AGE
default nfs-client-provisioner 1/1 1 1 12d
default nginx 1/1 1 1 29d
ingress-nginx nginx-ingress-controller 1/1 1 1 14d
kube-system coredns 2/2 2 2 15d
kube-system kubernetes-dashboard 1/1 1 1 29d
没问题,config文件可以使用了,接下来将config配置文件添加到Jenkins 凭证里。
将nicksors.kubeconfig里的内容贴进去,点击确定即可:
查看ID:f664aa68-fbdc-4f41-9dfb-b241f0951241
7.2.3、资源部署文件
资源部署文件里定义了将交付的容器部署到k8s集群中,其中定义了有deployment资源、Service资源和Ingress提供域名服务资源。
[root@k8s-master-128 jenkins]# cat deploy.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 1
selector:
matchLabels:
app: java-demo
template:
metadata:
labels:
app: java-demo
spec:
imagePullSecrets:
- name: $SECRET_NAME
containers:
- name: tomcat
image: $IMAGE_NAME
ports:
- containerPort: 8080
name: web
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 60
timeoutSeconds: 5
failureThreshold: 12
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 60
timeoutSeconds: 5
failureThreshold: 12
apiVersion: v1
kind: Service
metadata:
name: web
spec:
type: NodePort
selector:
app: java-demo
ports:
- protocol: TCP
port: 80
targetPort: 8080
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: web
spec:
rules:
$SECRET_NAME # 这个是k8s运行拉取Harbor镜像的许可,在部署文件里需要配置
$IMAGE_NAME # 镜像名称, 因为每次发版的镜像版本都不同,名称固然不同,因此每次都需要Jenkins传入进来;
那这里需要配置K8s允许拉取Harbor私有仓库的权限(其实就是登录的用户名和密码保存在k8s中),操作如下:
[root@k8s-master-128 jenkins]# kubectl create secret docker-registry registry-pull-secret --docker-server=172.16.194.130 --docker-username=admin --docker-password=123123 [email protected]
secret/registry-pull-secret created
[root@k8s-master-128 jenkins]# kubectl get secret|grep registry-pull-secret
registry-pull-secret kubernetes.io/dockerconfigjson 1 15s
7.2.4、流水线语法生成脚本
经过上面两步骤后,可以来到流水线语法这里了,Kuberconfig选择咱们刚刚添加的config授权,Config Files填写我们的资源部署文件名称。
点击“生成流水线脚本”,得到如下内容:
kubernetesDeploy configs: ‘deploy.yml’, kubeConfig: [path: ‘’], kubeconfigId: ‘f664aa68-fbdc-4f41-9dfb-b241f0951241’, secretName: ‘’, ssh: [sshCredentialsId: ‘*’, sshServer: ‘’], textCredentials: [certificateAuthorityData: ‘’, clientCertificateData: ‘’, clientKeyData: ‘’, serverUrl: ‘https://’]
这个内容有些无用的信息,经过调教后内容如下:
kubernetesDeploy configs: ‘deploy.yml’, kubeconfigId: “f664aa68-fbdc-4f41-9dfb-b241f0951241”
7.2.5、最终的Jenkinsfile
[root@k8s-master-128 jenkins]# cat Jenkinsfile
// 公共
def registry = “172.16.194.130”
// 项目
def project = “library”
def app_name = “java-demo”
def image_name = “ r e g i s t r y / {registry}/ registry/{project}/ a p p n a m e : {app_name}: appname:{Branch}-${BUILD_NUMBER}”
def git_address = “[email protected]:/home/git/app.git”
// 认证
def secret_name = “registry-pull-secret” // K8s从harbor拉取镜像的权限认证
def docker_registry_auth = “9df0c22e-9cce-4f54-b7d4-ede2a7755fc7” // Jenkins配置harbor凭证的ID
def git_auth = “a3672571-97ed-4088-b407-43a9c54d3f5a” // Jenkins配置Git凭证的ID
def k8s_auth = “f664aa68-fbdc-4f41-9dfb-b241f0951241” // Jenkins配置k8s凭证的ID
// 生成Jenkins-slave Pod的模板
podTemplate(label: ‘jenkins-slave’, cloud: ‘kubernetes’, containers: [
containerTemplate(
name: ‘jnlp’,
image: “${registry}/library/jenkins-slave-jdk:1.8”
),
],
volumes: [ // jenkins-slave镜像没有docker环境,因此需要将宿主机上的docker环境挂载到Pod中(Jenkins-slave需要使用docker push来推送镜像)
hostPathVolume(mountPath: ‘/var/run/docker.sock’, hostPath: ‘/var/run/docker.sock’),
hostPathVolume(mountPath: ‘/usr/bin/docker’, hostPath: ‘/usr/bin/docker’)
],
)
// 脚本式的Pipeline
{
node(“jenkins-slave”){
// 第一步
stage(‘拉取代码’){
checkout([ c l a s s : ′ G i t S C M ′ , b r a n c h e s : [ [ n a m e : ′ class: 'GitSCM', branches: [[name: ' class:′GitSCM′,branches:[[name:′{Branch}’]], userRemoteConfigs: [[credentialsId: “ g i t a u t h " , u r l : " {git_auth}", url: " gitauth",url:"{git_address}”]]])
}
// 第二步
stage(‘代码编译’){
sh “mvn clean package -Dmaven.test.skip=true” // 编译的目的就是生成一个可部署的java包
}
// 第三步
stage(‘构建镜像’){
withCredentials([usernamePassword(credentialsId: “${docker_registry_auth}”, passwordVariable: ‘password’, usernameVariable: ‘username’)]) {
sh “”"
echo ’
FROM nicksors/tomcat
RUN rm -rf /usr/local/tomcat/webapps/*
ADD target/*.war /usr/local/tomcat/webapps/ROOT.war
’ > Dockerfile
docker build -t ${image_name} .
docker login -u u s e r n a m e − p ′ {username} -p ' username−p′{password}’ ${registry}
docker push ${image_name}
“”"
}
}
// 第四步
stage(‘部署到K8S平台’){
sh “”"
pwd
ls
sed -i ‘s#$IMAGE_NAME#${image_name}#’ deploy.yml
sed -i 's#$SECRET_NAME#KaTeX parse error: Expected 'EOF', got '#' at position 14: {secret_name}#̲' deploy.yml …{k8s_auth}"
}
}
}
增加了第四步,主要的内容就是改变deploy.yml文件那两个变量,然后拿着kubeconfig权限去执行部署deploy.yml,将容器部署到集群中。
7.2.6、配置Pipeline使用Jenkinsfile
当然,app项目里一定得有Jenkinsfile文件才行:
[root@k8s-master-128 app]# tree -L 1
.
├── db
├── deploy.yml
├── Jenkinsfile
├── LICENSE
├── pom.xml
├── README.md
└── src
2 directories, 5 files
[root@k8s-master-128 app]# git add .
[root@k8s-master-128 app]# git commit -m ‘create Jenkinsfile’
[root@k8s-master-128 app]# git push
7.2.6、最终构建:部署到K8s集群
点击构建,选择master分支:
构建失败:
找不到部署的文件,Pipeline执行流水线时会在下载的app代码仓库寻找文件,我们并没有将deploy.yml文件提交到app仓库,这才使得文件找不到,接下来将文件提交到app仓库,继续重新构建:
[root@k8s-master-128 app]# tree -L 1
.
├── db
├── deploy.yml
├── LICENSE
├── pom.xml
├── README.md
└── src
2 directories, 4 files
[root@k8s-master-128 app]# git add .
[root@k8s-master-128 app]# git commit -m ‘create deploy file’
[root@k8s-master-128 app]# git push
部署成功:
查看k8s集群启动的Pod及资源:
[root@k8s-master-128 app]# kubectl get pod|grep web
web-5d59d4c58f-frwp8 1/1 Running 0 3m
web-5d59d4c58f-nclfr 1/1 Running 0 3m
web-5d59d4c58f-sjdrn 1/1 Running 0 3m
[root@k8s-master-128 app]# kubectl get ingress|grep java
web java.nicksors.cc 80 3m39s
[root@k8s-master-128 app]# kubectl get svc|grep web
web NodePort 10.0.0.140 80:48780/TCP 3m56s
7.2.7、访问项目:进入测试阶段
将java.nicksors.cc配置本地host:
$ sudo vim /etc/hosts
172.16.194.129 jenkins.nicksors.cc java.nicksors.cc
浏览器访问:
7.3、小结
1、使用Jenkins三个插件:
Kubernetes
Pipeline
Kubernetes Continuous Deploy
2、CI/CD环境特点:
Slave弹性伸缩
基于镜像隔离构建环境
流水线发布,易于维护
3、Jenkins参数化构建可以帮助你完成更复杂的CI/CD
感谢阅读,文完。
构建企业级CICD平台这系列有四篇文章,文中用到的文件可以到我的GitHub地址去寻找
Github: https://github.com/nicksors/k8sDeployFile/tree/master/jenkins