kubernetes(k8s)滚动发布,不宕机实战
前言
- 之前一直想怎么能让自己的服务在发布的时候也能提供服务,做到无缝衔接。所幸k8s就提供滚动发布的功能。下面我们先了解下什么是滚动发布。
滚动发布
- 滚动发布就是在升级过程中,先启动一台新版本的服务,等新的那台服务稳定后,就把旧的一台服务干掉,后面的老版本服务都是这样替换的过程。如下图所示。
- 虚线部分表示服务已经被替换掉了。
- 理论来说可以实现更新时使用户无感知,做到无缝衔接。
开始前的准备
镜像
这个镜像其实是我学
kubernetes ConfigMap
的时候准备的,ConfigMap
怎么使用可参考拙作kubernetes(k8s) ConfigMap实战。
- 看过kubernetes(k8s) ConfigMap实战就知道这2个版本没什么差别,只是
7.7.1909
版本的打印增加了customValue
。 - 我要做的就是把
7.7.1908
升级到7.7.1909
。
k8s配置文件
- k8s的配置文件(spring-boot-kubernetes-deployment.yaml)如下代码。
a1030907690/centos_java:7.7.1908 ;a1030907690/centos_java:7.7.1909我一早就下载好了。
# 把jar包打到centos里的办法
# 从本地文件创建ConfigMap kubectl create cm spring-boot-kubernetes-conf --from-file=application.yml
# 修改 kubectl edit cm spring-boot-kubernetes-conf ,也可以先删除 kubectl delete cm spring-boot-kubernetes-conf 再从本地文件创建
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-boot-kubernetes-deployment
spec:
replicas: 2
selector:
matchLabels:
app: spring-boot-kubernetes-deployment
template:
metadata:
labels:
app: spring-boot-kubernetes-deployment
spec:
containers:
- name: spring-boot-kubernetes
image: a1030907690/centos_java:7.7.1908
command: [ "java","-jar","spring-boot-kubernetes-0.0.1-SNAPSHOT.jar" ]
imagePullPolicy: Never # 只使用本地镜像,防止ErrImagePull异常
ports:
- containerPort: 8080
env: # 解决Java程序时区问题
- name: TZ
value: Asia/Shanghai
volumeMounts:
- name: config
mountPath: /config # 应用配置文件路径
volumes:
- name: config
configMap:
name: spring-boot-kubernetes-conf # 这个名字与创建时对应
items:
- key: application.yml
path: application.yml
注意:这个配置并不完美,先埋个伏笔。
验证工具
- 我使用
JMeter
一直调用接口,然后执行滚动发布,看汇总结果就可以了。当然也可以使用其他工具(手写一个也行呀)。
实战
- 执行命令,创建
deployment
。
kubectl apply -f spring-boot-kubernetes-deployment.yaml
-
创建
service
的过程就不演示了。可以参考拙作使用Kubernetes(k8s)+ Docker运行Java服务。 -
运行
JMeter
观察接口是否一直能通,如下图所示。
中途我会使用
kubectl set image deployment/spring-boot-kubernetes-deployment spring-boot-kubernetes=a1030907690/centos_java:7.7.1909 --record
来滚动更新。
- 可以看到有异常情况发生,有的请求不通。
- 本想实现更新时不宕机,无缝衔接,我这波被打脸了。
问题
-
经过大半天的苦战,各处搜索,多方实战。终于让我发现了问题。
- k8s就绪检查:在新的
pod
创建后,这是程序正在初始化,请求不应该转发到新的pod,并且旧pod
的也不应该在新pod
真正能接收请求前被删除。配置k8s的就绪探测器
可以解决。 - 删除pod前的优雅停机:旧的
pod
内部可能还有些请求未处理,有可能是个耗时操作,我们需要实现优雅停机,简单的利用睡眠,在删除pod
前阻塞一点时间,给程序多一些时间处理未完成的请求。
- k8s就绪检查:在新的
-
要解决前面的问题,我新建了yaml文件配置(spring-boot-kubernetes-deployment-rolling-publishing.yaml),配置如下。
# 参考 https://www.jianshu.com/p/c63c9efaeac3
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-boot-kubernetes-deployment
spec:
replicas: 2
strategy:
rollingUpdate:
maxSurge: 1 # 最大峰值用来指定可以创建的超出期望 Pod 个数的 Pod 数量。此值可以是绝对数(例如,5)或所需 Pods 的百分比(例如,10%)
maxUnavailable: 0 #最大不可用 是一个可选字段,用来指定 更新过程中不可用的 Pod 的个数上限。该值可以是绝对数字(例如,5)也可以是所需 Pods 的百分比(例如,10%)
selector:
matchLabels:
app: spring-boot-kubernetes-deployment
template:
metadata:
labels:
app: spring-boot-kubernetes-deployment
spec:
terminationGracePeriodSeconds: 300 #如果需要的优雅终止时间比较长 (preStop + 业务进程停止可能超过 30s),可根据实际情况自定义 terminationGracePeriodSeconds,避免过早的被 SIGKILL杀死,与下面preStop有关联,300属于总时间
containers:
- name: spring-boot-kubernetes
image: a1030907690/centos_java:7.7.1908
command: [ "java","-jar","spring-boot-kubernetes-0.0.1-SNAPSHOT.jar" ]
imagePullPolicy: Never # 只使用本地镜像,防止ErrImagePull异常
ports:
- containerPort: 8080
readinessProbe: #就绪探针
httpGet:
path: /
port: 8080
initialDelaySeconds: 50 #容器启动后要等待多少秒后才启动存活和就绪探测器, 默认是 0 秒,最小值是 0
periodSeconds: 5 # 指定了 kubelet 应该每 5 秒执行一次存活探测。
successThreshold: 1 #探测器在失败后,被视为成功的最小连续成功数。默认值是 1。 存活和启动探测的这个值必须是 1。最小值是 1。
failureThreshold: 2 #当探测失败时,Kubernetes 的重试次数。 对存活探测而言,放弃就意味着重新启动容器。 对就绪探测而言,放弃意味着 Pod 会被打上未就绪的标签。默认值是 3。最小值是 1
env: # 解决Java程序时区问题
- name: TZ
value: Asia/Shanghai
volumeMounts:
- name: config
mountPath: /config # 应用配置文件路径
lifecycle:
preStop:
exec:
command: ["/bin/sh","-c","echo this pod is stopping. > /stop.log && sleep 90s"]
volumes:
- name: config
configMap:
name: spring-boot-kubernetes-conf # 这个名字与创建时对应
items:
- key: application.yml
path: application.yml
terminationGracePeriodSeconds
终止宽限期秒属于是一个总的时间控制,默认值是30s
,如果不修改这个值,后面preStop
钩子里sleep 90s
是无效的。
再次实战
- 再创建deployment
kubectl apply -f spring-boot-kubernetes-deployment-rolling-publishing.yaml
- 滚动更新命令还是一样的。
kubectl set image deployment/spring-boot-kubernetes-deployment spring-boot-kubernetes=a1030907690/centos_java:7.7.1909 --record
- 依旧来看Jmeter的结果,如下所示。
- 这次滚动发布就没有问题了。实现了不宕机,无缝衔接。
- 删除
pod
的宽限期差不多是300
秒,和terminationGracePeriodSeconds
配置的差不多。
web管理后台我用的是
kubesphere
-
滚动更新其他命令(
spring-boot-kubernetes-deployment
是我的deployment)。- 查看滚动更新状态
kubectl rollout status deployment/spring-boot-kubernetes-deployment
- 历史记录
kubectl rollout history deployment/spring-boot-kubernetes-deployment
- 查看某个历史详情
kubectl rollout history deployment/spring-boot-kubernetes-deployment --revision=2
- 回滚(回到上次)
kubectl rollout undo deployment/spring-boot-kubernetes-deployment
- 回滚(回到指定版本)
kubectl rollout undo deployment/spring-boot-kubernetes-deployment --to-revision=2
小结
- k8s真的很强大,滚动发布很好用,只需要一些配置就可以了。理论上任何编程语言的程序都可以使用。为企业发布程序解决了大难题。
- 不过滚动发布是不像灰度(金丝雀)发布那样可以细粒度控制流量。只要发布好的状态流量就会进来。所以要求生产发布的程序保证经过严密测试,如果到处bug,造成脏数据就尴尬了。