Kubernetes 部署方案比较 | 控制平面

2022 03 27, Sun

我大概半年以来的工作都在 K8S 上,研究网络怎么回事、存储怎么回事、怎么支持同事在应用的层面解决问题。感觉最大的问题还是私有部署的条件千差万别,而且如果你的应用涉及到状态,这些状态的问题抛到集群外面还是在集群内,都是问题。

先讨论一下 K8S 自己的问题,在 K8S 控制平面中实现高可用。

目标

  • 部署一个控制平面高可用的 Kubernetes

K8S 组件

K8S 的组成主要是有几方面:

  • etcd 作为存储,提供状态的持久化以及强一致性
  • apiserver 连接 etcd 和其他的所有组件,也负责身份认证和授权
  • controllers,比如说 controller manager、scheduler、cloud controller manager,负责维护 K8S 核心 api 的状态,比如说该有几个副本、service 后端都有谁
  • kubelet 和 kube-proxy 执行上面这些组件的决策
  • 网络插件
  • 存储插件
  • 其他自定义 api 的 consumer、provider

问题

  • etcd 的部署和备份
  • apiserver 高可用
  • 其他组件的高可用
  • 节点网络
  • 入口流量高可用

部署方案和比较

二进制 vs kubeadm

曾几何时我还挺喜欢看着 Kubernetes the Hard Way。但是回头一看现在大都是 kubeadm 的天下。

kubeadm 是 Kubernetes 官方写的一个基于 Go 的安装器,主要是通过一个配置文件定义节点的功能啊、网络啊什么什么的。比如说 service 的网段、pod 的网段,etcd 用集群内还是集群外的。

而直接二进制部署就是在容器之外运行各种组件,kubelet 只运行业务容器,只是因为这种方法太麻烦,所以演化成了另一种形式,一般都是控制平面的组件直接包到单个的二进制里面,执行一个文件等于跑了所有东西,比如说 k3s。究其根源其实和 kubeadm 类似,只不过一个 kubeadm 用了容器而 k3s 直接运行的其他东西。

集群管理

怎么部署的问题之外还有一个集群怎么定义怎么管理的问题,对于 kubeadm 来说并不能解决集群部署的问题。所以就有 rancher,有 kubernetes 自己的 cluster api。

rancher 的方案应该比 cluster api 要早一些,主要是解决了怎么部署,怎么在云服务环境里面部署自己的集群,用什么 lb,etcd 怎么处理。rancher 通过在一个服务器上维护这些配置,其他服务器执行自己的角色,自动下载配置文件,自动部署。

cluster api 是另一个思路,但是更通用一些,需要启动一个 kubernetes 集群,在这个集群内安装 cluster api 的各种东西,通过定义资源这种描述性的方法描述想要什么样的集群,然后它会自动帮你创建并且维护集群的状态。

相比之下 cluster api 和 rancher 做了类似的事情,出现的更晚一些,实践和资料略微少了一些,但是没有 docker 之类的依赖。而 rancher 整个栈都依赖于 docker。

etcd 部署

大概判断清要什么样的集群的同时也要考虑 k8s 的数据存储怎么做。一般来说就是说 etcd 的部署问题。比如说 etcd 在容器内还是容器外,tls 怎么处理,etcd 的网络,etcd 有几个节点,apiserver 怎么连接 etcd。

数据库放到容器内外是个争论已经挺久的问题,只能说有利有弊。kubeadm 可以启动一个 etcd 容器,但是如果需要单独跑一个 etcd 容器,得自己动手丰衣足食。像是 kubekey 这种部署工具会自动帮你部署一套。

节点数通常是奇数个,比如说 3 个 5 个这样,因为集群要有超过一半数量的节点才能工作,比如说 3 节点集群最多可以容忍 1 个节点故障,4 节点要超过 2 个节点才能工作所以最多也只能容忍 1 个节点故障,而当我们有 5 个节点时才能容忍 2 个节点故障,这样才能在脑裂的情况下保持集群正确。所以从这方面考虑奇数个更划算,而其他的设施看情况,按习惯是一比一的。

至于高可用来说,etcd 的客户端会自己完成高可用,只要在连接时写清楚每个节点和对应的 endpoint,客户端就可以在其中一个节点不正常时去找其他节点。apiserver 也支持直接指定一个四层的 etcd 的 lb 的方案。

etcd 备份

etcd 是 Kubernetes 中最主要的数据库。对于数据库来说,备份至关重要。

通常是两方面一方面是文件系统,另一方面是业务。比如说我们可以从 lvm 或者块设备层面做快照,或者用 velero 配合 csi 做快照,这都是文件系统层面的备份。也可以用 etcd 做 snapshot,导出恢复以后还是一条好汉。

备份储存的当然不能在自己这个系统里面,我们可以存到本地目录以后复制到其他地方,也可以直接存到比如说 s3 啊 webdav 啊这种地方。

入口流量

入口流量也是一个问题,Pod 和 container 有自己的 IP,没有特别配置的话集群外访问不到集群内的服务。这里就需要根据网络分两种情况。

一种情况是网络使用了 SDN,或者使用了动态路由协议,这种情况会比较简单。比如说 BGP 这种。那么集群外的服务可以通过 BGP 得到路由信息,我们可以直接通过 service 的 clusterip 来访问具体的服务。比如说在阿里云、Google cloud 这些云服务环境中,如果配置了虚拟网络,或者集群交换机有 BGP,我们在交换机和比如说 MetalLB 或者 calico 上配置了 bgp,也可以直接通过 service 的 clusterip 来访问具体的服务。

另一种则是裸机器,并没有什么特殊的配置,那么我们就需要有 service/nodeport 啊、或者用 hostNetwork 去直接监听一个端口,也可以通过 ipvs 或者 nginx 代理流量转发到 pod 后端。各种方法各有利弊。

APIServer 之类的服务高可用

这些都是常规的无状态服务,不需要做什么额外的事情,就是提供多个实例+负载均衡和高可用的 IP 就行。

APIServer 到 etcd 由客户端发现节点就行,其他组件到 apiserver 有两种情况,一种是 kubelet 这种不可能用集群内资源的,就需要一个高可用的代理或者高可用的 IP 指向 apiserver。另一种情况是集群内的服务,可以用 service,这种情况稍微简单一些,因为 kube-proxy 会自动根据 endpoint 指定 service 的后端,而 endpoint 只会选取存活的容器。

当然如果你的控制平面是二进制部署的,就不那么好做健康检查,也就没有办法享受到 service/endpoint 根据健康检查自动选择 apiserver 的福利。会完全依赖于自己搭的高可用代理,还需要自己写健康检查同时更新 endpoint,让集群内的服务可以正常访问到 apiserver。所以如果真的需要高可用,除非研发能力很强,不要轻易选择二进制部署的方案。