本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。
Gardener 项目更新
去年,我们在 Kubernetes 社区会议上和 Kubernetes 博客上的一篇文章中介绍了 Gardener。在 SAP,我们运行 Gardener 已经两年多了,并成功地在所有主要的超大规模提供商以及通过收购并入企业的众多基础设施和私有云上,管理着数千个符合标准的各种版本的集群。
我们经常被问到,为什么少数几个可动态扩展的集群就不能满足需求。我们最初进入 Kubernetes 领域时也抱有类似的想法。但我们意识到,在生产场景中应用 Kubernetes 的架构和原则时,我们的内部和外部客户很快就需要合理地分离关注点和所有权,这在大多数情况下导致了使用多个集群。因此,可扩展和托管的 Kubernetes 即服务解决方案也常常是采用的基础。特别是当一个大型组织在不同的提供商和不同的区域运行多个产品时,集群数量很快就会上升到数百甚至数千个。
今天,我们想就过去一年中我们在可扩展性和可定制性方面所做的工作,以及我们为下一个里程碑计划开展的工作提供一个更新。
简要回顾:Gardener 是什么?
Gardener 的主要原则是利用 Kubernetes 原语进行所有操作,通常被称为内嵌或 kubeception。社区的反馈是,最初我们的架构图看起来“令人不知所措”,但在稍加研究后,我们所做的一切都是“Kubernetes 方式”。人们可以重用所有关于 API、控制循环等方面的知识。
核心思想是使用所谓的**种子(seed)**集群来托管最终用户集群(植物学上称为**芽(shoots)**)的控制平面。
Gardener 以同构方式提供独立于底层基础设施提供商的纯粹 Kubernetes 集群即服务,利用上游提供的 `k8s.gcr.io/*` 镜像作为开放分发(更新:`k8s.gcr.io` 已被弃用,转而使用 `registry.k8s.io`)。该项目完全建立在 Kubernetes 扩展概念之上,因此添加了自定义 API 服务器、控制器管理器和调度器来创建和管理 Kubernetes 集群的生命周期。它通过自定义资源扩展了 Kubernetes API,最突出的是 Gardener 集群规范(`Shoot` 资源),可以以声明方式“订购”Kubernetes 集群(用于第一天,但也协调所有第二天管理活动)。
通过将 Kubernetes 作为基础架构,我们能够设计出一种组合的水平和垂直 Pod 自动伸缩器(HVPA),当配置了自定义启发式算法时,它可以自动地向上/向下或向外/向内伸缩所有控制平面组件。这使得快速伸缩成为可能,甚至超出了通常固定数量的主节点的容量。这一架构特性是与其他许多 Kubernetes 集群供应工具的主要区别之一。但在我们的生产中,Gardener 不仅通过打包控制平面有效地降低了总拥有成本。它还简化了“第二天操作”(如集群更新或鲁棒性质量)的实现。再次强调,这主要依赖于所有成熟的 Kubernetes 功能和能力。
新引入的 Gardener 扩展概念现在使提供商只需维护其特定的扩展,而无需在核心源代码树内进行开发。
可扩展性
由于 Kubernetes 在过去几年的发展,其代码库中包含了大量的特定于提供商的代码,这些代码现在正从其核心源代码树中外部化。Gardener 项目也发生了同样的情况:随着时间的推移,积累了许多针对云提供商、操作系统、网络插件等的具体内容。通常,这会导致在可维护性、可测试性或新版本发布方面的工作量显著增加。我们的社区成员 Packet 在树内为其基础设施贡献了 Gardener 支持,并遭遇了上述缺点。
因此,与 Kubernetes 社区决定将他们的云控制器管理器移出树外,或者将卷插件移到 CSI 等方式类似,Gardener 社区也提议并实现了类似的扩展概念。Gardener 核心源代码树现在没有任何供应商特有的内容,这使得供应商可以专注于其基础设施特有的内容,并使核心贡献者再次变得更加敏捷。
通常,设置集群需要一系列相互依赖的步骤,从证书生成和基础设施准备开始,接着是控制平面和工作节点的供应,最后是系统组件的部署。我们想在此强调,所有这些步骤都是必要的(参见Kubernetes the Hard Way),所有 Kubernetes 集群创建工具都以某种方式实现了相同的步骤(在一定程度上是自动化的)。
Gardener 可扩展性概念的总体思想是使此流程更通用,并为每个步骤刻画自定义资源,这些资源可以作为理想的扩展点。

图 1 集群协调流程与扩展点。
借助 Gardener 的流程框架,我们隐式地为所有基础设施和集群所有可能状态提供了一个可重现的状态机。
Gardener 的可扩展性方法定义了可作为以下类别理想扩展点的自定义资源:
- DNS 提供商(例如,Route53、CloudDNS 等),
- Blob 存储提供商(例如,S3、GCS、ABS 等),
- 基础设施提供商(例如,AWS、GCP、Azure 等),
- 操作系统(例如,CoreOS Container Linux、Ubuntu、FlatCar Linux 等),
- 网络插件(例如,Calico、Flannel、Cilium 等),
- 非必要扩展(例如,Let's Encrypt 证书服务)。
扩展点
除了利用自定义资源定义之外,我们还在种子集群中有效地使用变异/验证 Webhook。扩展控制器本身在这些集群中运行,并对其负责的 CRD 和工作负载资源(如 `Deployment`、`StatefulSet` 等)作出反应。与 集群 API 的方法类似,这些 CRD 也可以包含提供商特定的信息。
步骤 2 - 10 [参见图 1] 涉及基础设施特定的元数据,指代基础设施特定的实现,例如,对于 DNS 记录,可能有 `aws-route53`、`google-clouddns`,或者对于隔离网络,甚至 `openstack-designate` 等等。我们将在接下来的段落中,以步骤 4 和 6 为例,基于 AWS 的实现,考察通用概念。如果您有兴趣,可以在我们的可扩展性文档中阅读完整记录的 API 契约。
示例:`Infrastructure` CRD
AWS 上的 Kubernetes 集群在使用前需要进行特定的基础设施准备。这包括,例如,创建 VPC、子网等。`Infrastructure` CRD 的目的是触发此准备工作
apiVersion: extensions.gardener.cloud/v1alpha1
kind: Infrastructure
metadata:
name: infrastructure
namespace: shoot--foobar--aws
spec:
type: aws
region: eu-west-1
secretRef:
name: cloudprovider
namespace: shoot--foobar—aws
sshPublicKey: c3NoLXJzYSBBQUFBQ...
providerConfig:
apiVersion: aws.provider.extensions.gardener.cloud/v1alpha1
kind: InfrastructureConfig
networks:
vpc:
cidr: 10.250.0.0/16
zones:
- name: eu-west-1a
internal: 10.250.112.0/22
public: 10.250.96.0/22
workers: 10.250.0.0/19
基于 `Shoot` 资源,Gardener 在其协调流程中创建此 `Infrastructure` 资源。AWS 特定的 `providerConfig` 是最终用户在 `Shoot` 资源中的配置的一部分,不经 Gardener 评估,而是直接传递给种子集群中的扩展控制器。
在其当前实现中,AWS 扩展在 `eu-west-1a` 区域创建了一个新的 VPC 和三个子网。此外,它还创建了一个 NAT 和一个互联网网关、弹性 IP、路由表、安全组、IAM 角色、实例配置文件以及一个 EC2 密钥对。
完成任务后,它将报告状态和一些提供商特定的输出
apiVersion: extensions.gardener.cloud/v1alpha1
kind: Infrastructure
metadata:
name: infrastructure
namespace: shoot--foobar--aws
spec: ...
status:
lastOperation:
type: Reconcile
state: Succeeded
providerStatus:
apiVersion: aws.provider.extensions.gardener.cloud/v1alpha1
kind: InfrastructureStatus
ec2:
keyName: shoot--foobar--aws-ssh-publickey
iam:
instanceProfiles:
- name: shoot--foobar--aws-nodes
purpose: nodes
roles:
- arn: "arn:aws:iam::<accountID>:role/shoot..."
purpose: nodes
vpc:
id: vpc-0815
securityGroups:
- id: sg-0246
purpose: nodes
subnets:
- id: subnet-1234
purpose: nodes
zone: eu-west-1b
- id: subnet-5678
purpose: public
zone: eu-west-1b
`providerStatus` 中的信息可以在后续步骤中使用,例如配置云控制器管理器或操作机器控制器管理器。
示例:集群控制平面部署
Gardener 的主要特性之一是它所管理的集群在不同基础设施之间具有同构性。因此,它仍然负责将与提供商无关的控制平面组件(如 etcd、kube-apiserver)部署到种子集群中。提供商特定的控制平面组件(如云控制器管理器或 CSI 控制器)的部署由专门的 `ControlPlane` CRD 触发。然而,在本段中,我们想重点讨论标准组件的定制化。
让我们关注 kube-apiserver 和 kube-controller-manager 的 `Deployment`。我们针对 Gardener 的 AWS 扩展尚未使用 CSI,但依赖于树内 EBS 卷插件。因此,它需要启用 `PersistentVolumeLabel` 准入插件,并向 kube-apiserver 提供云提供商配置。同样,kube-controller-manager 将被指示使用其树内卷插件。
kube-apiserver `Deployment` 包含 `kube-apiserver` 容器,并由 Gardener 部署如下:
containers:
- command:
- /hyperkube
- apiserver
- --enable-admission-plugins=Priority,...,NamespaceLifecycle
- --allow-privileged=true
- --anonymous-auth=false
...
通过使用 `MutatingWebhookConfiguration`,AWS 扩展注入了上述标志并修改了规范,如下所示:
containers:
- command:
- /hyperkube
- apiserver
- --enable-admission-plugins=Priority,...,NamespaceLifecycle,PersistentVolumeLabel
- --allow-privileged=true
- --anonymous-auth=false
...
- --cloud-provider=aws
- --cloud-config=/etc/kubernetes/cloudprovider/cloudprovider.conf
- --endpoint-reconciler-type=none
...
volumeMounts:
- mountPath: /etc/kubernetes/cloudprovider
name: cloud-provider-config
volumes:
- configMap:
defaultMode: 420
name: cloud-provider-config
name: cloud-provider-config
kube-controller-manager `Deployment` 也以类似方式处理。
种子集群中的 Webhook 可用于修改 Gardener 或任何其他扩展部署的 Shoot 集群控制平面相关的任何内容。在 Shoot 集群中也有类似的资源 Webhook 概念,以防扩展控制器需要自定义 Gardener 部署的系统组件。
扩展控制器注册
Gardener API 使用两个特殊资源来注册和安装扩展。注册本身通过 `ControllerRegistration` 资源声明。最简单的选项是定义 Helm chart 以及渲染 chart 的一些值,但是,也支持通过自定义代码实现任何其他部署机制。
Gardener 确定特定种子集群中是否需要扩展控制器,并创建一个 `ControllerInstallation` 来触发部署。
迄今为止,所有注册的扩展控制器都部署到每个种子集群,这通常不是必需的。未来,Gardener 将变得更具选择性,仅部署那些在特定种子集群上所需的扩展。
我们的动态注册方法允许在运行系统中添加或移除扩展,而无需重建或重启任何组件。

图 2 包含扩展控制器的 Gardener 架构。
现状
我们最近引入了新的 `core.gardener.cloud` API 组,它包含了完全向前和向后兼容的 `Shoot` 资源,并允许提供商在不修改其核心源代码树的任何内容的情况下使用 Gardener。
我们已经将所有控制器调整为使用这个新的 API 组,并已弃用旧的 API。最终,几个月后我们将将其删除,因此建议最终用户尽快开始迁移到新的 API。
除此之外,我们已启用所有相关扩展,以贡献 Shoot 健康状态,并实现了相应的契约。基本思想是 CRD 可能具有 `.status.conditions`,这些条件被 Gardener 拾取并与其标准健康检查合并到 `Shoot` 状态字段中。
此外,我们还将实现一些易于使用的库函数,以便为 CRD 提供默认值和验证 webhook,从而验证由最终用户控制的 `providerConfig` 字段。
最后,我们将把 `gardener/gardener-extensions` 仓库拆分成独立的仓库,并仅保留用于编写扩展控制器的通用库函数。
后续步骤
Kubernetes 已经将许多基础设施管理挑战外部化。内嵌设计通过将生命周期操作委托给独立的管理平面(种子集群)来解决其中大部分问题。但是,如果园丁集群或种子集群宕机怎么办?我们如何扩展到需要并行协调的数万个托管集群?我们正在进一步投入,以增强 Gardener 的可扩展性和灾难恢复功能。让我们简要介绍其中三个功能:
Gardenlet
从 Gardener 项目一开始,我们就开始实施operator 模式:我们有一个自定义控制器管理器,它作用于我们自己的自定义资源。现在,当你开始思考 Gardener 架构时,你会发现与 Kubernetes 架构有一些有趣的相似之处:Shoot 集群可以与 Pod 相比,而 Seed 集群可以看作是工作节点。基于这一观察,我们引入了 **gardener-scheduler**。它的主要任务是为新订购的集群寻找合适的 Seed 集群来托管控制平面,类似于 kube-scheduler 为新创建的 Pod 寻找合适的节点。通过为一个区域(或提供商)提供多个 Seed 集群并分配工作负载,我们也降低了潜在故障的影响范围。

图 3 Kubernetes 与 Gardener 架构的相似之处。
然而,Kubernetes 和 Gardener 架构之间仍然存在显著差异:Kubernetes 在每个节点上运行一个主要的“代理”,即 kubelet,它主要负责管理其特定节点上的 Pod 和容器。Gardener 使用其控制器管理器,该管理器负责所有种子集群上的所有 Shoot 集群,并从花园集群集中执行其协调循环。
虽然这在当前数千个集群的规模下运行良好,但我们的目标是实现遵循 Kubernetes 原则的真正可扩展性(超越单个控制器管理器的容量):我们现在正在努力将逻辑(或 Gardener operator)分发到种子集群中,并将引入一个相应的组件,恰当地命名为 **gardenlet**。它将是 Gardener 在每个种子集群上的主要“代理”,并且将只负责位于其特定种子集群中的 Shoot 集群。
gardener-controller-manager 仍将保留其对 Gardener API 其他资源的控制循环,但是,它将不再与种子/芽集群通信。
反转控制流甚至可以允许将种子/芽集群放置在防火墙后,而不再需要直接可访问性(通过 VPN 隧道)。

图 4 包含 Gardenlet 的详细架构。
种子集群之间的控制平面迁移
当种子集群出现故障时,用户的静态工作负载将继续运行。但是,集群管理将不再可能,因为运行在故障种子中的 Shoot 集群的 API 服务器将无法访问。
我们已经实现了将因种子灾难而导致故障的控制平面重新定位到另一个种子集群,现在正在努力全面自动化这一独特功能。事实上,这种方法不仅可行,我们已经在生产中多次执行了故障转移过程。
自动化故障转移功能将使我们能够实现更全面的灾难恢复和可伸缩性特性,例如,种子集群的自动化供应和重新平衡,或所有不可预见情况的自动化迁移。再次思考与 Kubernetes 在 Pod 逐出和节点排水方面的相似之处。
园丁环
Gardener Ring 是我们用于配置和管理 Kubernetes 集群的新颖方法,它不依赖于外部供应工具来构建初始集群。通过递归地使用 Kubernetes,我们通过避免命令式工具集,可以极大地降低管理复杂性,同时通过自稳定的循环系统创建新的特性。
环形方法在概念上不同于自托管和基于静态 pod 的部署。其思想是创建由三个(或更多)Shoot 集群组成的环,每个集群托管其后继者的控制平面。
一个集群的宕机不会影响 Ring 的稳定性和可用性,并且由于控制平面是外部化的,故障集群可以由 Gardener 的自愈能力自动恢复。只要至少有 `n/2+1` 个可用集群的法定人数,Ring 就会始终自行稳定。在不同的云提供商(或至少在不同的区域/数据中心)上运行这些集群,可以减少法定人数损失的可能性。

图 5 自稳定 Kubernetes 集群环。
Gardener 的分布式实例共享相同数据的方式是部署独立的 kube-apiserver 实例,这些实例连接到同一个 etcd 集群。这些 kube-apiserver 形成了一个无节点的 Kubernetes 集群,可以作为 Gardener 及其相关应用程序的“数据容器”。
我们内部运行着受 ring 保护的测试环境,它使我们免去了人工干预。随着自动化控制平面迁移的到位,我们可以轻松引导 Ring,并解决“初始集群问题”,同时提高整体健壮性。
开始吧!
如果您有兴趣编写扩展,您可能需要查看以下资源:
- GEP-1:可扩展性提案文档
- GEP-4:新的 `core.gardener.cloud/v1alpha1` API
- AWS 扩展控制器实现示例
- Gardener 扩展 Golang 库
- 扩展契约文档
- Gardener API 参考
当然,也欢迎对我们项目的任何其他贡献!我们一直在寻找新的社区成员。
如果您想试用 Gardener,请查看我们的快速安装指南。此安装程序将在几分钟内设置一个完整的 Gardener 环境,可用于测试和评估。
欢迎贡献!
Gardener 项目以开源形式开发,并托管在 GitHub 上:https://github.com/gardener
如果您看到 Gardener 项目的潜力,请通过 GitHub 加入我们。
我们每周五欧洲中部时间上午 10-11 点举行公开社区会议,并在 Kubernetes 工作区中设有公开的 #gardener Slack 频道。此外,我们计划在 2020 年第一季度举办Gardener 黑客松,期待与您在那里见面!