服务可用性管理
高可用
生产级别应用,为了保证应用的可用性,除了特殊应用(例如批次应用)都会保持高可用,所以在设计应用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中应用做了简单的可用性保障,在生产中,应用不仅仅是它自己,还关联上游、下游的应用,所以全链路的应用可用性保障才能让应用更稳定。