管理数千个集群:Gardener项目更新
- 2019 年 12 月 10 日
- 笔记
作者:Rafael Franzke(SAP),Vasu Chandrasekhara(SAP)
去年,我们在Kubernetes社区会议和Kubernetes博客上介绍了Gardener。在SAP,我们已经运行了两年多的Gardener,并且成功地管理了所有大型云供应商上不同版本的数千个通过一致性的集群,以及大量的基础架构和私有云,这些私有云通常是通过收购来加入企业的。
https://kubernetes.io/blog/2018/05/17/gardener/
https://gardener.cloud/
https://k8s-testgrid.appspot.com/conformance-gardener
我们经常被问到,为什么少量的动态可伸缩集群是不够的。我们也以类似的心态,开始了我们的Kubernetes之旅。但是我们意识到,将Kubernetes的架构和原则应用到生产场景中,我们的内部和外部客户很快就需要合理地分离关注点和所有权,这在大多数情况下导致使用多个集群。因此,作为服务解决方案的可伸缩和受管理的Kubernetes通常是采用的基础。特别是,当一个较大的组织,在不同的供应商和不同的地区运行多个产品时,集群的数量将很快增加到数百甚至数千。
今天,我们想对过去一年,在可扩展性和可定制性方面的实现,以及下一个里程碑的工作计划进行更新。
简要回顾:Gardener是什么?
Gardener的主要原则,是利用Kubernetes的基本功能做所有操作,通常称为先启(inception)或kubeception。来自社区的反馈是,我们最初的架构图看起来“overwhelming”,但在对材料进行了一些深入研究之后,我们所做的一切都是“Kubernetes方式”。可以重用所有关于api、调节循环等的知识。
https://github.com/gardener/documentation/wiki/Architecture
其基本思想是使用所谓的种子(seed)集群来承载最终用户(end-user)集群(植物学上称为shoot)的控制平面。Gardener利用上游k8s.gcr.io/*的镜像作为开放的发行版,将vanilla Kubernetes集群作为独立于底层基础设施供应商的服务。该项目完全构建在Kubernetes扩展概念之上,并因此添加了一个自定义API服务器、一个控制器-管理器(controller-manager)和一个调度器来创建和管理Kubernetes集群的生命周期。它使用自定义资源扩展了Kubernetes API,最突出的是Gardener集群规范(Shoot资源),可以使用它以声明式的方式“命令”Kubernetes集群(针对day-1,也可以协调day-2的所有管理活动)。
通过利用Kubernetes作为基础设施,我们能够设计一个组合水平和垂直Pod自动伸缩器(HVPA),当配置自定义启发式时,自动缩放所有控制平面组件上/下或大/小。这使得快速扩展成为可能,甚至超出了某些固定数量的主节点的能力。与许多其他Kubernetes集群配置工具相比,此架构特性是主要的区别之一。但在我们的生产中,Gardener不仅通过容器控制平面有效地降低了总的拥有成本。它还简化了“第2天操作”(如集群更新或健壮性质量)的实现。再一次,本质上依靠所有成熟的Kubernetes功能和能力。
https://github.com/gardener/hvpa-controller
新引入的Gardener扩展概念,现在允许提供者只维护其特定的扩展,而无需在核心源树中进行开发。
可扩展性
由于Kubernetes代码库在过去几年的增长,它包含了大量特定于提供程序的代码,这些代码现在正从其核心源代码树外部化。同样的事情也发生在了Gardener项目上:随着时间的推移,云提供商、操作系统、网络插件等方面的细节已经积累了很多。通常,当涉及到可维护性、可测试性或新版本时,这将导致大量工作的增加。我们的社区成员Packet为他们的树内基础设施提供了Gardener支持,并遭受了上述缺点。
因此,与Kubernetes社区决定将他们的云-控制器-管理器移出树,或者将卷插件移到CSI等类似,Gardener社区也提出并实现了类似的扩展概念。Gardener核心源代码树现在没有任何提供者细节,允许供应商只关注他们的基础设施细节,并使核心贡献者再次变得更加敏捷。
通常,设置集群需要一系列相互依赖的步骤,从证书的生成和基础设施的准备开始,接着是控制平面和工作节点的提供,最后是系统组件的部署。我们在这里要强调的是,所有这些步骤都是必要的(cf. Kubernetes the Hard Way),所有Kubernetes集群创建工具都以差不多这样或那样的方式实现了相同的步骤(在某种程度上是自动化的)。
https://github.com/kelseyhightower/kubernetes-the-hard-way
Gardener的可扩展性概念的总体思想,是使这个流更加通用,并为每个步骤开辟定制的资源,这些资源可以作为理想的扩展点。
https://github.com/gardener/gardener/blob/0.31.1/pkg/controllermanager/controller/shoot/shoot_control_reconcile.go#L69-L298

图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证书服务)。
扩展点
除了利用自定义资源定义之外,我们还有效地使用了种子集群中的变异/验证(mutating/validating)webhook。扩展控制器本身在这些集群中运行,并对它们负责的CRD和工作负载资源(如Deployment、StatefulSet等)做出反应。与Cluster API的方法类似,这些CRD也可能包含特定于提供者的信息。
https://cluster-api.sigs.k8s.io/
图1步骤2-10涉及特定于基础设施的元数据,涉及特定于基础设施的实现,例如,对于DNS记录,可能有aws-route53、google-clouddns,或者对于隔离网络,甚至是openstack-designate等等。我们将在下一段中以步骤4和步骤6作为一般概念的示例(基于AWS的实现)。如果您感兴趣,可以阅读我们的可扩展性文档中的完整文档化的API契约。
https://github.com/gardener/gardener/tree/master/docs/extensions
例子:Infrastructure CRD
Kubernetes在AWS上的集群在使用之前需要一定的基础设施准备。这包括,例如,创建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和internet网关、弹性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的主要特性之一是跨不同基础设施管理的集群具有同质性。因此,它仍然负责将独立于提供商的控制平面组件部署到seed集群(如etcd、kube-apiserver)。特定于提供商的控制平面组件(如云控制器-管理器或CSI控制器)的部署由专ControlPlane CRD触发。但是,在这一段中,我们将重点讨论标准组件的定制。
让我们同时关注kube-apiserver和kube-controller-manager部署。我们的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 ...
AWS扩展使用一个可变的webhookconfiguration注入上述的标志并修改规范如下:
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将许多基础设施管理方面的挑战具体化了。先启的设计通过将生命周期操作委托给一个单独的管理平面(种子集群)来解决大部分问题。但是,如果garden或种子集群倒下了怎么办?我们如何扩展数以万计的需要并行协调的托管集群?我们正在进一步投入加强Gardener的可伸缩性和灾难恢复功能。让我们更详细地简要介绍其中的三个特性:
Gardenlet
从Gardener项目一开始,我们就开始实现操作器模式:我们有一个自定义控制器-管理器,它对我们自己的自定义资源起作用。现在,当您开始考虑Gardener架构时,您会发现与Kubernetes架构有一些有趣的相似之处:Shoot集群可以与Pod进行比较,而种子集群可以视为工作节点。在这个观察的指导下,我们引入了gardener-scheduler。它的主要任务是找到一个合适的种子集群来承载新命令集群的控制平面,类似于kube-scheduler为新创建的pod找到合适的节点。通过为一个区域(或提供商)提供多个种子集群并分配工作负载,我们还减少了潜在异常的爆炸半径。

图3:Kubernetes和Gardener架构之间的相似性。
但是,Kubernetes和Gardener架构之间仍然有一个显著的区别:Kubernetes在每个节点上运行一个主“代理”,即kubelet,它主要负责管理特定节点上的pod和容器。Gardener使用它的控制器-管理器,它负责所有种子集群上的所有Shoot集群,并从garden集群中央执行调节循环。
虽然这适用于今天的成千上万的集群规模,我们的目标是使后真正的可伸缩性Kubernetes原则(超出了单个controller-manager)的能力:我们现在正在分发逻辑(或Gardener操作器)到种子集群,将引入相应的组件,以gardenlet命名。它将是Gardener在每个种子集群的主要“代理人”,并将只负责的芽集群位于其特定的种子集群。
Gardener-控制-管理器将仍然保持对GardenerAPI的其他资源的调节循环,但是,它将不再与种子/shoot集群对话。
反转控制流甚至可以在防火墙后放置种子/shoot集群,而不再需要直接访问(通过VPN隧道)。

图4:带有Gardenlet的详细架构。
种子集群间的控制平面迁移
当种子集群失败时,用户的静态工作负载将继续运行。但是,管理集群将不再可能,因为在失败的种子中运行的shoot集群的API服务器将不再可用。
我们已经实施了将被一些种子灾难击中的失败的控制平面转移到另一个种子上,现在正在努力使这种独特的能力完全自动化。实际上,这种方法不仅可行,而且在我们的生产中已经多次执行了故障转移过程。
自动故障转移功能将使我们能够实现更全面的灾难恢复和可伸缩性质量,例如,种子集群的自动供应和重新平衡,或者在所有不可预见的情况下实现自动迁移。再一次,想想与Kubernetes在pod eviction和node drain方面的相似之处。
Gardener环
Gardener环(Ring)是我们提供和管理Kubernetes集群的新方法,它不需要为初始集群依赖外部供应工具。通过递归地使用Kubernetes,我们可以通过避免命令式的工具集来大幅降低管理复杂性,同时通过自稳定的循环系统来创建新的质量。
从概念上讲,Ring(环)方法不同于自托管和基于静态pod的部署。这个想法是创建一个由三个(或更多)shoot集群组成的环,每个集群都有它的后继者的控制平面。
一个集群的中断不会影响环的稳定性和可用性,当控制平面外部化时,失败的集群可以通过Gardener的自修复功能自动恢复。只要有至少n/2+1个可用集群的仲裁,该环将始终保持自身稳定。在不同的云提供商(或至少在不同的地区/数据中心)上运行这些集群可以减少仲裁损失的可能性。

图5:Kubernetes集群的自稳定环。
Gardener的分布式实例共享相同数据的方式是,部署单独的kube-apiserver实例与相同的etcd集群通信。这些kube-apiserver正在形成一个无节点的Kubernetes集群,可以用作Gardener及其相关应用程序的“数据容器”。
我们正在运行受环内部保护的测试环境,它使我们免于人工干预。通过自动控制平面迁移,我们可以轻松地引导Ring,并解决“初始群集问题”,并提高整体稳健性。
来开始!
如果你有兴趣写一个扩展,你可能想看看以下资源:
- GEP-1: Extensibility proposal document
- GEP-4: New core.gardener.cloud/v1alpha1 API
- Example extension controller implementation for AWS
- Gardener Extensions Golang library
- Extension contract documentation
- Gardener API Reference https://github.com/gardener/gardener/blob/master/docs/proposals/01-extensibility.md https://github.com/gardener/gardener/blob/master/docs/proposals/04-new-core-gardener-cloud-apis.md https://github.com/gardener/gardener-extensions/tree/master/controllers/provider-aws https://godoc.org/github.com/gardener/gardener-extensions/pkg https://github.com/gardener/gardener/tree/master/docs/extensions https://gardener.cloud/api-reference/
当然,任何其他贡献,我们的项目是非常欢迎的!我们一直在寻找新的社区成员。
如果你想尝试Gardener,请查看我们的快速安装指南。该安装程序将设置一个完整的Gardener环境,只需几分钟准备好用于测试和评估。
https://gardener.cloud/installer/
欢迎贡献!
Gardener项目是作为开放源代码开发的,并托管在GitHub上:https://github.com/gardener
如果你看到Gardener项目的潜力,请通过GitHub加入我们。
我们每周在中欧时间每周五上午10-11点召开一次社区会议,并在Kubernetes的工作区内设一条公共的#gardener Slack频道。同时,我们计划在2020年第一季度举办Gardener黑客松,期待与您的见面!
https://docs.google.com/document/d/1314v8ziVNQPjdBrWp-Y4BYrTDlv7dq2cWDFIa9SMaP4
https://kubernetes.slack.com/messages/gardener
https://docs.google.com/document/d/1EQ_kt70gwybiL7FY8F7Dx–GtiNwdv0oRDwqQqAIYMk/edit#heading=h.a43vkkp847f1