Kubernetes应用质量管理(下)

fldj
发布于 2022-9-5 17:56
浏览
0收藏

作者 | 老郑

来源 |运维开发故事(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中应用做了简单的可用性保障,在生产中,应用不仅仅是它自己,还关联上游、下游的应用,所以全链路的应用可用性保障才能让应用更稳定。

分类
标签
已于2022-9-5 17:56:29修改
收藏
回复
举报
回复
    相关推荐