如何在 Kubernetes 实现容灾调度?

作者: 王炳明 分类: Kubernetes 发布时间: 2021-08-01 20:16 热度:1,259

1. 背景知识

在开始真正的案例讲解前,需要和你同步一下对一些名词和概念的共识。

  • 业务组:多个有强关联的Pod 可以组成一个业务组
  • 多副本:每个 Pod 可以由单独的 Pod 来定义,也可以由 Deployment 来定义,区别在于 Deployment 有定义多副本的能力,因此后面的案例都采用Deployment
  • 域:也称为 zone,是定义在 Node 上的标签,K8S 内置三种域,分别是 kubernetes.io/hostnametopology.kubernetes.io/regiontopology.kubernetes.io/zone
  • 容灾:真正的容灾需要在一个集群内有多个域,而这些域通常要是不同的物理区域(机房或者机架),但由于测试环境有限,以下只能使用 kubernetes.io/hostname 这个域来演示。
  • 容灾类型:以下我的演示有两种容灾类型,分别是 业务组副本容灾分散容灾,这些是我自己方便总结记忆而自定义的类型,不是专有名词。

2. 业务组副本容灾

多业务组副本容灾是在同一个域内,最多只能有一个业务组部署,这样能绝对安全的保证其中一个域故障后,服务不会受太大影响。

多业务组副本容灾需要满足如下几个要求:

  1. 同一业务组内有多个 Pod(以两个 Pod 为例),必要放在同一 zone (假设以节点级的域为例)
  2. 该业务组需要多个副本(假设是3个副本),副本要分别位于多个不同的 zone 内

拆解需求并提出对应解决方案:

  1. 需要多副本:因此要使用 Deployment
  2. 两个Pod:使用两个 Deployment
  3. 副本要位于同的 zone:使用反亲和调度策略
  4. 同业务组的Pod在同个zone:使用亲和调度策略

不管是反亲和调度还是亲和调度策略,其中最关键的是反亲和调度的单位,由于这里以节点域为例,因此 topologyKey 应该填写 kubernetes.io/hostname

如下的 yaml 中,定义的是第一个 Pod 的内容,提取关键点

  • replicas: 3:三副本
  • app: store:给该 Pod 的每个副本都打上 app=store 标签,后面反亲和性调度需要使用
  • podAntiAffinity:反亲和性调度,确保所有副本不会在同一个域(节点)中
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-cache
spec:
  selector:
    matchLabels:
      app: store
  replicas: 3
  template:
    metadata:
      labels:
        app: store
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: redis-server
        image: redis:3.2-alpine

第一个 Pod 已经保证了在不同的 zone 中了,那么对于第二个 Pod 它更严格,它需要满足

  1. 在不同域中 –> 可以参考 第一个 Pod 的方案使用反亲和调度策略
  2. 每个副本还要创建在已经有第一个 Pod 的域中 –> 可以使用亲和性调度策略
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
spec:
  selector:
    matchLabels:
      app: web-store
  replicas: 3
  template:
    metadata:
      labels:
        app: web-store
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - web-store
            topologyKey: "kubernetes.io/hostname"
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: web-app
        image: nginx:1.16-alpine

3. 分散容灾

同类型Pod分散容灾与多业务组副本容不同,由于域的数量大于业务组的副本数量,所以它允许在同一个域内有多个业务组。

为了实现因单台节点故障而带来影响最小化,可以借助 Kubernetes 1.16 引入的 Even Pod Spreading 特性让同类型的 Pod 均匀的分布的不同的域中。

而如果每个 Pod 也都有与之强关联的 Pod 需要部署在一个域中,可以再加入亲和性调度策略。

具体的步骤如下:

  1. 先选择一个 Pod 使用 Even Pod Spreading 让他们尽量分散在不同的域中。
  2. 再对另一个 Pod 使用亲和性调度与第一个 Pod 绑定在一起,但为了避免所有的 Pod 都集中在第一个 Pod 中的某个副本节点上,该 Pod 也应该使用 Even Pod Spreading 让他们尽量分散在不同的域中。

第一个 Pod

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-cache
spec:
  selector:
    matchLabels:
      app: store
  replicas: 3
  template:
    metadata:
      labels:
        app: store
    spec:
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: "kubernetes.io/hostname"
          whenUnsatisfiable: DoNotSchedule
          labelSelector: 
            matchLabels:
              app: store
      containers:
      - name: redis-server
        image: redis:3.2-alpine

第二个Pod

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
spec:
  selector:
    matchLabels:
      app: web-store
  replicas: 3
  template:
    metadata:
      labels:
        app: web-store
    spec:
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: "kubernetes.io/hostname"
          whenUnsatisfiable: DoNotSchedule
          labelSelector: 
            matchLabels:
              app: web-store
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: web-app
        image: nginx:1.16-alpine

4. 参考阅读

文章有帮助,请作者喝杯咖啡?

发表评论