1 - 以独立模式运行 kubelet

本教程将向你展示如何运行一个独立的 kubelet 实例。

你可能会有不同的动机来运行一个独立的 kubelet。 本教程旨在向你介绍 Kubernetes,即使你对此并没有太多经验也没有关系。 你可以跟随本教程学习节点设置、基本(静态)Pod 以及 Kubernetes 如何管理容器。

你学习完本教程后,就可以尝试使用带一个控制平面的集群来管理 Pod、节点和其他类别的对象。例如,你好,Minikube。

你还可以以独立模式运行 kubelet 来满足生产场景要求,例如为高可用、弹性部署的集群运行控制平面。 本教程不涵盖运行弹性控制平面所需的细节。

教程目标

  • 在 Linux 系统上安装 cri-o 和 kubelet,并将其作为 systemd 服务运行。
  • 启动一个运行 nginx 的 Pod,监听针对此 Pod 的 IP 地址的 TCP 80 端口的请求。
  • 学习此方案中不同组件之间如何交互。

准备开始

  • 对使用 systemd 和 iptables(或使用 iptables 仿真的 nftables)的 Linux 系统具有管理员(root)访问权限。
  • 有权限访问互联网以下载本教程所需的组件,例如:

准备好系统

配置内存交换

默认情况下,如果在节点上检测到内存交换,kubelet 将启动失败。 这意味着内存交换应该被禁用或被 kubelet 容忍。

如果你启用了交换内存,则禁用它或在 kubelet 配置文件中添加 failSwapOn: false。

要检查交换内存是否被启用:

sudo swapon --show

如果此命令没有输出,则交换内存已被禁用。

临时禁用交换内存:

sudo swapoff -a

要使此变更持续到重启之后:

确保在 /etc/fstab 或 systemd.swap 中禁用交换内存,具体取决于它在你的系统上是如何配置的。

启用 IPv4 数据包转发

检查 IPv4 数据包转发是否被启用:

cat /proc/sys/net/ipv4/ip_forward

如果输出为 1,则 IPv4 数据包转发已被启用。如果输出为 0,按照以下步骤操作。

要启用 IPv4 数据包转发,创建一个配置文件,将 net.ipv4.ip_forward 参数设置为 1:

sudo tee /etc/sysctl.d/k8s.conf <<EOF
net.ipv4.ip_forward = 1
EOF

将变更应用到系统:

sudo sysctl --system

输出类似于:

...
* Applying /etc/sysctl.d/k8s.conf ...
net.ipv4.ip_forward = 1
* Applying /etc/sysctl.conf ...

下载、安装和配置组件

说明: 本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循CNCF 网站指南,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读内容指南。

安装容器运行时

下载所需软件包的最新可用版本(推荐)。

本教程建议安装 CRI-O 容器运行时(外部链接)。

根据你安装的特定 Linux 发行版,有几种安装容器运行时的方式。 尽管 CRI-O 推荐使用 deb 或 rpm 包,但本教程使用 CRI-O Packaging 项目中的静态二进制包脚本, 以简化整个安装过程,并保持与 Linux 发行版无关。

此脚本安装并配置更多必需的软件,例如容器联网所用的 cni-plugins 以及运行容器所用的 crun 和 runc。

此脚本将自动检测系统的处理器架构(amd64 或 arm64),并选择和安装最新版本的软件包。

设置 CRI-O

查阅发布版本页面(外部链接)。

下载静态二进制包脚本:

curl https://raw.githubusercontent.com/cri-o/packaging/main/get > crio-install

运行安装器脚本:

sudo bash crio-install

启用并启动 crio 服务:

sudo systemctl daemon-reload
sudo systemctl enable --now crio.service

快速测试:

sudo systemctl is-active crio.service

输出类似于:

active

详细的服务检查:

sudo journalctl -f -u crio.service

安装网络插件

cri-o 安装器安装并配置 cni-plugins 包。你可以通过运行以下命令来验证安装包:

/opt/cni/bin/bridge --version

输出类似于:

CNI bridge plugin v1.5.1
CNI protocol versions supported: 0.1.0, 0.2.0, 0.3.0, 0.3.1, 0.4.0, 1.0.0

检查默认配置:

cat /etc/cni/net.d/11-crio-ipv4-bridge.conflist

输出类似于:

{
  "cniVersion": "1.0.0",
  "name": "crio",
  "plugins": [
    {
      "type": "bridge",
      "bridge": "cni0",
      "isGateway": true,
      "ipMasq": true,
      "hairpinMode": true,
      "ipam": {
        "type": "host-local",
        "routes": [
            { "dst": "0.0.0.0/0" }
        ],
        "ranges": [
            [{ "subnet": "10.85.0.0/16" }]
        ]
      }
    }
  ]
}

下载并设置 kubelet

下载 kubelet 的最新稳定版本。


curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubelet"

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/arm64/kubelet"

配置:

sudo mkdir -p /etc/kubernetes/manifests
sudo tee /etc/kubernetes/kubelet.yaml <<EOF
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
authentication:
  webhook:
    enabled: false # 请勿在生产集群中使用!
authorization:
  mode: AlwaysAllow # 请勿在生产集群中使用!
enableServer: false
logging:
  format: text
address: 127.0.0.1 # 限制对 localhost 的访问
readOnlyPort: 10255 # 请勿在生产集群中使用!
staticPodPath: /etc/kubernetes/manifests
containerRuntimeEndpoint: unix:///var/run/crio/crio.sock
EOF

安装:

chmod +x kubelet
sudo cp kubelet /usr/bin/

创建 systemd 服务单元文件:

sudo tee /etc/systemd/system/kubelet.service <<EOF
[Unit]
Description=Kubelet

[Service]
ExecStart=/usr/bin/kubelet \
 --config=/etc/kubernetes/kubelet.yaml
Restart=always

[Install]
WantedBy=multi-user.target
EOF

服务配置文件中故意省略了命令行参数 --kubeconfig。此参数设置 kubeconfig 文件的路径,指定如何连接到 API 服务器,以启用 API 服务器模式。省略此参数将启用独立模式。

启用并启动 kubelet 服务:

sudo systemctl daemon-reload
sudo systemctl enable --now kubelet.service

快速测试:

sudo systemctl is-active kubelet.service

输出类似于:

active

详细的服务检查:

sudo journalctl -u kubelet.service

检查 kubelet 的 API /healthz 端点:

curl http://localhost:10255/healthz?verbose

输出类似于:

[+]ping ok
[+]log ok
[+]syncloop ok
healthz check passed

查询 kubelet 的 API /pods 端点:

curl http://localhost:10255/pods | jq '.'

输出类似于:

{
  "kind": "PodList",
  "apiVersion": "v1",
  "metadata": {},
  "items": null
}

在 kubelet 中运行 Pod

在独立模式下,你可以使用 Pod 清单运行 Pod。这些清单可以放在本地文件系统上, 或通过 HTTP 从配置源获取。

为 Pod 创建一个清单:

cat <<EOF > static-web.yaml
apiVersion: v1
kind: Pod
metadata:
  name: static-web
spec:
  containers:
    - name: web
      image: nginx
      ports:
        - name: web
          containerPort: 80
          protocol: TCP
EOF

将 static-web.yaml 清单文件复制到 /etc/kubernetes/manifests 目录。

sudo cp static-web.yaml /etc/kubernetes/manifests/

查找 kubelet 和 Pod 的信息

Pod 网络插件为每个 Pod 创建一个网络桥(cni0)和一对 veth 接口 (这对接口的其中一个接口在新创建的 Pod 内,另一个接口在主机层面)。

查询 kubelet 的 API 端点 http://localhost:10255/pods:

curl http://localhost:10255/pods | jq '.'

要获取 static-web Pod 的 IP 地址:

curl http://localhost:10255/pods | jq '.items[].status.podIP'

输出类似于:

"10.85.0.4"

连接到 nginx 服务器 Pod,地址为 http://<IP>:<Port>(端口 80 是默认端口),在本例中为:

curl http://10.85.0.4

输出类似于:

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

了解更多细节

如果你需要排查在学习本教程时遇到的问题,你可以在以下目录中查找监控和故障排查资料:

/var/lib/cni
/var/lib/containers
/var/lib/kubelet

/var/log/containers
/var/log/pods

清理

kubelet

sudo systemctl disable --now kubelet.service
sudo systemctl daemon-reload
sudo rm /etc/systemd/system/kubelet.service
sudo rm /usr/bin/kubelet
sudo rm -rf /etc/kubernetes
sudo rm -rf /var/lib/kubelet
sudo rm -rf /var/log/containers
sudo rm -rf /var/log/pods

容器运行时

sudo systemctl disable --now crio.service
sudo systemctl daemon-reload
sudo rm -rf /usr/local/bin
sudo rm -rf /usr/local/lib
sudo rm -rf /usr/local/share
sudo rm -rf /usr/libexec/crio
sudo rm -rf /etc/crio
sudo rm -rf /etc/containers

网络插件

sudo rm -rf /opt/cni
sudo rm -rf /etc/cni
sudo rm -rf /var/lib/cni

结论

本页涵盖了以独立模式部署 kubelet 的各个基本方面。你现在可以部署 Pod 并测试更多功能。

请注意,在独立模式下,kubelet 不支持从控制平面获取 Pod 配置(因为没有控制平面连接)。

你还不能使用 ConfigMap 或 Secret 来配置静态 Pod 中的容器。

接下来

2 - 在 Kubernetes 节点上配置交换内存

本文演示了如何使用 kubeadm 在 Kubernetes 节点上制备和启用交换内存。

教程目标

  • 使用 kubeadm 在 Kubernetes 节点上制备交换内存。
  • 学习配置加密和未加密的交换内存。
  • 学习如何在系统启动时启用交换内存。

准备开始

你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

你的 Kubernetes 服务器版本必须不低于版本 1.33.

要获知版本信息,请输入 kubectl version.

你需要在集群中至少有一个运行 Linux 操作系统的工作节点。 本次演示需要先安装 kubeadm 工具,安装步骤请参考 kubeadm 安装指南。

在每个需要配置交换内存的工作节点上,你需要以下工具:

  • fallocate
  • mkswap
  • swapon

对于加密的交换空间(推荐),你还需要:

  • cryptsetup

使用 kubeadm 安装支持交换内存的集群

创建交换文件并启用交换内存

如果当前节点未启用交换内存,则需要先制备交换空间。 本节将展示如何以加密和未加密的方式创建 4GiB 的交换文件。

你可以使用如下命令设置加密的交换文件。 请注意,此示例使用的是 cryptsetup 工具(在大多数 Linux 发行版中都可用):

# 分配存储空间并限制访问权限
fallocate --length 4GiB /swapfile
chmod 600 /swapfile

# 基于已分配的存储空间创建加密设备
cryptsetup --type plain --cipher aes-xts-plain64 --key-size 256 -d /dev/urandom open /swapfile cryptswap

# 格式化此交换空间
mkswap /dev/mapper/cryptswap

# 为换页激活交换空间
swapon /dev/mapper/cryptswap

未加密的交换文件可以按以下方式配置:

# 分配存储空间并限制访问权限
fallocate --length 4GiB /swapfile
chmod 600 /swapfile

# 格式化此交换空间
mkswap /swapfile

# 为换页激活交换空间
swapon /swapfile

验证交换内存是否启用

你可以使用 swapon -s 命令或 free 命令来验证交换内存是否启用。

使用 swapon -s:

Filename       Type		Size		Used		Priority
/dev/dm-0      partition 	4194300		0		-2

使用 free -h:

               total        used        free      shared  buff/cache   available
Mem:           3.8Gi       1.3Gi       249Mi        25Mi       2.5Gi       2.5Gi
Swap:          4.0Gi          0B       4.0Gi

引导时启用交换内存

在设置好交换内存后,若要在系统引导时启动交换文件,通常有两种做法: 你可以设置一个 systemd 单元来激活(加密的)交换内存,或者在 /etc/fstab 文件中添加类似于 /swapfile swap swap defaults 0 0 的行。

使用 systemd 激活交换内存,可以确保在交换内存可用之前延迟启动 kubelet(如果你有这个需求)。 同样,使用 systemd 还可以让服务器在 kubelet(以及通常的容器运行时)关闭之前保持交换内存处于启用状态。

配置 kubelet

在节点上启用交换内存后,需要按如下方式配置 kubelet。 你需要为此节点选择一种交换内存行为。 在本教程中,你将配置 LimitedSwap 行为。

找到并编辑 kubelet 配置文件,然后:

  • 将 failSwapOn 设置为 false
  • 将 memorySwap.swapBehavior 设置为 LimitedSwap
# 此代码片段应添加到 kubelet 的配置文件中
failSwapOn: false
memorySwap:
    swapBehavior: LimitedSwap

为了使这些配置生效,需重启 kubelet。 通常你需要为此运行以下命令:

systemctl restart kubelet.service

你应该会发现 kubelet 现在处于健康状态,并且你可以根据需要运行使用交换内存的 Pod。

3 - 使用 Pod 级资源与 kubelet 资源管理器

特性状态: Beta since Kubernetes v1.37; (默认禁用)
More information about this feature

To use this feature, you (or a cluster administrator) will need to enable the PodLevelResourceManagers feature gate for all relevant components in your cluster.

See Enable Or Disable Feature Gates for more information.

本教程演示如何配置 kubelet 的资源管理器(拓扑、CPU 和内存) 以支持 Pod 级资源规约。你可以定义混合分配模型: 其中一些容器获得独占的、NUMA 对齐的基础设施资源, 而另一些容器则共享 Pod 级共享池中的剩余资源。

要了解该特性背后的概念,请阅读 Pod 级资源管理器概念页面。

教程目标

  • 配置 kubelet 资源管理器(CPU、内存和拓扑)以支持 Pod 级资源。
  • 使用 pod 拓扑管理器作用域部署工作负载,以实现混合的独占容器和共享容器,并达到单 NUMA 对齐。
  • 查看并验证 CPU 和内存资源如何在独占容器与 Pod 共享池之间划分。
  • 了解当 Pod 级共享池将为空时的准入拒绝规则。
  • 使用 container 拓扑管理器作用域和混合的容器分配部署工作负载。

准备开始

你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

你的 Kubernetes 服务器版本必须不低于版本 v1.36.

要获知版本信息,请输入 kubectl version.

要完成本教程,你需要:

  • 一个带有 Linux 工作节点的 Kubernetes 集群 (Pod 级资源管理器在 Windows 节点上不受支持)。
  • 至少一个具有 NUMA 拓扑的工作节点 (最好有多个 NUMA 节点,以便观察对齐情况)。
  • 在工作节点上拥有管理权限(root 或 sudo), 以修改 kubelet 配置并重启 kubelet 服务。
  • 拥有 kubectl 访问权限,且具备创建名字空间和 Pod 的权限。

确保为控制平面和工作节点启用了以下特性门控:

  • PodLevelResources
  • PodLevelResourceManagers

创建一个名字空间

创建一个名字空间,使本教程中创建的资源与集群的其余部分隔离开来:

kubectl create namespace plrm-tutorial

在混合分配场景中使用 Pod 作用域

当拓扑管理器的作用域设置为 pod 时,kubelet 会基于 .spec.resources 为整个 Pod 执行一次 NUMA 对齐。得到的资源预算随后被划分: 请求 Guaranteed 资源的容器获得独占切片, 而未获得独占分配的容器则在 Pod 级共享池中共享剩余的预算。

第 1 步:针对 Pod 作用域配置 kubelet

若要启用此行为,请在希望以所需策略运行这些工作负载的目标工作节点上配置 kubelet。 你可以按如下方式更新这些节点的 kubelet 配置:

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cpuManagerPolicy: "static"
memoryManagerPolicy: "Static"
topologyManagerScope: "pod"
topologyManagerPolicy: "single-numa-node"
  • 对于 topologyManagerPolicy,有效值为 single-numa-node、restricted 或 best-effort。 使用 Pod 级资源管理时,你不能指定其他任何值。

重启 kubelet 以应用该配置。例如,在使用 systemd 的 Linux 上: systemctl restart kubelet.service。

第 2 步:部署混合分配的 Pod

请考虑以下示例 Pod 清单。该 Pod 在 Pod 级别(.spec.resources) 请求总计 4 个 CPU 的预算。在该 Pod 内部:

  • main-app 容器请求 2 个完整 CPU 核心的独占分配 (requests = limits = 2 CPU)。
  • 由于 metrics-sidecar 和 logging-sidecar 未指定容器级别的 requests, 这两个边车容器共享 Pod 级共享池中剩余的 CPU 核心:2 个 CPU 核心。
apiVersion: v1
kind: Pod
metadata:
  name: pod-scope-mixed
  annotations:
    kubernetes.io/description: "A pod demonstrating pod-level scope where one container gets exclusive resources and others share the remaining pod resources in a shared pool."
spec:
  # 在 Pod 级别,该 Pod 的 CPU request 等于 limits,内存 request 也等于内存 limits。
  # main-app 容器在容器级别满足 Guaranteed QoS 类的要求,而各边车容器未指定任何资源 request。
  # 在 pod 范围下,这意味着 kubelet 可以静态地为整个 Pod 分配
  # 4 个 CPU,其中 2 个独占分配给 main-app 容器,剩余的 2 个由边车在该 Pod 的共享池中共享。
  resources:
    requests:
      cpu: "4"
      memory: "4Gi"
    limits:
      cpu: "4"
      memory: "4Gi"
  initContainers:
  - name: metrics-sidecar
    # 注意:这是仅用于演示的占位镜像,并不是真正的 metrics 助手。
    image: registry.k8s.io/pause:3.9
    restartPolicy: Always
  - name: logging-sidecar
    # 注意:这是仅用于演示的占位镜像,并不是真正的日志代理。
    image: registry.k8s.io/pause:3.9
    restartPolicy: Always
  containers:
  - name: main-app
    # 注意:这是仅用于演示的占位镜像。
    image: registry.k8s.io/pause:3.9
    resources:
      requests:
        cpu: "2"
        memory: "2Gi"
      limits:
        cpu: "2"
        memory: "2Gi"

将清单应用到你的集群:

kubectl apply -f https://k8s.io/examples/pods/resource/pod-level-resource-managers-pod-scope-mixed.yaml --namespace=plrm-tutorial

第 3 步:验证和检查资源划分

  1. 检查 Pod 是否成功运行:

    kubectl get pod pod-scope-mixed --namespace=plrm-tutorial
    
  1. 了解背后发生了什么:

    flowchart TD
        subgraph Pod["Pod-Level Budget: 4 CPUs, 4Gi Memory"]
            direction TB
            C1["main-app<br/>(Exclusive: 2 CPUs, 2Gi Memory)"]
            subgraph Pool["Pod Shared Pool: 2 CPUs, 2Gi Memory"]
                C2["metrics-sidecar"]
                C3["logging-sidecar"]
            end
        end
    • Pod 对齐: 拓扑管理器评估了 4 个 CPU 的 Pod 请求 (spec.resources),并将整个 Pod 分配到单个 NUMA 节点。
    • 独占分配: CPU 管理器为 main-app 分配了一个专属的 2 个 CPU 切片。 main-app 的 CPU CFS 配额节流被禁用,使其能够不受节流地访问这些独占核心。
    • Pod 共享池: 剩余的 2 个 CPU 构成一个 Pod 级共享池。 metrics-sidecar 和 logging-sidecar 未指定任何容器级资源 (resources: {}),因此它们在这个 Pod 隔离的共享池中运行, 并启用了 CFS 配额强制执行。虽然该池由这两个边车共享, 但它与节点上的外部工作负载隔离开来,为这些边车提供专属的 NUMA 局部性,并防止节点级资源争用。

观察空共享池的准入限制

使用 pod 作用域时,如果存在需要共享池的容器,而某些 Pod 规约会导致 Pod 共享池为空, 则 kubelet 的准入控制会拒绝这些 Pod 规约。

如果来自 Guaranteed 容器的独占资源请求之和等于 Pod 级总预算, 并且至少还有一个容器需要共享池,那么 kubelet 将拒绝该 Pod。

第 4 步:检查无效的 Pod 清单

请考虑以下清单。该 Pod 请求总计 4 个 CPU 的预算。 container-a 请求独占的 1 个 CPU,container-b 请求独占的 3 个 CPU (合计 4 个 CPU)。container-c 未请求独占资源且需要共享池,但已剩下 0 个 CPU:

apiVersion: v1
kind: Pod
metadata:
  name: empty-shared-pool
  annotations:
    kubernetes.io/description: "A pod demonstrating a configuration that is rejected because exclusive containers consume the entire pod resource budget, leaving no resources for the remaining container in the shared pool."
spec:
  # 在 Pod 级别,该 Pod 的 CPU request 等于 limits,内存 request 也等于内存 limits。
  # main-app 和 metrics-sidecar 容器在容器级别满足 Guaranteed QoS 类的要求,而
  # logging-sidecar 容器未指定任何资源 request。由于这些 Guaranteed 容器耗尽了
  # Pod 的全部资源预算,logging-sidecar 所需的共享池中已剩下 0 个 CPU,
  # 因此该 Pod 会在准入时被拒绝。
  resources:
    requests:
      cpu: "4"
      memory: "4Gi"
    limits:
      cpu: "4"
      memory: "4Gi"
  initContainers:
  - name: metrics-sidecar
    # 注意:这是仅用于演示的占位镜像,并不是真正的 metrics 助手。
    image: registry.k8s.io/pause:3.9
    restartPolicy: Always
    resources:
      requests:
        cpu: "1"
        memory: "1Gi"
      limits:
        cpu: "1"
        memory: "1Gi"
  - name: logging-sidecar
    # 注意:这是仅用于演示的占位镜像,并不是真正的日志代理。
    image: registry.k8s.io/pause:3.9
    restartPolicy: Always
  containers:
  - name: main-app
    # 注意:这是仅用于演示的占位镜像。
    image: registry.k8s.io/pause:3.9
    resources:
      requests:
        cpu: "3"
        memory: "3Gi"
      limits:
        cpu: "3"
        memory: "3Gi"

第 5 步:尝试部署并观察拒绝

  1. 应用此清单:

    kubectl apply -f https://k8s.io/examples/pods/resource/pod-level-resource-managers-empty-shared-pool.yaml --namespace=plrm-tutorial
    
  1. 查看 Pod 事件以观察准入错误:

    kubectl describe pod empty-shared-pool --namespace=plrm-tutorial
    

    请注意事件消息,它说明 kubelet 拒绝该 Pod 的原因是: 对于需要共享资源的容器而言,Pod 级共享池将为空:

    Status:           Failed
    Reason:           TopologyAffinityError
    Message:          Pod was rejected: Pod Scope pod with pod-level resources failed admission under pod-scope topology manager
    
    flowchart TD
        subgraph Pod["Pod-Level Budget: 4 CPUs, 4Gi Memory"]
            direction TB
            C1["container-a<br/>(Exclusive: 1 CPU, 1Gi Memory)"]
            C2["container-b<br/>(Exclusive: 3 CPUs, 3Gi Memory)"]
            subgraph Pool["Pod Shared Pool: 0 CPUs, 0Gi Memory"]
                C3["container-c<br/>(Requires shared pool)"]
            end
        end
        Pool --> Rejection["Admission Error: Pod Rejected!"]
        style Rejection fill:#ffcccc,stroke:#ff0000,stroke-width:2px

在混合分配场景下使用 container 作用域

你也可以将拓扑管理器的作用域配置为 container。在此模式下,kubelet 会逐个评估每个容器以进行独占分配, 而 .spec.resources 中的 Pod 总体预算仍会强制执行 QoS 和 cgroup 限额边界。

第 6 步:为 container 作用域配置 kubelet

在目标工作节点上更新你的 kubelet 配置,以使用 container 作用域:

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cpuManagerPolicy: "static"
memoryManagerPolicy: "Static"
topologyManagerScope: "container"
topologyManagerPolicy: "single-numa-node"

重启 kubelet 以应用该配置。例如,在使用 systemd 的 Linux 上: systemctl restart kubelet.service。

第 7 步:部署容器作用域的混合工作负载

请考虑以下示例 Pod 清单。该 Pod 的总预算为 4 个 CPU:

  • infrastructure-sidecar 请求独占的 2 个 CPU 切片(requests = limits = 2 CPU)。
  • worker-1 和 worker-2 未指定容器级别的 requests,而是在常规的节点级共享池中运行。
apiVersion: v1
kind: Pod
metadata:
  name: container-scope-mixed
  annotations:
    kubernetes.io/description: "A pod demonstrating container-level scope where one container gets exclusive resources and others run in the node's shared pool."
spec:
  # 在 Pod 级别,该 Pod 的 CPU request 等于 limits,内存 request 也等于内存 limits。
  # infrastructure-sidecar 容器在容器级别满足 Guaranteed QoS 类的要求,而各 worker
  # 容器未指定任何资源 request。在 container 范围下,kubelet 会逐个评估容器以进行独占分配。
  # 这意味着 infrastructure-sidecar 获得独占的 2 个 CPU 切片,
  # 而 worker 容器在节点的常规共享池中运行,同时所有容器的总量仍受 Pod 级限额约束。
  resources:
    requests:
      cpu: "4"
      memory: "4Gi"
    limits:
      cpu: "4"
      memory: "4Gi"
  initContainers:
  - name: infrastructure-sidecar
    # 注意:这是仅用于演示的占位镜像,并不是真正的基础设施助手。
    image: registry.k8s.io/pause:3.9
    restartPolicy: Always
    resources:
      requests:
        cpu: "2"
        memory: "2Gi"
      limits:
        cpu: "2"
        memory: "2Gi"
  containers:
  - name: worker-1
    # 注意:这是仅用于演示的占位镜像。
    image: registry.k8s.io/pause:3.9
  - name: worker-2
    # 注意:这是仅用于演示的占位镜像。
    image: registry.k8s.io/pause:3.9

应用该清单:

kubectl apply -f https://k8s.io/examples/pods/resource/pod-level-resource-managers-container-scope-mixed.yaml --namespace=plrm-tutorial

第 8 步:验证部署

  1. 检查 Pod 是否正在运行:

    kubectl get pod container-scope-mixed --namespace=plrm-tutorial
    
  1. 了解背后发生了什么:

    flowchart TD
        subgraph Pod["Pod-Level Budget: 4 CPUs, 4Gi Memory"]
            direction TB
            C1["infrastructure-sidecar<br/>(Exclusive NUMA Slice: 2 CPUs, 2Gi Memory)"]
            subgraph NodePool["Node Shared Pool: Pod-level limit"]
                C3["worker-2"]
                C2["worker-1"]
            end
        end
    • 容器作用域对齐: 在 container 作用域下,kubelet 会逐个评估容器。 infrastructure-sidecar 直接从节点的可分配池中获得一个独占的、NUMA 对齐的 2 个 CPU 切片。
    • 节点共享池: worker-1 和 worker-2 未指定任何容器级资源请求 (resources: {}),因此在 container 作用域下,它们在节点的常规共享池中运行(而不是在 Pod 隔离的池中)。
    • Pod 限额强制执行: 所有容器的 CPU 消耗总量仍由 Pod 级限额(spec.resources.limits)限制在 4 个 CPU 以内。

清理现场

删除本教程中创建的名字空间和所有示例 Pod:

kubectl delete namespace plrm-tutorial

接下来

4 - 名字空间演练

Kubernetes 名字空间有助于不同的项目、团队或客户去共享 Kubernetes 集群。

名字空间通过以下方式实现这点:

  1. 为名字设置作用域.
  2. 为集群中的部分资源关联鉴权和策略的机制。

使用多个名字空间是可选的。

此示例演示了如何使用 Kubernetes 名字空间细分集群。

准备开始

你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

要获知版本信息,请输入 kubectl version.

环境准备

此示例作如下假设:

  1. 你已拥有一个配置好的 Kubernetes 集群。
  2. 你已对 Kubernetes 的 Pod、 服务 和 Deployment 有基本理解。

理解默认名字空间

默认情况下,Kubernetes 集群会在配置集群时实例化一个默认名字空间,用以存放集群所使用的默认 Pod、Service 和 Deployment 集合。

假设你有一个新的集群,你可以通过执行以下操作来检查可用的名字空间:

kubectl get namespaces
NAME      STATUS    AGE
default   Active    13m

创建新的名字空间

在本练习中,我们将创建两个额外的 Kubernetes 名字空间来保存我们的内容。

我们假设一个场景,某组织正在使用共享的 Kubernetes 集群来支持开发和生产:

开发团队希望在集群中维护一个空间,以便他们可以查看用于构建和运行其应用程序的 Pod、Service 和 Deployment 列表。在这个空间里,Kubernetes 资源被自由地加入或移除, 对谁能够或不能修改资源的限制被放宽,以实现敏捷开发。

运维团队希望在集群中维护一个空间,以便他们可以强制实施一些严格的规程, 对谁可以或谁不可以操作运行生产站点的 Pod、Service 和 Deployment 集合进行控制。

该组织可以遵循的一种模式是将 Kubernetes 集群划分为两个名字空间:development 和 production。

让我们创建两个新的名字空间来保存我们的工作。

文件 namespace-dev.yaml 描述了 development 名字空间:

apiVersion: v1
kind: Namespace
metadata:
  name: development
  labels:
    name: development

使用 kubectl 创建 development 名字空间。

kubectl create -f https://k8s.io/examples/admin/namespace-dev.yaml

将下列的内容保存到文件 namespace-prod.yaml 中, 这些内容是对 production 名字空间的描述:

apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    name: production

让我们使用 kubectl 创建 production 名字空间。

kubectl create -f https://k8s.io/examples/admin/namespace-prod.yaml

为了确保一切正常,我们列出集群中的所有名字空间。

kubectl get namespaces --show-labels
NAME          STATUS    AGE       LABELS
default       Active    32m       <none>
development   Active    29s       name=development
production    Active    23s       name=production

在每个名字空间中创建 Pod

Kubernetes 名字空间为集群中的 Pod、Service 和 Deployment 提供了作用域。

与一个名字空间交互的用户不会看到另一个名字空间中的内容。

为了演示这一点,让我们在 development 名字空间中启动一个简单的 Deployment 和 Pod。

我们首先检查一下当前的上下文:

kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: REDACTED
    server: https://130.211.122.180
  name: lithe-cocoa-92103_kubernetes
contexts:
- context:
    cluster: lithe-cocoa-92103_kubernetes
    user: lithe-cocoa-92103_kubernetes
  name: lithe-cocoa-92103_kubernetes
current-context: lithe-cocoa-92103_kubernetes
kind: Config
preferences: {}
users:
- name: lithe-cocoa-92103_kubernetes
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED
    token: 65rZW78y8HbwXXtSXuUw9DbP4FLjHi4b
- name: lithe-cocoa-92103_kubernetes-basic-auth
  user:
    password: h5M0FtUUIflBSdI7
    username: admin
kubectl config current-context
lithe-cocoa-92103_kubernetes

下一步是为 kubectl 客户端定义一个上下文,以便在每个名字空间中工作。 "cluster" 和 "user" 字段的值将从当前上下文中复制。

kubectl config set-context dev --namespace=development \
  --cluster=lithe-cocoa-92103_kubernetes \
  --user=lithe-cocoa-92103_kubernetes

kubectl config set-context prod --namespace=production \
  --cluster=lithe-cocoa-92103_kubernetes \
  --user=lithe-cocoa-92103_kubernetes

默认情况下,上述命令会添加两个上下文到 .kube/config 文件中。 你现在可以查看上下文并根据你希望使用的名字空间并在这两个新的请求上下文之间切换。

查看新的上下文:

kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: REDACTED
    server: https://130.211.122.180
  name: lithe-cocoa-92103_kubernetes
contexts:
- context:
    cluster: lithe-cocoa-92103_kubernetes
    user: lithe-cocoa-92103_kubernetes
  name: lithe-cocoa-92103_kubernetes
- context:
    cluster: lithe-cocoa-92103_kubernetes
    namespace: development
    user: lithe-cocoa-92103_kubernetes
  name: dev
- context:
    cluster: lithe-cocoa-92103_kubernetes
    namespace: production
    user: lithe-cocoa-92103_kubernetes
  name: prod
current-context: lithe-cocoa-92103_kubernetes
kind: Config
preferences: {}
users:
- name: lithe-cocoa-92103_kubernetes
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED
    token: 65rZW78y8HbwXXtSXuUw9DbP4FLjHi4b
- name: lithe-cocoa-92103_kubernetes-basic-auth
  user:
    password: h5M0FtUUIflBSdI7
    username: admin

让我们切换到 development 名字空间进行操作。

kubectl config use-context dev

你可以使用下列命令验证当前上下文:

kubectl config current-context
dev

此时,我们从命令行向 Kubernetes 集群发出的所有请求都限定在 development 名字空间中。

让我们创建一些内容。

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: snowflake
  name: snowflake
spec:
  replicas: 2
  selector:
    matchLabels:
      app: snowflake
  template:
    metadata:
      labels:
        app: snowflake
    spec:
      containers:
      - image: registry.k8s.io/serve_hostname
        imagePullPolicy: Always
        name: snowflake

应用清单文件来创建 Deployment。

kubectl apply -f https://k8s.io/examples/admin/snowflake-deployment.yaml

我们创建了一个副本大小为 2 的 Deployment,该 Deployment 运行名为 snowflake 的 Pod, 其中包含一个仅提供主机名服务的基本容器。

kubectl get deployment
NAME         READY   UP-TO-DATE   AVAILABLE   AGE
snowflake    2/2     2            2           2m
kubectl get pods -l app=snowflake
NAME                         READY     STATUS    RESTARTS   AGE
snowflake-3968820950-9dgr8   1/1       Running   0          2m
snowflake-3968820950-vgc4n   1/1       Running   0          2m

这很棒,开发人员可以做他们想要的事情,而不必担心影响 production 名字空间中的内容。

让我们切换到 production 名字空间,展示一个名字空间中的资源如何对另一个名字空间不可见。

kubectl config use-context prod

production 名字空间应该是空的,下列命令应该返回的内容为空。

kubectl get deployment
kubectl get pods

生产环境需要以放牛的方式运维,让我们创建一些名为 cattle 的 Pod。

kubectl create deployment cattle --image=registry.k8s.io/serve_hostname --replicas=5
kubectl get deployment
NAME         READY   UP-TO-DATE   AVAILABLE   AGE
cattle       5/5     5            5           10s
kubectl get pods -l run=cattle
NAME                      READY     STATUS    RESTARTS   AGE
cattle-2263376956-41xy6   1/1       Running   0          34s
cattle-2263376956-kw466   1/1       Running   0          34s
cattle-2263376956-n4v97   1/1       Running   0          34s
cattle-2263376956-p5p3i   1/1       Running   0          34s
cattle-2263376956-sxpth   1/1       Running   0          34s

此时,应该很清楚地展示了用户在一个名字空间中创建的资源对另一个名字空间是不可见的。

随着 Kubernetes 中的策略支持的发展,我们将扩展此场景,以展示如何为每个名字空间提供不同的授权规则。