本文已发表一年以上。较旧的文章可能包含过时内容。请检查页面中的信息自发布以来是否已不正确。
Gardener - Kubernetes 园丁
如今,Kubernetes 已成为在云中运行软件的自然选择。越来越多的开发者和企业正在将其应用程序容器化,其中许多正在采用 Kubernetes 来自动化部署其云原生工作负载。
有许多开源工具可以帮助创建和更新单个 Kubernetes 集群。然而,您需要的集群越多,对其进行操作、监控、管理并保持其活跃和最新就越困难。
这正是项目 "Gardener" 所关注的。它不仅仅是另一个配置工具,而是被设计用来将 Kubernetes 集群作为一项服务进行管理。它在各种云提供商上提供符合 Kubernetes 标准的集群,并具备大规模维护数百甚至数千个集群的能力。在 SAP,我们不仅在自己的平台中面临这种异构的多云和本地部署挑战,而且在所有实施 Kubernetes 和云原生的更大或更小的客户那里也遇到了同样的需求。
受 Kubernetes 可能性和自托管能力的启发,Gardener 的基础就是 Kubernetes 本身。虽然自托管(即在 Kubernetes 内部运行 Kubernetes 组件)是社区中的热门话题,但我们采用了一种特殊的模式来满足以最低的总拥有成本运营大量集群的需求。我们使用一个初始的 Kubernetes 集群(称为“seed”集群),并将最终用户集群的控制平面组件(如 API server、scheduler、controller-manager、etcd 等)作为简单的 Kubernetes Pod 进行“种下”(seed)。本质上,seed 集群的重点是提供大规模健壮的 Control-Plane-as-a-Service。按照我们的植物学术语,准备“发芽”(sprout) 的最终用户集群被称为“shoot”集群。考虑到网络延迟和其他故障场景,我们建议每个云提供商和区域部署一个 seed 集群,用于托管众多 shoot 集群的控制平面。
总的来说,重用 Kubernetes 原语的这一概念已经简化了控制平面的部署、管理、扩展和修补/更新。由于它建立在高可用性的初始 seed 集群之上,我们可以避免 shoot 集群控制平面所需的多个 master 节点仲裁数量,从而减少浪费/成本。此外,实际的 shoot 集群仅包含 worker 节点,可以将完整的管理访问权限授予各自所有者,从而构建了必要的分离关注点,以提供更高级别的 SLO。因此,架构角色和操作所有权定义如下(参见Figure 1
):
- Kubernetes 即服务提供商拥有、运营和管理 garden 和 seed 集群。它们代表了所需环境/基础设施的一部分。
- shoot 集群的控制平面运行在 seed 中,因此也运行在服务提供商的独立安全域内。
- shoot 集群的机器运行在客户的云提供商账户和环境中,归客户所有,但仍由 Gardener 管理。
- 对于本地部署或私有云场景,将 seed 集群(和 IaaS)的所有权和管理权委托出去是可行的。

Figure 1 Gardener 技术架构图及组件。
Gardener 被开发为一个聚合的 API server,并附带一组捆绑的控制器。它运行在另一个专门的 Kubernetes 集群(称为“garden”集群)中,并通过自定义资源扩展 Kubernetes API。最显著的是,Shoot 资源允许以声明式方式描述用户的 Kubernetes 集群的完整配置。相应的控制器,就像原生的 Kubernetes 控制器一样,将观察这些资源,并将世界的实际状态调整到期望状态(从而执行创建、协调、更新、升级或删除操作)。以下示例清单显示了需要指定的内容:
apiVersion: garden.sapcloud.io/v1beta1
kind: Shoot
metadata:
name: dev-eu1
namespace: team-a
spec:
cloud:
profile: aws
region: us-east-1
secretBindingRef:
name: team-a-aws-account-credentials
aws:
machineImage:
ami: ami-34237c4d
name: CoreOS
networks:
vpc:
cidr: 10.250.0.0/16
...
workers:
- name: cpu-pool
machineType: m4.xlarge
volumeType: gp2
volumeSize: 20Gi
autoScalerMin: 2
autoScalerMax: 5
dns:
provider: aws-route53
domain: dev-eu1.team-a.example.com
kubernetes:
version: 1.10.2
backup:
...
maintenance:
...
addons:
cluster-autoscaler:
enabled: true
...
一旦发送到 garden 集群,Gardener 将接收它并配置实际的 shoot。上面没有显示的是,每次操作都会丰富 Shoot
的 status
字段,指示当前是否有操作正在运行,并记录最后的错误(如果有的话)以及所涉及组件的健康状况。用户能够以真正的 Kubernetes 风格配置和监控他们的集群状态。我们的用户甚至编写了自己的自定义控制器来观察和修改这些 Shoot
资源。
技术深入探讨
Gardener 实现了 Kubernetes 嵌套(inception)方法;因此,它利用 Kubernetes 的能力来执行其操作。它提供了一些控制器(参见[A]
)来观察 Shoot
资源,其中主控制器负责标准的创建、更新和删除等操作。另一个名为“shoot care”的控制器执行定期的健康检查和垃圾回收,而第三个(“shoot maintenance”)的任务是覆盖诸如将 shoot 的机器镜像更新到最新可用版本等操作。
对于每个 shoot,Gardener 在 seed 中创建一个专用的 Namespace
,并应用适当的安全策略,并在其中预创建后续需要的证书,这些证书作为 Secrets
进行管理。
etcd
Kubernetes 集群的后端数据存储 etcd(参见[B]
)被部署为一个包含一个副本和一个 PersistentVolume(Claim)
的 StatefulSet
。遵循最佳实践,我们运行另一个 etcd 分片实例来存储 shoot 的 Events
。此外,主 etcd Pod 通过一个 sidecar 进行了增强,该 sidecar 验证静态数据并定期进行快照,然后将这些快照高效地备份到对象存储中。如果 etcd 的数据丢失或损坏,sidecar 会从最新的可用快照中恢复数据。我们计划开发增量/连续备份,以避免在恢复时,恢复后的 etcd 状态与实际状态之间出现差异 [1]。
Kubernetes 控制平面
如上所述,我们将其他 Kubernetes 控制平面组件放入原生的 Deployments
中,并以滚动更新策略运行它们。通过这样做,我们不仅可以利用 Kubernetes 现有的部署和更新能力,还可以利用其监控和活性探测能力。虽然控制平面本身使用集群内通信,但 API Server 的 Service
通过负载均衡器暴露以进行外部通信(参见[C]
)。为了统一生成部署清单(主要取决于 Kubernetes 版本和云提供商),我们决定使用 Helm charts,Gardener 仅利用 Tiller 的渲染能力,但直接部署生成的清单而根本不运行 Tiller [2]。
基础设施准备
创建集群时的首要需求之一是在云提供商侧准备完善的基础设施,包括网络和安全组。在我们当前的 Gardener 特定提供商树内实现(称为“Botanist”)中,我们使用 Terraform 来完成此任务。Terraform 为主要云提供商提供了良好的抽象,并实现了并行性、重试机制、依赖图、幂等性等功能。然而,我们发现 Terraform 在错误处理方面具有挑战性,并且不提供用于提取错误根本原因的技术接口。目前,Gardener 根据 shoot 规范生成 Terraform 脚本,并将其存储在 seed 集群的相应命名空间中的 ConfigMap
内。然后,Terraformer 组件作为 Job
运行(参见[D]
),执行挂载的 Terraform 配置,并将产生的状态写回另一个 ConfigMap
。以这种方式使用 Job 原语有助于继承其重试逻辑,并实现对临时连接问题或资源限制的容错。此外,Gardener 只需访问 seed 集群的 Kubernetes API 即可为底层的 IaaS 提交 Job。这种设计对于通常不公开暴露 IaaS API 的私有云场景非常重要。
机器控制器管理器
接下来需要的是用于调度集群实际工作负载的节点。然而,Kubernetes 不提供请求节点的原语,迫使集群管理员使用外部机制。需要考虑的因素包括完整的生命周期,从初始配置开始,到提供安全修复、执行健康检查和滚动更新。虽然我们最初通过实例化静态机器或利用云提供商的实例模板来创建 worker 节点,但我们得出结论(也结合了我们先前在运行云平台方面的生产经验),这种方法需要大量的精力。在 KubeCon 2017 的讨论中,我们认识到管理集群节点的最佳方式当然是再次应用核心 Kubernetes 概念,并教会系统自管理其运行的节点/机器。为此,我们开发了 机器控制器管理器(参见[E]
),它通过 MachineDeployment
、MachineClass
、MachineSet
和 Machine
资源扩展 Kubernetes,并能够在 Kubernetes 上下文内以声明式方式管理(虚拟)机器,就像管理 Deployments
、ReplicaSets
和 Pods
一样。我们重用了现有的 Kubernetes 控制器代码,只需要抽象出一些用于创建、删除和列出特定驱动程序中机器的 IaaS/云提供商特定方法。比较 Pods 和 Machines 时,一个细微的差别变得明显:创建虚拟机直接产生费用,如果发生不可预见的情况,这些费用会迅速增加。为了防范这种失控,机器控制器管理器带有一个安全控制器,该控制器会终止孤立的机器,并在超过特定阈值和超时时间时冻结 MachineDeployments 和 MachineSets 的滚动更新。此外,我们利用现有的官方 cluster-autoscaler,它已经包含了确定哪个节点池应该扩容或缩容的复杂逻辑。由于其云提供商接口设计良好,我们使 autoscaler 能够在触发扩缩容时直接修改相应 MachineDeployment
资源中的副本数量。
附加组件
除了提供正确设置的控制平面,每个 Kubernetes 集群还需要一些系统组件才能工作。通常,这些组件包括 kube-proxy、覆盖网络、集群 DNS 和一个 ingress 控制器。除此之外,Gardener 允许用户订购可选的附加组件(可在 shoot 资源定义中配置),例如 Heapster、Kubernetes Dashboard 或 Cert-Manager。同样,Gardener 通过 Helm charts(部分是修改和策划自 上游 charts 仓库)渲染所有这些组件的清单。然而,这些资源是在 shoot 集群中管理的,因此具有完全管理访问权限的用户可以对其进行调整。因此,Gardener 通过利用现有的看门狗 kube-addon-manager(参见[F]
)确保这些已部署的资源始终与计算/期望的配置匹配。
网络隔离
虽然 shoot 集群的控制平面运行在由您友好的平台提供商管理和提供的 seed 中,但 worker 节点通常配置在用户独立的云提供商(计费)账户中。通常,这些 worker 节点被放置在私有网络中 [3],seed 控制平面中的 API Server 通过基于 ssh 的简单 VPN 解决方案(参见[G]
)与这些网络建立直接通信。我们最近将基于 SSH 的实现迁移到了基于 OpenVPN 的实现,这显著提高了网络带宽。
监控与日志
监控、告警和日志记录对于监督集群并保持其健康、避免中断和其他问题至关重要。Prometheus 已成为 Kubernetes 领域使用最广泛的监控系统。因此,我们在每个 seed 的 garden
命名空间中部署了一个中央 Prometheus 实例。它收集来自所有 seed 的 kubelet 的指标,包括 seed 集群中所有 Pod 运行的指标。此外,在每个控制平面旁边,还会为 shoot 本身配置一个专用的租户 Prometheus 实例(参见[H]
)。它收集自身控制平面以及运行在 shoot worker 节点上的 Pod 的指标。前者通过从中央 Prometheus 的 federation 端点获取数据并过滤出特定 shoot 的相关控制平面 Pod 来完成。除此之外,Gardener 还会部署两个 kube-state-metrics 实例,一个负责控制平面,一个负责工作负载,暴露集群级别指标以丰富数据。node exporter 提供更详细的节点统计信息。专用的租户 Grafana 仪表盘通过清晰的仪表盘显示分析和洞察。我们还为关键事件定义了告警规则,并使用 AlertManager 在触发任何告警时向操作员和支持团队发送电子邮件。
[1] 这也是不支持时间点恢复 (point-in-time recovery) 的原因。目前 Kubernetes 中尚未实现可靠的基础设施协调。因此,在不刷新相关集群实际工作负载和状态的情况下,从旧备份中恢复通常不会有太大帮助。
[2] 做出此决定的最相关标准是 Tiller 需要端口转发连接进行通信,我们发现在自动化用例中,这种连接太不稳定且容易出错。尽管如此,我们期待着 Helm v3 能够有望使用 CustomResourceDefinitions
与 Tiller 进行交互。
[3] Gardener 可以选择使用 Terraformer 创建和准备这些网络,或者可以被指示重用现有的网络。
可用性和交互
尽管管理 Gardener 只需要熟悉的 kubectl
命令行工具,但我们提供了一个中央仪表盘,方便用户进行交互。它使用户能够轻松跟踪其集群的健康状况,并使操作员能够监控、调试和分析他们负责的集群。Shoot 被分组到逻辑项目 (project) 中,在这些项目中,管理一组集群的团队可以协作,甚至可以通过集成工单系统(例如 GitHub Issues)跟踪问题。此外,该仪表盘帮助用户添加和管理其基础设施账户 secret,并在一个地方查看所有 shoot 集群的最相关数据,同时不依赖于它们部署到的云提供商。
Figure 2 动态 Gardener 仪表盘。
更侧重于开发者和操作员的职责,Gardener 命令行客户端 gardenctl
通过引入具有简单命令的易用高级抽象来简化管理任务,这些抽象有助于从大量 seed 和 shoot 集群中/向其浓缩和多路复用信息和操作。
$ gardenctl ls shoots
projects:
- project: team-a
shoots:
- dev-eu1
- prod-eu1
$ gardenctl target shoot prod-eu1
[prod-eu1]
$ gardenctl show prometheus
NAME READY STATUS RESTARTS AGE IP NODE
prometheus-0 3/3 Running 0 106d 10.241.241.42 ip-10-240-7-72.eu-central-1.compute.internal
URL: https://user:password@p.prod-eu1.team-a.seed.aws-eu1.example.com
前景与未来计划
Gardener 已经能够管理 AWS、Azure、GCP、OpenStack 上的 Kubernetes 集群 [4]。事实上,由于它仅依赖于 Kubernetes 原生组件 (primitives),它可以很好地满足私有云或本地部署 (on-premise) 的需求。从 Gardener 的角度来看,唯一的区别将是底层基础设施的质量和可扩展性——Kubernetes 的通用语言确保了我们方法的强大可移植性保证。
然而,前方仍面临挑战。我们正在探索在这个开源项目中,加入一个选项来创建委托给多个 shoot 集群的 federation control plane 的可能性。在前面的章节中,我们没有解释如何引导 (bootstrap) garden 和 seed 集群本身。您确实可以使用任何生产就绪的集群供应工具或云提供商的 Kubernetes 即服务产品。我们基于 Terraform 构建了一个名为 Kubify 的统一工具,并重用了许多前面提到的 Gardener 组件。我们设想所需的 Kubernetes 基础设施能够由最初的引导 Gardener 完整地生成,并且已经在讨论如何实现这一点。
我们关注的另一个重要话题是灾难恢复。当 seed 集群发生故障时,用户的静态工作负载将继续运行。然而,将无法再对集群进行管理。我们正在考虑将受灾的 shoot 集群的控制平面迁移到另一个 seed。从概念上讲,这种方法是可行的,并且我们已经具备实现所需的组件,例如自动化的 etcd 备份和恢复。该项目的贡献者不仅肩负着开发生产级 Gardener 的使命,而且我们大多数人甚至在真正的 DevOps 模式下运行它。我们完全信任 Kubernetes 的概念,并致力于遵循“自产自销 (eat your own dog food)”的方法。
为了使 Botanists(包含基础设施提供商特定的实现部分)能够更独立地演进,我们计划定义清晰的接口,并将 Botanists 拆分为独立的组件。这类似于 Kubernetes 当前对 cloud-controller-manager 所做的工作。目前,所有云相关的特定代码都是 Gardener 核心仓库的一部分,这为扩展或支持新的云提供商带来了软障碍。
当考察 shoot 集群的实际供应方式时,我们需要获得更多关于拥有数千(或更多)节点和 Pod 的超大型集群如何表现的经验。可能对于大型集群,我们需要采用横向扩展的方式部署例如 API 服务器和其他组件,以分散负载。幸运的是,基于 Prometheus 的自定义指标的水平 Pod 自动伸缩将使我们的设置相对容易。此外,在我们的集群上运行生产工作负载的团队反馈是,Gardener 应该支持预配置的 Kubernetes QoS。不言而喻,我们的愿景是整合并贡献到 Kubernetes Autopilot 的愿景中。
[4] 原型已验证支持天翼云 (CTyun) 和阿里云 (Aliyun)。
Gardener 是开源项目
Gardener 项目以开源形式开发,并托管在 GitHub 上:https://github.com/gardener
SAP 自 2017 年年中开始致力于 Gardener 项目,并专注于构建一个易于演进和扩展的项目。因此,我们现在正在寻找更多的合作伙伴和贡献者。正如上面所述,我们完全依赖于 Kubernetes 的原生组件、附加组件和规范,并采用其创新的云原生方法。我们期待与 Kubernetes 社区保持一致并作出贡献。事实上,我们设想将整个项目贡献给 CNCF。
目前,与社区协作的一个重要焦点是几个月前成立的 SIG Cluster Lifecycle 下的 Cluster API 工作组。其主要目标是定义一个代表 Kubernetes 集群的可移植 API。这包括控制平面和底层基础设施的配置。我们已经拥有的 Shoot 和 Machine 资源与社区正在开发的内容之间的重叠非常显著。因此,我们加入了这个工作组,并积极参与他们的定期会议,试图贡献我们在生产环境中获得的经验教训。私下来说,塑造一个健壮的 API 也符合我们的利益。
如果您看到了 Gardener 项目的潜力,请在 GitHub 上了解更多信息,并通过提问、参与讨论和贡献代码来帮助我们让 Gardener 变得更好。此外,请尝试我们的 快速入门设置。
期待在那里见到您!