跨云数据中心的 Kubernetes 边缘计算实践
docker
得益于阿里云、腾讯云和华为云等公共云平台计算能力售价的大幅下调,我购买了一堆 2C2G4M 的虚拟机,并在上面部署了各种自己开发和第三方的服务,比如 Git, RDBMS, MQ, Calibre, Wireguard, ELK 等,此外还有一些无服务器服务,部署在阿里云 OSS 和 Vercel 以及 Cloudflare 上。
这些依赖服务器运行的服务的部署方式五花八门,访问和控制方式也各不相同,因此在逐步的实践中,我统一采用 Docker 来部署和管理它们,这提供了一定的便利。为实现这些“微服务”彼此的通讯,我不得不在每个节点安装 OpenResty 并配置泛域名的 HTTPS 证书,这种方式提供了安全性,同时避免暴露过多端口导致的风险。但管理依旧麻烦,比如我需要每隔 3 个月就更新一次 HTTPS 证书,并逐个更新这些虚拟机的 OpenResty 配置。
此外,这些虚拟机通常都是一次购买半年或一年,公有云也经常在年中或年末打折,因此我也需要周期性的跨虚拟机迁移服务。因此,我不得不写了一些 GUI 工具来管理这些服务,包括虚拟机管理、服务管理、证书管理、DNS 管理,以及一些辅助的能力:包括 Wireguard 配置管理、备份管理、容器镜像和仓库管理。此外,我还为每一台虚拟机安装了 filebeat 和 metricbeat,将容器的日志和指标发送到 ELK,并创建监控仪表盘,设置告警规则,以实现可观测系统。
在一些虚拟机上,我探索了使用 Wireguard 实现跨云互通的方法,实现内部服务无鉴权的彼此访问,但只是实验性的,大部分情况虚拟机之间还是通过 OpenResty 实现 HTTPS 的七层互通,这样更灵活。此外,在我的个人本地服务器上,也使用了 Wireguard 技术,实现从任意地区接入即可访问到本地 ESXi 虚拟机、本地开发机提供的服务。
microk8s
我之前研究过 Kubernetes,Istio,但很长时间以来都没有把这些虚拟机迁移到 Kubernetes 上的想法,因此从概念上来说,Kubernetes 是一个重量级的数据中心解决方案,和这些 2C2G 的虚拟机“八字不合”。但偶然的机会,我利用本地 ESXi 的几台虚拟机进行了轻量级 Kubernetes 方案尝试,一开始尝试了 microk8s,它利用 Helm 提供了一系列开箱即用的插件,包括 Observability 的 Prometheus Stack 以及 Istio、Dashboard 等,但这种过多隐藏复杂性的方式是伴随着代价的 —— 虽然上手很友好 —— 如果解决了容器镜像拉取的问题,就以 Prometheus Stack 而言,microk8s Plugin 隐藏了 Helm 层、Prometheus Operator 层、Prometheus 层,Grafana 层,因此一旦想要自定义数据采集,需要进行的操作困难重重,且出问题后定位和诊断也非常困难。正如同网友所常讲的:“Kubernetes 用起来很棒,但别让我去维护它”。
即便如此,倘若你有毅力,问题还是能解决的,大不了从头开始部署 Prometheus Stack,自己一步步弄清楚每个组件和 CR 的用处。于是我就找了两台公有云虚机把 microk8s 部署了,对于控制节点,没什么问题,内存占用略大但可以接受,但工作节点就有问题了 —— microk8s 的节点默认依赖第一个网卡 IP 的三层互通,换言之,它假定你的虚机位于同一网段或者说可路由的网段中,这对数据中心没有任何问题,但对跨公有云供应商的带 NAT 的虚机来说,这显然是不可能的。
k3s: day0
于是我切换到了 k3s 上,它默认使用 flannel 的 VXLAN 模式,问题依旧存在,但切换到 Wireguard 模式后,问题解决了,很显然,Wireguard 这种 VPN 解决方案对 IP 穿越和 NAT 更友好,而 VXLAN 这种大二层方案则为数据中心而生,不仅需要 VTEP 端点互通,还需要 EVPN 分发 VTEP IP 和 MAC 地址。
由于默认的 k3s 提供的组件并不适合边缘跨云厂商的集群实现 —— Traefik 只在某个节点提供,而 ServiceLB 则用于将每个节点的流量中转到此 Ingress,这用于对非云供应商环境提供负载均衡实现,但对我的用例而言并不合适 —— 因为节点之间路由流量的代价要通过 Wireguard 和不确定的路径,因此可能成本比较高,因此需要修改为将 Traefik 作为 DamonSet 部署,在每个节点提供服务,这种模式和之前我在每个节点均部署 OpenResty 作为入口的方式类似,提供了更好的路由性能,且同时保留了从其他节点 Wireguard 通道内部访问的可能性(经过 CNI)。此外,在较长一段时间的磕磕绊绊之后,我终于稳定的部署了Prometheus,Grafana,Loki 这一套日志、指标和告警的可观测性栈:这里头 Prometheus Stack 的 Helm 参数需要修改的很多 —— 一方面是 k3s 的原因:k3s 没有容器化 kubelet,其仅通过 agent 暴露了 kubelet 和 kubeproxy 服务和 metric 接口,需要调整 ServiceMonitor 抓取的 endpoint。但更多是跨云导致的问题,比如 NodeExporter 使用 hostNetwork 导致节点私有 IP 无法被跨节点的 Prometheus 抓取,需要改为 hostNetwork 等问题。
最后,我选择使用 Rancher 实现集群管理,而非简陋的 Dashboard,Rancher 提供了 GitOps 和 Helm,Istio 集成,自定义了很多 CR,Rancher 删除和重新安装可能导致各种资源残留问题 —— 大部分是 finalizer 导致的,这都让人头疼。在解决这些问题后,跨云架构还面临着存储的问题,CSI 插件可选的不多,因为跨云存储的成本不低,大部分时候服务都是无状态的,emptyDir 足够使用,而在另一些时候则需要小心指定工作负载亲和性,并自定义 local PV 实现资源存储。最后,在国内网络环境下,镜像的拉取也是一个不大不小的问题,一些自己的镜像可以从私有库拉取,但很多 docker.io 和 k8s.gcr.io 的镜像,修改配置较为麻烦,因此需要一个高可靠的镜像仓库服务实现镜像拉取,我选择的是阿里云的企业仓库,算是基本能够解决镜像拉取和私有仓库问题。
到这里,可以正式部署业务了:创建 Kubernetes 命名空间,在 Rancher 界面创建 Deployment 或者 StatefulSet,设置资源占用、镜像和命令,资源限制,标签以及调度,暴露端口并创建服务,最后创建 Traefik IngressRoutes 和 ServiceMonitor 实现监控和外部访问,控制路径访问权限、限流和认证。
k3s: day1
当然,这只是一个开始,Day1 依旧有很多工作:之前我在每个虚拟机都部署了 Filebeat 收集日志,Metricbeat 收集指标,现在 Loki 和 Prometheus 自动从 Kubernetes API 和 Node Exporter, Kubernetes State Metrics 以及 Kubelet 和 Kubeproxy 抓取指标。之前需要手动为每个虚拟机的 OpenResty 配置 HTTPS 证书,设置路由和访问鉴权,现在可以通过 Traefik 的 IngressRoute 和 TLSStores 集中设置。之前需要在业务应用中使用 Redis 实现 HTTP 访问和错误统计,自行实现限流,现在可以暴露 Prometheus /metrics 端点以自动和 Prometheus 和 Grafana 整合,实现可视化和告警,而限流则由网关负责。此外,之前使用 crontab 实现定时数据库备份并通过 rclone 发送到 OSS,然后记录到我的备份中心,现在则使用 CronJob 定义定时创建 rclone 镜像和 postgresql 镜像,备份并直接发送到 OSS,备份失败通过 Grafana Alerting 实现告警通知。最后,在 Kubernetes 平台隐含的高度分布式语义下,解决问题的视角也有了变化,比如 rClone 需要保持 OAuth 权限刷新,之前我在每个虚拟机使用 mount 挂载实现,现在则只在某个虚拟机挂载,并将其配置暴露给一个服务,此服务为集群任意需要访问 rClone 配置的 CronJob 提供密钥。比如我的业务需要日志记录 IP 所在城市的经纬度以供 Loki 读取并在 Grafana 可视化地图,之前的做法肯定是为业务应用增加一个数据库和读取客户端,而现在则通过暴露一个专门的 IP 转 GPS 服务实现。比如,之前我的 Clojure 程序诊断和修改问题更多是通过 nREPL 进行,更新代码则通过 git pull 和 docker restart 进行,现在虽然我也随时准备了一个 nREPL Deployment,但却更倾向于重新部署触发 initContainer 访问 Git 仓库拉取最新代码并启动进程。
就这样,我的七台虚机、14 核心 19 GB 内存的 Kubernetes 边缘集群便正式投入生产了。这一套 14C 19GB 的集群,实际由几台 2C4G6M 的虚拟机和几台 2C2G4M 的虚拟机组成,大约每年的开销在 500 元左右。如果你问,为什么不在本地搭建 Kubernetes,非要用云平台的虚拟机跨网络组件集群?显而易见的答案是:在线,在中国,云计算最昂贵的部分永远不是算力,而是网络。
总的来说,你如果问我切换到 Kubernetes 值不值?我觉得这个问题取决于你自己:
【控制面】Kubernetes 提供了一套标准的基础设施架构,包括内置的声明式计算、网络、存储以及通过 Operator 提供的 CR 扩展,这允许你实现可观测、路由,甚至是基于 Istio 的服务治理。这些能力需要占用一定的存储和计算资源,但不论如何,实现这些能力都要占用这么多资源,甚至更多,比如 ELK 实现的指标、日志和搜索、告警和可视化,这些资源消耗都是不可避免的。Kubernetes 的好处在于,它提供了统一的数据中心抽象和资源操作入口,这对于大规模分布式环境而言非常重要,毕竟在此之前,要配置一个节点,要安装一堆的 Agent 和 Gateway,并且还要实时调整这些配置以保证节点负载正常观测和路由,这些操作都是脆弱且容易出错的,因此 Kubernetes 提供的声明式能力就至关重要了:新建节点只需要设置 swap,放开防火墙,安装 Wireguard,配置镜像仓库地址,然后安装并运行 Agent 即可。
【数据面】流量路径基本上和不使用 Kubernetes 一样,请求直接在节点的 Gateway 被处理,转发到本地容器化的服务中。甚至更棒的是,如果单个节点无法承受负载,使用 DNS 解析到其他节点即可实现简单的流量分发。此外,访问内部服务的方式被大大简化了,无需担心权限和安全问题,和业务之间彼此解耦。最后,对于配置实现了集中式的管理,这提升了工作负载跨节点迁移的方便性 —— 当然,存储还是比较麻烦,需要手动迁移数据并在其他节点提供 PV 并绑定 PVC,但总的来说,瑕不掩瑜,一言以蔽之,对于分布式系统的数据面,Kubernetes 的集中存储配置和密钥、服务之间互访提供了更好的便捷性和更优的灵活性。