前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Kubernetes-in-Kubernetes和Kubefarm

Kubernetes-in-Kubernetes和Kubefarm

作者头像
CNCF
发布2022-01-10 14:12:45
7650
发布2022-01-10 14:12:45
举报
文章被收录于专栏:CNCF

作者:Andrei Kvapil(WEDOS)

当你拥有两个数据中心、数千台物理服务器、虚拟机和成千上万个站点的托管时,Kubernetes 实际上可以简化所有这些东西的管理。实践表明,通过使用 Kubernetes,你不仅可以声明式地描述和管理应用程序,还可以描述和管理基础设施本身。我为捷克最大的主机提供商 WEDOS Internet a.s 工作,今天我将向你们展示我的两个项目——Kubernetes-in-Kubernetes[1]Kubefarm[2]

在他们的帮助下,只需几个命令,你就可以在其他 Kubernetes 中部署一个完全工作的 Kubernetes 集群。如何做以及为什么这样做?

让我向你们介绍一下我们的基础设施是如何运作的。我们所有的物理服务器可以分为两组:控制平面和计算节点。控制平面节点通常手动搭建,安装了稳定的操作系统,用于运行包括 Kubernetes 控制平面在内的所有集群服务。这些节点的主要任务是保证集群本身的平稳运行。计算节点默认不安装任何操作系统,而是直接从控制平面节点通过网络启动操作系统镜像。他们的工作是进行工作量。

一旦节点下载了它们的镜像,它们就可以继续工作,而无需保持与 PXE 服务器的连接。也就是说,PXE 服务器只保留 rootfs 镜像,不包含任何其他复杂的逻辑。在我们的节点启动后,我们可以安全地重新启动 PXE 服务器,不会发生任何重要的事情。

在引导之后,我们的节点要做的第一件事就是加入到现有的 Kubernetes 集群中,也就是说,执行 kubeadm join 命令,以便 kube-scheduler 可以在它们上调度一些 pod,然后启动各种工作负载。从一开始我们使用的方案是:节点加入到控制平面节点的同一个集群。

这个方案稳定运行了两年多。但是后来我们决定添加容器化的 Kubernetes。现在,我们可以很容易地在我们的控制平面节点上生成新的 kubernetes 集群,这些节点现在是特殊管理集群的成员。现在,计算节点可以直接连接到它们自己的集群——这取决于配置。

Kubefarm

这个项目的目标是让任何人使用 Helm,只需几个命令就可以部署这样的基础设施,并最终得到大致相同的结果。

在这个时候,我们摆脱了单集群的想法。因为它对于管理同一个集群中的几个开发团队的工作不是很方便。事实上,Kubernetes 从来没有被设计成一个多租户解决方案,目前它还没有提供足够的项目隔离手段。因此,为每个团队运行单独的集群是一个好主意。但是,集群不应该太多,这样便于管理。它也不会小到开发团队之间没有足够的独立性。

在此更改之后,我们的集群的可伸缩性变得明显更好。每个节点的集群数量越多,故障域就越小,它们工作起来就越稳定。作为额外好处,我们得到了一个完全声明式描述的基础设施。因此,现在你可以像在 Kubernetes 中部署任何其他应用程序一样部署新的 Kubernetes 集群。

它使用 Kubernetes-in-Kubernetes 作为基础,LTSP[3]作为节点引导的 PXE-server,并使用dnsmasq-controller[4]自动化 DHCP 服务器配置:

它是如何工作的

现在让我们看看它是如何工作的。一般来说,如果你从应用程序的角度来看 Kubernetes,你会注意到它遵循了The Twelve-Factor App[5]的所有原则,而且实际上写得非常好。因此,这意味着在不同的 Kubernetes 中把 Kubernetes 作为应用程序运行不应该是一个大问题。

在 Kubernetes 中运行 Kubernetes

现在让我们来看看 Kubernetes-in-Kubernetes 项目,它提供了一个现成的在 Kubernetes 中运行 Kubernetes 的 chart。

以下是你可以在值文件中传递给 Helm 的参数:

  • kubernetes/values.yaml[6]

除了持久化(集群的存储参数),Kubernetes 的控制平面组件也在这里描述:即:etcd cluster、apiserver、controller-manager 和 scheduler。这些都是非常标准的 Kubernetes 组件。有一种轻松的说法:“Kubernetes 只是五个二进制文件!”。这就是配置这些二进制文件的位置。

如果你曾经尝试过使用 kubeadm 引导集群,那么这个配置将提醒你它的配置。但是除了 Kubernetes 实体之外,你还有一个管理容器。实际上,它是一个容器,其中包含两个二进制文件:kubectl 和 kubeadm。它们用于为上述组件生成 kubeconfig,并执行集群的初始配置。此外,在紧急情况下,你总是可以执行到它以检查和管理你的集群。

发布后[7],你可以看到一个 pod 列表:admin-container、apiserver 在两个副本,controller-manager、etcd-cluster、scheduler 和初始化集群的初始作业。最后你有一个命令行,它允许你 shell 进入管理容器,你可以用它来看看里面发生了什么:

另外,让我们看看证书。如果你曾经安装过 Kubernetes,那么你就会知道它有一个可怕的目录/etc/kubernetes/pki,里面有一堆证书。对于 Kubernetes-in-Kubernetes,你可以使用证书管理器对它们进行完全自动化的管理。因此,在安装期间将所有证书参数传递给 Helm 就足够了,将自动为你的集群生成所有证书。

看其中一张的 apiserver 证书,你可以看到它有一个 DNS 名称和 IP 地址列表。如果你想让这个集群在外部可访问,那么只需在 values 文件中描述额外的 DNS 名称,并更新版本。这将更新证书资源,cert-manager 将重新生成证书。你不用再想这些了。如果 kubeadm 证书每年至少需要更新一次,cert-manager 将会注意并自动更新它们。

现在,让我们登录到管理容器并查看集群和节点。当然,还没有节点,因为此时你只部署了 Kubernetes 的空白控制平面。但是在 kube-system 命名空间中,你可以看到一些 coredns pod 正在等待调度和配置映射。也就是说,你可以得出集群正在工作的结论:

下面是部署的集群的示意图。你可以看到 Kubernetes 所有组件的服务:apiserver、controller-manager、etc-cluster 和 scheduler。右边是流量转发到的 pod。

顺便说一下,这个图表是用 ArgoCD(我们用来管理集群的 GitOps 工具)绘制的,很酷的图表是它的功能之一。

编排物理服务器

好了,现在你可以看到 Kubernetes 控制平面是如何部署的,但是工作节点呢?我们如何添加它们呢?正如我已经说过的,我们所有的服务器都是裸金属的。我们不使用虚拟化来运行 Kubernetes,但是我们自己编排所有的物理服务器。

此外,我们非常积极地使用 Linux 网络引导特性。此外,这就是引导,而不是安装的某种自动化。当节点启动时,它们只需为它们运行一个现成的系统镜像。也就是说,要更新任何节点,我们只需要重新启动它——它将下载一个新的镜像。它非常容易,简单和方便。

为此,创建了 Kubefarm 项目,它允许你自动化此操作。最常用的示例可以在examples[8]目录中找到。其中最标准的是generic[9]。让我们看看 values.yaml:

  • generic/values.yaml[10]

在这里,你可以指定传递到上游 Kubernetes-in-Kubernetes chart 的参数。为了让你的控制平面能够被外部访问,在这里指定 IP 地址就足够了,但是如果你希望,你可以在这里指定一些 DNS 名称。

在 PXE 服务器配置中可以指定时区。你还可以添加一个 SSH 密钥,用于在不需要密码的情况下登录(但是你也可以指定一个密码),以及应该在引导系统期间应用的内核模块和参数。

接下来是 nodePools 配置,即节点本身。如果你曾经为 gke 使用过 terraform 模块,那么这个逻辑会听熟悉。这里你用一组参数静态地描述所有节点:

  • Name(主机名)
  • MAC-addresses——我们有两个网卡的节点,每个节点都可以从这里指定的任何 MAC 地址启动。
  • IP-address,DHCP 服务器应该发给这个节点。

在本例中,你有两个池:第一个池有 5 个节点,第二个池只有一个节点,第二个池也分配了两个标记。标记是描述特定节点配置的方式。例如,你可以为一些池添加特定的 DHCP 选项,PXE 服务器的启动选项(例如,这里是调试选项启用),以及 kubernetesLabels 和 kubernetesTaints 选项的设置。这是什么意思?

例如,在这个配置中,你有一个带有一个节点的第二个 nodePool。池中分配了 debug 和 foo 标记。现在在 kubernetesLabels 中看到 foo 标签的选项。这意味着 m1c43 节点将在引导时使用这两个标签和分配的污染。一切似乎都很简单。现在我们来实践一下[11]

演示

转到 examples 并将以前部署的 chart 更新到 Kubefarm。用 generic 的参数,看看这些 pod。你可以看到添加了一个 PXE 服务器和一个作业。这个 job 本质上是转到已部署的 Kubernetes 集群,并创建一个新的令牌。现在它将每 12 小时重复运行一次以生成一个新的令牌,这样节点就可以连接到你的集群。

在图形表示中,它看起来差不多,但现在 apiserver 开始暴露在外面。

在图中,IP 以绿色突出显示,可以通过它访问 PXE 服务器。目前,Kubernetes 默认情况下不允许为 TCP 和 UDP 协议创建单个 LoadBalancer 服务,所以你必须使用相同的 IP 地址创建两个不同的服务。一个用于 TFTP,第二个用于 HTTP,通过它下载系统镜像。

但是这个简单的示例并不总是足够的,有时你可能需要在引导时修改逻辑。例如,这里有一个目录advanced_network[12],其中有一个带有简单 shell 脚本的values 文件[13]。让我们称它为 network.sh:

  • network.sh[14]

这个脚本所做的只是在启动时获取环境变量,并基于它们生成一个网络配置。它创建一个目录并将 netplan 配置放在其中。例如,此处创建了绑定接口。基本上,这个脚本可以包含你需要的所有内容。它可以保存网络配置或生成系统服务,添加一些钩子或描述任何其他逻辑。任何可以用 bash 或 shell 语言描述的内容都可以在这里工作,并且将在引导时执行。

让我们看看如何部署[15]它。让我们传递 generic values 文件作为第一个参数,传递附加 values 文件作为第二个参数。这是一个标准的 Helm 功能。通过这种方式,你也可以传递秘密,但在这种情况下,配置只是由第二个文件扩展:

让我们看看 netboot 服务器的 configmap foo-kubernetes-ltsp,并确保 network.sh 脚本确实存在。这些命令用于在启动时配置网络:

这里[16]你可以看到它的原理。机箱接口(我们使用 HPE Moonshots 1500)有节点,你可以输入 show node list 命令来获得所有节点的列表。现在可以看到引导过程。

你也可以通过 show node macaddr all 命令来获取他们的 MAC 地址。我们有一个聪明的操作器,从机箱自动收集 mac 地址,并把它们传递给 DHCP 服务器。实际上,它只是为运行在同一个管理 Kubernetes 集群中的 dnsmasq-controller 创建自定义配置资源。此外,通过这个接口,你可以控制节点本身,例如打开和关闭它们。

如果你没有这样的机会通过 iLO 进入机箱并为你的节点收集 MAC 地址列表,你可以考虑使用catchall cluster[17]模式。简单地说,它只是一个动态 DHCP 池的集群。因此,配置中没有描述到其他集群的所有节点将自动加入到该集群。

例如,你可以看到一个具有某些节点的特殊集群。它们使用基于其 MAC 地址自动生成的名称加入集群。从这一点开始,你可以连接到他们,看看会发生什么。在这里,你可以以某种方式准备它们,例如,设置文件系统,然后将它们重新加入到另一个集群。

现在,让我们尝试连接到节点终端,看看它是如何引导的。BIOS 设置好后,网卡会从特定的 MAC 地址向 DHCP 服务器发送一个请求,这个请求会将网卡重定向到特定的 PXE 服务器。然后使用标准的 HTTP 协议从服务器下载内核和 initrd 镜像:

加载内核后,节点下载 rootfs 镜像并将控制权传输给 systemd。然后启动照常进行,然后节点加入 Kubernetes:

如果你看一下 fstab,你只能在那里看到两个条目:/var/lib/docker 和/var/lib/kubelet,它们被装载为 tmpfs(实际上,来自 RAM)。与此同时,root 分区被作为 overlayfs 挂载,因此你在系统上所做的所有更改将在下次重新启动时丢失。

查看节点上的块设备,你可以看到一些 nvme 磁盘,但它还没有被挂载到任何地方。还有一个 loop 设备——这是确切的 rootfs 镜像下载从服务器。目前,它位于 RAM 中,占用 653MB,并使用 loop 选项进行挂载。

如果你查看/etc/ltsp,你会发现 network.sh 文件是在引导时执行的。从容器中,你可以看到正在运行 kube-proxy 和 pause 容器。

细节

网络引导镜像

但是主镜像是从哪里来的呢?这里有一个小技巧。节点的镜像通过 Dockerfile 和服务器一起构建。Docker 多阶段构建[18]特性允许你在镜像构建阶段轻松地添加任何包和内核模块。它看起来是这样的:

  • Dockerfile[19]

这是怎么回事?首先,我们使用一个普通的 Ubuntu 20.04 并安装我们需要的所有软件包。首先,我们安装内核、lvm、systemd、ssh。通常,你希望在最终节点上看到的所有内容都应该在这里描述。在这里,我们还安装了 docker,以及 kubelet 和 kubeadm,它们用于将节点加入集群。

然后我们做一个额外的设定。在最后一个阶段,我们简单地安装 tftp 和 nginx(为客户端提供镜像)、grub(引导加载程序)。然后将前一阶段的根复制到最终的镜像中,并从它生成压缩镜像。我们得到了一个 docker 镜像,其中包含节点的服务器镜像和引导镜像。同时,它可以通过更改 Dockerfile 轻松更新。

webhook 和 API 聚合层

我想特别关注 webhook 和聚合层的问题。一般来说,webhooks 是 Kubernetes 的一个特性,它允许你对任何资源的创建或修改做出响应。因此,你可以添加一个处理程序,以便在应用资源时,Kubernetes 必须向某个 pod 发送请求,并检查该资源的配置是否正确,或者对其进行额外的更改。

但问题是,为了让 webhook 正常工作,apiserver 必须能够直接访问它所运行的集群。如果它是在一个单独的集群中启动的,就像我们的例子,或者甚至是单独的集群,那么 Konnectivity 服务可以在这里帮助我们。Konnectivity 是可选的、但官方支持的 Kubernetes 组件之一。

让我们以四个节点的集群为例,每个节点都运行一个 kubelet,我们还有其他 Kubernetes 组件在外部运行:kube-apiserver、kube-scheduler 和 kube-controller-manager。默认情况下,所有这些组件都直接与 apiserver 交互——这是 Kubernetes 逻辑中最广为人知的部分。但事实上,也有反向的联系。例如,当你想查看日志或者执行 kubectl exec 命令时,API 服务器会独立地与 kubelet 建立连接:

但问题是,如果我们有一个 webhook,那么它通常作为一个标准的 pod 运行,在我们的集群中有一个服务。当 apiserver 尝试访问它时,它会失败,因为它会尝试访问一个名为 webhook.namespace.svc 的集群内服务,该服务实际上在集群之外运行:

在这里,Konnectivity 为我们提供了帮助。Konnectivity 是一个专门为 Kubernetes 开发的代理服务器。它可以作为服务器部署在 apiserver 旁边。而 Konnectivity-agent 直接部署在你想要访问的集群中的几个副本中。代理建立与服务器的连接,并建立一个稳定的通道,使 apiserver 能够访问集群中的所有 webhook 和 kubelet。因此,现在所有与集群的通信都将通过 Konnectivity-server 进行:

下一步

当然,我们不会止步于此。对这个项目感兴趣的人经常给我写信。如果有足够多的人对此感兴趣,我希望将 Kubernetes-in-Kubernetes 项目移到Kubernetes SIGs[20]下,以官方 Kubernetes Helm chart 的形式表示。也许,通过使这个项目独立,我们将聚集一个更大的社区。

我还在考虑将它与 Machine Controller Manager 集成,这将允许创建工作节点,不仅是物理服务器,还可以使用 kubevirt 创建虚拟机,并在相同的 Kubernetes 集群中运行它们。顺便说一下,它还允许在云中生成虚拟机,并在本地部署一个控制平面。

我还在考虑与 Cluster-API 集成的选项,这样你就可以直接通过 Kubernetes 环境创建物理 Kubefarm 集群。但目前我对这个想法还不是很确定。如果你对这个问题有什么想法,我很乐意听。

参考资料

[1]

Kubernetes-in-Kubernetes: https://github.com/kvaps/kubernetes-in-kubernetes

[2]

Kubefarm: https://github.com/kvaps/kubefarm

[3]

LTSP: https://github.com/ltsp/ltsp/

[4]

dnsmasq-controller: https://github.com/kvaps/dnsmasq-controller

[5]

The Twelve-Factor App: https://12factor.net/

[6]

kubernetes/values.yaml: https://github.com/kvaps/kubernetes-in-kubernetes/tree/v0.13.1/deploy/helm/kubernetes

[7]

发布后: https://asciinema.org/a/407280

[8]

examples: https://github.com/kvaps/kubefarm/tree/v0.13.1/examples

[9]

generic: https://github.com/kvaps/kubefarm/tree/v0.13.1/examples/generic

[10]

generic/values.yaml: https://github.com/kvaps/kubefarm/blob/v0.13.1/examples/generic/values.yaml

[11]

实践一下: https://asciinema.org/a/407282

[12]

advanced_network: https://github.com/kvaps/kubefarm/tree/v0.13.1/examples/advanced_network

[13]

values 文件: https://github.com/kvaps/kubefarm/tree/v0.13.1/examples/advanced_network

[14]

network.sh: https://github.com/kvaps/kubefarm/blob/v0.13.1/examples/advanced_network/values.yaml#L14-L78

[15]

部署: https://asciinema.org/a/407284

[16]

这里: https://asciinema.org/a/407286

[17]

catchall cluster: https://asciinema.org/a/407287

[18]

Docker 多阶段构建: https://docs.docker.com/develop/develop-images/multistage-build/

[19]

Dockerfile: https://github.com/kvaps/kubefarm/blob/v0.13.1/build/ltsp/Dockerfile

[20]

Kubernetes SIGs: https://github.com/kubernetes-sigs

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-12-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 CNCF 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Kubefarm
  • 它是如何工作的
    • 在 Kubernetes 中运行 Kubernetes
      • 编排物理服务器
        • 演示
        • 细节
          • 网络引导镜像
            • webhook 和 API 聚合层
            • 下一步
              • 参考资料
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档