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

前言:本文是构建企业级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 = “registry/{registry}/{project}/appname:{app_name}:{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([class:GitSCM,branches:[[name:class: 'GitSCM', branches: [[name: '{Branch}’]], userRemoteConfigs: [[credentialsId: “gitauth",url:"{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 usernamep{username} -p '{password}’ ${registry}
docker push ${image_name}
“”"
}
}
}
}
代码字段说明:(前面说过的以及在上脚本有注释的这里不再说明)

def project # 定义Harbor项镜像库名称,一般都是私有项目镜像库;
app_name # 本次构建的应用名称,会显示在Harbor上的名称;
image_name # 提交到Harbor的镜像名称,’BranchJob{Branch}’参数需要在Job里定义,,{BUILD_NUMBER}参数是每次编译的版本号(Jenkins内置变量,会自动获取)
git_address # Git项目地址
7.1.2、定义Branch参数
在Job里定义’${Branch}’参数:(点击test项目–>配置–>勾选参数化构建过程–>选择字符参数,填写指定Branch变量的值为master)
kubernetes系列之《构建企业级CICD平台(四)》

7.1.3、编译、构建、推送镜像
将上面的Jenkinsfile粘贴到流水线脚本内容里,覆盖原有的脚本内容:
kubernetes系列之《构建企业级CICD平台(四)》

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

保存后,点击Job构建:
kubernetes系列之《构建企业级CICD平台(四)》

构建成功后,Harbor仓库应多一个java-demo镜像,这就是所谓的镜像交付了,接下来就是把这个镜像通过Jenkins Pipeline部署到K8s中,即可完成整套CI/CD。
kubernetes系列之《构建企业级CICD平台(四)》

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搜索,安装:
kubernetes系列之《构建企业级CICD平台(四)》

安装完成后,在“流水线语法”中会出现下面选项:
kubernetes系列之《构建企业级CICD平台(四)》

在这里可以定义你需要部署的资源,自动生成流水线脚本。

需要注意的是,在这里配置部署资源,有两个必要的条件:

Kuberconfig:连接k8s api需要kubeconfig认证,
Config Files:部署资源的文件名
那这两个文件都还没有,那接下来进生成这两个必要的条件吧。

7.2.2、K8s生成config文件
在K8s集群里生成一个拥有管理员权限的config文件

[[email protected] k8s-cret]# wget https://raw.githubusercontent.com/rootsongjc/kubernetes-handbook/master/tools/create-user/create-user.sh
[[email protected] k8s-cret]# vim create-user.sh
#!/bin/bash

注意修改KUBE_APISERVER为你的API Server的地址

KUBE_APISERVER=$1
USER=KaTeX parse error: Undefined control sequence: \n at position 55: …ver> <username>\̲n̲ ̲Example: https:…{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"];thenecho"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=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 USERadminbindingclusterrole=clusteradminuser={USER}-admin-binding --clusterrole=cluster-admin --user=USER

kubectl config get-contexts

echo “Congratulations!”
echo “Your kubeconfig file is ${USER}.kubeconfig”

[[email protected] k8s-cret]# chmod +x create-user.sh
[[email protected] k8s-cret]# sh create-user.sh https://172.16.194.128:6443 nicksors
执行完成后,当前目录下会生成一个文件,配置测试使用:

[[email protected] k8s-cret]# cp nicksors.kubeconfig /root/.kube/config
[[email protected] 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里的内容贴进去,点击确定即可:
kubernetes系列之《构建企业级CICD平台(四)》

查看ID:f664aa68-fbdc-4f41-9dfb-b241f0951241

7.2.3、资源部署文件
资源部署文件里定义了将交付的容器部署到k8s集群中,其中定义了有deployment资源、Service资源和Ingress提供域名服务资源。

[[email protected] 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中),操作如下:

[[email protected] 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
[[email protected] jenkins]# kubectl get secret|grep registry-pull-secret
registry-pull-secret kubernetes.io/dockerconfigjson 1 15s
7.2.4、流水线语法生成脚本
经过上面两步骤后,可以来到流水线语法这里了,Kuberconfig选择咱们刚刚添加的config授权,Config Files填写我们的资源部署文件名称。
kubernetes系列之《构建企业级CICD平台(四)》

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

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
[[email protected] jenkins]# cat Jenkinsfile
// 公共
def registry = “172.16.194.130”
// 项目
def project = “library”
def app_name = “java-demo”
def image_name = “registry/{registry}/{project}/appname:{app_name}:{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([class:GitSCM,branches:[[name:class: 'GitSCM', branches: [[name: '{Branch}’]], userRemoteConfigs: [[credentialsId: “gitauth",url:"{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 usernamep{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
kubernetes系列之《构建企业级CICD平台(四)》
当然,app项目里一定得有Jenkinsfile文件才行:

[[email protected] app]# tree -L 1
.
├── db
├── deploy.yml
├── Jenkinsfile
├── LICENSE
├── pom.xml
├── README.md
└── src

2 directories, 5 files
[[email protected] app]# git add .
[[email protected] app]# git commit -m ‘create Jenkinsfile’
[[email protected] app]# git push
7.2.6、最终构建:部署到K8s集群
点击构建,选择master分支:
kubernetes系列之《构建企业级CICD平台(四)》
构建失败:
kubernetes系列之《构建企业级CICD平台(四)》

找不到部署的文件,Pipeline执行流水线时会在下载的app代码仓库寻找文件,我们并没有将deploy.yml文件提交到app仓库,这才使得文件找不到,接下来将文件提交到app仓库,继续重新构建:

[[email protected] app]# tree -L 1
.
├── db
├── deploy.yml
├── LICENSE
├── pom.xml
├── README.md
└── src

2 directories, 4 files
[[email protected] app]# git add .
[[email protected] app]# git commit -m ‘create deploy file’
[[email protected] app]# git push
部署成功:
kubernetes系列之《构建企业级CICD平台(四)》

查看k8s集群启动的Pod及资源:

[[email protected] 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
[[email protected] app]# kubectl get ingress|grep java
web java.nicksors.cc 80 3m39s
[[email protected] 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
浏览器访问:
kubernetes系列之《构建企业级CICD平台(四)》

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