跨多个大洲的 Kubernetes:在全球数据中心实现 k3s 和 k8s 高可用

发布于 阅读时间 21 分钟

横跨美国、欧洲和亚洲、能够承受整个大洲故障的 Kubernetes:etcd 为何不适合远距离部署、每个区域一个 k3s 集群、通过 Flux 实现 GitOps、Geo-DNS 故障转移、跨区域的数据,以及与 kubeadm 的区别。

如果希望应用以高可用、可自动扩缩容的方式运行,Kubernetes 就是事实上的标准。一个很自然的想法,是让单个 Kubernetes 集群横跨美国、欧洲和亚洲的服务器,但这个想法会卡在一个细节上:etcd,也就是 Kubernetes 保存其全部状态的数据库。本文介绍如何让跨多个大洲的 Kubernetes 依然可行,而且能够承受整个大洲的故障:每个区域一个集群,通过 GitOps 统一交付,再由 Geo-DNS 把用户引导到最近的健康区域。

我们使用 k3s 搭建这套架构。k3s 是一个轻量且经过完整认证的 Kubernetes 发行版,几分钟就能装好。文末还会说明,改用 kubeadm 部署传统 Kubernetes 时有哪些变化。关于跨多个机房的可用性、Quorum 和故障转移,基础知识在文章跨三大洲的 Docker Swarm中有详细介绍;本文聚焦于 Kubernetes 的不同之处。

一个集群横跨所有大洲,还是每个区域一个集群?

对于跨多个大洲的 Kubernetes,正确的架构是每个区域一个集群,而不是用一个集群横跨所有大洲。每个集群独立运行,全部从同一个 Git 仓库发布,并由带健康检查的 Geo-DNS 分配用户。某个区域发生故障时,其他区域会接管,无需跨越大洋去协调一个共享的集群状态。

为什么 etcd 不适合远距离部署

Kubernetes 把每一项状态、每一份配置和每一次变更都保存在 etcd 中,这是一个基于 Raft 共识的键值存储。每一次写入都需要得到全部 etcd 成员中多数成员的确认。etcd 默认的心跳间隔为 100 毫秒,选举超时为 1 秒;而欧洲、北美和亚洲之间的数据包延迟在 80 到 250 毫秒之间。这些时间可以调大,但这样一来,集群中的每一次写入都会变慢,从调度一个 Pod 到保存一个 Secret 都不例外。k3s 文档在这一点上说得很明确:分布在多个网络中的集群不支持内置 etcd,所有服务器都应位于同一机房。Kubernetes 本身的设计也是让一个集群覆盖同一区域内的多个可用区,而不是多个大洲。

三种模式对比

模式工作方式评价
一个集群横跨所有大洲控制平面和 etcd 分布在多个大洲不推荐:写入缓慢,etcd 不稳定,k3s 使用内置 etcd 时不受支持
控制平面位于一个区域,Worker 遍布全球Server 节点位于一个机房,Agent 节点位于其他大洲技术上可行,但一旦控制平面所在的区域发生故障,全球范围内都无法再进行任何重新调度
每个区域一个集群三个相互独立的集群,通过 GitOps 统一发布,前置 Geo-DNS推荐:任何一个区域都能在其他区域故障时继续运行,错误的影响仅限于单个区域

“每个区域一个集群”的方案还有第二个常被低估的优点:控制平面中的错误、对集群所做的错误变更,或是一次失败的升级,影响的始终只是一个区域。其他区域的用户对此毫无察觉。

k3s 还是 k8s?

两者都是真正的 Kubernetes,API、清单和工具完全相同。区别在于架构和投入的工作量:

特性k3s使用 kubeadm 的 k8s
安装一条命令,一个二进制文件需要逐一配置容器运行时、kubeadm、kubelet 和网络插件
单个 Server 节点所需资源至少 2 个核心和 2 GB 内存明显更多,视组件而定
自带组件Ingress 控制器(Traefik)、网络(Flannel)、存储 Provisioner、Service 负载均衡器只有核心组件,其余一切由您自行选择
高可用由三台服务器组成的内置 etcd三个控制平面节点,API 前置负载均衡器
适用于大多数应用、小型团队、每个区域单节点希望自行决定每个组件的团队

对于每个区域一个集群的架构,我们推荐使用 k3s:只有每个集群本身都足够简单,同时运维三个集群才会轻松。如果您已有 kubeadm 经验,可以原样沿用这套架构,下文关于 kubeadm 的章节会说明其中的区别。

架构:三个区域、三个集群、一个入口

组件作用可应对哪种故障
每个区域一个 k3s 集群在靠近用户的地方运行应用整个区域发生故障
每个区域三台服务器(扩展阶段)在区域内保持 etcd 和控制平面的高可用区域内个别服务器发生故障
Git 仓库和每个集群中的 Flux每个集群自行从 Git 获取期望状态不存在会成为故障点的中央交付服务器
通过 DNS 验证获取证书的 Ingress接收用户的请求证书不受 DNS 切换的影响
带健康检查的 Geo-DNS把用户引导到最近的健康区域无法访问的区域
复制式数据存储与备份在多个区域保存数据区域故障时的数据丢失

入门与扩展

入门配置是三台服务器,美国、欧洲和亚洲各一台,每台都是一个独立的单节点集群。一台服务器发生故障,它所在的区域也随之失效,Geo-DNS 会把该区域的用户引导到相邻区域。这一阶段在设计上就已经能够应对整个大洲的故障。扩展阶段,每个区域在同一机房部署三台服务器,这样每个区域自身也能承受单台服务器的故障,而无需把用户引导到其他区域。

哪些 KernelHost 机房位置适合

KernelHost 在美因河畔法兰克福的 maincubes 数据中心运营服务器,并在欧洲、北美和亚太地区的更多机房位置提供虚拟服务器,其中包括美国的三个机房位置,以及加拿大、伦敦、斯特拉斯堡、华沙、赫尔辛基、新加坡、日本、悉尼和孟买。完整列表见服务器机房位置页面。本文示例中,欧洲使用美因河畔法兰克福,北美使用美国东海岸,亚洲使用新加坡。

教程:使用 k3s 在三个区域部署 Kubernetes

示例使用三台运行 Debian 12 或 13 的服务器:k3s-eu、k3s-us 和 k3s-asia。公网地址取自文档专用网段 203.0.113.0/24,域名为 example.com。请将两者替换为您自己的值。

第 1 步:在三个区域准备服务器

在三个区域订购三台服务器,配置至少为 2 vCPU 和 4 GB 内存,这样除了 k3s 之外,您的应用也有足够的空间。按照新 Root 服务器检查清单完成基础加固,并设置含义清晰的主机名。etcd 能从高速 SSD 中明显受益,KernelHost 服务器的 NVMe 存储正好满足这一点。

第 2 步:安装 k3s

在三台服务器上分别用一条命令安装 k3s。这样,每台服务器都会成为一个只有一个节点的完整 Kubernetes 集群:

curl -sfL https://get.k3s.io | sh -
kubectl get nodes

大约一分钟后,kubectl get nodes 会显示该节点为 Ready。k3s 自带 Traefik 作为 Ingress 控制器、Flannel 作为网络,以及一个用于本地卷的 Provisioner。

第 3 步:扩展为每个区域三台服务器

如果要让某个区域自身也具备高可用性,就在同一机房部署三台服务器。第一台服务器启动内置 etcd,另外两台使用共享令牌加入。etcd 要求服务器数量为奇数,有三台服务器时,该区域可以承受一台服务器的故障:

curl -sfL https://get.k3s.io | K3S_TOKEN=机密_令牌 sh -s - server \
    --cluster-init \
    --tls-san=api.eu.example.com
curl -sfL https://get.k3s.io | K3S_TOKEN=机密_令牌 sh -s - server \
    --server https://203.0.113.21:6443 \
    --tls-san=api.eu.example.com

同一区域的所有服务器,在网段和功能方面的设置都必须一致。服务器之间需要开放以下端口:etcd 使用的 2379 至 2380/TCP、API 使用的 6443/TCP、Kubelet 使用的 10250/TCP,以及 Flannel 网络使用的 8472/UDP;对外则保持封锁。令牌属于机密:任何知道它的人,都能把自己的服务器加入集群。

第 4 步:配置对全部三个集群的访问

k3s 把访问凭据保存在 /etc/rancher/k3s/k3s.yaml 中。把每个集群的这个文件复制到您的工作电脑上,将其中的 127.0.0.1 替换为服务器的地址,并按区域为各个上下文命名:

kubectl config rename-context default eu
kubectl config use-context eu
kubectl --context us get nodes

6443 端口上的 API 是进入集群权限最大的入口。请在防火墙中只允许来自您自己的地址或 VPN 的访问,绝不能对整个互联网开放。k3s.yaml 文件包含一张管理员证书,应当像 Root 密码一样妥善保管。

第 5 步:在每个集群中用 Flux 实现 GitOps

为了让三个区域运行同一个应用的同一个版本,期望状态保存在一个 Git 仓库中,每个集群中都运行着 Flux,由它自动实现这一状态。也就是说,每个集群都自行获取自己的配置;不存在可能发生故障的中央交付服务器。一种经过实践检验的仓库结构:

apps/
  web/            应用的共享清单
clusters/
  eu/             欧洲的配置和版本
  us/             北美的配置和版本
  asia/           亚洲的配置和版本
flux bootstrap git --url=ssh://git@git.example.com/infra/fleet.git --branch=main --path=clusters/eu

在各自的上下文中,分别用 --path=clusters/us 和 --path=clusters/asia 执行同一条命令。由于每个区域都有自己的目录,您可以先在一个区域发布新版本,之后再发布到其他区域。

第 6 步:以高可用的方式定义应用

在一个区域内,有三样东西能确保应用挺过维护和服务器故障:分布到不同节点上的多个副本、能识别不健康 Pod 的探针,以及一个防止维护操作同时移除所有 Pod 的中断预算:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: ScheduleAnyway
          labelSelector:
            matchLabels:
              app: web
      containers:
        - name: web
          image: registry.example.com/web:1.0
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            periodSeconds: 10
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web
spec:
  minAvailable: 1
  selector:
    matchLabels:
      app: web

Readiness 探针会在 Pod 没有响应期间将其从流量中摘除,Liveness 探针则会在 Pod 卡死时将其重启。在单节点集群中,跨多个节点的分布还不起作用;一旦该区域有了三台服务器,它就会自动生效。

第 7 步:Ingress 与证书

入口由自带的 Traefik 负责。由于三个区域都服务于同一个域名,请使用 cert-manager 通过 DNS 验证申请 TLS 证书:无论 DNS 记录当前指向哪里,它在每个区域都能正常工作。相反,HTTP 验证在 DNS 记录当前没有指向的每个区域都会失败。如果您不使用 Kubernetes Ingress,可以在文章配置 nginx 反向代理中找到基础知识。

第 8 步:配置带故障转移的 Geo-DNS

一个支持地理路由和健康检查的 DNS 服务,会把来自欧洲的用户引导到美因河畔法兰克福,把来自美洲的用户引导到美国东海岸,把来自亚洲的用户引导到新加坡,并每隔 30 到 60 秒检查一次各区域的入口是否有响应。某个区域发生故障时,它就不再返回该区域的地址。请将记录的 TTL 设为 60 秒,这样切换通常在一到两分钟内即可完成。如果希望从集群内部维护这些记录,可以使用 external-dns。

第 9 步:逐个区域发布并测试故障

新版本先写入某一个区域的目录,观察该区域几分钟,然后再把这个版本应用到其他区域。这样,一个任何检查都没能发现的错误,最多只会波及一个区域。要演练区域故障,可以在该区域的服务器上停止 k3s,同时检查 Geo-DNS 是否把该区域从解析结果中移除,以及相邻区域是否承接了负载:

systemctl stop k3s
systemctl start k3s

在拥有三台服务器的区域中,还要额外演练单台服务器的维护。kubectl drain 会在遵守第 6 步中断预算的前提下迁移 Pod,kubectl uncordon 则让该服务器重新投入使用:

kubectl drain k3s-eu-2 --ignore-daemonsets --delete-emptydir-data
kubectl uncordon k3s-eu-2

跨区域的数据

Kubernetes 分发的是 Pod,而不是数据。自带 Provisioner 创建的 PersistentVolume 只位于某一个节点上,而集群之间默认根本没有共享的数据存储。因此,凡是涉及数据的部分,都与任何跨多个机房的集群一样,适用相同的规则:

  • 数据库自行复制。经过实践检验的做法是:主实例位于一个区域,其他区域运行副本;PostgreSQL 的 CloudNativePG 之类的数据库 Operator 也支持跨集群边界的副本。跨大洲时,复制以异步方式进行,出现紧急情况时,最后几秒的写入可能会丢失。如果需要在全球范围内无损写入,可以使用 CockroachDB 或 YugabyteDB 这类面向多区域的数据库。
  • 文件和上传内容应存放在兼容 S3 的对象存储中,并复制到第二个区域。
  • 会话保存在经过复制的数据库或缓存中,或者由应用使用签名令牌。
  • 备份依然必不可少,因为复制会像传播正确数据一样传播错误。Kubernetes 对象和卷适合用 Velero 备份,基础知识请参阅服务器备份策略。

用 kubeadm 代替 k3s 部署 Kubernetes

使用 kubeadm 时,架构保持不变:每个区域一个集群、GitOps、Geo-DNS。不同的是每个集群的搭建方式。您需要在所有节点上安装 containerd 之类的容器运行时以及 kubeadm、kubelet 和 kubectl,在该区域的 API 前面放置一个负载均衡器或一个虚拟地址(例如借助 kube-vip),然后初始化第一个控制平面节点:

kubeadm init --control-plane-endpoint "api.eu.example.com:6443" --upload-certs

输出中包含两条加入命令:一条带有 --control-plane --certificate-key,用于另外两个控制平面节点;另一条用于 Worker 节点。之后,还要安装 Calico 或 Cilium 之类的网络插件,以及 k3s 本来就自带的 Ingress 控制器。如果您需要有针对性地挑选各个组件,或者必须紧跟上游版本,这些额外的工作就是值得的。

为什么选择 KernelHost 部署跨大洲的 Kubernetes

需求为什么重要KernelHost 的方案
多个大洲的机房位置每个区域一个集群,就需要在每个区域都有服务器美因河畔法兰克福,外加欧洲、北美和亚太地区的机房位置,一站式提供
不限流量镜像下载、数据库复制和备份会持续产生流量无限流量 VPS,没有流量总量限制
高速存储etcd 对慢速磁盘很敏感组建 RAID 的 NVMe SSD
DDoS 防护每个 Ingress 都可从公网访问每个机房位置均已包含,在核心机房美因河畔法兰克福采用 3.2 Tbps Arbor 实时过滤,不做黑洞路由
完整的 Root 权限k3s、防火墙和内核设置都需要完全的控制权每一台 KVM Root 服务器和独立服务器均提供
无合约绑定节点和测试集群随时增减PrePaid 预付费,无最低合约期,无开通费
自动化新节点应当通过脚本创建通过 KernelHost API 下单和控制

所有云服务器套餐的概览,以及与大型云服务商的费用对比,请见云服务器租用页面。

常见错误及避免方法

  • etcd 横跨多个大洲。后果是写入缓慢、选举不稳定;k3s 使用内置 etcd 时,这种部署方式不受支持。解决方法:每个区域一个集群。
  • 一个区域部署两台服务器。etcd 需要多数成员在线,两台服务器无法承受任何一台故障。解决方法:一台或三台。
  • API 向整个互联网开放。6443 端口就是集群的万能钥匙。解决方法:只允许来自自己的地址或通过 VPN 访问。
  • 中央交付服务器。一旦它发生故障,就什么都无法发布了。解决方法:在每个集群中运行 Flux,由它自行从 Git 获取配置。
  • 所有区域同时更新。这样一来,一个错误就会影响所有用户。解决方法:通过仓库中的目录逐个区域更新。
  • 通过 HTTP 验证申请证书。在 DNS 记录当前未指向的区域,证书续期会失败。解决方法:DNS 验证。
  • 数据库放在本地卷中且没有复制。一旦节点故障,数据就无法访问。解决方法:通过数据库 Operator 实现复制。
  • 没有探针,也没有中断预算。不健康的 Pod 仍会继续接收流量,维护时所有 Pod 会同时下线。解决方法:Readiness 探针和 Liveness 探针,再加上 PodDisruptionBudget。

要点总结

  • 对于跨多个大洲的 Kubernetes,每个区域一个集群才是正确的做法;横跨所有大洲的单个集群会败在 etcd 的延迟上。
  • 入门配置是三台服务器,美国、欧洲和亚洲各一台;这一阶段就已经能够承受整个大洲的故障。
  • 扩展阶段,每个区域在同一机房部署三台服务器,这样每个区域也能承受个别服务器的故障。
  • 每个集群中的 Flux 从同一个 Git 仓库逐个区域发布应用。
  • 带健康检查和短 TTL 的 Geo-DNS 会把用户引导到最近的健康区域,证书通过 DNS 验证获取。
  • Kubernetes 分发的是 Pod,而不是数据:数据库需要自己的复制机制,备份依然必不可少。
  • 对于这种架构,k3s 通常比 kubeadm 更合适,因为运维三个简单的集群比运维三个复杂的集群更轻松。

常见问题

一个 Kubernetes 集群能横跨多个大洲运行吗?
技术上可以把控制平面分散部署,但并不推荐。Kubernetes 把状态保存在 etcd 中,每一次写入都需要得到全部 etcd 成员中多数成员的确认。大洲之间的延迟为 80 到 250 毫秒,而 etcd 默认的心跳间隔为 100 毫秒。按照 k3s 文档,内置 etcd 不支持跨分布式网络部署。正确的做法是每个区域一个集群。
如何搭建跨多个区域的高可用 Kubernetes?
为每个区域单独建立一个集群,例如在美国、欧洲和亚洲各部署一个 k3s 集群。所有集群都通过 GitOps 从同一个 Git 仓库发布,例如在每个集群中运行 Flux,再由带健康检查的 Geo-DNS 把用户引导到最近的健康区域。某个区域发生故障时,其他区域会接管,无需跨越大洋协调共享状态。
k3s 和 k8s 有什么区别?
两者都是功能完整的 Kubernetes,API 和清单完全相同。k3s 是一个轻量、经过认证的发行版,只有一个二进制文件,一条命令即可安装,并自带 Ingress 控制器、网络和存储 Provisioner;一个 Server 节点至少需要 2 个核心和 2 GB 内存。使用 kubeadm 搭建的 k8s 集群由各个独立组件组合而成,选择余地更大,所需的工作量也更大。
一个高可用的 k3s 集群需要多少台服务器?
三个使用内置 etcd 的 Server 节点,位于同一机房。etcd 需要多数成员在线,因此三台服务器可以承受一台服务器的故障。两台服务器没有任何好处,因为其中任意一台故障都会失去多数。对于跨多个大洲的 Kubernetes,入门阶段三个单节点集群就足够了,每个区域一个,因为 Geo-DNS 可以应对整个区域的故障。
k3s 需要哪些端口?
Kubernetes API 和 k3s Supervisor 运行在 6443/TCP 端口,Kubelet 使用 10250/TCP,Flannel 网络在使用 VXLAN 时占用 8472/UDP,使用 WireGuard 时占用 51820/UDP。如果有多台使用内置 etcd 的服务器,服务器之间还需要开放 2379 至 2380/TCP 端口。这些端口都不应向互联网开放,API 只允许来自您自己的地址或通过 VPN 访问。
如何将应用发布到多个 Kubernetes 集群?
通过 GitOps:所有集群的期望状态都保存在一个 Git 仓库中,每个集群中都运行着 Flux 之类的工具,由它自动实现这一状态。每个区域都有自己的目录,因此可以先在一个区域发布新版本,经过一段观察期后再发布到其他区域。在这种方式下,不存在可能发生故障的中央交付服务器。
Kubernetes 区域之间的故障转移是如何实现的?
通过一个支持地理路由和健康检查的 DNS 服务。它把用户引导到距离最近的区域,并每隔 30 到 60 秒检查一次该区域的 Ingress 是否有响应。某个区域发生故障时,它就不再返回该区域的地址,而是把用户引导到最近的健康区域。TTL 设为 60 秒时,切换通常在一到两分钟内即可完成。
区域发生故障时,如何保住数据?
Kubernetes 分发的是 Pod,而不是数据。因此数据库需要自行复制,例如使用 CloudNativePG Operator 的 PostgreSQL,该 Operator 也能跨集群边界运行副本。跨大洲时,复制以异步方式进行,出现紧急情况时,最后几秒的写入可能会丢失。文件应存放在经过复制的对象存储中,定期备份(例如使用 Velero)依然必不可少。
跨多个大洲,选 Kubernetes 还是 Docker Swarm?
Docker Swarm 可以作为单个集群横跨三个大洲,运维也简单得多。Kubernetes 提供更多自动化能力和更大的生态系统,但跨大洲时要按每个区域一个集群的方式运行,再通过 GitOps 整合起来。对于应用规模不大的小型团队,Swarm 往往是务实的选择;对于复杂的平台,则更适合 Kubernetes。
哪些 KernelHost 机房位置适合部署跨多个大洲的 Kubernetes?
KernelHost 在美因河畔法兰克福以及欧洲、北美和亚太地区的更多机房位置提供服务器,其中包括美国的三个机房位置,以及加拿大、伦敦、斯特拉斯堡、华沙、赫尔辛基、新加坡、日本、悉尼和孟买。一种经过实践检验的组合是美因河畔法兰克福、美国东海岸和新加坡,每个区域在同一机房部署一台或三台服务器。
跨三个大洲运行 Kubernetes 的费用是多少?
入门阶段需要三台服务器,每个区域一台;扩展阶段需要九台,另外还需要一个带健康检查的 DNS 服务。k3s 本身免费。由于复制、备份和镜像下载会持续产生流量,选择不限流量的套餐至关重要。KernelHost 的无限流量 VPS 没有流量总量限制,采用 PrePaid 预付费,无最低合约期,也无开通费,因此可以根据需要扩大或缩小集群。

Kubernetes k3s k8s kubeadm 高可用 多区域 GitOps Flux Geo-DNS 云计算