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

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

虽然许多开发人员和安全专业人员都认为 Linux 容器是有效的边界,但许多用户需要更强的隔离程度,特别是那些在多租户环境中运行的用户。可悲的是,如今,这些用户被迫在虚拟机内部运行他们的容器,甚至是每个容器一个虚拟机。

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

在本文中,我们将介绍 HyperContainer,一种基于虚拟机管理程序的容器,并了解它是如何自然地融入 Kubernetes 设计中,并使用户能够直接使用虚拟化容器为其客户提供服务,而不是将它们包裹在完整的虚拟机中。

HyperContainer

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

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

作为 Pod 的 HyperContainer

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

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

HyperContainer 包装一个轻量级应用程序容器的 Pod,并在 Pod 级别公开容器接口。在 Pod 内部,启动了一个名为 HyperKernel 的极简 Linux 内核。此 HyperKernel 使用一个名为 HyperStart 的微型 Init 服务构建。它将充当 PID 1 进程并创建 Pod、设置挂载命名空间,并从加载的镜像启动应用程序。

此模型与 Kubernetes 配合良好。正如我们在标题中指出的那样,HyperContainer 与 Kubernetes 的集成构成了 Hypernetes 项目。

Hypernetes

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

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

OCI 标准和 kubelet 的多运行时架构使这种集成变得容易得多,即使 HyperContainer 不是基于 Linux 容器技术栈。

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

下面我们将详细介绍上述所有内容的实现方式。

身份和身份验证

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

多租户网络模型

对于多租户容器集群,每个租户都需要彼此之间具有强大的网络隔离。在 Hypernetes 中,每个租户都有自己的网络。使用 Hypernetes,您只需像下面一样创建一个 Network 对象,而不是使用 OpenStack 配置新的网络(这很复杂)。

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 将自动创建一个新的 Neutron 网络,其中包含默认路由器和子网 192.168.0.0/24。

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

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

如果命名空间没有 Network spec,它将改为使用默认的 Kubernetes 网络模型,包括默认的 kube-proxy。因此,如果用户在具有关联 Network 的命名空间中创建 Pod,Hypernetes 将遵循 Kubernetes 网络插件模型 为此 Pod 设置 Neutron 网络。这是一个高级示例

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

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 Service 能够提供 OpenStack 负载均衡器,就像 GCE 上“外部”负载均衡器一样。当用户创建一个带有外部 IP 的 Service 时,将创建一个 OpenStack 负载均衡器,并且端点将通过上述 kubestack 工作流程自动更新。

持久存储

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

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

OpenStack 提供程序将找到目标 Pod 运行在哪个虚拟机上。

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

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

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

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。