发布流程设计

准备基础环境:Harbor、Gitlab、Jenkins

Harbor镜像仓库

项目地址:https://github.com/goharbor/harbor

1.安装docker与docker-compose

2.解压离线包部署

# tar zxvf harbor-offline-installer-v2.0.0.tgz
# cd harbor
# cp harbor.yml.tmpl harbor.yml
# vi harbor.yml
hostname: 192.168.0.14
https: # 先注释https相关配置
harbor_admin_password: Harbor12345
# ./prepare
# ./install.sh --with-chartmuseum
# docker-compose ps
  1. 在Jenkins主机配置Docker可信任,如果是HTTPS需要拷贝证书

由于habor未配置https,还需要在docker配置可信任。

# cat /etc/docker/daemon.json 
{"registry-mirrors": ["https://b9pmyelo.mirror.aliyuncs.com"],
"insecure-registries": ["192.168.0.12"]
}
# systemctl restart docker

Gitlab代码仓库

在Gitlab创建一个项目,然后提交微服务项目代码。

如果没有Gitlab可以使用Docker启动一个:

mkdir /opt/gitlab 
GITLAB_HOME=/opt/gitlab # 数据持久化目录
docker run --detach \
--publish 443:443 \
--publish 88:80 \
--publish 2222:22 \
--name gitlab \
--restart always \
--volume $GITLAB_HOME/config:/etc/gitlab \
--volume $GITLAB_HOME/logs:/var/log/gitlab \
--volume $GITLAB_HOME/data:/var/opt/gitlab \
gitlab/gitlab-ce:latest

访问地址:http://IP:88

初次会先设置管理员密码 ,然后登陆,默认管理员用户名 root,密码就是刚设置的。

Jenkins 发布系统

Jenkins是一款开源 CI&CD 系统,用于自动化各种任务,包括构建、测试和部署。

Jenkins官方提供了镜像:https://hub.docker.com/r/jenkins/jenkins

使用Deployment来部署这个镜像,会暴露两个端口:8080 Web访问端口,50000 Slave通 信端口,容器启动后Jenkins数据存储在/var/jenkins_home目录,所以需要将该目录使用 PV持久化存储。

配置PV持久化存储:

1、部署NFS共享服务器

#安装nfs安装包(每个k8s节点都要安装)
yum install nfs-utils

2、找一个节点作为NFS共享存储服务器

#创建nfs共享目录
mkdir -p /ifs/kubernetes/jenkins-data
#修改nfs配置文件
vim /etc/exports
/ifs/kubernetes 192.168.0.0/24(rw,no_root_squash)
#启动nfs并加入开机自启
systemctl start nfs
systemctl enable nfs
#在别的节点验证是否能挂载成功
mount -t nfs 192.168.0.13:/ifs/kubernetes /mnt/
umount /mnt/

3、为Jenkins准备PV

#vi pv.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
name: pv0001
spec:
capacity:
storage: 5Gi
accessModes: ["ReadWriteOnce"]
nfs:
path: /ifs/kubernetes/jenkins-data
server: 192.168.0.13

#kubectl apply -f pv.yaml

在k8s中部署jenkins

kubectl apply -f jenkins.yml 

先安装后面所需的插件:

Jenkins下载插件默认服务器在国外,会比较慢,建议修改国内源:

 # 进入到nfs共享目录
cd /ifs/kubernetes/jenkins-data
sed -i 's/https:\/\/updates.jenkins.io\/download/https:\/\/mirrors.tuna.tsinghua.edu.cn\/jenkins/g' default.json
sed -i 's/http:\/\/www.google.com/https:\/\/www.baidu.com/g' default.json

# 删除pod重建,pod名称改成你实际的
kubectl delete pod jenkins-d58f4db66-9cthj -n ops

管理Jenkins->系统配置–>管理插件–>分别搜索Git Parameter/Git/Pipeline/kubernetes/Config File Provider, 选中点击安装。

  • Git:拉取代码

  • Git Parameter:Git参数化构建

  • Pipeline:流水线

  • kubernetes:连接Kubernetes动态创建Slave代理

  • Config File Provider:存储配置文件

  • Extended Choice Parameter:扩展选择框参数,支持多选

Jenkins在K8s中动态创建代理

Jenkins主从架构介绍

Jenkins Master/Slave架构,Master(Jenkins本身)提供Web页面 让用户来管理项目和从节点(Slave),项目任务可以运行在Master 本机或者分配到从节点运行,一个Master可以关联多个Slave,这样 好处是可以让Slave分担Master工作压力和隔离构建环境。

当触发Jenkins任务时,Jenkins会调用Kubernetes API 创建Slave Pod,Pod启动后会连接Jenkins,接受任务 并处理。

Kubernetes插件配置

Kubernetes插件:用于Jenkins在Kubernetes集群中运行动态代理

插件介绍:https://github.com/jenkinsci/kubernetes-plugin

配置插件:管理Jenkins->管理Nodes和云->管理云->添加Kubernetes

自定义Jenkins Slave镜像

jenkins-slave.zip

构建salve镜像

unzip jenkins-slave.zip 
cd jenkins-slave/

课件目录里涉及六个文件:

构建并推送到镜像仓库:

docker build -t 192.168.0.14/library/jenkins-slave-jdk:1.8 .
docker push 192.168.0.14/library/jenkins-slave-jdk:1.8

测试主从架构是否正常

新建项目->流水线->Pipeline脚本(可生成示例)


pipeline {
agent {
kubernetes {
label "jenkins-slave"
yaml '''
apiVersion: v1
kind: Pod
metadata:
name: jenkins-slave
spec:
containers:
- name: jnlp
image: "192.168.0.14/library/jenkins-slave-jdk:1.8"
'''
}
}
stages {
stage('Main') {
steps {
sh 'hostname'
}
}
}
}

Jenkins Pipeline流水线

Jenkins Pipeline 介绍

Jenkins Pipeline是一套运行工作流框架,将原本独立运行单个或者多个节点的任 务链接起来,实现单个任务难以完成的复杂流程编排和可视化。

  • Jenkins Pipeline是一套插件,支持在Jenkins中实现持续集成和持续交付;

  • Pipeline通过特定语法对简单到复杂的传输管道进行建模;

  • Jenkins Pipeline的定义被写入一个文本文件,称为Jenkinsfile。

Jenkins Pipeline 语法

Jenkins Pipeline 示例

  • Stages 是 Pipeline 中最主要的组成部分,Jenkins 将会按照 Stages 中描述的顺序 从上往下的执行。

  • Stage:阶段,一个 Pipeline 可以划分为若干个 Stage,每个 Stage 代表一组操作, 比如:Build、Test、Deploy

  • Steps:步骤,Steps 是最基本的操作单元,可以是打印一句话,也可以是构建一 个 Docker 镜像,由各类 Jenkins 插件提供,比如命令:sh ‘mvn’,就相当于我 们平时 shell 终端中执行 mvn命令一样。

流水线自动发布微服务项目

发布需求

在将微服务项目自动化部署到K8s平台会有这些需求:

  • 尽量完全自动化部署,无需过多人工干预

  • 可以选择升级某个、某些微服务

  • 在部署、升级微服务时,可对微服务某些特性做配置,例如命名 空间、副本数量

实现思路

Pipeline编写思路:

在微服务架构中,会涉及几个、几十个微服务,如果每个服务都创建一个item,势必 给运维维护成本增加很大,因此需要编写一个通用Pipeline脚本,将这些微服务部署 差异化部分使用Jenkins参数化,人工交互确认发布的微服务、环境配置等。 但这只是解决用户交互层面,在K8s实际部署项目用YAML创建对应资源,现在问题是 如何接收用户交互参数,自动化生成YAML文件,这就会用到Helm完成YAML文件高 效复用和微服务部署。

部署一个微服务项目,每个微服务的差异化部分在哪里?

  • 服务名称

  • 代码版本

  • 镜像

  • 端口

  • 副本数

  • 标签

  • 域名

编写Pipeline流水线脚本

对于课件中的Pipeline脚本,重点修改这几个变量:

1、将harbor认证和gitlab认证保存到Jenkins凭据

管理Jenkins->安全–>管理凭据->Jnekins->添加凭据->Username with password

分别添加连接gitlab和harbor的用户名到Jenkins凭据,然后获取该凭据ID替换到脚本中docker_registry_auth和git_auth变量的值。

2、将kubeconfig存储在Jenkins,用于slave镜像里kubectl连接k8s集群

管理Jenkins-> Managed files->Add->Custom file ->Content字段内容是kubeconfig(kubeadm部署k8s默认路径在master节点 /root/.kube/config,如果你是二进制部署,需要自己生成,参考下面),然后复制ID替换上述脚本中k8s_auth变量的值。

说明:将kubectl、helm工具封装到Slave镜像中,并通过Config File Provider插件存储连接K8s集群的kubeconfig认证文件,然后挂载到 Slave容器中,这样就能用kubectl apply deploy.yaml --kubeconfig=config管理K8s应用了,为提高安全性,kubeconfig文件可分配权限。

3.上传 helm chart包到镜像仓库

ms-0.1.0.tgz

创建命名空间并部署eureka和MySQL服务(记得还要部署ingress控制器)

kubectl create namespace ms
kubectl apply -f mysql.yaml
#启动部署(修改eureka-service微服务yaml文件中的requests,请求资源设置小一点0.2)
./docker_build.sh eureka-service
kubectl apply -f ingress-controller.yaml

jenkinsfile

#!/usr/bin/env groovy
// 所需插件: Git Parameter/Git/Pipeline/Config File Provider/kubernetes/Extended Choice Parameter
// 公共
def registry = "192.168.0.14"
// 项目
def project = "microservice"
def git_url = "http://192.168.0.14:88/root/k8s-microservice.git"
def gateway_domain_name = "gateway.ctnrs.com"
def portal_domain_name = "portal.ctnrs.com"
// 认证
def image_pull_secret = "registry-pull-secret"
def harbor_auth = "dc8877f5-3238-407f-a9fb-96932501a9b0"
def git_auth = "8d022667-0d69-4e5c-a6f8-6d0ac532f596"
// ConfigFileProvider ID
def k8s_auth = "cf3c93bc-dc97-4305-8379-53a669a2b2b7"

pipeline {
agent {
kubernetes {
label "jenkins-slave"
yaml """
apiVersion: v1
kind: Pod
metadata:
name: jenkins-slave
spec:
containers:
- name: jnlp
image: "${registry}/library/jenkins-slave-jdk:1.8"
imagePullPolicy: Always
volumeMounts:
- name: docker-cmd
mountPath: /usr/bin/docker
- name: docker-sock
mountPath: /var/run/docker.sock
- name: maven-cache
mountPath: /root/.m2
volumes:
- name: docker-cmd
hostPath:
path: /usr/bin/docker
- name: docker-sock
hostPath:
path: /var/run/docker.sock
- name: maven-cache
hostPath:
path: /tmp/m2
"""
}

}
parameters {
gitParameter branch: '', branchFilter: '.*', defaultValue: 'origin/master', description: '选择发布的分支', name: 'Branch', quickFilterEnabled: false, selectedValue: 'NONE', sortMode: 'NONE', tagFilter: '*', type: 'PT_BRANCH'
extendedChoice defaultValue: 'none', description: '选择发布的微服务', \
multiSelectDelimiter: ',', name: 'Service', type: 'PT_CHECKBOX', \
value: 'gateway-service:9999,portal-service:8080,product-service:8010,order-service:8020,stock-service:8030'
choice (choices: ['ms', 'demo'], description: '部署模板', name: 'Template')
choice (choices: ['1', '3', '5', '7'], description: '副本数', name: 'ReplicaCount')
choice (choices: ['ms'], description: '命名空间', name: 'Namespace')
}
stages {
stage('拉取代码'){
steps {
checkout([$class: 'GitSCM',
branches: [[name: "${params.Branch}"]],
extensions: [],
userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]
])
}
}
stage('代码编译') {
// 编译指定服务
steps {
sh """
mvn clean package -Dmaven.test.skip=true
"""
}
}
stage('构建镜像') {
steps {
withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
sh """
docker login -u ${username} -p '${password}' ${registry}
for service in \$(echo ${Service} |sed 's/,/ /g'); do
service_name=\${service%:*}
image_name=${registry}/${project}/\${service_name}:${BUILD_NUMBER}
cd \${service_name}
if ls |grep biz &>/dev/null; then
cd \${service_name}-biz
fi
docker build -t \${image_name} .
docker push \${image_name}
cd ${WORKSPACE}
done
"""
configFileProvider([configFile(fileId: "${k8s_auth}", targetLocation: "admin.kubeconfig")]){
sh """
# 添加镜像拉取认证
kubectl create secret docker-registry ${image_pull_secret} --docker-username=${username} --docker-password=${password} --docker-server=${registry} -n ${Namespace} --kubeconfig admin.kubeconfig |true
# 添加私有chart仓库
helm repo add --username ${username} --password ${password} myrepo http://${registry}/chartrepo/${project}
"""
}
}
}
}
stage('Helm部署到K8S') {
steps {
sh """
common_args="-n ${Namespace} --kubeconfig admin.kubeconfig"

for service in \$(echo ${Service} |sed 's/,/ /g'); do
service_name=\${service%:*}
service_port=\${service#*:}
image=${registry}/${project}/\${service_name}
tag=${BUILD_NUMBER}
helm_args="\${service_name} --set image.repository=\${image} --set image.tag=\${tag} --set replicaCount=${replicaCount} --set imagePullSecrets[0].name=${image_pull_secret} --set service.targetPort=\${service_port} myrepo/${Template}"

# 判断是否为新部署
if helm history \${service_name} \${common_args} &>/dev/null;then
action=upgrade
else
action=install
fi

# 针对服务启用ingress
if [ \${service_name} == "gateway-service" ]; then
helm \${action} \${helm_args} \
--set ingress.enabled=true \
--set ingress.host=${gateway_domain_name} \
\${common_args}
elif [ \${service_name} == "portal-service" ]; then
helm \${action} \${helm_args} \
--set ingress.enabled=true \
--set ingress.host=${portal_domain_name} \
\${common_args}
else
helm \${action} \${helm_args} \${common_args}
fi
done
# 查看Pod状态
sleep 10
kubectl get pods \${common_args}
"""
}
}
}
}

最终效果图:

流水线脚本与源代码一起版本管理

Jenkinsfile文件建议与源代码一起版本管理,实现流水线即 代码(Pipeline as Code)。

这样做的好处:

  • 自动为所有分支创建流水线脚本

  • 方便流水线代码复查、追踪、迭代

  • 可被项目成员查看和编辑

小结

使用Jenkins的插件

  • Git & gitParameter

  • Kubernetes

  • Pipeline

  • Config File Provider

  • Extended Choice Parameter

CI/CD环境特点

  • Slave弹性伸缩

  • 基于镜像隔离构建环境

  • 流水线发布,易维护

Jenkins参数化构建可帮助你完成更复杂环境CI/CD

回滚思路:

1.使用kubectl rollout ,将资源名称和命名空间等参数化传入

2.每次发布记录发布的镜像版本写到一个历史文件中,Extended choice Parameter从历史文件中作为选择项