滚动发布
常用发布方式有蓝绿发布、灰度发布、滚动发布,由于k8s中deployment的特性,默认情况下是滚动发布,其实只要更新deployment中的镜像标签,即是滚动发布,通过spec.strategy
中参数来具体实现,如下介绍所示:
strategy:
rollingUpdate:
maxSurge: 25% #最大峰值:是一个可选字段,用来指定可以创建的超出 期望 Pod 个数的 Pod 数量。此值可以是绝对数(例如,5)或所需 Pods 的百分比(例如,10%)。 如果 MaxUnavailable 为 0,则此值不能为 0。百分比值会通过向上取整转换为绝对数。 此字段的默认值为 25%。
maxUnavailable: 1 #最大不可用:是一个可选字段,用来指定 更新过程中不可用的 Pod 的个数上限。该值可以是绝对数字(例如,5),也可以是 所需 Pods 的百分比(例如,10%)。百分比值会转换成绝对数并去除小数部分。 如果 .spec.strategy.rollingUpdate.maxSurge 为 0,则此值不能为 0。 默认值为 25%。
type: RollingUpdate #更新创建策略,这里是滚动发布
发布细节及注意事项
- 微服务注册中心采用Eureka,部署后一般不会改动,直接手动部署,不放在cicd中;cicd只发布各个微服务的deployment,service/ingress也不经常改动,直接在阿里ACK控制台中手动安装,或新建Jenkins job发布;扩缩容也通过阿里控制台完成,自建k8s,单独新建job来实现,不推荐写复杂的逻辑判断,将扩缩容、更新发布放在一个Jenkins 任务重实现。
- 构建镜像的tag通过环境变量BUILD_NUMBER来命名;同时yaml中定义两个变量,镜像tag同理通过BUILD_NUMBER,yaml中的副本数replicas的值,通过parameter获取,传递进来;
- 第三步部署时,有引用到第二部定义的配置文件变量 admin.kubeconfig,此处要注意,当yaml在工作目录时,才能引用,如果yaml文件是在子文件夹,会提示找不到文件。
Jenkinsfile文件:
// 所需插件: Git Parameter/Git/Pipeline/Config File Provider/kubernetes/Extended Choice Parameter
pipeline {
agent {
kubernetes {
label "jenkins-slave"
yaml """
kind: Pod
metadata:
name: jenkins-slave
spec:
containers:
- name: jnlp
image: "47.8.9.9/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
"""
}
}
environment {
//Harbor镜像仓库地址
registry = "47.8.8.9"
//Harbor项目名称,根据项目实际,可以直接引用,不用定义变量也行
project = "microservice"
harbor_registry_auth = "27e8ae31-f1c5-4asc-9811-XXXX"
image_pull_secret = "registry-pull-secret"
git_url = "http://47.8.8.9:580/root/ms.git"
git_auth = "7c0668b4-43ee-427f-b654-219e11XXXX"
k8s_auth = "9cabea02-4461-43b1-8cbe-798d0656XXXX"
}
parameters {
gitParameter branch: '', branchFilter: '.*', defaultValue: '', 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, portal, product, order, stock'
choice (choices: ['ms', 'demo'], description: '部署模板', name: 'Template')
choice (choices: ['1', '2', '3', '5', '0'], description: '副本数', name: 'ReplicaCount')
//此处可以不用定义参数,直接引用ms
choice (choices: ['ms'], description: '命名空间', name: 'Namespace')
}
stages {
stage('拉取代码'){
steps {
checkout([$class: 'GitSCM', branches: [[name: "${params.Branch}"]], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]])
}
}
stage('代码编译') {
// 编译指定服务
steps {
sh "mvn clean package -Dmaven.test.skip=true"
}
}
stage('构建镜像') {
steps {
withCredentials([usernamePassword(credentialsId: "${harbor_registry_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
sh """
docker login -u ${username} -p '${password}' ${registry}
#遍历extendedChoice中选定的service的Service
for service in \$(echo ${Service} |sed 's/,/ /g'); do
service_name=\${service}-service
image_name=${registry}/${project}/\${service}:${BUILD_NUMBER}
cd \${service_name}
#if Dockerfile在biz目录下
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
"""
}
}
}
}
stage('kubectl部署到K8S') {
steps {
sh """
common_args="--kubeconfig admin.kubeconfig" #yaml在子文件夹里面 cd k8s 后,不能识别admin.kubeconfig, 上一个stage进入ks后,在此步骤默认到workspace下面
#遍历extendedChoice中选定的service的Service
for service in \$(echo ${Service} |sed 's/,/ /g'); do
service_yaml=\${service}.yaml
sed -i "s/<BUILD_NUMBER>/${BUILD_NUMBER}/" "\${service_yaml}"
sed -i "s/<REPLICACOUNT>/${ReplicaCount}/" "\${service_yaml}"
cat \${service_yaml}
kubectl apply -f \${service_yaml} \${common_args}
done
# 查看Pod状态
sleep 10
kubectl get pods \${common_args} -n ${Namespace}
"""
}
}
}
}
关于变量
- step中sh ""shell里面定义变量,并引用该变量时,需要加上“$”声明作为shell变量来调用,例如service_name=${service}-service,cd ${service_name},shell和groovy变量调用方式相同,加上会在运行时获得变量值,作为shell变量;不加的话,会当做groovy变量调用,因为找不到会提示“groovy.lang.MissingPropertyException: No such property: service_yaml for class: groovy.lang.Binding”的错误。
sed -i "s/<BUILD_NUMBER>/${BUILD_NUMBER}/" "\${service_yaml}"
和sed -i "s/<REPLICACOUNT>/${ReplicaCount}/" "\${service_yaml}"
,同理service_yaml需要加上“\”,另外sed命令引用变量获取文件名,需要加上双引号;
关于K8S发布
- 仅当 Deployment Pod 模板(即 .spec.template)发生改变时,例如模板的标签或容器镜像被更新, 才会触发 Deployment 上线。 其他更新(如对 Deployment 执行扩缩容的操作)不会触发上线动作;因此当代码更新时才通过此发布更新;如果只是扩缩容,可以通过控制台操作伸缩,如下图所示:
如果是自建K8S集群,只需要专门新建一个扩缩容的k8s,Jenkins job即可。
灰度发布(金丝雀发布)
Istio和Ingress实现灰度:先理解灰度发布,假设v1版本有100个Pod,灰度发布是“灰色”v2的Pod逐渐增加比如从0依次调整到20,30,蓝色v1的Pod逐渐减小依次从100调低到80,70,两个版本的总Pod数量在某一时刻保持100。
下面是另外一个实例:
For non istio application, we offer you a simple implement to control the traffic during the rollout, argo rollouts will control the replica number base on the traffic weight.
For example, if you set replica number to 5, and set weight as 20, you can get below result:
In the screenshot, 4 stable pods can get 80% traffic and 1 canary pod can get other 20% traffic.
service层实现灰度发布
此处参考官方文档介绍
K8S Service中实现灰度有点类似于蓝绿发布,区别是切换的百分比不一样,相同点是K8S中都部署两套deployment。
需要多标签的场景是用来区分同一组件的不同版本或者不同配置的多个部署。 常见的做法是部署一个使用金丝雀发布来部署新应用版本 (在 Pod 模板中通过镜像标签指定),保持新旧版本应用同时运行。 这样,新版本在完全发布之前也可以接收实时的生产流量,官方给的例子直接是pod,同理如果在deployment中,通过 spec.template.metadata.lables来区分”stable"和“canary”的pod。
例如,你可以使用 track 标签来区分不同的版本。
主要稳定的发行版将有一个 track 标签,其值为 stable:
name: frontend
replicas: 3
...
labels:
app: guestbook
tier: frontend
track: stable
...
image: gb-frontend:v3
然后,你可以创建 guestbook 前端的新版本,让这些版本的 track 标签带有不同的值 (即 canary),以便两组 Pod 不会重叠:
name: frontend-canary
replicas: 1
...
labels:
app: guestbook
tier: frontend
track: canary
...
image: gb-frontend:v4
前端service通过选择标签的公共子集(即忽略 track 标签)来覆盖两组副本, 以便流量可以转发到两个应用:
selector:
app: guestbook
tier: frontend
你可以调整 stable 和 canary 版本的副本数量,以确定每个版本将接收 实时生产流量的比例(在本例中为 3:1)。 一旦有信心,你就可以将新版本应用的 track 标签的值从 canary 替换为 stable,并且将老版本应用删除。
此种方法只需要额外部署金丝雀版本的deployment即可,不需要额外添加ingress和service
INGRESS实现灰度发布
基于ingress的canary-weight
注解来实现,另外可以通过七层请求头Request Header实现流量切分,此处可参考阿里官方文档介绍
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "20"
nginx.ingress.kubernetes.io/canary-by-header: "ack"
nginx.ingress.kubernetes.io/canary-by-header-value: "alibaba"
由上介绍的两种灰度发布方式可知,如果没有需要基于七层请求头控制流量的需求,用第一种方法,基于service的方式实现灰度就可以了,这样更简单一些。
ISTIO实现灰度
The following virtual service routes requests to different versions of a service depending on whether the request comes from a particular user.
- name: simple-service-with-header
match:
- uri:
prefix: "/simple-service/"
headers:
end-user:
exact: whoami
rewrite:
uri: "/"
route:
- destination:
host: simple-service.shapp-dev.svc.cluster.local
subset: v1
port:
number: 8080
weight: 0
- destination:
host: simple-service.shapp-dev.svc.cluster.local
subset: v2
port:
number: 8080
weight: 100
It is also supported to specify “perfix” or “regex” for the match patterns. From here you will be able to find more explanations and constraints.
Istio Virtual Service请求头、权重这块和上面的Ingress类似
蓝绿发布
蓝绿发布:举例说明,是v1的Pod仍然是100,同时部署v2版本的Pod100个(这里根据实际情况做调整,可以小于100),然后直接切换流量,蓝绿部署方式,可以是两个K8S集群,新版本放cluster2,老版本放cluster1,当然这种方式比较豪。也可以在同一个K8S cluster按照上面的方式部署两套。
总结
灰度(金丝雀)发布主要是通过调整权重、请求头等控制流量比例进行发布,而蓝绿发布直接从旧到新的迁移。