Fork me on GitHub

kubernetes系列之《构建企业级CICD平台(四)》

前言:本文是构建企业级CICD的最后一篇文章,以实战为导向,讲解Jenkins 通过Pipeline流水线如何实现在kubernetes系统中做到持续集成持续部署的,请关注!

七、Jenkins在K8s中持续部署

7.1、构建镜像推送到Harbor

上面我们运行了一个拉取代码的示例,这里基于上面的Pipeline构建环境,实现代码的拉取–>构建镜像–>推送镜像三大发布操作

7.1.1、Jenkinsfile

首先来看Pipeline定义的流水线代码:

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
// 公共
def registry = "172.16.194.130"
// 项目
def project = "library"
def app_name = "java-demo"
def image_name = "${registry}/${project}/${app_name}:${Branch}-${BUILD_NUMBER}"
def git_address = "git@172.16.194.130:/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([$class: 'GitSCM', branches: [[name: '${Branch}']], userRemoteConfigs: [[credentialsId: "${git_auth}", 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 ${username} -p '${password}' ${registry}
docker push ${image_name}
"""
}
}
}
}

代码字段说明:(前面说过的以及在上脚本有注释的这里不再说明)

  • def project # 定义Harbor项镜像库名称,一般都是私有项目镜像库;
  • app_name # 本次构建的应用名称,会显示在Harbor上的名称;
  • image_name # 提交到Harbor的镜像名称,’${Branch}’参数需要在Job里定义,,${BUILD_NUMBER}参数是每次编译的版本号(Jenkins内置变量,会自动获取)
  • git_address # Git项目地址

7.1.2、定义Branch参数

在Job里定义’${Branch}’参数:(点击test项目–>配置–>勾选参数化构建过程–>选择字符参数,填写指定Branch变量的值为master)

7.1.3、编译、构建、推送镜像

将上面的Jenkinsfile粘贴到流水线脚本内容里,覆盖原有的脚本内容:

注意:左下角有一个“流水线语法”,可以根据流水线语法来生成Pipeline脚本内容。

保存后,点击Job构建:

构建成功后,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文件

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
[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为你的API Server的地址
KUBE_APISERVER=$1
USER=$2
USAGE="USAGE: create-user.sh <api_server> <username>\n
Example: https://172.22.1.1:6443 brand"
CSR=`pwd`/user-csr.json
SSL_PATH="/opt/kubernetes/ssl" # 请填写自己的k8s ssl目录
SSL_FILES=(ca-key.pem ca.pem ca-config.json)
CERT_FILES=(${USER}.csr $USER-key.pem ${USER}.pem)

if [[ $KUBE_APISERVER == "" ]]; then
echo -e $USAGE
exit 1
fi
if [[ $USER == "" ]];then
echo -e $USAGE
exit 1
fi

# 创建用户的csr文件
function createCSR(){
cat>$CSR<<EOF
{
"CN": "USER",
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"ST": "BeiJing",
"L": "BeiJing",
"O": "k8s",
"OU": "System"
}
]
}
EOF
# 替换csr文件中的用户名
sed -i "s/USER/$USER/g" $CSR
}

function ifExist(){
if [ ! -f "$SSL_PATH/$1" ]; then
echo "$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=${SSL_PATH}/ca.pem \
--embed-certs=true \
--server=${KUBE_APISERVER} \
--kubeconfig=${USER}.kubeconfig

# 设置客户端认证参数
kubectl config set-credentials $USER \
--client-certificate=$SSL_PATH/${USER}.pem \
--client-key=$SSL_PATH/${USER}-key.pem \
--embed-certs=true \
--kubeconfig=${USER}.kubeconfig

# 设置上下文参数
kubectl config set-context kubernetes \
--cluster=kubernetes \
--user=$USER \
--kubeconfig=${USER}.kubeconfig

# 设置默认上下文
kubectl config use-context kubernetes --kubeconfig=${USER}.kubeconfig


# 绑定角色(创建集群角色,绑定管理员权限)
kubectl create clusterrolebinding ${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

执行完成后,当前目录下会生成一个文件,配置测试使用:

1
2
3
4
5
6
7
8
[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提供域名服务资源。

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
[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:
- host: java.nicksors.cc
http:
paths:
- path: /
backend:
serviceName: web
servicePort: 80

在Deployment配置下有两个参数是通过变量传入进来的:

  • $SECRET_NAME # 这个是k8s运行拉取Harbor镜像的许可,在部署文件里需要配置
  • $IMAGE_NAME # 镜像名称, 因为每次发版的镜像版本都不同,名称固然不同,因此每次都需要Jenkins传入进来;

那这里需要配置K8s允许拉取Harbor私有仓库的权限(其实就是登录的用户名和密码保存在k8s中),操作如下:

1
2
3
4
[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 --docker-email=admin@nicksors.cc
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填写我们的资源部署文件名称。

点击“生成流水线脚本”,得到如下内容:

1
kubernetesDeploy configs: 'deploy.yml', kubeConfig: [path: ''], kubeconfigId: 'f664aa68-fbdc-4f41-9dfb-b241f0951241', secretName: '', ssh: [sshCredentialsId: '*', sshServer: ''], textCredentials: [certificateAuthorityData: '', clientCertificateData: '', clientKeyData: '', serverUrl: 'https://']

这个内容有些无用的信息,经过调教后内容如下:

1
kubernetesDeploy configs: 'deploy.yml', kubeconfigId: "f664aa68-fbdc-4f41-9dfb-b241f0951241"

7.2.5、最终的Jenkinsfile

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
[root@k8s-master-128 jenkins]# cat Jenkinsfile
// 公共
def registry = "172.16.194.130"
// 项目
def project = "library"
def app_name = "java-demo"
def image_name = "${registry}/${project}/${app_name}:${Branch}-${BUILD_NUMBER}"
def git_address = "git@172.16.194.130:/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([$class: 'GitSCM', branches: [[name: '${Branch}']], userRemoteConfigs: [[credentialsId: "${git_auth}", 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 ${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#${secret_name}#' deploy.yml
"""
kubernetesDeploy configs: 'deploy.yml', kubeconfigId: "${k8s_auth}"
}
}
}

增加了第四步,主要的内容就是改变deploy.yml文件那两个变量,然后拿着kubeconfig权限去执行部署deploy.yml,将容器部署到集群中。

7.2.6、配置Pipeline使用Jenkinsfile

当然,app项目里一定得有Jenkinsfile文件才行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[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仓库,继续重新构建:

1
2
3
4
5
6
7
8
9
10
11
12
13
[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及资源:

1
2
3
4
5
6
7
8
[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 <none> 80:48780/TCP 3m56s

7.2.7、访问项目:进入测试阶段

将java.nicksors.cc配置本地host:

1
2
$ 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

参考文章:

-------------本文结束感谢您的阅读-------------