本文已超过一年。较旧的文章可能包含过时内容。请检查页面信息自发布以来是否已不再正确。

Gardener 项目更新

去年,我们在 Kubernetes 社区会议上和 Kubernetes 博客的一篇文章中介绍了 Gardener。在 SAP,我们运行 Gardener 已经两年多了,并在所有主要的超大规模云提供商以及通过收购并入企业的众多基础设施和私有云中,成功管理着数千个不同版本的一致性集群。

我们经常被问到,为什么少数动态可扩展的集群不足以满足需求。我们开始探索 Kubernetes 时也持有类似的心态。但我们意识到,将 Kubernetes 的架构和原则应用于生产场景时,我们的内部和外部客户很快就需要合理地分离关注点和所有权,这在大多数情况下导致了使用多个集群。因此,一个可扩展的托管式 Kubernetes 即服务解决方案通常也是采用的基础。特别是当一个大型组织在不同的提供商和不同的区域运行多个产品时,集群数量将很快上升到数百甚至数千个。

今天,我们想就过去一年我们在可扩展性和可定制性方面实施的内容,以及我们为下一个里程碑计划开展的工作提供最新信息。

简要回顾:Gardener 是什么?

Gardener 的主要原则是利用 Kubernetes 原语进行所有操作,这通常被称为 inception 或 kubeception。社区的反馈是,最初我们的架构图看起来“令人不知所措”,但在稍微深入研究了资料后,我们所做的一切都是“Kubernetes 的方式”。可以重用关于 API、控制循环等方面的所有学习成果。
核心思想是,使用所谓的 seed 集群来托管最终用户集群的控制平面 (按植物学命名为 shoots)。
Gardener 提供原生的 Kubernetes 集群即服务,独立于底层基础设施提供商,以同质化的方式运行,利用上游提供的 k8s.gcr.io/* 镜像作为开放分发 (更新:k8s.gcr.io 已弃用,转而使用 registry.k8s.io)。该项目完全基于 Kubernetes 扩展概念构建,因此增加了自定义 API 服务器、一个 controller-manager 和一个调度器,用于创建和管理 Kubernetes 集群的生命周期。它使用自定义资源扩展了 Kubernetes API,最突出的是 Gardener 集群规范 (Shoot 资源),可用于以声明式方式“订购”一个 Kubernetes 集群 (用于 day-1,但也协调 day-2 的所有管理活动)。

通过利用 Kubernetes 作为基础架构,我们能够设计出一个组合的水平和垂直 Pod 自动扩缩器 (HVPA),当配置了自定义启发式算法时,它可以自动向上/向下或向外/向内扩展所有控制平面组件。这使得快速向外扩展成为可能,甚至超出通常固定数量的 master 节点的能力。这一架构特性是与许多其他 Kubernetes 集群供应工具相比的主要区别之一。但在我们的生产环境中,Gardener 不仅通过对控制平面进行 bin-packing 有效地降低了总拥有成本,它还简化了“day-2 操作”(如集群更新或健壮性特性)的实施。再次强调,本质上是依靠所有成熟的 Kubernetes 功能和能力。

Gardener 新引入的扩展概念现在使提供商能够只维护其特定的扩展,无需在核心源代码树内开发。

可扩展性

由于过去几年的增长,Kubernetes 代码库包含了大量的特定于提供商的代码,这些代码现在正在从其核心源代码树中外部化。Gardener 项目也发生了同样的事情:随着时间的推移,积累了大量特定于云提供商、操作系统、网络插件等的特定内容。通常,这会导致在可维护性、可测试性或新版本发布方面工作量的显著增加。我们的社区成员 Packet 贡献了他们基础设施对 Gardener 的树内支持,并遭受了上述缺点。

因此,类似于 Kubernetes 社区决定将其 cloud-controller-manager 移出树外,或将卷插件移至 CSI 等,Gardener 社区提出了并实施了类似的扩展概念。Gardener 核心源代码树现在不包含任何特定于提供商的代码,允许供应商只专注于其基础设施的特定内容,并使核心贡献者再次变得更加敏捷。

通常,设置集群需要一系列相互依赖的步骤,从生成证书和准备基础设施开始,接着供应控制平面和工作节点,并最终部署系统组件。我们想在这里强调,所有这些步骤都是必需的 (参见 Kubernetes the Hard Way),并且所有 Kubernetes 集群创建工具都以某种方式实现了相同的步骤 (在某种程度上是自动化的)。

Gardener 可扩展性概念的核心思想是使这个流程更加通用,并为每个步骤提取出自定义资源,这些可以作为理想的扩展点。

Cluster reconciliation flow with extension points

图 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 证书服务)。

扩展点

除了利用自定义资源定义之外,我们还在 seed 集群中有效地使用了 mutating / validating webhook。扩展控制器本身运行在这些集群中,并响应它们负责的 CRD 和工作负载资源 (如 DeploymentStatefulSet 等)。类似于 Cluster API 的方法,这些 CRD 也可能包含特定于提供商的信息。

步骤 2 - 10 [参见图 1] 涉及基础设施特定的元数据,指的是基础设施特定的实现,例如对于 DNS 记录,可能有 aws-route53google-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 特定的 providerConfigShoot 资源中最终用户配置的一部分,Gardener 不会对其进行评估,而只是将其传递给 seed 集群中的扩展控制器。

在当前实现中,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 中的信息可以在后续步骤中使用,例如,用于配置 cloud-controller-manager 或为 machine-controller-manager 提供信息。

示例:集群控制平面的部署

Gardener 的主要特性之一是其管理的集群跨越不同基础设施具有同质性。因此,它仍然负责将与提供商无关的控制平面组件 (如 etcd, kube-apiserver) 部署到 seed 集群中。特定于提供商的控制平面组件 (如 cloud-controller-manager 或 CSI 控制器) 的部署由一个专门的 ControlPlane CRD 触发。然而,在本段中,我们希望专注于标准组件的定制。

让我们关注 kube-apiserver 和 kube-controller-manager 的 Deployment。我们的 Gardener AWS 扩展尚未 P使用 CSI,而是依赖于树内的 EBS 卷插件。因此,它需要启用 PersistentVolumeLabel admission 插件,并将云提供商配置提供给 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 扩展使用 MutatingWebhookConfiguration 注入上述标志并如下修改 Spec

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 以类似的方式处理。

seed 集群中的 Webhook 可用于修改与由 Gardener 或任何其他扩展部署的 shoot 集群控制平面相关的任何内容。对于 shoot 集群中的资源,存在类似的 webhook 概念,以防扩展控制器需要定制由 Gardener 部署的系统组件。

扩展控制器的注册

Gardener API 使用两个特殊的资源来注册和安装扩展。注册本身通过 ControllerRegistration 资源声明。最简单的选项是定义 Helm chart 以及一些用于渲染 chart 的值,然而,也支持通过自定义代码使用任何其他部署机制。

Gardener 确定在特定 seed 集群中是否需要某个扩展控制器,并创建一个 ControllerInstallation,用于触发部署。

迄今为止,每个注册的扩展控制器都被部署到每个 seed 集群,这通常是不必要的。未来,Gardener 将变得更具选择性,只部署特定 seed 集群上所需的扩展。

我们的动态注册方法允许在运行的系统中添加或移除扩展,无需重建或重启任何组件。

Gardener architecture with extension controllers

图 2 带有扩展控制器的 Gardener 架构。

现状

我们最近引入了新的 core.gardener.cloud API Group,该 Group 包含了完全向前和向后兼容的 Shoot 资源,并允许提供商使用 Gardener,无需修改其核心源代码树中的任何内容。

我们已经调整了所有控制器来使用这个新的 API Group,并弃用了旧的 API。最终,几个月后我们将移除它,因此建议最终用户尽快开始迁移到新的 API。

除此之外,我们已经启用了所有相关的扩展来贡献 shoot 集群的健康状态,并实现了相应的契约。基本思想是 CRD 可能包含 .status.conditions,Gardener 会拾取这些 conditions,并将其与标准健康检查合并到 Shoot 资源的 status 字段中。

此外,我们还想实现一些易于使用的库函数,以方便实现 CRD 的默认值设置和验证 webhook,以便验证最终用户控制的 providerConfig 字段。

最后,我们将把 gardener/gardener-extensions 仓库拆分到独立的仓库中,并仅保留其用于编写扩展控制器的通用库函数。

后续步骤

Kubernetes 已经将许多基础设施管理挑战外部化。Inception 设计通过将生命周期操作委托给一个独立管理平面 (seed 集群) 解决了其中大部分问题。但是,如果 garden 集群或 seed 集群发生故障怎么办?我们如何扩展到数万个需要并行协调的托管集群以上?我们正在进一步投入,以增强 Gardener 的可扩展性和灾难恢复特性。让我们更详细地简要突出其中三个特性

Gardenlet

从 Gardener 项目一开始,我们就开始实施 Operator 模式:我们有一个自定义 controller-manager,它对我们自己的自定义资源进行操作。现在,当你开始思考 Gardener 架构时,你会发现它与 Kubernetes 架构有一些有趣的相似之处:Shoot 集群可以与 Pod 进行比较,seed 集群可以被视为工作节点。受此观察启发,我们引入了 gardener-scheduler。它的主要任务是找到合适的 seed 集群来托管新“订购”集群的控制平面,类似于 kube-scheduler 为新创建的 Pod 找到合适的节点。通过为一个区域 (或提供商) 提供多个 seed 集群并分发工作负载,我们也减小了潜在故障的影响范围。

Similarities between Kubernetes and Gardener architecture

图 3 Kubernetes 与 Gardener 架构的相似之处。

然而,在 Kubernetes 和 Gardener 架构之间仍然存在一个显著的区别:Kubernetes 在每个节点上运行一个主要的“代理”,即 kubelet,它主要负责管理其特定节点上的 Pod 和容器。Gardener 使用其 controller-manager,它负责所有 seed 集群上的所有 shoot 集群,并且它从 garden 集群集中执行其协调循环。

虽然今天这种方式在数千个集群规模下运行良好,但我们的目标是遵循 Kubernetes 原则实现真正的可扩展性 (超越单个 controller-manager 的能力):我们现在正在努力将逻辑 (或 Gardener operator) 分发到 seed 集群中,并将引入一个相应的组件,恰当地命名为 gardenlet。它将成为 Gardener 在每个 seed 集群上的主要“代理”,并且将只负责位于其特定 seed 集群中的 shoot 集群。

gardener-controller-manager 仍将保留其对 Gardener API 其他资源的控制循环,然而,它将不再与 seed/shoot 集群通信。

反转控制流甚至将允许将 seed/shoot 集群放置在防火墙后,不再需要直接可访问性 (通过 VPN 隧道)。

Detailed architecture with Gardenlet

图 4 包含 Gardenlet 的详细架构。

seed 集群之间的控制平面迁移

当 seed 集群发生故障时,用户的静态工作负载将继续运行。然而,将无法再管理集群,因为运行在发生故障的 seed 集群中的 shoot 集群 API 服务器不再可达。

我们已经实现了将因某些种子灾难而发生故障的控制平面迁移到另一个种子的功能,目前正在努力全面自动化这一独特功能。事实上,这种方法不仅可行,而且我们已经在生产环境中多次执行了故障转移过程。

自动化的故障转移能力将使我们能够实现更全面的灾难恢复和可扩展性特性,例如种子集群的自动化供应和重新平衡,或所有不可预见情况的自动化迁移。再次提醒,想想它与 Kubernetes 在 pod 驱逐和节点排空方面的相似之处。

Gardener 环

Gardener 环是我们在供应和管理 Kubernetes 集群方面的一种新颖方法,它无需依赖外部供应工具来创建初始集群。通过以递归方式使用 Kubernetes,我们可以避免使用命令式工具集,从而大幅降低管理复杂性,同时通过自稳定循环系统创造新的特性。

Ring 方法在概念上不同于自托管和基于静态 Pod 的部署。其理念是创建一个由三个(或更多)Shoot 集群组成的环,每个集群托管其后继集群的控制平面。

一个集群发生故障不会影响 Ring 的稳定性与可用性,而且由于控制平面是外部化的,故障集群可以通过 Gardener 的自愈能力自动恢复。只要有至少 n/2+1 个可用集群组成法定人数(quorum),Ring 就会始终自行稳定。在不同的云提供商(或至少在不同的区域/数据中心)上运行这些集群,降低了法定人数丢失的可能性。

Self-stabilizing ring of Kubernetes clusters

图 5 Kubernetes 集群的自稳定环。

Gardener 的分布式实例共享相同数据的方式,是通过部署连接到同一个 etcd 集群的独立 kube-apiserver 实例。这些 kube-apiserver 组成了一个无节点的 Kubernetes 集群,可以用作 Gardener 及其相关应用的“数据容器”。

我们内部运行受 Ring 保护的测试环境(landscapes),这使我们省去了手动干预。随着自动化控制平面迁移的到位,我们可以轻松地引导启动 (bootstrap) Ring,这也将解决“初始集群问题”并提高整体健壮性。

入门!

如果你有兴趣编写扩展,可以参考以下资源

当然,也非常欢迎对我们项目的任何其他贡献!我们一直在寻找新的社区成员。

如果你想尝试 Gardener,请查看我们的快速安装指南。这个安装程序将设置一个完整的 Gardener 环境,几分钟内即可用于测试和评估。

欢迎贡献!

Gardener 项目以开源方式开发,并托管在 GitHub 上:https://github.com/gardener

如果你看到了 Gardener 项目的潜力,请通过 GitHub 加入我们。

我们每周五中欧时间上午 10-11 点举行一次公开社区会议,并在 Kubernetes 工作区中有一个公开的#gardener Slack 频道。此外,我们计划在 2020 年第一季度举办一次Gardener 黑客马拉松,期待在那里与您相见!