1 - 网络插件
Kubernetes中的网络插件有几种类型:
- CNI 插件:遵守容器网络接口(Container Network Interface,CNI)
规范,其设计上偏重互操作性。
- Kubernetes 遵从 CNI 规范的 v0.4.0 版本。
- Kubenet 插件:使用
bridge
和host-local
CNI 插件实现了基本的cbr0
。
安装
kubelet 有一个单独的默认网络插件,以及一个对整个集群通用的默认网络。 它在启动时探测插件,记住找到的内容,并在 Pod 生命周期的适当时间执行 所选插件(这仅适用于 Docker,因为 CRI 管理自己的 CNI 插件)。 在使用插件时,需要记住两个 kubelet 命令行参数:
cni-bin-dir
: kubelet 在启动时探测这个目录中的插件network-plugin
: 要使用的网络插件来自cni-bin-dir
。 它必须与从插件目录探测到的插件报告的名称匹配。 对于 CNI 插件,其值为 "cni"。
网络插件要求
除了提供
NetworkPlugin
接口
来配置和清理 Pod 网络之外,该插件还可能需要对 kube-proxy 的特定支持。
iptables 代理显然依赖于 iptables,插件可能需要确保 iptables 能够监控容器的网络通信。
例如,如果插件将容器连接到 Linux 网桥,插件必须将 net/bridge/bridge-nf-call-iptables
系统参数设置为1
,以确保 iptables 代理正常工作。
如果插件不使用 Linux 网桥(而是类似于 Open vSwitch 或者其它一些机制),
它应该确保为代理对容器通信执行正确的路由。
默认情况下,如果未指定 kubelet 网络插件,则使用 noop
插件,
该插件设置 net/bridge/bridge-nf-call-iptables=1
,以确保简单的配置
(如带网桥的 Docker )与 iptables 代理正常工作。
CNI
通过给 Kubelet 传递 --network-plugin=cni
命令行选项可以选择 CNI 插件。
Kubelet 从 --cni-conf-dir
(默认是 /etc/cni/net.d
) 读取文件并使用
该文件中的 CNI 配置来设置各个 Pod 的网络。
CNI 配置文件必须与
CNI 规约
匹配,并且配置所引用的所有所需的 CNI 插件都应存在于
--cni-bin-dir
(默认是 /opt/cni/bin
)下。
如果这个目录中有多个 CNI 配置文件,kubelet 将会使用按文件名的字典顺序排列 的第一个作为配置文件。
除了配置文件指定的 CNI 插件外,Kubernetes 还需要标准的 CNI
lo
插件,最低版本是0.2.0。
支持 hostPort
CNI 网络插件支持 hostPort
。 你可以使用官方
portmap
插件,它由 CNI 插件团队提供,或者使用你自己的带有 portMapping 功能的插件。
如果你想要启动 hostPort
支持,则必须在 cni-conf-dir
指定 portMappings capability
。
例如:
{
"name": "k8s-pod-network",
"cniVersion": "0.3.0",
"plugins": [
{
"type": "calico",
"log_level": "info",
"datastore_type": "kubernetes",
"nodename": "127.0.0.1",
"ipam": {
"type": "host-local",
"subnet": "usePodCidr"
},
"policy": {
"type": "k8s"
},
"kubernetes": {
"kubeconfig": "/etc/cni/net.d/calico-kubeconfig"
}
},
{
"type": "portmap",
"capabilities": {"portMappings": true}
}
]
}
支持流量整形
实验功能
CNI 网络插件还支持 pod 入口和出口流量整形。 你可以使用 CNI 插件团队提供的 bandwidth 插件,也可以使用你自己的具有带宽控制功能的插件。
如果你想要启用流量整形支持,你必须将 bandwidth
插件添加到 CNI 配置文件
(默认是 /etc/cni/net.d
)并保证该可执行文件包含在你的 CNI 的 bin
文件夹内 (默认为 /opt/cni/bin
)。
{
"name": "k8s-pod-network",
"cniVersion": "0.3.0",
"plugins": [
{
"type": "calico",
"log_level": "info",
"datastore_type": "kubernetes",
"nodename": "127.0.0.1",
"ipam": {
"type": "host-local",
"subnet": "usePodCidr"
},
"policy": {
"type": "k8s"
},
"kubernetes": {
"kubeconfig": "/etc/cni/net.d/calico-kubeconfig"
}
},
{
"type": "bandwidth",
"capabilities": {"bandwidth": true}
}
]
}
现在,你可以将 kubernetes.io/ingress-bandwidth
和 kubernetes.io/egress-bandwidth
注解添加到 pod 中。例如:
apiVersion: v1
kind: Pod
metadata:
annotations:
kubernetes.io/ingress-bandwidth: 1M
kubernetes.io/egress-bandwidth: 1M
...
kubenet
Kubenet 是一个非常基本的、简单的网络插件,仅适用于 Linux。 它本身并不实现更高级的功能,如跨节点网络或网络策略。 它通常与云驱动一起使用,云驱动为节点间或单节点环境中的通信设置路由规则。
Kubenet 创建名为 cbr0
的网桥,并为每个 pod 创建了一个 veth 对,
每个 Pod 的主机端都连接到 cbr0
。
这个 veth 对的 Pod 端会被分配一个 IP 地址,该 IP 地址隶属于节点所被分配的 IP
地址范围内。节点的 IP 地址范围则通过配置或控制器管理器来设置。
cbr0
被分配一个 MTU,该 MTU 匹配主机上已启用的正常接口的最小 MTU。
使用此插件还需要一些其他条件:
- 需要标准的 CNI
bridge
、lo
以及host-local
插件,最低版本是0.2.0。 kubenet 首先在/opt/cni/bin
中搜索它们。 指定cni-bin-dir
以提供 其它搜索路径。首次找到的匹配将生效。 - Kubelet 必须和
--network-plugin=kubenet
参数一起运行,才能启用该插件。 - Kubelet 还应该和
--non-masquerade-cidr=<clusterCidr>
参数一起运行, 以确保超出此范围的 IP 流量将使用 IP 伪装。 - 节点必须被分配一个 IP 子网,通过kubelet 命令行的
--pod-cidr
选项或 控制器管理器的命令行选项--allocate-node-cidrs=true --cluster-cidr=<cidr>
来设置。
自定义 MTU(使用 kubenet)
要获得最佳的网络性能,必须确保 MTU 的取值配置正确。 网络插件通常会尝试推断出一个合理的 MTU,但有时候这个逻辑不会产生一个最优的 MTU。 例如,如果 Docker 网桥或其他接口有一个小的 MTU, kubenet 当前将选择该 MTU。 或者如果你正在使用 IPSEC 封装,则必须减少 MTU,并且这种计算超出了大多数网络插件的能力范围。
如果需要,你可以使用 network-plugin-mtu
kubelet 选项显式的指定 MTU。
例如:在 AWS 上 eth0
MTU 通常是 9001,因此你可以指定 --network-plugin-mtu=9001
。
如果你正在使用 IPSEC ,你可以减少它以允许封装开销,例如 --network-plugin-mtu=8873
。
此选项会传递给网络插件; 当前 仅 kubenet 支持 network-plugin-mtu
。
用法总结
--network-plugin=cni
用来表明我们要使用cni
网络插件,实际的 CNI 插件 可执行文件位于--cni-bin-dir
(默认是/opt/cni/bin
)下, CNI 插件配置位于--cni-conf-dir
(默认是/etc/cni/net.d
)下。--network-plugin=kubenet
用来表明我们要使用kubenet
网络插件,CNIbridge
,lo
和host-local
插件位于/opt/cni/bin
或cni-bin-dir
中。--network-plugin-mtu=9001
指定了我们使用的 MTU,当前仅被kubenet
网络插件使用。
What's next
2 - 设备插件
Kubernetes v1.10 [beta]
Kubernetes 提供了一个 设备插件框架,你可以用它来将系统硬件资源发布到 Kubelet。
供应商可以实现设备插件,由你手动部署或作为 DaemonSet 来部署,而不必定制 Kubernetes 本身的代码。目标设备包括 GPU、高性能 NIC、FPGA、 InfiniBand 适配器以及其他类似的、可能需要特定于供应商的初始化和设置的计算资源。
注册设备插件
kubelet
提供了一个 Registration
的 gRPC 服务:
service Registration {
rpc Register(RegisterRequest) returns (Empty) {}
}
设备插件可以通过此 gRPC 服务在 kubelet 进行注册。在注册期间,设备插件需要发送下面几样内容:
- 设备插件的 Unix 套接字。
- 设备插件的 API 版本。
ResourceName
是需要公布的。这里ResourceName
需要遵循 扩展资源命名方案, 类似于vendor-domain/resourcetype
。(比如 NVIDIA GPU 就被公布为nvidia.com/gpu
。)
成功注册后,设备插件就向 kubelet 发送它所管理的设备列表,然后 kubelet 负责将这些资源发布到 API 服务器,作为 kubelet 节点状态更新的一部分。
比如,设备插件在 kubelet 中注册了 hardware-vendor.example/foo
并报告了
节点上的两个运行状况良好的设备后,节点状态将更新以通告该节点已安装 2 个
"Foo" 设备并且是可用的。
然后,用户可以请求设备作为 Pod 规范的一部分, 参见Container。 请求扩展资源类似于管理请求和限制的方式, 其他资源,有以下区别:
- 扩展资源仅可作为整数资源使用,并且不能被过量使用
- 设备不能在容器之间共享
示例
假设 Kubernetes 集群正在运行一个设备插件,该插件在一些节点上公布的资源为 hardware-vendor.example/foo
。
下面就是一个 Pod 示例,请求此资源以运行一个工作负载的示例:
---
apiVersion: v1
kind: Pod
metadata:
name: demo-pod
spec:
containers:
- name: demo-container-1
image: k8s.gcr.io/pause:2.0
resources:
limits:
hardware-vendor.example/foo: 2
#
# 这个 pod 需要两个 hardware-vendor.example/foo 设备
# 而且只能够调度到满足需求的节点上
#
# 如果该节点中有 2 个以上的设备可用,其余的可供其他 Pod 使用
设备插件的实现
设备插件的常规工作流程包括以下几个步骤:
-
初始化。在这个阶段,设备插件将执行供应商特定的初始化和设置, 以确保设备处于就绪状态。
-
插件使用主机路径
/var/lib/kubelet/device-plugins/
下的 Unix 套接字启动 一个 gRPC 服务,该服务实现以下接口:service DevicePlugin { // GetDevicePluginOptions 返回与设备管理器沟通的选项。 rpc GetDevicePluginOptions(Empty) returns (DevicePluginOptions) {} // ListAndWatch 返回 Device 列表构成的数据流。 // 当 Device 状态发生变化或者 Device 消失时,ListAndWatch // 会返回新的列表。 rpc ListAndWatch(Empty) returns (stream ListAndWatchResponse) {} // Allocate 在容器创建期间调用,这样设备插件可以运行一些特定于设备的操作, // 并告诉 kubelet 如何令 Device 可在容器中访问的所需执行的具体步骤 rpc Allocate(AllocateRequest) returns (AllocateResponse) {} // GetPreferredAllocation 从一组可用的设备中返回一些优选的设备用来分配, // 所返回的优选分配结果不一定会是设备管理器的最终分配方案。 // 此接口的设计仅是为了让设备管理器能够在可能的情况下做出更有意义的决定。 rpc GetPreferredAllocation(PreferredAllocationRequest) returns (PreferredAllocationResponse) {} // PreStartContainer 在设备插件注册阶段根据需要被调用,调用发生在容器启动之前。 // 在将设备提供给容器使用之前,设备插件可以运行一些诸如重置设备之类的特定于 // 具体设备的操作, rpc PreStartContainer(PreStartContainerRequest) returns (PreStartContainerResponse) {} }
Note:插件并非必须为
GetPreferredAllocation()
或PreStartContainer()
提供有用 的实现逻辑,调用GetDevicePluginOptions()
时所返回的DevicePluginOptions
消息中应该设置这些调用是否可用。kubelet
在真正调用这些函数之前,总会调用GetDevicePluginOptions()
来查看是否存在这些可选的函数。
- 插件通过 Unix socket 在主机路径
/var/lib/kubelet/device-plugins/kubelet.sock
处向 kubelet 注册自身。 - 成功注册自身后,设备插件将以服务模式运行,在此期间,它将持续监控设备运行状况,
并在设备状态发生任何变化时向 kubelet 报告。它还负责响应
Allocate
gRPC 请求。 在Allocate
期间,设备插件可能还会做一些设备特定的准备;例如 GPU 清理或 QRNG 初始化。 如果操作成功,则设备插件将返回AllocateResponse
,其中包含用于访问被分配的设备容器运行时的配置。 kubelet 将此信息传递到容器运行时。
处理 kubelet 重启
设备插件应能监测到 kubelet 重启,并且向新的 kubelet 实例来重新注册自己。
在当前实现中,当 kubelet 重启的时候,新的 kubelet 实例会删除 /var/lib/kubelet/device-plugins
下所有已经存在的 Unix 套接字。
设备插件需要能够监控到它的 Unix 套接字被删除,并且当发生此类事件时重新注册自己。
设备插件部署
你可以将你的设备插件作为节点操作系统的软件包来部署、作为 DaemonSet 来部署或者手动部署。
规范目录 /var/lib/kubelet/device-plugins
是需要特权访问的,所以设备插件
必须要在被授权的安全的上下文中运行。
如果你将设备插件部署为 DaemonSet,/var/lib/kubelet/device-plugins
目录必须要在插件的
PodSpec
中声明作为 卷(Volume) 被挂载到插件中。
如果你选择 DaemonSet 方法,你可以通过 Kubernetes 进行以下操作: 将设备插件的 Pod 放置在节点上,在出现故障后重新启动守护进程 Pod,来进行自动升级。
API 兼容性
Kubernetes 设备插件支持还处于 beta 版本。所以在稳定版本出来之前 API 会以不兼容的方式进行更改。 作为一个项目,Kubernetes 建议设备插件开发者:
- 注意未来版本的更改
- 支持多个版本的设备插件 API,以实现向后/向前兼容性。
如果你启用 DevicePlugins 功能,并在需要升级到 Kubernetes 版本来获得较新的设备插件 API 版本的节点上运行设备插件,请在升级这些节点之前先升级设备插件以支持这两个版本。 采用该方法将确保升级期间设备分配的连续运行。
监控设备插件资源
Kubernetes v1.15 [beta]
为了监控设备插件提供的资源,监控代理程序需要能够发现节点上正在使用的设备,
并获取元数据来描述哪个指标与容器相关联。
设备监控代理暴露给 Prometheus 的指标应该遵循
Kubernetes Instrumentation Guidelines,
使用 pod
、namespace
和 container
标签来标识容器。
kubelet 提供了 gRPC 服务来使得正在使用中的设备被发现,并且还未这些设备提供了元数据:
// PodResourcesLister 是一个由 kubelet 提供的服务,用来提供供节点上
// Pods 和容器使用的节点资源的信息
service PodResourcesLister {
rpc List(ListPodResourcesRequest) returns (ListPodResourcesResponse) {}
rpc GetAllocatableResources(AllocatableResourcesRequest) returns (AllocatableResourcesResponse) {}
}
List
gRPC 端点
这一 List
端点提供运行中 Pods 的资源信息,包括类似独占式分配的
CPU ID、设备插件所报告的设备 ID 以及这些设备分配所处的 NUMA 节点 ID。
此外,对于基于 NUMA 的机器,它还会包含为容器保留的内存和大页的信息。
// ListPodResourcesResponse 是 List 函数的响应
message ListPodResourcesResponse {
repeated PodResources pod_resources = 1;
}
// PodResources 包含关于分配给 Pod 的节点资源的信息
message PodResources {
string name = 1;
string namespace = 2;
repeated ContainerResources containers = 3;
}
// ContainerResources 包含分配给容器的资源的信息
message ContainerResources {
string name = 1;
repeated ContainerDevices devices = 2;
repeated int64 cpu_ids = 3;
repeated ContainerMemory memory = 4;
}
// ContainerMemory 包含分配给容器的内存和大页信息
message ContainerMemory {
string memory_type = 1;
uint64 size = 2;
TopologyInfo topology = 3;
}
// Topology 描述资源的硬件拓扑结构
message TopologyInfo {
repeated NUMANode nodes = 1;
}
// NUMA 代表的是 NUMA 节点
message NUMANode {
int64 ID = 1;
}
// ContainerDevices 包含分配给容器的设备信息
message ContainerDevices {
string resource_name = 1;
repeated string device_ids = 2;
TopologyInfo topology = 3;
}
List
端点中的 ContainerResources
中的 cpu_ids 对应于分配给某个容器的专属 CPU。
如果要统计共享池中的 CPU,List
端点需要与 GetAllocatableResources
端点一起使用,如下所述:
- 调用
GetAllocatableResources
获取所有可用的 CPUs。 - 在系统中所有的
ContainerResources
上调用GetCpuIds
。 - 用
GetAllocatableResources
获取的 CPU 数减去GetCpuIds
获取的 CPU 数。
GetAllocatableResources
gRPC 端点
Kubernetes v1.23 [beta]
端点 GetAllocatableResources
提供工作节点上原始可用的资源信息。
此端点所提供的信息比导出给 API 服务器的信息更丰富。
GetAllocatableResources
应该仅被用于评估一个节点上的可分配的
资源。如果目标是评估空闲/未分配的资源,此调用应该与 List() 端点一起使用。
除非暴露给 kubelet 的底层资源发生变化 否则 GetAllocatableResources
得到的结果将保持不变。
这种情况很少发生,但当发生时(例如:热插拔,设备健康状况改变),客户端应该调用 GetAlloctableResources
端点。
然而,调用 GetAllocatableResources
端点在 cpu、内存被更新的情况下是不够的,
Kubelet 需要重新启动以获取正确的资源容量和可分配的资源。
// AllocatableResourcesResponses 包含 kubelet 所了解到的所有设备的信息
message AllocatableResourcesResponse {
repeated ContainerDevices devices = 1;
repeated int64 cpu_ids = 2;
repeated ContainerMemory memory = 3;
}
从 Kubernetes v1.23 开始,GetAllocatableResources
被默认启用。
你可以通过关闭 KubeletPodResourcesGetAllocatable
特性门控 来禁用。
在 Kubernetes v1.23 之前,要启用这一功能,kubelet
必须用以下标志启动:
--feature-gates=KubeletPodResourcesGetAllocatable=true
ContainerDevices
会向外提供各个设备所隶属的 NUMA 单元这类拓扑信息。
NUMA 单元通过一个整数 ID 来标识,其取值与设备插件所报告的一致。
设备插件注册到 kubelet 时
会报告这类信息。
gRPC 服务通过 /var/lib/kubelet/pod-resources/kubelet.sock
的 UNIX 套接字来提供服务。
设备插件资源的监控代理程序可以部署为守护进程或者 DaemonSet。
规范的路径 /var/lib/kubelet/pod-resources
需要特权来进入,
所以监控代理程序必须要在获得授权的安全的上下文中运行。
如果设备监控代理以 DaemonSet 形式运行,必须要在插件的
PodSpec
中声明将 /var/lib/kubelet/pod-resources
目录以
卷的形式被挂载到设备监控代理中。
对“PodResourcesLister 服务”的支持要求启用 KubeletPodResources
特性门控。
从 Kubernetes 1.15 开始默认启用,自从 Kubernetes 1.20 开始为 v1。
设备插件与拓扑管理器的集成
Kubernetes v1.18 [beta]
拓扑管理器是 Kubelet 的一个组件,它允许以拓扑对齐方式来调度资源。
为了做到这一点,设备插件 API 进行了扩展来包括一个 TopologyInfo
结构体。
message TopologyInfo {
repeated NUMANode nodes = 1;
}
message NUMANode {
int64 ID = 1;
}
设备插件希望拓扑管理器可以将填充的 TopologyInfo 结构体作为设备注册的一部分以及设备 ID 和设备的运行状况发送回去。然后设备管理器将使用此信息来咨询拓扑管理器并做出资源分配决策。
TopologyInfo
支持定义 nodes
字段,允许为 nil
(默认)或者是一个 NUMA 节点的列表。
这样就可以使设备插件可以跨越 NUMA 节点去发布。
下面是一个由设备插件为设备填充 TopologyInfo
结构体的示例:
pluginapi.Device{ID: "25102017", Health: pluginapi.Healthy, Topology:&pluginapi.TopologyInfo{Nodes: []*pluginapi.NUMANode{&pluginapi.NUMANode{ID: 0,},}}}
设备插件示例
下面是一些设备插件实现的示例:
- AMD GPU 设备插件
- Intel 设备插件 支持 Intel GPU、FPGA 和 QuickAssist 设备
- KubeVirt 设备插件 用于硬件辅助的虚拟化
- 为 Container-Optimized OS 所提供的 NVIDIA GPU 设备插件
- RDMA 设备插件
- SocketCAN 设备插件
- Solarflare 设备插件
- SR-IOV 网络设备插件
- Xilinx FPGA 设备插件
What's next
- 查看调度 GPU 资源 来学习使用设备插件
- 查看在上如何公布节点上的扩展资源
- 学习拓扑管理器
- 阅读如何在 Kubernetes 中使用 TLS Ingress 的硬件加速