这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

计算、存储和网络扩展

1 - 网络插件

Kubernetes中的网络插件有几种类型:

安装

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-bandwidthkubernetes.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 bridgelo 以及 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 网络插件,CNI bridgelohost-local 插件位于 /opt/cni/bincni-bin-dir 中。
  • --network-plugin-mtu=9001 指定了我们使用的 MTU,当前仅被 kubenet 网络插件使用。

接下来

2 - 设备插件

使用 Kubernetes 设备插件框架来实现适用于 GPU、NIC、FPGA、InfiniBand 以及类似的需要特定于供应商设置的资源的插件。
FEATURE STATE: 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" 设备并且是可用的。

然后用户需要请求其他类型的资源的时候,就可以在 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 {
          // 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() 来查看是否存在这些可选的函数。

  • 插件通过 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 版本的节点上运行设备插件,请在升级这些节点之前先升级设备插件以支持这两个版本。 采用该方法将确保升级期间设备分配的连续运行。

监控设备插件资源

FEATURE STATE: Kubernetes v1.15 [beta]

为了监控设备插件提供的资源,监控代理程序需要能够发现节点上正在使用的设备, 并获取元数据来描述哪个指标与容器相关联。 设备监控代理暴露给 Prometheus 的指标应该遵循 Kubernetes Instrumentation Guidelines, 使用 podnamespacecontainer 标签来标识容器。

kubelet 提供了 gRPC 服务来使得正在使用中的设备被发现,并且还未这些设备提供了元数据:

// PodResourcesLister 是一个由 kubelet 提供的服务,用来提供供节点上 
// Pods 和容器使用的节点资源的信息
service PodResourcesLister {
    rpc List(ListPodResourcesRequest) returns (ListPodResourcesResponse) {}
    rpc GetAllocatableResources(AllocatableResourcesRequest) returns (AllocatableResourcesResponse) {}
}

这一 List 端点提供运行中 Pods 的资源信息,包括类似独占式分配的 CPU ID、设备插件所报告的设备 ID 以及这些设备分配所处的 NUMA 节点 ID。

// 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;
}

// 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;
}

端点 GetAllocatableResources 提供最初在工作节点上可用的资源的信息。 此端点所提供的信息比导出给 API 服务器的信息更丰富。

// AllocatableResourcesResponses 包含 kubelet 所了解到的所有设备的信息
message AllocatableResourcesResponse {
    repeated ContainerDevices devices = 1;
    repeated int64 cpu_ids = 2;
}

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。

设备插件与拓扑管理器的集成

FEATURE STATE: 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,},}}}

设备插件示例

下面是一些设备插件实现的示例:

接下来