Kubernetes中的多租户会带来各种复杂的挑战,例如安全性、公平性和资源分配。本博客讨论了与多租户相关的挑战,以及为名为Labs4grabs.io的基于Kubernetes的学习平台所做的技术选择。我将探讨两个关键技术vCluster和Kubevirt的需求、优势和劣势。这些技术在开发Labs4grabs.io的后端时进行了试验。尽管vCluster非常出色,但我还是决定完全放弃它。
译自 Exploring Multi-Tenancy Solutions for my Kubernetes Learning Platform 。
我的平台是一个Kubernetes学习平台,旨在通过实验环境模拟现实中的问题。但是,除了每个实验的简要描述和几条提示之外,人们需要自行研究和解决问题,指导很少。
实验内容基于我在Berops公司担任Kubernetes工程师时遇到的真实问题,以及我以前在该领域的经验。
挑战是直接从Slack开始的。
Kubernetes的多租户类似于管理公寓大楼,不同租户共享空间。每个租户都需要自己的空间,如浴室、厨房和卧室,以及水、煤气、电等公共设施。但最重要的是,公寓的租户不能进入其他人的区域或使用其他人的设施。此外,如果其他租户进入他们的私人区域,每个租户都会深感不快。这意味着其他租户的生活质量降低。
Kubernetes中的租户情况也是如此,他们不能访问其他租户的资源、网络带宽等。这会降低想在我的平台上提高Kubernetes技能的人的体验质量。此外,与公寓租户不同的另一个最重要的组成部分是主机系统。
如果租户能逃出自己的环境进入主机系统,影响其他租户,使用全部计算能力进行挖矿或其他活动,那将是最大的灾难。在为Labs4grabs.io租户环境选择正确的多租户技术时,这是我最担心的。
主机平台的注意事项:我选择了Kubernetes作为托管租户环境的平台。Kubernetes具有调度、网络、存储管理、安全等功能,最重要的是背后有强大的社区支持可以解答我可能遇到的任何问题。*
租户部署在工作节点上的宿主Kubernetes集群。
在选择和测试正确的解决方案时,有几个因素需要考虑:
- 安全性: 提供计算能力和root访问权限时,必须考虑安全影响。
- 学习内容: 使用某些租户解决方案时,可用的学习内容可能受到限制。
- 资源消耗: 某些解决方案的资源消耗更大,这会降低主机集群上的租户密度。
- 置备时间: 与其他解决方案相比,某些解决方案启动实验室需要更长时间。
最简单的多租户形式是为每个学生配置一个新的 Kubernetes 用户,提供证书和密钥以访问主机集群命名空间。这种解决方案简单但风险较大,需要学生通过主机集群的 API 服务器访问实验环境。但是,这种方法可能导致问题,例如学生创建过多 NodePort 服务,降低其他学生的体验,或者向 API 服务器发送数百万请求,影响合法用户的性能。
虽然使用 Kyverno、Gatekeeper 等策略引擎有助于防止用户违反某些规则,但需要通过大量试错为每个实验正确配置策略。此外,这些策略可能会限制学生创建自己的命名空间、访问根文件系统或部署特权容器等,这些都是 Kubernetes 学习的重要方面。
主机Kubernetes集群中,学生无法直接访问主机集群的控制平面,但可以自由访问他们声明的环境。
vCluster 是在主机 Kubernetes 集群之上运行的 Kubernetes 集群。vCluster 不具备自己的节点池或网络,它会在主机集群内调度工作负载,同时维护自己的控制平面。
vCluster 是我的多租户问题的绝佳解决方案。它提供了速度、更好的安全性和易用性。最出色的是 syncer 功能,它可以复制租户环境中的学生创建的资源到主机集群上。您可以指定要复制的资源类型和数量。这个功能改变了我可以为学生提供的内容。
比如在第一个中级实验“调试 Python Flask 应用程序”中使用了 syncer,允许学生创建 ingress 资源并使他们的应用程序在主机集群上可公开访问。
vCluster在租户上创建了一个指向主机集群上调度的pod的假服务和pod。
在这个演示中,我们将在 student
名字空间中创建一个基本的 vCluster 租户环境。然后我们将在这个租户环境中创建一个 NGINX pod,并通过 NodePort 服务将其暴露出来。这个服务将被同步到主集群中,允许从外界通过主机的公网 IP 访问 NGINX pod。
- 使用特权 pod 安全准入创建
student
名字空间:
apiVersion: v1
kind: Namespace
metadata:
name: student
labels:
pod-security.kubernetes.io/enforce: privileged
- 创建 vCluster 租户环境:
vcluster create tenant -n student --connect=false
- 查看 vCluster 租户环境中运行的所有 pod:
$ vcluster connect tenant --namespace student -- kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-5c96599dbd-fsmwj 1/1 Running 0 116s
- 在租户环境内创建一个 NGINX pod,然后通过 NodePort 服务暴露,并通过主机的公网 IP 访问:
$ vcluster connect tenant --namespace student -- kubectl run pod nginx --image nginx
service/nginx exposed
$ vcluster connect tenant --namespace student -- kubectl expose pod nginx --type=NodePort --port 80
service/nginx exposed
$ kubectl get service -n student
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
....
nginx-x-default-x-tenant NodePort 10.43.229.163 <none> 80:31871/TCP 2m36s
$ curl $HOST_PUBLIC_IP:31871
<!DOCTYPE html>
GENERIC NGINX OUTPUT
</html>
💡 您可以不通过
vcluster
命令连接到 vCluster,而是通过公共 kubeconfig,这是我的实验要求,因为不能要求学生安装 vCluster 命令行并期望获得“真正的 Kubernetes 体验”。
- Syncer syncer syncer!其他解决方案无法通过与主集群同步来提供公网访问,我不得不自己编写解决方案。
sync:
services:
enabled: true
ingresses:
enabled: true
persistentvolumeclaims:
enabled: true
- vCluster 相比虚拟机更轻量,所以我可以在硬件上获得更好的实验密度。
- 启动快,因为 vCluster 只是几个容器,只需要几秒钟的时间即可完成设置。
- 我不必使用物理机来托管我的实验环境,我可以使用虚拟机来托管 vCluster 租户,并利用虚拟机的所有可扩展性优势。
- 它不提供核心组件的可见性。如果连接到 vCluster,您将看不到 API 服务器、调度程序或控制器管理器。这限制了核心 Kubernetes 组件的学习内容。
- vCluster 不允许拥有比主集群更多的节点。这限制了高级调度的学习内容。
- 我设想该平台可以包括模拟诸如操作系统组件损坏、kubelet 损坏、网络桥接配置错误、DNS 损坏或控制平面损坏等场景的内容。然而,所有这些场景都需要 SSH 访问租户环境,而 vCluster 和容器通常不支持这种访问。
总而言之,vCluster 的限制会严重限制我想为学生提供的学习平台中的某些内容和场景。虽然 syncer 确实启用了其他解决方案无法实现的某些内容访问,但它也会阻止比它允许的更多的内容,这与我对该平台的目标不符。此外,我仍可以探索开发自定义 syncer 以在更小规模上复制 vCluster 同步器的功能的可能性。然而,我放弃了 vCluster,决定采用虚拟化。
这是关于 Firecracker 和 Kata 容器这两项技术的概述,它们在 Kubernetes 中启用了 Firecracker 运行时。我调查并实验了这些技术,但决定不使用 Kata 容器,因为它需要为 Firecracker 运行时进行额外的配置,特别是与设备映射器相关的配置,这让我感到不太舒服。配置 Firecracker 容器的 SSH 连接也需要额外的步骤,这可能会导致一个非常大的容器,可能无法实现我想要的结果:一个基本的 Kubernetes 集群,具有完整的操作系统,我可以随意破坏。
考虑到 vCluster 在可能缺少的学习内容选项方面的限制,研究指向了虚拟化。这种方法在安全性和与宿主系统的完全隔离方面提供了保证,允许使用完整的操作系统和无限的学习内容。
幸运的是,Kubernetes 中有两种可用的虚拟化技术:Kubevirt 和 Virtlet。
- Virtlet 似乎已经被放弃了,在调研时,最后一次提交是 4 年前。
- 而 Kubevirt 具有良好的文档和定期更新,在调研时,最后一次提交就在几个小时前。所以,选择很明显!
在查看演示之前,您需要根据此指南安装 Kubevirt 和 QEMU 虚拟化程序。
与 vCluster 相比,实际实验环境的安装更加复杂,因为需要考虑用户数据脚本和存储等因素。但是我将省略这些细节,重点关注最重要的方面。在这个演示中,我将使用通用的容器磁盘镜像。
下面的 YAML 是每个实验环境中的三个虚拟机之一的定义,有一个控制平面节点,两个工作节点,用户数据略有不同。
- 包含向 ubuntu 用户添加自定义 SSH 密钥的用户数据的 Kubevirt 虚拟机定义:
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
labels:
kubevirt.io/vm: myvm
name: controlplane
namespace: student
spec:
running: true
template:
metadata:
labels:
kubevirt.io/vm: myvm
spec:
# 详细配置
volumes:
- name: datavolumedisk1
containerDisk:
image: "quay.io/containerdisks/ubuntu:22.04"
- name: cloudinitdisk
cloudInitNoCloud:
userData: |
#!/bin/bash
echo "ssh-rsa public key" >> /home/ubuntu/.ssh/authorized_keys
- 创建提供对虚拟机的 SSH 访问的服务。
apiVersion: v1
kind: Service
metadata:
name: ssh
namespace: student
spec:
type: NodePort
ports:
- port: 27017
targetPort: 22
selector:
kubevirt.io/vm: myvm
- 使用自定义 SSH 密钥直接 SSH 访问:
$ ssh -i ~/.ssh/student ubuntu@$HOST_PUBLIC_IP -p <NODE_PORT>
...
ubuntu@controlplane:~$ ls /
# 列出根目录
KubeVirt 虚拟机是一种允许在 Kubernetes 生态系统中运行虚拟化实例的工具。从本质上讲,KubeVirt 虚拟机是一个 Pod,与 QEMU 虚拟机实例紧密耦合。
Kubevirt 有几个组件,但我将重点介绍与我的用例相关的两个重要组件:VirtualMachine 和 VirtualMachineInstance。这两个组件都作为自定义资源定义(CRD)部署到主机 Kubernetes 集群中。
-
VirtualMachineInstance
:由VirtualMachine
组件创建,直接负责 QEMU 虚拟机实例。它是 Kubevirt 的临时部分,当虚拟机被删除时也会被删除。通常通过VirtualMachine
CRD 或VirtualMachineInstanceReplicaSet
CRD 创建,一般不会单独创建。 -
VirtualMachine
:通过提供停止、启动以及存储虚拟机数据和状态的功能,扩展了 VirtualMachineInstance 的功能。
当新的 VirtualMachine
定义被添加到 Kubernetes API 时,Kubevirt 执行以下步骤:
-
virt-handler
DaemonSet Pod 生成一个virt-launcher
Pod。 -
virt-launcher
创建一个VirtualMachineInstance
对象。 -
virt-launcher
使用来自VirtualMachine
定义的转换定义查询 libvirtd API。 - libvirtd 启动 QEMU 虚拟机。
-
VirtualMachine
组件的任何更新都反映在 QEMU 虚拟机实例上
更多详细信息请访问 Kubevirt 官网。
您可以在 PVC 或容器镜像磁盘上运行 Kubevirt 实例,这些镜像是作为容器安装的整个操作系统的快照。
最初,我尝试使用 PVC,但操作很复杂。我必须创建一个包含所有 Kubernetes 组件的通用 “金” PVC,跨命名空间克隆它需要约 3 分钟。其他平台的克隆可以实现瞬时完成!
因此我尝试使用容器磁盘镜像。一旦这些镜像被拉取到我的 Kubernetes集群上,初始化新环境的速度会更快,大概需要 90 秒左右。
尽管有所改进,时间仍然较长。为进一步优化,我创建了一组预置就绪的租户环境缓存。这将初始化环境的时间缩短到不到30秒,达到可接受的程度。但我仍计划在未来进一步加快速度。
缓存由运行在主机集群的预置就绪环境数据库组成。学生启动实验时,相应的就绪环境会从数据库中删除并分配给学生。缓存使用定时任务定期进行补充。
为优化实验节点的资源管理,我将每个节点的 CPU 上限从100m 提高到1000m。这一调优带来更快的启动和响应。
内存方面,控制平面分配 1.5G,每个节点分配 1G,每个 Kubevirt 虚拟机实例会额外消耗 100MB 内部容器。每个实验环境消耗约 3.5-4G 的内存和 3 个 CPU 核。在 64G 机器上,可以同时运行约 12 个并行实验环境,同时考虑其他组件和虚拟机上没有挖矿程序。
网络安全主要通过限制性的网络策略来管理。这些策略可以有效防止访问其他学生环境和其他 Kubernetes 组件。仅允许三个 Kubernetes 组件间相互通信,学生可以通过 SSH 或 kubeconfig 直接与控制平面通信。然后,再从控制平面节点跳转至 node1 或 node2。
学生视角的网络拓扑
- 在多租户场景下,虚拟化是安全方面最佳的解决方案。
- 内容就是一切!通过操作系统级访问和使用 Kubeadm 安装 Kubernetes,我可以做任何事。这是平台最关键的方面。
- 与 Kubevirt 一起运行完整的操作系统和 Kubernetes 组件,租户密度较低。
- 作为完整的操作系统,启动时间较慢。
- 没有同步工具,我得自己编写!
- 我不得不使用裸机服务器托管实验环境,因为许多公有云不允许嵌套虚拟化,即使允许也很昂贵!
操作系统为 Ubuntu 22.04。而 Kubernetes 版本,我选择在租户环境上使用 Kubeadm 安装 Kubernetes。最初,我计划使用 k3s 或其变体,但我意识到,如果故意造成 k3s 故障,k3s 二进制文件就直接无法启动。因此,调试 k3s 可能需要找到正确的命令,这与我想要创建的内容的重点不符。
我的意图是通过故意损坏各种层级,从操作系统到 Kubernetes 的各个组件,来教授 Kubernetes 的工作原理。Kubeadm 版本是我能想到的唯一一个可以实现我的愿景的选择。
总的来说,我的决定是基于我可以为学生提供的内容,而不考虑所使用技术的缺点。尽管 Kubevirt 在资源消耗等方面可能不够完美,但我可以通过每月分配略高一些的预算来缓解。我可以在 Hetzner 拍卖中轻松租用大型裸机服务器,每个月不到 50 欧,提供足够的内存和计算能力。我最终认为,Kubevirt 可能有一些劣势,但它可以提供更加灵活的学习内容,同时通过虚拟化提供更高的安全性。
我最大的挑战一直是,现在也是,平时对各种技术有所了解,但在有效集成应用方面艰难前行。我尝试了解熟悉的技术,遇到障碍,然后转向其他技术,浪费了大量时间。虽然目前一切都运行良好,但我相信仍有很大的改进空间。
例如,在放弃 vCluster 后, 我尝试使用 Packer 为 Kubevirt 构建实验环境的金镜像。然而,我后来也放弃了 Packer,因为我已经使用了 Ansible 初始化,不需要再添加新的工具。
当前,我正在根据学生反馈和待办事项的 Backlog,持续改进基础架构。然而,进度缓慢,因为我同时需处理内容、用户体验、Slack机器人、营销、安全等各种不同任务,时常在它们之间切换。我的任务队列中积压了近 70 项任务,这已经成为一个大型的业余项目,我还要兼顾全职工作。
我现在意识到,Slack 可能不是构建社区的理想工具。它更适合团队协作,并且免费账户在 API 调用上有限制。此外,Slack 的价格是基于用户人数,考虑到我 Slack 工作区中不断增长的学生群体,如果我想访问只在付费账户可用的 API,成本会很高。
- 当实验环境创建时会创建 NodePort,这会在所有待分配的租户主机集群上打开多个 NodePort,这引发了安全风险,因为攻击者可能利用租户环境,这会对真正想学习的用户产生负面影响。
- 可以为每个租户设置网络带宽限制,作为额外的防护措施。这里介绍了可用于此目的的机制。
- 根据我的使用案例创建自定义 syncer。这可以是一个在每个租户命名空间内部署的应用,用于监控租户环境中的新服务或入口对象,并复制到主机集群上。
- 例如,学生创建 NodePort 服务,通过我的 syncer 复制到主机集群。这可能无法完全按我设想的方式工作,但我会试一试。
- 学生创建 NodePort 服务,通过我的 syncer 复制到主机集群。
通过我的 syncer 同步学生创建的 NodePort 服务到宿主集群
- 许多实验环境并不需要三个节点,大多数只需要一个节点。因此,拥有动态环境会更好。一种方法是维护一个待加入控制平面节点的缓存池,根据请求进行分配。我之前试过这种方法,但由于平台还不够成熟而失败。当资源变得紧缺时我会重新评估这一想法。
目前,缓存的实验环境只支持一个 Kubernetes 版本,不能用于其他版本。这意味着提供所有学习内容需要20到30秒,而沙箱是 1.5 分钟。我将会缓存更多的版本,加速沙箱的初始化速度。