***1.技术背景***
1.所有互联网企业应用场景都离不开应用上线流程.故需要一套CICD流程系统来承载业务功能的高速迭代.(上线流程:研发提交代码-->代码仓库-->根据项目类型定制CI持续集成--> 根据项目进行CD流程--> 发布到虚拟机
/容器化K8S--> 根据域名或者IP地址进行测试验证)
本文主要描述针对中小型企业发布需求设计的发布系统.k8s容器化发布 本文主要的纬度主要针对快速发布
需求,如果其中有缺点之处或者各位之处.毕竟个人水平有限错误之处请各位大佬理解和指导.
***2.CICD发布技术流程图***
#注意本文前部分--只设计容器化发布(虚拟机后期看时间)
***3.Jenkins配置基于webhook 自动CI配置***
3.1 需要提前自行安装插件列表
#
插件名称
插件版本
插件用途
1
Blue Ocean
1.24.8
流水线蓝图
2
Build Timeout
1.20
构建超时插件
3
HTTP Request
1.8.26
发送http请求插件
4
LDAP
1.24
接入ldap插件
5
Pipeline
2.6
发布流水线
6
Pipeline Utility Steps
2.6.1
流水线内部方法插件
7
Pipeline: Groovy
2.93
Groovy流水线插件
8
Pipeline: Stage Step
2.5
多分支流水线插件
9
Generic Webhook Trigger
1.67
自动触发gitlab webhook插件很重要
10
Git Parameter
0.9.12
git插件
11
Credentials
2.5
认证插件
3.2 开启Jenkins Generic Webhook Trigger 配置
官网参考文档: https://plugins.jenkins.io/generic-webhook-trigger/ #详细
#标红是jenkins Webhook地址gitlab 配置时候需要
1.获取git项目的tags触发
2.获取项目路径
3.获取项目的ssh连接项目地址
4.配置webhook 的认证token值
5.配置webhook 正则表达式
6.自动CI项目流水线配置-红色部分为: jenkins用户生成的id
自动触发CI流水线如下
pipeline{agent any
environment {
voucherId = "d845c81d-4622-4f30-bbc7-af5d3123478b" //用户认证信息-jenkins 拉取gilab代码用户
}
stages{
// 打包,最后把jar包打成tar包,存放
stage('项目CI阶段'){
steps{
script{
//变量处理
server = server.split('/')[-1] # 以/分割切组和项目-拿到项目名称
server = server.replaceAll("_", "-") #项目名称中下划线_替换成-中横线
tmp = serverTag.split('/')[-1] #示例 refs/tags/k8s#v1.0.1_20211217_001 #拿到tags名称
branch = tmp.split('#')[0] #分组名称分支
version = tmp.split('#')[1] #项目版本号
git branch: branch, changelog: false, credentialsId: voucherId, poll: false, url: gitUrl #git拉取自动触发项目代码
language = sh(script: "cat type.txt", returnStdout: true).trim() #获取项目下面的type.txt文件内容
if ( language == 'java' ){ #判断项目根路径下的 type文件内容
sh("mvn -U clean && mvn -U -Dmaven.test.skip=true -q compile package") #java 项目打包
sh("docker build -t harbor.breaklinux.com/k8s/${language}/${server}:${version} .") #构建项目dockefile
sh("sudo docker push harbor.breaklinux.com/k8s/${language}/${server}:${version}") #推送镜像到镜像仓库
sh("sudo docker rmi harbor.breaklinux.com/k8s/${language}/${server}:${version}") #删除CI机器上应用镜像
}else if ( language == 'node' ){ #如果类型是前端node项目
sh("docker build -t harbor.breaklinux.com/k8s/${language}/${server}:${version} .") #构建项目dockefile
sh("sudo docker push harbor.breaklinux.com/k8s/${language}/${server}:${version}") #推送镜像到镜像仓库
sh("sudo docker rmi harbor.breaklinux.com/k8s/${language}/${server}:${version}") #删除CI机器上应用镜像
}
}
}
}
}
// 清理工作空间
post{
always {
deleteDir()
}
}
}
*3.3.Gitlab webhook配置*
每一个项目都需要配置一次
配置官网参考地址:
https://docs.gitlab.com/ee/user/project/integrations/webhooks.html
如图: 配置地址
http://JENKINS_URL/generic-webhook-trigger/invoke
JENKINS_URL: 实际的地址jenkins访问地址
token: 如果jenkins配置有token需要在这里填写如: k8sbuild
*4.Jenkins配置基于shell脚本完成项目创建*
用户输入-首次选择的镜像需要先ci到镜像仓库-在镜像处填写
4.2 选型参数配置
4.3 流水线脚本
pipeline{agent any
environment {
environmental="${选择环境}"
service="${填写服务}"
mem="${内存限制}"
cpu="${CPU限制}"
port="${应用监听端口}"
health="${应用探活}"
java_opts="${JAVA_OPTS}"
app_image="${首次镜像地址}"
domain="${应用访问域名}"
scriptDir="/app/devops/script"
scriptName="app_deploy_k8s.sh"
args1="deploy_deploymente"
args2="deploy_svc"
args3="deploy_ingress"
args4="run_app"
}
stages{
// 接送用户输入参数传递给部署部署脚本
stage('create-deploy'){
steps{
script{
sh("cd ${scriptDir} && sh $scriptName ${args1} ${environmental} ${service} ${cpu} ${mem} ${port} ${health} ${java_opts} ${app_image}")
}
}
}
stage('create-service'){
steps{
script{
sh("cd ${scriptDir} && sh $scriptName ${args2} ${environmental} ${service} ${port}")
}
}
}
stage('create-ingress'){
steps{
script{
sh("cd ${scriptDir} && sh $scriptName ${args3} ${service} ${environmental} ${domain} ${port}" )
}
}
}
stage('k8s_run'){
steps{
script{
sh("cd ${scriptDir} && /bin/bash $scriptName ${args4} ${service}")
}
}
}
}
// 清理工作空间
post{
always {
deleteDir()
}
}
}
4.4 自动生成dp,svc,ingress脚本
set -eu
yamlDir="/app/devops/yaml"
if [ ! -d $yamlDir ]
then
mkdir -p $yamlDir
fi
function dp_deploy(){
env=$2
appName=$3
setCpu=$4
setMem=$5
port=$6
health=$7
java_opts=$8
appImage=$9
cat <<EOF >/$yamlDir/${appName}_deploymente.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: $appName # Deployment 对象的名称,与应用名称保持一致
namespace: $env #命名空间
labels:
appName: $appName # 应用名称
spec:
selector:
matchLabels:
app: $appName #app 标签名称
replicas: 2 #Pod
strategy: #部署策略更多策略 1.https://www.qikqiak.com/post/k8s-deployment-strategies/
type: RollingUpdate #其他类型如下 1.重建(Recreate) 开发环境使用 2.RollingUpdate(滚动更新)
rollingUpdate:
maxSurge: 25% #一次可以添加多少个Pod
maxUnavailable: 25% #滚动更新期间最大多少个Pod不可用
template:
metadata:
labels:
app: '$appName'
spec:
terminationGracePeriodSeconds: 120 #优雅关闭时间,这个时间内优雅关闭未结束,k8s 强制 kill
affinity:
podAntiAffinity: # pod反亲和性,尽量避免同一个应用调度到相同node
preferredDuringSchedulingIgnoredDuringExecution: #硬需求 1.preferredDuringSchedulingIgnoredDuringExecution 软需求
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- $appName
topologyKey: "kubernetes.io/hostname"
containers:
- name: $appName # 容器名称,与应用名称保持一致
image: $appImage #遵守镜像命名规范
imagePullPolicy: Always #镜像拉取策略 1.IfNotPresent如果本地存在镜像就优先使用本地镜像。2.Never直接不再去拉取镜像了,使用本地的.如果本地不存在就报异常了。
livenessProbe: #存活探针器配置
failureThreshold: 3 #处于成功时状态时,探测操作至少连续多少次的失败才被视为检测不通过,显示为#failure属性.默认值为3,最小值为 1,存活探测情况下的放弃就意味着重新启动容器。
httpGet: #1.存活探针器三种方式 1.cmd命令方式进行探测 2.http 状态码方式 3.基于tcp端口探测
path: $health #k8s源码中healthz 实现 https://github.com/kubernetes/kubernetes/blob/master/test/images/agnhost/liveness/server.go
port: $port #应用程序监听端口
initialDelaySeconds: 600 #存活性探测延迟时长,即容器启动多久之后再开始第一次探测操作,显示为delay属性.默认值为0,即容器启动后立刻便开始进行探测.
periodSeconds: 10 #执行探测的时间间隔(单位是秒)。默认是 10 秒。最小值是 1秒,过高的频率会对Pod对象带来较大的额外开销,而过低的频率又会使得对错误的反应不及时.
successThreshold: 1 #处于失败状态时,探测操作至少连续多少次的成功才被人为是通过检测,显示为#success属性,默认值为1,最小值也是1
timeoutSeconds: 3 #存活性探测的超时时长,显示为timeout属性,默认值1s,最小值也是1s
readinessProbe: #定义就绪探测器
failureThreshold: 3 #处于成功时状态时,探测操作至少连续多少次的失败才被视为检测不通过,显示为#failure属性.默认值为3,最小值为 就绪探测情况下的放弃 Pod 会被打上未就绪的标签.
tcpSocket: # 1.就绪探针三种方式 1.cmd命令方式进行探测 2.http 状态码方式 3.基于tcp端口探测
port: $port #应用程序监听端口
initialDelaySeconds: 10 #执行探测的时间间隔(单位是秒)。默认是 10 秒。最小值是 1秒,过高的频率会对Pod对象带来较大的额外开销,而过低的频率又会使得对错误的反应不及时.
periodSeconds: 10 #执行探测的时间间隔(单位是秒)。默认是 10 秒。最小值是 1秒,过高的频率会对Pod对象带来较大的额外开销,而过低的频率又会使得对错误的反应不及时
successThreshold: 1 #处于失败状态时,探测操作至少连续多少次的成功才被人为是通过检测,显示为#success属性,默认值为1,最小值也是1
timeoutSeconds: 3 #存活性探测的超时时长,显示为timeout属性,默认值1s,最小值也是1s
ports:
- containerPort: $port #应用监听的端口
protocol: TCP #协议 tcp和 udp
env: #应用配置中心环境变量
- name: env
value: $env
- name: spring.profiles.active
value: $env
- name: JAVA_OPTS
value: -server "$java_opts"
resources: #qos限制 1.QoS 主要分为Guaranteed、Burstable 和 Best-Effort三类,优先级从高到低
requests:
memory: ${setMem}Mi #内存4G
cpu: ${setCpu}m #cpu 0.5
limits:
memory: ${setMem}Mi
cpu: ${setCpu}m
#volumeMounts: #挂载NAS POD主机目录
# - name: nas-pvc # sidecar-sre
# mountPath: /xwkj/logs/commentcenter #该目录作为程序日志sidecar路径收集
#volumes:
# - name: nas-pvc
# persistentVolumeClaim:
# claimName: $appName
EOF
}
function dp_svc(){
env=$2
appName=$3
port=$4
cat <<EOF >/$yamlDir/${appName}_service.yaml
apiVersion: v1
kind: Service
metadata:
name: $appName # Service 名称,与应用名称保持一致
namespace: dev # 命名空间,与环境名称保持一致,小写字母
spec:
type: ClusterIP # 线下环境,统一采用 NodePort
selector:
app: $appName # 应用名称,与 Deployment 中定义的 Pod 中的 app 标签保持一致
ports:
- name: 'tcp-$port' # 端口名称,与 targetPort 字段保持一致
port: $port # Service 的端口,与 Pod 中定义的 containerPort 一致
targetPort: $port # 目标容器的端口,与 Pod 中定义的 containerPort 保持一致
protocol: TCP
EOF
}
function dp_ingress(){
appName=$2
env=$3
domain=$4
port=$5
cat <<EOF >/$yamlDir/${appName}_ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: $appName
namespace: $env
spec:
rules:
- host: $domain
http:
paths:
- backend:
service:
name: $appName
port:
number: $port
path: /
pathType: ImplementationSpecific
EOF
}
function del_app() {
appName=$1
/usr/sbin/kubectl delete --kubeconfig=/root/.kube/config -f /app/devops/yaml/${appName}_ingress.yaml
/usr/sbin/kubectl delete --kubeconfig=/root/.kube/config -f /app/devops/yaml/${appName}_service.yaml
/usr/sbin/kubectl delete --kubeconfig=/root/.kube/config -f /app/devops/yaml/${appName}_deploymente.yaml
}
function run_app() {
appName=$2
/usr/sbin/kubectl apply --kubeconfig=/root/.kube/config -f /app/devops/yaml/${appName}_ingress.yaml
/usr/sbin/kubectl apply --kubeconfig=/root/.kube/config -f /app/devops/yaml/${appName}_service.yaml
/usr/sbin/kubectl apply --kubeconfig=/root/.kube/config -f /app/devops/yaml/${appName}_deploymente.yaml
}
function main() {
dp_deploy $*
dp_svc $*
dp_ingress $*
run_app $*
}
case $1 in
"all")
main $*
;;
"deploy_deploymente")
dp_deploy $*
;;
"deploy_svc")
dp_svc $*
;;
"deploy_ingress")
dp_ingress $*
;;
"delete_app")
del_app $*
;;
"run_app")
run_app $*
;;
"deploy_app")
main $*
;;
*)
echo -e "\033[32m 参数如下: \033[0m"
echo -e "\033[32m deploy_deploymente \033[0m 部署App Deploymente资源对象 后面跟参数"
echo -e "\033[32m deploy_svc \033[0m 部署App Service资源对象 后面跟参数"
echo -e "\033[32m deploy_ingress \033[0m 部署App Ingress资源对象 后面跟参数"
echo -e "\033[32m delete_app \033[0m 删除应用资源对象Deploymente,Service,Ingess 后面跟服务名称"
echo -e "\033[32m run_app \033[0m 创建执行k8s应用 后面跟服务名称"
echo -e "\033[32m deploy_app \033[0m 一键部署kubernetes 应用后面跟参数"
;;
esac
*5.Jenkins配置基于harbor 自动CD更新配置*
5.1 CD流程
用户选择运行环境env和应用名称->根据应用名称查询通过harbor接口获取镜像名称和近期的镜像CI镜像版本->通过返回数据处理结果用户进行选择-->执行更新k8s pod镜像-->k8s自动滚动升级重启服务.
用户输入界面
用户选择当前应用版本
5.2 参数化配置
运行环境=k8s命名空间
服务名称harbor仓库应用镜像名称
5.3 应用更新流水线脚本
#注意habor镜像仓库版本已验证版本 V2版本 v1没有验证api不同1.版本v2.3.1-1058f330
2.版本V2.2.2-56D7937f
pipeline{
agent any
stages{
stage('update'){
steps{
script{
tmp = server.split('-')
group = tmp[0]
server = tmp[1]
// 获取语言和服务
url = "https://harbor.breaklinux.com/api/v2.0/search?q=${server}"
ret = httpRequest acceptType: 'APPLICATION_JSON', contentType: 'APPLICATION_JSON', quiet: true, responseHandle: 'LEAVE_OPEN', url: url, wrapAsMultipart: false
jsres = readJSON text: ret.content
print(jsres)
for (i in jsres['repository']){
if ( i['repository_name'] =~ 'k8s' ){
image = i['repository_name']
}
}
tmp = image.split('/')
project = tmp[0]
language = tmp[1]
server = tmp[2]
// 获取镜像
url = "https://harbor.breaklinux.com/api/v2.0/projects/k8s/repositories/${language}%252F${server}/artifacts?page=1&page_size=15&with_tag=true&with_label=false&with_scan_overview=false&with_signature=false&with_immutable_status=false"
ret = httpRequest acceptType: 'APPLICATION_JSON', consoleLogResponseBody: true, contentType: 'APPLICATION_JSON', responseHandle: 'LEAVE_OPEN', url: url, wrapAsMultipart: false
jsres = readJSON text: ret.content
versions = ""
for (int i = 0; i < jsres.size(); i++ ){
print(jsres[i]['tags']['name'][0])
versions=versions+jsres[i]['tags']['name'][0]+"\n"
}
print(versions)
imagesurl = "harbor.breaklinux.com"
version= input message: '请选择更新包:',ok:'确认',parameters: [choice(name: '',choices: "${versions}", description: '')]
print(version)
sh("kubectl --kubeconfig=/root/.kube/config set image deployment/${language}-${group}-${server} ${language}-${group}-${server}=${imagesurl}/${project}/${language}/${server}:${version} -n ${runenv}")
}
}
}
}
}