高可用

在本节,将针对 Kubernetes 实现高可用进行介绍,主要包括两个方面的高可用:

  1. 客户应用的高可用;

  2. 集群自身高可用;

应用高可用

微服务应用在 Kubernetes 集群中运行可以充分的发挥优势,例如 Kubernetes 提供的 Deployment 应用可以维护应用的多个实例,并且若有实例挂掉,Deployment 控制器也会立刻进行恢复,以保证服务不间断工作。

针对无服务的应用,使用这种水平扩展的方式比较简单,因为它们可以同时运行,然而对于不能直接水平扩展的应用则需要采取 领导选举机制

领导选举机制

针对有状态应用,不可以直接水平扩展,因为这有可能导致数据冲突等不可预测的问题。

领导选举机制 是指在多应用实例情况下,对谁是领导者达成一致的机制。例如,领导者要么是唯一执行任务的那个,其他所有节点都在等待领导者宕机,然后成为新的领导者;或者都是活跃的节点,只是领导者负责写操作,其他节点负责读操作(数据库的读写分离)。

应用中领导选举机制的实现

幸运的是,在 Kubernetes 集群中,不需要我们自己的应用实现该机制,官方已经为我们提供了相关的容器可以替我们完成领导选举操作,镜像名为 k8s.gcr.io/leader-elector,由于 GFW 的原因,国内的用户可以下载 fredrikjanssonse/leader-elector:0.6 并重命名即可,同时加上参数 --election={app_name}

该容器完成的功能就是竞争领导者,当创建多个该容器时,这些容器会循环竞争,并选择领导者。我们先不考虑该容器是如何竞选领导的,我们只要知道通过向其发送 HTTP 请求的方式即能知道当前的领导者是谁即可:

curl localhost:4040
{"name":"leader-elector-6f5c59d88d-ttkbl"}%

从上面的输出中可以很容易的知道当前的领导者是名为 leader-elector-6f5c59d88d-ttkbl 的这个 Pod

由于一个 Pod 中可以包含多个容器,因此我们可以将我们的应用容器与该容器整合起来,将该容器以 sidecar 的形式与应用容器整合成一个 Pod,由于同一个 Pod 中的所有容器共享网络命名空间,因此在应用容器中同样可以使用上述方法知道哪个应用容器所在 Pod 是领导者。

领导选举机制原理

虽然可以直接使用官方提供的容器达到该功能,但是还是需要了解一下其中的原理的。

该容器的领导竞选相关原理可以从这里找到。

在 Kubernetes 中竞选领导需要用到锁机制,在集群中直接使用 Endpoint 资源或 ConfigMap 资源作为资源锁,因为这些资源是 Pod 通过 API 服务器可以共同接触到的,默认情况下使用 Endpoint 资源。

在这两个资源(或者说所有资源)中,竞选过程中需要使用到的有两个字段:

  • metadata.annotations : 以键值对的形式存在,所有竞争者会尝试向 control-plane.alpha.kubernetes.io/leader 键中填入值,若填入成功,则称为领导;

  • metadata.resourceVersion : 用于实现乐观锁,提高效率;

当使用 k8s.gcr.io/leader-elector 容器时,需要向其中传递参数,即上述的 --election={app_name},在容器运行后,便会在当前 namespace 下创建一个名为 {app_name} 的 Endpoint 资源,并填充上面说的两个字段。

案例如下:

apiVersion: v1
kind: Endpoints
metadata:
  annotations:
    control-plane.alpha.kubernetes.io/leader: '{"holderIdentity":"leader-elector-6f5c59d88d-ttkbl","leaseDurationSeconds":10,"acquireTime":"2020-03-11T12:58:17Z","renewTime":"2020-03-11T12:59:07Z","leaderTransitions":0}'
  creationTimestamp: "2020-03-11T12:37:06Z"
  name: example
  namespace: default
  resourceVersion: "787550"
  selfLink: /api/v1/namespaces/default/endpoints/example
  uid: 2a3897d9-386a-46a6-b3a6-725023a2ba97

annotation 中的 control-plane.alpha.kubernetes.io/leader 键对应的值中的个字段含义如下:

  • holderIdentity : 当前资源锁的所有者

  • leaseDurationSeconds : 资源锁租约时间是多长(资源锁的有效时长,所有者霸住该资源锁的时间超过这个限制,则会被其他竞争者抢占)

  • acquireTime : 锁获得的时间

  • renewTime : 续租的时间

  • leaderTransitions : leader 进行切换的次数

resourceVersion 用于乐观锁,即当竞争者 A 尝试竞争时,首先读取版本号,假设为 1,在本地将信息更新后准备填充至 annotation 中之前,先再次读取版本号,若仍然为 1,则可以填充;若大于 1,则表示资源被更新了,因此需要重新读取版本号,并重新更新信息,再尝试抢占。

Kubernetes 集群高可用

K8S 集群的高可用主要是指其控制平面的高可用,即下面几个组件:

  • etcd

  • API 服务器

  • ControllerManager

  • Scheduler

三节点高可用集群

在上面这个高可用集群中,控制平面的每一个部分都做了高可用,包括用于接受请求的负载均衡器。

负载均衡器

请求的负载均衡器可以使用 硬件软件 实现,在通常情况下使用 nginx, haproxy 等软件实现,其可以将请求分发至后端不同的 API 服务器中。

而负载均衡的高可用需要搭配虚拟 IP (vip) 以及 Keep-alive 软件实现,即所有来自客户端的请求都发送至虚拟 IP 中,即对用户而言可见的只有虚拟 IP,由软件决定将其交付给哪一个负载均衡器。

实际上不一定需要使用 keep-alive,直接使用 iptables 设定规则,将请求的目的 IP 进行更改同样能达到该效果。

etcd

etcd 原生支持多实例运行,即原生支持高可用。如 etcd 章节 所述,部署大于等于 3 个的奇数个实例即可实现高可用。

etcd 会跨实例复制数据以保证彼此之间的数据一致性。

API 服务器

API 服务器直接通过运行多实例的方式就可以实现高可用,因为它是无状态应用(所有数据均存放在 etcd 中,其本身不做缓存),它们彼此之间不会感知对方存在。

API 服务器 只会与本地的 etcd 交互,这样读写效率更高,因此在高可用的情况下,API 服务器和 etcd 实例是成对出现的。

多 API 服务器访问 etcd 时,会使用乐观锁,同样是看资源的 metadata.resourceVersion 的值,具体可见 etcd 章节 的描述。

ControllerManager 与 Scheduler

ControllerManager 和 Scheduler 都不是无状态应用,它们都需要接收来自 API 服务器的通知,并执行操作。在多实例的情况下,若多个实例执行了同一个操作,将会造成不可预测的问题。

因此这两个组件的高可用不能仅仅通过水平扩展的方式解决,需要使用 领导选举机制,即同一时间内只能有一个实例执行任务,其他实例都等待成为领导者。

这两个组件原生支持 领导者选举机制,该机制的实现与上述的 k8s.gcr.io/leader-elector 容器一致,都是通过 Endpoint 资源或 ConfigMap 资源实现的,可以通过 --leader-elect 开启组件的选举机制,该选项默认为 true

ControllerManager 与 Scheduler 可以与 API 服务器 和 etcd 在同一个节点上,此时直接与本地 API 服务器通信;也可以不在同一个节点上,此时需要通过负载均衡器与 API 服务器通信。

Last updated

Was this helpful?