本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。

Hypernetes:为 Kubernetes 带来安全和多租户

虽然许多开发人员和安全专业人员认为 Linux 容器是有效的隔离边界,但许多用户需要更强的隔离程度,尤其是在多租户环境中运行的用户。不幸的是,目前这些用户被迫将他们的容器运行在虚拟机中,甚至每个容器一个虚拟机。

不幸的是,这导致了云原生部署的许多优势的丧失:虚拟机启动时间慢;每个容器都有内存开销;低利用率导致资源浪费。

在这篇文章中,我们将介绍 HyperContainer,一种基于虚拟机管理程序的容器,并探讨它如何自然地融入 Kubernetes 设计,使M户能够直接为他们的客户提供虚拟化容器,而不是将它们封装在完整的虚拟机中。

HyperContainer

HyperContainer 是一种基于虚拟机管理程序的容器,它允许您使用标准虚拟机管理程序(KVM、Xen 等)启动 Docker 镜像。作为一个开源项目,HyperContainer 由一个兼容 OCI 的运行时实现,名为 runV,以及一个管理守护程序,名为 hyperd。HyperContainer 背后的理念非常简单:结合虚拟化和容器的最佳优势。

我们可以将容器视为两部分(就像 Kubernetes 所做的那样)。第一部分是容器运行时,HyperContainer 使用虚拟化来实现执行隔离和资源限制,而不是命名空间和 cgroup。第二部分是应用程序数据,HyperContainer 利用 Docker 镜像。因此,在 HyperContainer 中,虚拟化技术使得构建一个具有独立客机内核的完全隔离沙箱成为可能(因此 top 和 /proc 等工具都可以工作),但从开发人员的角度来看,它是可移植的,并且行为类似于标准容器。

HyperContainer 作为 Pod

HyperContainer 的有趣之处不仅在于它对于多租户环境(例如公共云)足够安全,还在于它如何完美地融入 Kubernetes 的理念。

Kubernetes 中最重要的概念之一是 Pods。Pod 的设计是从实际工作负载中吸取的经验教训(Borg 论文第 8.1 节),在许多情况下,人们希望有一个由多个容器组成的原子调度单元(请查看此示例以获取更多信息)。在 Linux 容器的上下文中,Pod 将多个容器包装和封装成一个逻辑组。但在 HyperContainer 中,虚拟机管理程序充当了一个自然的边界,Pod 被引入作为一流对象。

HyperContainer 将轻量级应用程序容器的 Pod 进行封装,并在 Pod 级别暴露容器接口。在 Pod 内部,会启动一个名为 HyperKernel 的极简 Linux 内核。这个 HyperKernel 是用一个名为 HyperStart 的微型 Init 服务构建的。它将作为 PID 1 进程,创建 Pod,设置 Mount 命名空间,并从加载的镜像中启动应用程序。

这个模型与 Kubernetes 配合得很好。HyperContainer 与 Kubernetes 的集成,正如我们在标题中所示,构成了 Hypernetes 项目。

Hypernetes

Kubernetes 最好的部分之一是它被设计为支持多种容器运行时,这意味着用户不会被锁定在单个供应商。我们很高兴地宣布,我们已经开始与 Kubernetes 团队合作,将 HyperContainer 集成到 Kubernetes 上游。这种集成包括:

  1. 容器运行时优化和重构
  2. 新的客户端-服务器模式运行时接口
  3. containerd 集成以支持 runV

OCI 标准和 kubelet 的多运行时架构使得这种集成变得更加容易,尽管 HyperContainer 并非基于 Linux 容器技术栈。

另一方面,为了在多租户环境中运行 HyperContainers,我们还创建了一个新的网络插件并修改了一个现有的卷插件。由于 Hypernetes 将 Pod 作为自己的虚拟机运行,因此它可以利用您现有的 IaaS 层技术来实现多租户网络和持久卷。当前的 Hypernetes 实现使用标准的 Openstack 组件。

下面我们将详细介绍所有这些是如何实现的。

身份和认证

在 Hypernetes 中,我们选择 Keystone 来管理不同的租户,并在任何管理操作期间为租户执行身份识别和认证。由于 Keystone 来自 OpenStack 生态系统,它与我们在 Hypernetes 中使用的网络和存储插件无缝协作。

多租户网络模型

对于多租户容器集群,每个租户都需要与其他租户进行强网络隔离。在 Hypernetes 中,每个租户都有自己的网络。与使用 OpenStack 配置新网络(这很复杂)不同,使用 Hypernetes,您只需像下面这样创建一个 Network 对象。

apiVersion: v1  
kind: Network  
metadata:  
  name: net1  
spec:  
  tenantID: 065f210a2ca9442aad898ab129426350  
  subnets:  
    subnet1:  
      cidr: 192.168.0.0/24  
      gateway: 192.168.0.1

请注意,tenantID 由 Keystone 提供。此 yaml 将自动创建一个带有默认路由器和子网 192.168.0.0/24 的新 Neutron 网络。

网络控制器将负责用户创建的任何网络实例的生命周期管理。此网络可以分配给一个或多个命名空间,属于同一网络的任何 Pod 都可以通过 IP 地址直接相互访问。

apiVersion: v1  
kind: Namespace  
metadata:  
  name: ns1  
spec:  
  network: net1

如果 Namespace 没有 Network 规范,它将使用默认的 Kubernetes 网络模型,包括默认的 kube-proxy。因此,如果用户在带有相关 Network 的 Namespace 中创建 Pod,Hypernetes 将遵循 Kubernetes Network Plugin Model 为此 Pod 设置 Neutron 网络。这是一个高级示例:

一个 Hypernetes 网络工作流程图.png{: HyperContainer 封装了一个轻量级应用程序容器的 Pod。}

Hypernetes 使用一个名为 kubestack 的独立 gRPC 处理程序,将 Kubernetes Pod 请求转换为 Neutron 网络 API。此外,kubestack 还负责处理另一个重要的网络功能:多租户服务代理。

在多租户环境中,默认基于 iptables 的 kube-proxy 无法访问单个 Pod,因为它们被隔离到不同的网络中。相反,Hypernetes 使用每个 HyperContainer 中内置的 HAproxy 作为门户。此 HAproxy 将代理该 Pod 命名空间中的所有 Service 实例。Kube-proxy 将通过遵循标准的 OnServiceUpdate 和 OnEndpointsUpdate 过程来负责更新这些后端服务器,因此用户不会注意到任何差异。这种方法的一个缺点是 HAproxy 必须监听某些特定端口,这可能与用户的容器冲突。这就是为什么我们计划在下一次发布中使用 LVS 替换此代理。

在基于 Neutron 的网络插件的帮助下,Hypernetes 服务能够提供一个 OpenStack 负载均衡器,就像 GCE 上的“外部”负载均衡器一样。当用户使用外部 IP 创建 Service 时,将创建一个 OpenStack 负载均衡器,并通过上述 kubestack 工作流自动更新端点。

持久存储

在考虑存储时,我们实际上正在 Kubernetes 中构建一个感知租户的持久卷。我们之所以决定不使用 Kubernetes 现有的 Cinder 卷插件,是因为它的模型在虚拟化情况下不起作用。具体来说:

Cinder 卷插件要求 OpenStack 作为 Kubernetes 提供程序。

OpenStack 提供程序将找到目标 Pod 正在运行的虚拟机。

Cinder 卷插件会将 Cinder 卷挂载到 Kubernetes 主机虚拟机内的路径。

kubelet 将此路径作为卷绑定挂载到目标 Pod 的容器中。

但在 Hypernetes 中,事情变得简单得多。由于 Pod 的物理边界,HyperContainer 可以直接将 Cinder 卷作为块设备挂载到 Pod 中,就像正常的虚拟机一样。这种机制消除了在上述现有 Cinder 卷工作流中查询 Nova 以找出目标 Pod 虚拟机所需额外时间。

Hypernetes 中 Cinder 插件的当前实现基于 Ceph RBD 后端,其工作方式与所有其他 Kubernetes 卷插件相同,只需记住预先创建 Cinder 卷(由下面的 volumeID 引用)。

apiVersion: v1  
kind: Pod  
metadata:  
  name: nginx  
  labels:  
    app: nginx  
spec:  
  containers:  
  - name: nginx  
    image: nginx  
    ports:  
    - containerPort: 80  
    volumeMounts:  
    - name: nginx-persistent-storage  
      mountPath: /var/lib/nginx  
  volumes:  
  - name: nginx-persistent-storage  
    cinder:  
      volumeID: 651b2a7b-683e-47e1-bdd6-e3c62e8f91c0  
      fsType: ext4

因此,当用户提供带有 Cinder 卷的 Pod yaml 时,Hypernetes 将检查 kubelet 是否正在使用 Hyper 容器运行时。如果是,Cinder 卷可以直接挂载到 Pod,无需任何额外的路径映射。然后,卷元数据将作为 HyperContainer 规范的一部分传递给 Kubelet RunPod 进程。完成!

感谢 Kubernetes 网络和卷的插件模型,我们能够轻松地为 HyperContainer 构建我们自己的上述解决方案,尽管它本质上与传统的 Linux 容器不同。我们还计划在运行时集成完成后,遵循 CNI 模型和卷插件标准,将这些解决方案提交给 Kubernetes 上游。

我们相信所有这些开源项目都是容器生态系统的重要组成部分,它们的成长在很大程度上取决于 Kubernetes 团队的开源精神和技术愿景。

结论

这篇文章介绍了一些关于 HyperContainer 和 Hypernetes 项目的技术细节。我们希望人们会对这种新型安全容器及其与 Kubernetes 的集成感兴趣。如果您想尝试 Hypernetes 和 HyperContainer,我们刚刚宣布了我们新的安全容器云服务 (Hyper_) 的公开测试版,该服务就是基于这些技术构建的。但即使您在本地运行,我们也相信 Hypernetes 和 HyperContainer 将让您以更安全的方式运行 Kubernetes。