【编者的话】作者 Yaron Haviv, Iguazio 的 CTO。 对于集群的要求指标不同,对于 Kubernetes 网络他们具有自己独特的方法论,此文是系列文章的第一篇,是 Iguazio 对于 Kubernetes 网络的一种处理方法说明。
我们借助于 Docker 在 Iguazio 构建的是原生云平台。使用到微服务,etcd、home-grown 等 Docker 集群管理工具。 目前我们正在逐渐迁移到使用 Kubernetes 作为容器的编排引擎,因为这些已经变得越来越成熟,我们就可以利用其更先进的功能专注于提供独特的服务。
与其他很多原生云应用不同,我们更专注实时性。为了提高应用的性能,我们使用底层直接访问网络、存储、CPU 和内存资源。对于容器和 Kubernetes 来说,这一点非常重要,而且需要使用一些独特并不常见的黑科技手段突破。
这篇文章是 这个系列的第一弹 ,我主要分享下如何使用一些黑客的技巧学习 Kubernetes 和 容器网络接口 的内部构件,以及如何操作它们。后续的博文将会覆盖高性能存储、进程间通信(IPC)在容器中的使用技巧等。
容器使用的是 Linux 中叫做 Cgroups 和 Namespace 的分区的功能来实现的。容器进程映射到网络、存储和其他的命名空间。每一个命名空间只能看到操作系统授权的那一部分,通过这种方式做到容器之间的隔离。
在网络方面,每一个命名空间都有自己的网络堆栈,包括网络接口、路由表、Socket 和 IPTABLE 规则等。一个接口只能属于某一个网络的命名空间。使用多容器就意味者需要多接口。另外一个选择是生成伪接口,并将它们软连接到真实的接口(我们还可以将容器映射到主机网络的命名空间,如 守护进程 的使用)
下面是创建并连接伪接口的几种选择:
虚拟网络模式: 虚拟桥, 多路复用 和 SR-IOV.
在很多场景下,用户希望可以创建跨越多个 L2/3 网段的逻辑网络子网。这需要覆盖封装协议(最常见的 VXLAN ,它将网路进一步包装成 UDP 的数据包)。VXLAN 可能会引入更多的网络开销,而且由于控制中缺乏标准化,来自不同供应商的多个 VXLAN 网络通常不能互相操作。
Kubernetes 还广泛使用 IPTABLES 和 NAT 来拦截流量,并将其路由到相应的物理目标。像 Flannel , Calico 和 Weave 使用 Veth 与桥接 / 路由器和覆盖或者路由 / NAT 的操作作为容器网络的解决方案。
有关各种 Linux 网络选项,请参与这个很好的 实践教程和使用指南 。
除了预期的数据包操作额外开销外,虚拟网络增加了隐藏的成本,可能会对 CPU 和内存并行行造成负面的影响,例如:
一些应用程序(如 Iguazio 的)使用了先进的 NIC 功能,如 RDMA, DPDK 快速网络处理库或加密来卸载消息传递,对 CPU 的并行性进行更严格的控制,减少中断或消除内存副本。这只能在使用直接网络接口或 SR-IOV 虚拟接口时使用。
没有简单的方法可以看到网络的命名空间,因为 Kubernetes 和 Docker 并没有注册它们("ip netns" 将不会与 Kubernetes 和 Docker 一起使用)。但是我们还是可以用一些黑科技从主机上查看、调试、管理和配置 POD 网络。
网络命名空间在 /proc/<PID>/ns/net 可以查看,所以我们需要从我们的 POD 中找到进程 ID(PID)。首先,通过以下命令可以找到容器 ID,注意只取前 12 个数字。
- kubectl get po <POD-NAME> -o jsonpath='{.status.containerStatuses[0].containerID}' | cut -c 10-21
再次,我们使用 Docker 命令找到进程 PID:
- kubectl get po <POD-NAME> -o jsonpath='{.status.containerStatuses[0].containerID}' | cut -c 10-21
一旦获取 PID,我们可以通过 POD 来监控和配置网络。在 POD 的命名空间中,使用 nsenter 工具来运行任意的命令,例如:
- nsenter - t $ {
- PID
- } - n ip addr
这样就可以显示所有的 POD 接口以及它们的 IP 地址。我们也可以使用其他的命令,例如 ping 或者 crul 来检验网络的连接性,可以使用特殊权限的操作或者在 POD 容器中安装容器的监控、调试或者配置该 POD 等(例如:ip route,nslookup)
如果我们对每个 POD 的单个接口不满意,还可以从主机命名空间中获取或创建接口,并将它们分配给 POD:
- ip link set netns $ {
- PID
- } < IFNAME >
如果想还原,我们设置 host 即可:
- nsenter - t $ {
- PID
- } - n ip link set < IFNAME > netns 1
Kubernetes 使用 CN1 插件来组织网络。每次初始化或者删除一个 POD 时,将使用默认配置调用默认的 CN1 插件。该 CN1 插件创建一个伪接口,将其附加到相关的底层网络,设置 IP 和路由将其映射到 POD 命名空间。
不幸的是 Kubernetes 仍然只支持每个 POD 只有一个 CN1 接口,具有一个群集级层次的配置。这就非常有限了,因为我们可能想要配置每个 POD 的多个网络接口,潜在地使用具有不同策略(子网,安全性,Qos)的不同覆盖解决方案。
让我们看下我们是怎么绕过这个限制的。
当 Kubelet Kubernetes 本地代理配置 POD 网络时,它会在 /etc/cni/net.d/ 目录路径中查找一个 CNI json 配置文件,并在 /opt/cni/bin/ 中找到一个相关的插件二进制文件(基于 type 属性) 。 CNI 插件可以调用辅助 IP 地址管理(IPAM)插件来设置每个接口的 IP 地址(例如主机本地或 DHCP)。 也可以通过 Kubelet 命令选项使用其他路径:
Kubelet 使用包含命令参数(CNI_ARGS,CNI_COMMAND,CNI_IFNAME,CNI_NETNS,CNI_CONTAINERID,CNI_PATH)的环境变量调用 CNI 插件,并通过 stdin 读写传输 json.conf 文件。 插件用 json 输出文本进行响应,描述结果和状态。 在这里查看更详细的解释和例子 。 如果您知道 Go 编程语言,开发自己的 CNI 插件是比较简单的,因为该框架可以做很多的魔法,您可以使用或扩展其中 一个现有的插件 。
Kubelet 将作为 CNI_ARGS 变量的一部分传递 POD 名称和命名空间(例如 "K8S_POD_NAMESPACE = default; K8S_POD_NAME = mytests-1227152546-vq7kw;")。 我们可以使用它来定制每个 POD 或 POD 命名空间的网络配置(例如,将每个命名空间放在不同的子网中)。 未来的 Kubernetes 版本会将网络视为平等的公民,并将网络配置作为 POD 或命名空间规范的一部分,就像内存,CPU 和存储卷一样。 目前,我们可以使用注释来存储配置或记录 POD 网络数据 / 状态。
CNI 插件一旦被调用就自动执行逻辑。 它还可以将多个网络接口连接到同一个 POD,并绕过当前的限制,这里有一个警告:Kubernetes 只会关注我们在报告中的结果(用于服务发现和路由)。 我最近偶然发现了一个很牛的英特尔开源 CNI 插件,称为 Multus ,它的功能正如以上所说的那样。 Linux 程序员 Doug Smith 在最近的 Slack 聊天之后写了一个详细的 Multus 的 从入门到提高 。
Multus 接受一个具有 CNI 定义数组的分层 conf 文件。 它将配置每个 POD 具有多个接口,每个定义一个,我们可以指定哪个接口是 "masterplugin" 被 Kubernetes 识别。 我们将其作为基准线与 POD 名称和 POD 注释相结合,以创建每个 POD 灵活且独特的网络配置。
英特尔的同一个开源 git 仓库中还包括另一个有趣的 CNI 驱动程序,用于 SR-IOV 和 DPDK 支持。
总结
我很喜欢原生云和微服务,他们对敏捷开发和持续交付的影响时巨大的。然后,似乎容器网络项目是新兴,仅仅只是走出了第一步,这就解决了跨部门 / 云连接的挑战。这些项目仍然需要定制,使其使用更广泛的应用基础和更高啊的性能。希望它能够快速提升其他更成熟的软件定义网络解决方案(如 OpenStack Neutron 或 VMware NSX )的功能水平。
我们如何实现所有这些不同的封装以及在控制面板上变得可协同操作,并允许在同一个虚拟网络两端是两个不同的供应商 / 云解决方案? 这是 64,000 美元的问题,我欢迎您的反馈。 这显然是一个需要更多关注和标准化的关键领域。
来源: http://www.tuicool.com/articles/jMFbInI