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集群。
在选择和测试正确的解决方案时,有几个因素需要考虑:
最简单的多租户形式是为每个学生配置一个新的 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。
student
名字空间:apiVersion: v1
kind: Namespace
metadata:
name: student
labels:
pod-security.kubernetes.io/enforce: privileged
vcluster create tenant -n student --connect=false
$ 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
$ 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 体验”。
sync:
services:
enabled: true
ingresses:
enabled: true
persistentvolumeclaims:
enabled: true
总而言之,vCluster 的限制会严重限制我想为学生提供的学习平台中的某些内容和场景。虽然 syncer 确实启用了其他解决方案无法实现的某些内容访问,但它也会阻止比它允许的更多的内容,这与我对该平台的目标不符。此外,我仍可以探索开发自定义 syncer 以在更小规模上复制 vCluster 同步器的功能的可能性。然而,我放弃了 vCluster,决定采用虚拟化。
这是关于 Firecracker 和 Kata 容器这两项技术的概述,它们在 Kubernetes 中启用了 Firecracker 运行时。我调查并实验了这些技术,但决定不使用 Kata 容器,因为它需要为 Firecracker 运行时进行额外的配置,特别是与设备映射器相关的配置,这让我感到不太舒服。配置 Firecracker 容器的 SSH 连接也需要额外的步骤,这可能会导致一个非常大的容器,可能无法实现我想要的结果:一个基本的 Kubernetes 集群,具有完整的操作系统,我可以随意破坏。
考虑到 vCluster 在可能缺少的学习内容选项方面的限制,研究指向了虚拟化。这种方法在安全性和与宿主系统的完全隔离方面提供了保证,允许使用完整的操作系统和无限的学习内容。
幸运的是,Kubernetes 中有两种可用的虚拟化技术:Kubevirt 和 Virtlet。
在查看演示之前,您需要根据此指南安装 Kubevirt 和 QEMU 虚拟化程序。
与 vCluster 相比,实际实验环境的安装更加复杂,因为需要考虑用户数据脚本和存储等因素。但是我将省略这些细节,重点关注最重要的方面。在这个演示中,我将使用通用的容器磁盘镜像。
下面的 YAML 是每个实验环境中的三个虚拟机之一的定义,有一个控制平面节点,两个工作节点,用户数据略有不同。
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
apiVersion: v1
kind: Service
metadata:
name: ssh
namespace: student
spec:
type: NodePort
ports:
- port: 27017
targetPort: 22
selector:
kubevirt.io/vm: myvm
$ 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。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。
学生视角的网络拓扑
操作系统为 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,成本会很高。
通过我的 syncer 同步学生创建的 NodePort 服务到宿主集群
目前,缓存的实验环境只支持一个 Kubernetes 版本,不能用于其他版本。这意味着提供所有学习内容需要20到30秒,而沙箱是 1.5 分钟。我将会缓存更多的版本,加速沙箱的初始化速度。