1 - 网络插件
Kubernetes(1.3 版本至最新 1.31,并可能包括未来版本)
允许你使用容器网络接口(CNI)
插件来完成集群联网。
你必须使用和你的集群相兼容并且满足你的需求的 CNI 插件。
在更广泛的 Kubernetes 生态系统中你可以使用不同的插件(开源和闭源)。
要实现 Kubernetes 网络模型,你需要一个 CNI 插件。
你必须使用与 v0.4.0
或更高版本的 CNI 规范相符合的 CNI 插件。
Kubernetes 推荐使用一个兼容 v1.0.0
CNI 规范的插件(插件可以兼容多个规范版本)。
安装
在网络语境中,容器运行时(Container Runtime)是在节点上的守护进程,
被配置用来为 kubelet 提供 CRI 服务。具体而言,容器运行时必须配置为加载所需的
CNI 插件,从而实现 Kubernetes 网络模型。
说明:
在 Kubernetes 1.24 之前,CNI 插件也可以由 kubelet 使用命令行参数 cni-bin-dir
和 network-plugin
管理。Kubernetes 1.24 移除了这些命令行参数,
CNI 的管理不再是 kubelet 的工作。
如果你在移除 dockershim 之后遇到问题,
请参阅排查 CNI 插件相关的错误。
要了解容器运行时如何管理 CNI 插件的具体信息,可参见对应容器运行时的文档,例如:
要了解如何安装和管理 CNI 插件的具体信息,可参阅对应的插件或
网络驱动(Networking Provider)
的文档。
网络插件要求
本地回路 CNI
除了安装到节点上用于实现 Kubernetes 网络模型的 CNI 插件外,Kubernetes
还需要容器运行时提供一个本地回路接口 lo
,用于各个沙箱(Pod 沙箱、虚机沙箱……)。
实现本地回路接口的工作可以通过复用
CNI 本地回路插件来实现,
也可以通过开发自己的代码来实现
(参阅 CRI-O 中的示例)。
支持 hostPort
CNI 网络插件支持 hostPort
。你可以使用官方
portmap
插件,它由 CNI 插件团队提供,或者使用你自己的带有 portMapping 功能的插件。
如果你想要启动 hostPort
支持,则必须在 cni-conf-dir
指定 portMappings capability
。
例如:
{
"name": "k8s-pod-network",
"cniVersion": "0.4.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},
"externalSetMarkChain": "KUBE-MARK-MASQ"
}
]
}
支持流量整形
实验功能
CNI 网络插件还支持 Pod 入站和出站流量整形。
你可以使用 CNI 插件团队提供的
bandwidth
插件,也可以使用你自己的具有带宽控制功能的插件。
如果你想要启用流量整形支持,你必须将 bandwidth
插件添加到 CNI 配置文件
(默认是 /etc/cni/net.d
)并保证该可执行文件包含在你的 CNI 的 bin
文件夹内(默认为 /opt/cni/bin
)。
{
"name": "k8s-pod-network",
"cniVersion": "0.4.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
...
接下来
2 - 设备插件
设备插件可以让你配置集群以支持需要特定于供应商设置的设备或资源,例如 GPU、NIC、FPGA 或非易失性主存储器。
特性状态: Kubernetes v1.26 [stable]
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: registry.k8s.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) {}
}
说明:
插件并非必须为 GetPreferredAllocation()
或 PreStartContainer()
提供有用的实现逻辑,
调用 GetDevicePluginOptions()
时所返回的 DevicePluginOptions
消息中应该设置一些标志,表明这些调用(如果有)是否可用。kubelet
在直接调用这些函数之前,总会调用
GetDevicePluginOptions()
来查看哪些可选的函数可用。
-
插件通过位于主机路径 /var/lib/kubelet/device-plugins/kubelet.sock
下的 UNIX
套接字向 kubelet 注册自身。
说明:
工作流程的顺序很重要。插件必须在向 kubelet 注册自己之前开始提供 gRPC 服务,才能保证注册成功。
-
成功注册自身后,设备插件将以提供服务的模式运行,在此期间,它将持续监控设备运行状况,
并在设备状态发生任何变化时向 kubelet 报告。它还负责响应 Allocate
gRPC 请求。
在 Allocate
期间,设备插件可能还会做一些特定于设备的准备;例如 GPU 清理或 QRNG 初始化。
如果操作成功,则设备插件将返回 AllocateResponse
,其中包含用于访问被分配的设备容器运行时的配置。
kubelet 将此信息传递到容器运行时。
AllocateResponse
包含零个或多个 ContainerAllocateResponse
对象。
设备插件在这些对象中给出为了访问设备而必须对容器定义所进行的修改。
这些修改包括:
- 注解
- 设备节点
- 环境变量
- 挂载点
- 完全限定的 CDI 设备名称
说明:
设备管理器处理完全限定的 CDI 设备名称时,
需要为 kubelet 和 kube-apiserver 启用 DevicePluginCDIDevices
特性门控。
在 Kubernetes v1.28 版本中作为 Alpha 特性被加入,并在 v1.29 版本中升级为 Beta 特性。
处理 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 兼容性
之前版本控制方案要求设备插件的 API 版本与 Kubelet 的版本完全匹配。
自从此特性在 v1.12 中进阶为 Beta 后,这不再是硬性要求。
API 是版本化的,并且自此特性进阶 Beta 后一直表现稳定。
因此,kubelet 升级应该是无缝的,但在稳定之前 API 仍然可能会有变更,还不能保证升级不会中断。
说明:
尽管 Kubernetes 的设备管理器(Device Manager)组件是正式发布的特性,
但设备插件 API 还不稳定。有关设备插件 API 和版本兼容性的信息,
请参阅设备插件 API 版本。
作为一个项目,Kubernetes 建议设备插件开发者:
- 注意未来版本中设备插件 API 的变更。
- 支持多个版本的设备插件 API,以实现向后/向前兼容性。
若在需要升级到具有较新设备插件 API 版本的某个 Kubernetes 版本的节点上运行这些设备插件,
请在升级这些节点之前先升级设备插件以支持这两个版本。
采用该方法将确保升级期间设备分配的连续运行。
监控设备插件资源
特性状态: Kubernetes v1.28 [stable]
为了监控设备插件提供的资源,监控代理程序需要能够发现节点上正在使用的设备,
并获取元数据来描述哪个指标与容器相关联。
设备监控代理暴露给 Prometheus 的指标应该遵循
Kubernetes Instrumentation Guidelines,
使用 pod
、namespace
和 container
标签来标识容器。
kubelet 提供了 gRPC 服务来使得正在使用中的设备被发现,并且还为这些设备提供了元数据:
// PodResourcesLister 是一个由 kubelet 提供的服务,用来提供供节点上
// Pod 和容器使用的节点资源的信息
service PodResourcesLister {
rpc List(ListPodResourcesRequest) returns (ListPodResourcesResponse) {}
rpc GetAllocatableResources(AllocatableResourcesRequest) returns (AllocatableResourcesResponse) {}
rpc Get(GetPodResourcesRequest) returns (GetPodResourcesResponse) {}
}
List
gRPC 端点
这一 List
端点提供运行中 Pod 的资源信息,包括类似独占式分配的
CPU ID、设备插件所报告的设备 ID 以及这些设备分配所处的 NUMA 节点 ID。
此外,对于基于 NUMA 的机器,它还会包含为容器保留的内存和大页的信息。
从 Kubernetes v1.27 开始,List
端点可以通过 DynamicResourceAllocation
API 提供在
ResourceClaims
中分配的当前运行 Pod 的资源信息。
要启用此特性,必须使用以下标志启动 kubelet
:
--feature-gates=DynamicResourceAllocation=true,KubeletPodResourcesDynamicResources=true
// 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;
repeated DynamicResource dynamic_resources = 5;
}
// 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;
}
// DynamicResource 包含通过 Dynamic Resource Allocation 分配到容器的设备信息
message DynamicResource {
string class_name = 1;
string claim_name = 2;
string claim_namespace = 3;
repeated ClaimResource claim_resources = 4;
}
// ClaimResource 包含每个插件的资源信息
message ClaimResource {
repeated CDIDevice cdi_devices = 1 [(gogoproto.customname) = "CDIDevices"];
}
// CDIDevice 指定 CDI 设备信息
message CDIDevice {
// 完全合格的 CDI 设备名称
// 例如:vendor.com/gpu=gpudevice1
// 参阅 CDI 规范中的更多细节:
// https://github.com/container-orchestrated-devices/container-device-interface/blob/main/SPEC.md
string name = 1;
}
说明:
List
端点中的 ContainerResources
中的 cpu_ids 对应于分配给某个容器的专属 CPU。
如果要统计共享池中的 CPU,List
端点需要与 GetAllocatableResources
端点一起使用,如下所述:
- 调用
GetAllocatableResources
获取所有可用的 CPU。
- 在系统中所有的
ContainerResources
上调用 GetCpuIds
。
- 用
GetAllocatableResources
获取的 CPU 数减去 GetCpuIds
获取的 CPU 数。
GetAllocatableResources
gRPC 端点
特性状态: Kubernetes v1.28 [stable]
端点 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;
}
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
目录以卷的形式被挂载到设备监控代理中。
说明:
在从 DaemonSet 或以容器形式部署在主机上的任何其他应用中访问
/var/lib/kubelet/pod-resources/kubelet.sock
时,
如果将套接字作为卷挂载,最好的做法是挂载目录 /var/lib/kubelet/pod-resources/
而不是 /var/lib/kubelet/pod-resources/kubelet.sock
。
这样可以确保在 kubelet 重新启动后,容器将能够重新连接到此套接字。
容器挂载是通过引用套接字或目录的 inode 进行管理的,具体取决于挂载的内容。
当 kubelet 重新启动时,套接字会被删除并创建一个新的套接字,而目录则保持不变。
因此,针对原始套接字的 inode 将变得无法使用,而到目录的 inode 将继续正常工作。
Get
gRPC 端点
特性状态: Kubernetes v1.27 [alpha]
Get
端点提供了当前运行 Pod 的资源信息。它会暴露与 List
端点中所述类似的信息。
Get
端点需要当前运行 Pod 的 PodName
和 PodNamespace
。
// GetPodResourcesRequest 包含 Pod 相关信息
message GetPodResourcesRequest {
string pod_name = 1;
string pod_namespace = 2;
}
要启用此特性,你必须使用以下标志启动 kubelet 服务:
--feature-gates=KubeletPodResourcesGet=true
Get
端点可以提供与动态资源分配 API 所分配的动态资源相关的 Pod 信息。
要启用此特性,你必须确保使用以下标志启动 kubelet 服务:
--feature-gates=KubeletPodResourcesGet=true,DynamicResourceAllocation=true,KubeletPodResourcesDynamicResources=true
设备插件与拓扑管理器的集成
特性状态: Kubernetes v1.27 [stable]
拓扑管理器是 Kubelet 的一个组件,它允许以拓扑对齐方式来调度资源。
为了做到这一点,设备插件 API 进行了扩展来包括一个 TopologyInfo
结构体。
message TopologyInfo {
repeated NUMANode nodes = 1;
}
message NUMANode {
int64 ID = 1;
}
设备插件希望拓扑管理器可以将填充的 TopologyInfo 结构体作为设备注册的一部分以及设备 ID
和设备的运行状况发送回去。然后设备管理器将使用此信息来咨询拓扑管理器并做出资源分配决策。
TopologyInfo
支持将 nodes
字段设置为 nil
或一个 NUMA 节点的列表。
这样就可以使设备插件通告跨越多个 NUMA 节点的设备。
将 TopologyInfo
设置为 nil
或为给定设备提供一个空的
NUMA 节点列表表示设备插件没有该设备的 NUMA 亲和偏好。
下面是一个由设备插件为设备填充 TopologyInfo
结构体的示例:
pluginapi.Device{ID: "25102017", Health: pluginapi.Healthy, Topology:&pluginapi.TopologyInfo{Nodes: []*pluginapi.NUMANode{&pluginapi.NUMANode{ID: 0,},}}}
设备插件示例
说明: 本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南。
下面是一些设备插件实现的示例:
接下来