Kubernetes应用质量管理(下)
作者 | 老郑
来源 |运维开发故事(ID:mygsdcsf)
转载请联系授权(微信ID:wanger5354)
服务可用性管理
高可用
生产级别应用,为了保证应用的可用性,除了特殊应用(例如批次应用)都会保持高可用,所以在设计应用Pod的时候,就要考虑应用的高可用。
最简单的就是多副本,也就是在创建应用的时候,至少需要2个副本,如下指定replicas为3就表示该应用有3个副本:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx-deployment
spec:
progressDeadlineSeconds: 600
replicas: 3
revisionHistoryLimit: 10
selector:
matchLabels:
app: nginx
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
containers:
- image: nginx:1.8
imagePullPolicy: IfNotPresent
name: nginx
resources:
requests:
cpu: 0.5
memory: 500M
limits:
cpu: 0.5
memory: 500M
ports:
- containerPort: 80
name: http
protocol: TCP
但是光配置多副本就够了么?
如果这三个副本都调度到一台服务器上,该服务器因某些原因宕机了,那上面的应用是不是就不可用?
为了解决这个问题,我们需要为同一个应用配置反亲和性,也就是不让同一应用的Pod调度到同一主机上,将上面的应用YAML改造成如下:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx-deployment
spec:
progressDeadlineSeconds: 600
replicas: 3
revisionHistoryLimit: 10
selector:
matchLabels:
app: nginx
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx
topologyKey: kubernetes.io/hostname
containers:
- image: nginx:1.8
imagePullPolicy: IfNotPresent
name: nginx
resources:
requests:
cpu: 0.5
memory: 500M
limits:
cpu: 0.5
memory: 500M
ports:
- containerPort: 80
name: http
protocol: TCP
这样能保证同应用不会被调度到同节点,基本的高可用已经做到了。
可用性
但是光保证应用的高可用,应用本身不可用,也会导致异常。
我们知道Kubernetes的Deployment的默认更新策略是滚动更新,如何保证新应用更新后是可用的,这就要使用readinessProbe,用来确保应用可用才会停止老的版本,上面的YAML修改成如下:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx-deployment
spec:
progressDeadlineSeconds: 600
replicas: 3
revisionHistoryLimit: 10
selector:
matchLabels:
app: nginx
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx
topologyKey: kubernetes.io/hostname
containers:
- image: nginx:1.8
imagePullPolicy: IfNotPresent
name: nginx
resources:
requests:
cpu: 0.5
memory: 500M
limits:
cpu: 0.5
memory: 500M
readinessProbe:
failureThreshold: 3
httpGet:
path: /
port: http
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 3
ports:
- containerPort: 80
name: http
protocol: TCP
这样至少能保证只有新版本可访问才接收外部流量。
但是应用运行过程中异常了呢?这就需要使用livenessProbe来保证应用持续可用,上面的YAML修改成如下:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx-deployment
spec:
progressDeadlineSeconds: 600
replicas: 3
revisionHistoryLimit: 10
selector:
matchLabels:
app: nginx
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx
topologyKey: kubernetes.io/hostname
containers:
- image: nginx:1.8
imagePullPolicy: IfNotPresent
name: nginx
resources:
requests:
cpu: 0.5
memory: 500M
limits:
cpu: 0.5
memory: 500M
readinessProbe:
failureThreshold: 3
httpGet:
path: /
port: http
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 3
livenessProbe:
failureThreshold: 3
httpGet:
path: /
port: http
scheme: HTTP
initialDelaySeconds: 20
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 3
ports:
- containerPort: 80
name: http
protocol: TCP
上面的readinessProbe和livenessProbe都是应用在运行过程中如何保证其可用,那应用在退出的时候如何保证其安全退出?
所谓安全退出,也就是能正常处理退出逻辑,能够正常处理退出信号,也就是所谓的优雅退出。
优雅退出有两种常见的解决方法:
- 应用本身可以处理SIGTERM信号。
- 设置一个preStop hook,在hook中指定怎么优雅停止容器
这里抛开应用本身可以处理SIGTERM信号不谈,默认其能够处理,我们要做的就是协助其能优雅退出。在Kubernetes中,使用preStop hook来协助处理,我们可以将上面的YAML修改成如下:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx-deployment
spec:
progressDeadlineSeconds: 600
replicas: 3
revisionHistoryLimit: 10
selector:
matchLabels:
app: nginx
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx
topologyKey: kubernetes.io/hostname
containers:
- image: nginx:1.8
imagePullPolicy: IfNotPresent
name: nginx
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- sleep 15
resources:
requests:
cpu: 0.5
memory: 500M
limits:
cpu: 0.5
memory: 500M
readinessProbe:
failureThreshold: 3
httpGet:
path: /
port: http
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 3
livenessProbe:
failureThreshold: 3
httpGet:
path: /
port: http
scheme: HTTP
initialDelaySeconds: 20
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 3
ports:
- containerPort: 80
name: http
protocol: TCP
当然,这里只是一个样例,实际的配置还需要根据企业情况做跳转,比如企业使用了注册中心如zk或者nacos,我们就需要把服务从注册中心下掉。
PDB
上面的那些配置基本可以让应用顺利的在Kubernetes里跑了,但是不可避免有维护节点的需求,比如升级内核,重启服务器等。
而且也不是所有的应用都可以多副本,当我们使用kubectl drain的时候,为了避免某个或者某些应用直接销毁而不可用,Kubernetes引入了PodDisruptionBudget(PDB)控制器,用来控制集群中Pod的运行个数。
在PDB中,主要通过两个参数来控制Pod的数量:
- minAvailable:表示最小可用Pod数,表示在Pod集群中处于运行状态的最小Pod数或者是运行状态的Pod数和总数的百分比;
- maxUnavailable:表示最大不可用Pod数,表示Pod集群中处于不可用状态的最大Pod数或者不可用状态Pod数和总数的百分比;
注意:minAvailable和maxUnavailable是互斥了,也就是说两者同一时刻只能出现一种。
kubectl drain命令已经支持了PodDisruptionBudget控制器,在进行kubectl drain操作时会根据PodDisruptionBudget控制器判断应用POD集群数量,进而保证在业务不中断或业务SLA不降级的情况下进行应用POD销毁。在进行kubectl drain或者Pod主动逃离的时候,Kubernetes会通过以下几种情况来进行判断:
- minAvailable设置成了数值5:应用POD集群中最少要有5个健康可用的POD,那么就可以进行操作。
- minAvailable设置成了百分数30%:应用POD集群中最少要有30%的健康可用POD,那么就可以进行操作。
- maxUnavailable设置成了数值5:应用POD集群中最多只能有5个不可用POD,才能进行操作。
- maxUnavailable设置成了百分数30%:应用POD集群中最多只能有30%个不可用POD,才能进行操作。
在极端的情况下,比如将maxUnavailable设置成0,或者设置成100%,那么就表示不能进行kubectl drain操作。同理将minAvailable设置成100%,或者设置成应用POD集群最大副本数,也表示不能进行kubectl drain操作。
注意:使用PodDisruptionBudget控制器并不能保证任何情况下都对业务POD集群进行约束,PodDisruptionBudget控制器只能保证POD主动逃离的情况下业务不中断或者业务SLA不降级,例如在执行kubectldrain命令时。
(1)、定义minAvailable
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: pdb-demo
spec:
minAvailable: 2
selector:
matchLabels:
app: nginx
(2)、定义maxUnavailable
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: pdb-demo
spec:
maxUnavailable: 1
selector:
matchLabels:
app: nginx
可以看到PDB是通过label selectors和应用Pod建立关联,而后在主动驱逐Pod的时候,会保证app: nginx的Pod最大不可用数为1,假如本身是3副本,至少会保证2副本正常运行。
总结
上面只是对Kubernetes中应用做了简单的可用性保障,在生产中,应用不仅仅是它自己,还关联上游、下游的应用,所以全链路的应用可用性保障才能让应用更稳定。