kubernetes生产化集群管理
生产化 Kubernetes 集群管理
1. 计算节点相关
生产化集群的考量
在构建和维护生产级别的 Kubernetes 集群时,计算节点和控制平面都需要仔细考虑以下问题:
计算节点 (Worker Nodes):
- 操作系统管理:
- 如何实现操作系统的大规模批量安装和升级?自动化工具(如 Kickstart, Preseed, Packer, Foreman)和镜像管理(如 ostree)是关键。
- 如何统一管理和配置节点的网络信息(IP 地址、DNS、路由等)?配置管理工具(Ansible, SaltStack, Chef, Puppet)或云平台提供的服务可以提供帮助。
- 节点多样性管理:
- 如何有效管理具有不同硬件配置(CPU, 内存, GPU, 存储类型等)的节点(即不同的 SKU - Stock Keeping Unit)?利用 Kubernetes 的标签(Labels)和污点(Taints)/容忍(Tolerations)机制进行分类和调度。
- 生命周期管理:
- 如何快速、安全地下架出现故障的计算节点?需要结合监控、自动化的故障检测和节点排空(Drain)机制。
- 如何根据负载需求快速扩容或缩容集群的计算节点规模?集群自动伸缩器(Cluster Autoscaler)是核心解决方案。
控制平面 (Control Plane):
- 组件部署与升级:
- 如何在主节点(Master Nodes)上高效地下载、安装和升级控制平面核心组件(kube-apiserver, etcd, kube-scheduler, kube-controller-manager)及其所需的配置文件?通常使用 kubeadm、k3s、RKE 或云厂商提供的托管服务。
- 如何确保集群依赖的其他关键插件(如 CoreDNS 用于服务发现、监控系统如 Prometheus/Grafana、日志系统如 EFK/Loki)已正确部署并正常运行?使用 Helm Charts 或 Operator 模式进行管理。
- 安全:
- 如何生成、分发和管理控制平面组件所需的各种安全证书(CA 证书、API Server 证书、kubelet 证书等)?需要建立一套安全的证书管理流程,可以使用
kubeadm
的证书管理功能或专门的工具如cert-manager
。
- 如何生成、分发和管理控制平面组件所需的各种安全证书(CA 证书、API Server 证书、kubelet 证书等)?需要建立一套安全的证书管理流程,可以使用
- 版本管理:
- 如何实现控制平面组件的快速、可靠升级或在出现问题时进行版本回滚?需要制定详细的升级计划,利用滚动升级、蓝绿部署等策略,并确保有可靠的备份和恢复机制(尤其是 etcd)。
2. 操作系统选择
为 Kubernetes 计算节点选择合适的操作系统是构建稳定、高效、安全集群的基础。
操作系统的评估与选择
主要有两大类操作系统可供选择:
- 通用操作系统 (General Purpose OS):
- 代表: Ubuntu, CentOS, Fedora
- 优点: 生态成熟、社区支持广泛、用户熟悉度高、软件包丰富。
- 缺点: 通常包含较多非必需组件,攻击面相对较大,系统升级可能涉及较多依赖和风险。
- 专为容器优化的操作系统 (Container-Optimized OS):
- 代表: CoreOS (已被 Red Hat 收购并演化), Red Hat Atomic Host, Snappy Ubuntu Core, RancherOS, Flatcar Container Linux (CoreOS 分支), Bottlerocket (AWS)
- 特点:
- 最小化: 只包含运行容器和管理节点所必需的组件,减少资源占用和攻击面。
- 原子化升级/回滚: 通常采用基于镜像或文件系统快照的方式进行升级,保证操作的原子性,易于回滚。
- 不可变性 (Immutability): 核心系统文件通常是只读的,提高了安全性,符合云原生理念。
评估和选型的标准:
- 生态系统: 是否有活跃的社区、商业支持、丰富的文档和工具链?
- 成熟度: 该操作系统在生产环境中的应用历史和稳定性表现如何?
- 内核版本: 内核版本是否足够新,以支持 Kubernetes 所需的特性(如 Cgroups v2, eBPF 等)?内核的稳定性和安全补丁更新频率如何?
- 容器运行时支持: 对 Docker, containerd, CRI-O 等主流容器运行时的兼容性和支持程度如何?
- 初始化系统 (Init System): 通常是 systemd,需要考虑其管理服务的便利性和稳定性。
- 包管理和系统升级: 包管理工具是否易用?系统升级机制是否可靠、安全(如原子化升级)?
- 安全: 是否提供强化的安全特性?补丁更新是否及时?社区或厂商的安全响应能力如何?
生态系统与成熟度
容器优化操作系统的优势:
- 小 (Small Footprint): 系统镜像体积小,启动速度快,资源消耗低。
- 原子级升级和回退 (Atomic Updates & Rollbacks): 升级过程要么完全成功,要么完全回退到之前的状态,降低了升级风险。
- 更高的安全性 (Enhanced Security): 最小化的组件和只读根文件系统减少了潜在的攻击面。
不同容器优化 OS 的成熟度比较 (基于原始 PDF 内容):
操作系统 | 类型 | 成熟度与特点 |
---|---|---|
CentOS / Ubuntu | 通用操作系统 | 成熟,生态庞大,用户基数大 |
CoreOS | 容器优化 | 最早的容器优化 OS 之一,理念先进,但原公司已被收购,现在更多以 Flatcar 或 Red Hat CoreOS 形式存在。 |
Atomic* | 容器优化 | Red Hat 出品,基于 RPM 生态,品质有保证,与 RHEL/CentOS 结合紧密。 |
Snappy | 容器优化 | Canonical (Ubuntu) 出品,最初为移动和 IoT 设备设计,采用 Snap 包格式。 |
RancherOS | 容器优化 | 理念独特,系统中几乎所有服务(包括 systemd/udev)都作为 Docker 容器运行,相对较新,现在是 SUSE 的一部分。 |
*注:Atomic Host 项目已不再积极开发,其理念和技术被整合到 RHEL CoreOS (RHCOS) 中,用于 OpenShift 集群。
云原生的原则:不可变基础设施
可变基础设施 (Mutable Infrastructure) 的风险:
- 配置漂移 (Configuration Drift): 在服务运行过程中,持续的手动修改或自动化脚本修改服务器配置,可能导致服务器状态与预期配置不一致,难以追踪和复现。
- 重建困难: 当发生灾难需要重建服务时,由于缺乏精确的操作记录和自动化流程,很难精确地恢复到之前的运行状态。
- 状态不一致: 持续的修改引入了中间状态,可能导致不可预知的问题,类似于程序中可变变量在并发环境下引入的风险。
不可变基础设施 (Immutable Infrastructure):
- 核心理念: 一旦部署,基础设施(服务器、容器等)就不再被修改。如果需要更新或修复,则用新的实例替换旧的实例。
- 实践:
- 不可变的容器镜像 (Immutable Container Images): Dockerfile 定义了镜像的构建过程,一旦构建完成,镜像内容不再改变。更新应用需要构建新镜像并重新部署。
- 不可变的主机操作系统 (Immutable Host OS): 采用容器优化 OS,操作系统本身是只读的,升级通过替换整个系统镜像或文件系统层来实现。
优势:
- 一致性: 保证了环境的一致性,简化了测试和部署。
- 可靠性: 减少了配置漂移和手动操作引入的错误。
- 可预测性: 部署和升级过程更加可预测和可重复。
- 安全性: 减少了攻击面,更易于管理安全补丁。
Atomic 操作系统
- 背景: 由 Red Hat 支持的,旨在提供面向容器优化的、不可变的基础设施。有基于 Fedora, CentOS, RHEL 的不同版本。 (注意:如前所述,此项目已演进)
- 核心优势:
- 不可变性: 操作系统核心部分是只读的(通常只有
/etc
和/var
可写),提高了安全性和稳定性。 - 面向容器优化: 最小化安装,集成了容器运行时和相关工具。
- 灵活性与安全性: 结合了传统 Linux 的灵活性和不可变基础设施的安全性。
- 不可变性: 操作系统核心部分是只读的(通常只有
- 关键技术: rpm-ostree:
- 定义: 一个开源项目,结合了 RPM 包管理和 OSTree(类似 Git 的文件系统版本管理)技术。
- 功能:
- 允许以原子方式管理系统包,构建可启动的操作系统镜像。
- 支持操作系统的原子升级和回滚。可以轻松切换到不同的系统版本(commit)。
- 使得在生产环境中构建和管理操作系统镜像更加简单和可靠。
最小化主机操作系统
- 原则:
- 只安装运行 Kubernetes 节点(kubelet, 容器运行时等)和基本系统维护所绝对必需的工具。
- 任何临时的调试工具(如
tcpdump
,strace
,perf
等性能或网络排查工具)都应在需要时以容器的形式运行(例如,通过kubectl debug
或部署特权调试 Pod)。
- 意义:
- 性能: 更少的后台进程和服务意味着更低的资源消耗(CPU, 内存),为业务容器提供更多资源。
- 稳定性: 更少的组件意味着更少的潜在故障点和软件冲突。
- 安全保障: 最小化的攻击面,减少了被利用的风险。补丁管理也更简单。
操作系统构建流程

- 流程概述:
- 基础 RPM 包: 从上游 (Upstream Patch) 或内部 Mirror 获取基础的 RPM 包。
- RPM 构建 (rpm builder): 可能用于构建自定义的 RPM 包。
- RPM 快照 (Snapshot): 将选定的 RPM 包集合形成一个快照。
- ostree 构建 (rpm-ostree): 使用
rpm-ostree
工具,基于 RPM 快照和可能的 Docker 镜像 (通过buildah
处理),构建成 ostree commit (版本化的文件系统树)。 - ostree 仓库 (ostree httpd): 将构建好的 ostree commit 存储在可通过 HTTP 访问的仓库中。
- 镜像构建 (Packer Builder): 使用 Packer 等工具,从 ostree 仓库拉取指定的 commit,并结合 Glance (OpenStack 镜像服务) 或其他机制,构建成虚拟机镜像 (如 qcow2, vhd) 或物理机部署所需的文件。
- 部署:
- 虚拟机: 通过 OpenStack 或其他虚拟化平台部署镜像。
- 物理机: 通过 Foreman 等 PXE 引导工具,加载 Kickstart/Preseed 文件,该文件指示系统从 ostree 仓库部署操作系统。
- 节点运行 (K8s Nodes): 部署好的节点加入 Kubernetes 集群。
- 更新 (OS Updater): 节点上的 OS Updater (可能是
rpm-ostree
自身或自定义脚本) 定期检查 ostree 仓库是否有新版本,并执行原子化升级。
ostree 详解
- 核心库: 提供
libostree
共享库和一系列命令行工具。 - 类 Git 操作: 提供类似 Git 的命令行体验 (
ostree commit
,ostree pull
,ostree checkout
等),用于提交、下载和管理完整的、可启动的文件系统树的版本。每个版本是一个 commit hash。 - 启动加载器集成: 提供将 ostree 管理的文件系统版本部署到 Bootloader (如 GRUB) 的机制,允许在启动时选择不同的系统版本。
- 示例 (Dracut 模块):
ostree-prepare-root
服务在 initramfs 阶段运行,准备挂载 ostree 管理的根文件系统。
- 示例 (Dracut 模块):
1 |
|
构建 ostree
- 工具:
rpm-ostree
- 功能:
- 基于
treefile
(JSON 格式的配置文件) 将指定的 RPM 包构建成 ostree commit。 - 管理 ostree 仓库和节点的 Bootloader 配置。
- 基于
- treefile (配置文件示例): 定义了构建 ostree commit 所需的元数据和包列表。
1 |
|
- 构建命令:
1 |
|
加载 ostree
节点初始化:
初始化 OS: 在目标节点上,初始化 ostree 管理环境。
1
2ostree admin os-init <osname>
# 例如: ostree admin os-init centos-atomic-host添加远程仓库: 添加存储 ostree commit 的 HTTP 仓库地址。
1
2ostree remote add <remote_name> <repo_url>
# 例如: ostree remote add atomic http://ostree.svr/ostree拉取 Commit: 从远程仓库拉取指定的 ostree commit (版本)。
1
2ostree pull <remote_name> <ref>
# 例如: ostree pull atomic centos-atomic-host/8/x86_64/standard部署 OS: 将拉取的 commit 部署为可启动的系统,并配置内核启动参数 (kargs)。
1
2ostree admin deploy --os=<osname> <ref> --karg='<kernel_argument>'
# 例如: ostree admin deploy --os=centos-atomic-host centos-atomic-host/8/x86_64/standard --karg='root=/dev/mapper/atomicos-root'
ostree 仓库结构 (示例): [Image 2: ostree 仓库目录结构示例]
objects/
: 存储所有文件内容的去重块 (类似 Git 的 objects)。refs/heads/
: 存储分支引用 (ref),指向具体的 commit hash。repo/config
: 仓库配置文件。
操作系统加载方式
- 物理机 (Bare Metal):
- 通常使用网络引导 (PXE Boot) 结合自动化部署工具,如 Foreman。
- PXE 服务器提供引导程序,加载 Kickstart (Red Hat/CentOS/Fedora) 或 Preseed (Debian/Ubuntu) 自动化安装脚本。
- Kickstart/Preseed 脚本中包含执行
ostree admin deploy
的命令,从 ostree 仓库完成操作系统的部署。
- 虚拟机 (Virtual Machine):
- 需要使用镜像构建工具 (如 Packer) 将 ostree commit 打包成特定虚拟化平台支持的镜像格式 (如 QCOW2 for KVM/OpenStack, VHD for Hyper-V, VMDK for VMware, RAW)。
- 然后通过虚拟化管理平台 (如 OpenStack Glance, vCenter) 上传和部署这些镜像。
生产环境陷阱与定制化参数
在生产环境中使用特定操作系统或配置时,可能会遇到各种问题:
遇到的陷阱示例:
- cloud-init Bug (0.7.7): 在某些版本中,cloud-init 可能无法正确应用静态网络配置,导致节点初始化失败或网络不通。
- Docker Bug (1.9.1): 旧版本 Docker 在处理快速输出的大量日志时可能存在内存泄漏问题。
- Kernel Panic (4.4.6): 特定内核版本在 Cgroup 创建和销毁操作频繁时可能触发内核崩溃 (Kernel Panic)。
- rootfs 分区过小:
- 根分区 (
/
) 空间不足会导致各种问题,例如 Docker 守护进程无法启动、无法写入日志、无法创建新容器等。 - 导致占满的原因:
- CI/CD 过程中的构建工具(如 Maven)可能将大量依赖下载到临时目录 (
/tmp
),如果/tmp
在根分区,可能耗尽空间。 - 容器日志增长过快,超出了日志轮转 (log rotation) 的处理能力,导致单个日志文件或日志总量撑爆磁盘。 解决方案: 合理配置容器日志驱动(如
json-file
的max-size
,max-file
),或使用集中的日志收集方案。
- CI/CD 过程中的构建工具(如 Maven)可能将大量依赖下载到临时目录 (
- 根分区 (
需要定制化的操作系统参数:
- 背景: 某些应用(如 Elasticsearch, Ceph)对操作系统的默认参数有特定要求。
- 示例: Elasticsearch 要求
vm.max_map_count
内核参数至少为262144
,而大多数 Linux 发行版默认值可能只有65530
。 - 解决方案: 需要在操作系统镜像构建阶段或节点初始化阶段(如通过 cloud-init, Ignition, 或配置管理工具)就将这些必要的参数配置好,确保节点启动后即满足应用需求。
3. 节点资源管理
在 Kubernetes 集群中,节点 (Node) 是运行工作负载 (Pod) 的基本单元。对节点的资源进行有效管理是确保集群稳定性、性能和资源利用率的关键。生产环境中的节点资源管理需要考虑多个维度,包括节点自身的健康状态、为系统组件预留资源、防止资源耗尽的机制以及如何精确地为容器分配和限制资源。本章将深入探讨这些方面。
NUMA Node (Non-Uniform Memory Access)
在现代多处理器服务器架构中,NUMA (Non-Uniform Memory Access) 是一个重要的内存设计。其核心思想是,处理器访问本地内存(直接连接到该处理器的内存)的速度要快于访问远程内存(连接到其他处理器的内存)。如下图所示,一个系统可能包含多个 NUMA Node,每个 Node 包含若干 CPU 核心和本地内存。

- 意义: 当一个 Pod 内的进程运行时,如果其使用的 CPU 核心和内存分布在不同的 NUMA Node 上,就会发生跨 NUMA Node 的内存访问,导致性能下降。对于需要高性能、低延迟的应用(如数据库、DPDK 应用、高性能计算),NUMA 亲和性至关重要。
- Kubernetes 处理: Kubernetes Kubelet 默认情况下并不保证 Pod 内的 CPU 和内存分配在同一个 NUMA Node 上。然而,通过 Topology Manager(Kubelet 的一个特性门控功能),可以实现更精细的资源拓扑感知调度。当设置为
single-numa-node
策略时,对于 Guaranteed QoS 的 Pod,Kubelet 会尝试将所有分配的 CPU 核心和内存都限制在同一个 NUMA Node 内,从而提升性能。这通常需要与 CPU Manager (static
策略) 和 Memory Manager (static
策略,较新版本支持) 配合使用。
状态上报 (Status Reporting)
Kubelet 作为运行在每个节点上的代理,承担着监控节点状态并将信息汇报给 API Server 的核心职责。这些信息对于集群的调度决策和健康状况判断至关重要。
- 汇报内容:
- 节点基础信息: 包括节点的 IP 地址、主机名、操作系统类型与版本、Linux 内核版本、容器运行时(Docker/Containerd)版本、Kubelet 版本、Kube-proxy 版本等。
- 节点资源信息: 包括 CPU 总量、内存总量、HugePages 大页内存、临时存储 (Ephemeral Storage)、可用的 GPU 等硬件设备信息,以及这些资源中可以分配给 Pod 使用的部分 (Allocatable Resources)。
- 节点状态 (Conditions): Kubelet 会定期更新一系列反映节点当前状况的状态条件。这些 Conditions 是调度器(kube-scheduler)和控制器(如 Node Controller)判断节点是否可用、是否处于压力状态的关键依据。
- 常见的节点 Conditions 及其意义:
状态 (Condition Type) 状态的意义 Ready 节点是否健康并准备好接受 Pod 调度。 True
表示健康,False
表示不健康,Unknown
表示节点控制器在一段时间内未收到 Kubelet 的心跳。MemoryPressure 节点是否存在内存资源压力。 True
表示内存紧张。PIDPressure 节点上是否存在进程 ID (PID) 资源压力,即进程数量过多。 True
表示 PID 紧张。DiskPressure 节点是否存在磁盘空间压力(通常指 Kubelet 使用的根分区或镜像分区)。 True
表示磁盘空间紧张。NetworkUnavailable 节点的网络配置是否尚未正确设置或存在问题。 True
表示网络不可用。 - 调度决策: kube-scheduler 在为 Pod 选择目标节点时,会过滤掉
Ready
状态不为True
的节点。同时,如果节点存在MemoryPressure
、DiskPressure
或PIDPressure
状态,调度器默认会给节点添加相应的 Taint (污点),阻止新的 Pod(除非 Pod 能容忍这些 Taint)被调度到该节点上,这是一种保护机制,避免节点资源进一步耗尽。
Lease 对象 (Lease Objects)
在早期 Kubernetes 版本中,Kubelet 通过频繁更新其对应的 Node 对象 来向 API Server 汇报心跳,表明自身存活。然而,Node 对象通常很大,包含了大量的静态信息和状态,频繁更新会对 API Server 和 etcd 造成显著压力,尤其是在大规模集群中。
为了解决这个问题,Kubernetes 引入了 Lease 对象 (位于 coordination.k8s.io
API 组)。
- 作用: Lease 对象是一种轻量级的资源,专门用于节点心跳。每个节点在
kube-node-lease
命名空间下维护一个对应的 Lease 对象。Kubelet 会定期更新这个 Lease 对象的renewTime
字段。 - 机制: Node Controller 会监控 Lease 对象。如果一个 Lease 对象在
nodeLeaseDurationSeconds
(默认 40 秒)内没有被更新,Node Controller 就会认为该节点失联,并将节点的Ready
Condition 更新为Unknown
。这大大降低了心跳机制对 API Server 和 etcd 的负载。 - 配置示例 (Lease Object YAML):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17apiVersion: coordination.k8s.io/v1
kind: Lease
metadata:
creationTimestamp: "2021-08-19T02:50:09Z"
name: k8snode # 通常是节点名
namespace: kube-node-lease
ownerReferences:
- apiVersion: v1
kind: Node
name: k8snode
uid: 58679942-e2dd-4ead-aada-385f099d5f56
resourceVersion: "1293702"
uid: 1bf51951-b832-49da-8708-4b224b1ec3ed
spec:
holderIdentity: k8snode # 持有者标识,通常是节点名
leaseDurationSeconds: 40 # 租约持续时间
renewTime: "2021-09-08T01:34:16.489589Z" # 上次续约时间
资源预留 (Resource Reservation)
Kubernetes 节点上除了运行用户的 Pod 外,还必须运行许多支撑系统运行的基础服务。这些服务包括 操作系统本身的守护进程 (如 systemd, journald, sshd),容器运行时 (如 dockerd, containerd),以及 Kubernetes 自身的组件 (如 kubelet, kube-proxy)。
- 问题: 如果不为这些系统关键进程预留资源,当用户 Pod 消耗过多资源时,可能导致系统进程因资源不足而运行缓慢甚至崩溃,进而影响整个节点的稳定性和功能。
- 解决方案: Kubelet 提供了参数来为这些非 Pod 进程预留一部分节点资源。这些资源将从节点总资源中扣除,不会被计入可供 Pod 分配的资源 (Allocatable)。
--kube-reserved=[cpu=100m,memory=256Mi,ephemeral-storage=1Gi,pid=1000]
: 为 Kubernetes 系统组件 (kubelet, kube-proxy, 容器运行时等) 预留资源。--system-reserved=[cpu=100m,memory=256Mi,ephemeral-storage=1Gi,pid=1000]
: 为操作系统级别的系统守护进程 (systemd, journald, sshd 等) 预留资源。--reserved-cpus=0,1
: (CPU Manager static 策略下)显式保留某些物理 CPU 核心,不用于 Pod 分配,专门给系统或 Kubelet 进程使用。
- 配置: 这些参数通常在 Kubelet 的启动配置文件 (如
/etc/kubernetes/kubelet.conf
或 systemd service unit 文件) 中设置。合理的预留值需要根据节点的实际负载和运行的系统服务来确定,通常需要进行测试和调整。
Capacity 与 Allocatable
理解节点的 Capacity (总容量) 和 Allocatable (可分配容量) 对于资源管理和调度至关重要。
Capacity: 指 Kubelet 检测到的节点硬件资源总量。
- CPU: 通常来源于 Linux
/proc/cpuinfo
文件,表示逻辑 CPU 核心数。 - Memory: 通常来源于 Linux
/proc/meminfo
文件,表示节点总物理内存。 - Ephemeral Storage: 指节点根分区 (
/
) 或者 Kubelet 配置的用于存储 Pod 日志、emptyDir
卷等的临时存储分区的总大小。
- CPU: 通常来源于 Linux
Allocatable: 指节点上实际可供用户 Pod 申请和使用的资源量。它是通过从 Capacity 中减去为系统预留的资源以及 Kubelet 驱逐阈值所保留的资源得到的。
Allocatable = Capacity - KubeReserved - SystemReserved - Eviction Thresholds
调度依据: kube-scheduler 只关心节点的 Allocatable 资源。当调度 Pod 时,它会检查节点的 Allocatable 资源是否满足 Pod 的
requests
。查看: 可以通过
kubectl describe node <node-name>
命令查看节点的 Capacity 和 Allocatable 信息。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 示例输出片段
Capacity:
cpu: 24
ephemeral-storage: 205838Mi
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 179504088Ki # 约 171 GiB
pods: 110
Allocatable:
cpu: 24
ephemeral-storage: 205838Mi # 示例中未配置预留或驱逐阈值
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 177304536Ki # 约 169 GiB (Capacity - 预留/驱逐)
pods: 110
节点磁盘管理 (Node Disk Management)
Kubelet 会监控节点上与 Pod 运行相关的两个主要文件系统(如果存在的话),以防止磁盘耗尽导致的问题。
nodefs
: 通常指包含 Kubelet 工作目录(默认为/var/lib/kubelet
)的文件系统,也可能是节点的根文件系统 (/
)。这个分区用于存储 Pod 的emptyDir
卷、容器日志、镜像层(如果imagefs
不独立)、以及 Kubelet 自身的一些数据。imagefs
: (可选)指容器运行时(如 Docker, Containerd)专门用于存储容器镜像和容器可写层 (writable layers) 的文件系统。如果配置了独立的imagefs
,它可以减轻nodefs
的压力。如果未配置,镜像和可写层通常会存储在nodefs
上(例如 Docker 默认的/var/lib/docker
可能与/var/lib/kubelet
在同一分区)。
Kubelet 会监控这两个文件系统的可用空间和 inode 数量,并在资源紧张时触发 DiskPressure 状态和可能的驱逐操作。
驱逐管理 (Eviction Management)
驱逐 (Eviction) 是 Kubelet 在节点资源不足时,为了保证节点稳定性而主动停止(驱逐)一个或多个 Pod 的过程。这是 Kubernetes 自愈能力 的体现,防止节点因资源耗尽而完全崩溃。
- 触发: 当节点的可用内存、磁盘空间、可用 inode 或可用 PID 低于 Kubelet 配置的驱逐阈值 (Eviction Thresholds) 时,驱逐机制会被触发。
- 行为:
- Kubelet 不会直接删除 Pod 对象。它会终止 Pod 内的容器进程。
- 被驱逐的 Pod 的
status.phase
会被标记为Failed
。 - Pod 的
status.reason
会被设置为Evicted
。 - Pod 的
status.message
会记录被驱逐的具体原因(例如 “Node tenia disk pressure: …”)。
- 重要性: 驱逐是一种 防御机制,旨在牺牲部分 Pod 以保证节点及其上运行的其他关键 Pod 和系统服务的存活。
资源可用额监控 (Available Resource Monitoring)
Kubelet 需要持续监控节点的可用资源,以便及时发现资源压力并做出响应(如设置 Condition 或触发驱逐)。
- 数据来源: Kubelet 内部集成了 cAdvisor (Container Advisor) 组件,用于收集节点和容器的资源使用情况。
- 监控指标: Kubelet 主要关注不可压缩资源 (Incompressible Resources) 的可用量,因为这些资源的耗尽会直接导致系统或应用失败。CPU 是可压缩资源,CPU 繁忙通常只会导致性能下降,一般不直接触发驱逐(除非配置了特定策略)。
memory.available
: 节点当前可用的内存量 (通常基于/proc/meminfo
中的MemAvailable
)。nodefs.available
:nodefs
文件系统的可用磁盘空间。nodefs.inodesFree
:nodefs
文件系统的可用 inode 数量。imagefs.available
: (如果存在)imagefs
文件系统的可用磁盘空间。imagefs.inodesFree
: (如果存在)imagefs
文件系统的可用 inode 数量。
- 检查周期: Kubelet 会定期(默认 10 秒)检查这些指标。
驱逐策略 (Eviction Policy)
Kubelet 根据预先配置的驱逐策略来决定何时以及如何驱逐 Pod。
- 驱逐阈值 (Eviction Thresholds): 定义了触发驱逐的资源条件。阈值可以是绝对值(如
memory.available<100Mi
)或百分比(如nodefs.available<10%
)。- 硬驱逐 (Hard Eviction): 当资源可用量低于硬驱逐阈值时,Kubelet 立即 开始驱逐 Pod,没有宽限期 (Grace Period)。这是为了尽快回收资源,防止节点崩溃。
- 配置示例:
--eviction-hard=memory.available<100Mi,nodefs.available<5%,nodefs.inodesFree<5%
- 配置示例:
- 软驱逐 (Soft Eviction): 当资源可用量低于软驱逐阈值,并且持续时间超过了指定的宽限期 (Grace Period) 后,Kubelet 才开始驱逐 Pod。这提供了一个缓冲期,让系统或 Pod 有机会自行恢复或被优雅地移除。
- 配置示例:
--eviction-soft=memory.available<200Mi,nodefs.available<10%
- 宽限期配置:
--eviction-soft-grace-period=memory.available=1m30s,nodefs.available=5m
(指定特定信号的宽限期) - Pod 终止宽限期: 软驱逐还会考虑 Pod 自身的
terminationGracePeriodSeconds
,取两者中的较小值。
- 配置示例:
- 硬驱逐 (Hard Eviction): 当资源可用量低于硬驱逐阈值时,Kubelet 立即 开始驱逐 Pod,没有宽限期 (Grace Period)。这是为了尽快回收资源,防止节点崩溃。
- 最小回收量 (
evictionMinimumReclaim
): 可以配置 Kubelet 在每次驱逐操作后尝试回收的最小资源量,以避免因阈值波动导致过于频繁的小规模驱逐。- 配置示例:
--eviction-minimum-reclaim=memory.available=0Mi,nodefs.available=500Mi,imagefs.available=2Gi
- 配置示例:
- 驱逐策略表:
Kubelet 参数 分类 驱逐方式 evictionSoft
软驱逐 当检测到当前资源达到软驱逐的阈值时,并不会立即启动驱逐操作,而是要等待一个 宽限期 (Grace Period)。这个宽限期选取 evictionSoftGracePeriod
和 Pod 指定的terminationGracePeriodSeconds
中较小的值。evictionHard
硬驱逐 没有宽限期,一旦检测到满足硬驱逐的条件,就直接中止 Pod 来释放资源。
基于内存压力的驱逐 (Eviction based on Memory Pressure)
当 memory.available
低于设定的驱逐阈值(默认硬驱逐是 memory.available<100Mi
)时:
- 设置 Condition: Kubelet 会将节点的
MemoryPressure
Condition 设置为True
。 - 调度器反应: 调度器会避免向该节点调度新的 BestEffort Pod(通过 Taint)。
- 启动驱逐: Kubelet 开始驱逐 Pod 以回收内存。
- 驱逐顺序:
- (候选): 首先判断 Pod 的内存使用量是否超出了其
requests
。超出请求的 Pod 成为主要的驱逐候选目标。如果所有 Pod 都在其请求范围内,那么所有 Pod 都可能被驱逐。 - (优先级): 按照 Pod 的 服务质量 (QoS) 等级 和 运行时优先级 (Priority) 进行排序。优先级最低的先被驱逐:
- BestEffort QoS 的 Pod (没有设置 requests 和 limits)。
- Burstable QoS 的 Pod,且内存使用量超出了其
requests
。 - Guaranteed QoS 的 Pod,以及内存使用量未超出
requests
的 Burstable Pod(理论上不应发生,除非系统预留不足或计算错误,但在极端情况下可能被驱逐)。
- (内存使用量): 在相同优先级/QoS 等级内,当前内存使用量超出其
requests
值. 最多. 的 Pod 会被优先驱逐。
- (候选): 首先判断 Pod 的内存使用量是否超出了其
基于磁盘压力的驱逐 (Eviction based on Disk Pressure)
当 nodefs
或 imagefs
的可用空间或 inode 低于设定的驱逐阈值时:
- 设置 Condition: Kubelet 会将节点的
DiskPressure
Condition 设置为True
。 - 调度器反应: 调度器会避免向该节点调度新的 Pod(通过 Taint)。
- 启动驱逐/清理: Kubelet 开始回收磁盘空间。
- 回收行为:
- 第一阶段:清理容器和镜像 (如果适用)
- 有独立
imagefs
分区:- Kubelet 首先删除已退出 (dead) 的容器 (清理
nodefs
上的日志和可写层残留)。 - 然后 Kubelet 删除未被使用的镜像 (清理
imagefs
)。
- Kubelet 首先删除已退出 (dead) 的容器 (清理
- 无独立
imagefs
分区 (共享nodefs
):- Kubelet 同时删除已退出的容器和未被使用的镜像。
- 有独立
- 第二阶段:驱逐运行中的 Pod (如果第一阶段后仍满足驱逐条件)
- (候选): 判断 Pod 的本地临时存储 (ephemeral-storage) 使用量是否超出了其
requests
。超出请求的 Pod 成为主要驱逐候选目标。 - (优先级): 同样按照 Pod 的 QoS 等级和运行时优先级排序(BestEffort -> Burstable -> Guaranteed)。
- (磁盘使用量): 在相同优先级/QoS 等级内,当前本地临时存储使用量超出其
requests
值最多的 Pod 会被优先驱逐。
- (候选): 判断 Pod 的本地临时存储 (ephemeral-storage) 使用量是否超出了其
- 第一阶段:清理容器和镜像 (如果适用)
容器和资源配置 (Cgroup)
Kubernetes 使用 Linux Cgroups (Control Groups) 来限制和隔离 Pod 及容器的资源使用。Kubelet 会根据 Pod 的 QoS (Quality of Service) Class 将 Pod 组织在不同的 Cgroup 层级下。
- QoS Classes:
- Guaranteed: Pod 中所有容器都必须同时设置了 CPU 和 Memory 的
requests
和limits
,并且requests
值必须等于limits
值。 - Burstable: Pod 中至少有一个容器设置了 CPU 或 Memory 的
requests
,但不满足 Guaranteed 的条件(例如requests
<limits
,或只有部分容器设置了资源)。 - BestEffort: Pod 中所有容器都没有设置 CPU 或 Memory 的
requests
和limits
。
- Guaranteed: Pod 中所有容器都必须同时设置了 CPU 和 Memory 的
- Cgroup Hierarchy (示例 for CPU): Kubelet 通常会在
/sys/fs/cgroup/cpu/kubepods.slice/
(或其他配置的 Cgroup Root 下) 创建层级结构:kubepods-besteffort.slice
: 用于 BestEffort Pod。kubepods-burstable.slice
: 用于 Burstable Pod。kubepods-pod<PodUID>.slice
: 每个 Guaranteed 和 Burstable Pod 拥有自己的 Cgroup Slice。docker-<ContainerID>.scope
(或crio-...
): 每个容器在 Pod 的 Slice 下有自己的 Cgroup Scope。
1
2
3
4
5
6
7
8
9
10
11
12
13/sys/fs/cgroup/cpu
└── kubepods.slice
├── kubepods-besteffort.slice
│ └── kubepods-pod<PodUID_A>.slice
│ ├── docker-<ContainerID_A1>.scope
│ └── docker-<ContainerID_A2>.scope
├── kubepods-burstable.slice
│ └── kubepods-pod<PodUID_B>.slice
│ ├── docker-<ContainerID_B1>.scope
│ └── docker-<ContainerID_B2>.scope
└── kubepods-pod<PodUID_C>.slice # Guaranteed Pod directly under kubepods
├── docker-<ContainerID_C1>.scope
└── docker-<ContainerID_C2>.scope(注意: 实际层级可能因 Kubelet Cgroup driver (systemd/cgroupfs) 和版本略有不同,上图为简化示意)
- Cgroup Hierarchy (示例 for Memory): 内存 Cgroup 结构类似。
1
2
3
4
5
6
7
8
9
10
11
12
13/sys/fs/cgroup/memory
└── kubepods.slice
├── kubepods-besteffort.slice
│ └── kubepods-pod<PodUID_A>.slice
│ ├── docker-<ContainerID_A1>.scope
│ └── docker-<ContainerID_A2>.scope
├── kubepods-burstable.slice
│ └── kubepods-pod<PodUID_B>.slice
│ ├── docker-<ContainerID_B1>.scope
│ └── docker-<ContainerID_B2>.scope
└── kubepods-pod<PodUID_C>.slice
├── docker-<ContainerID_C1>.scope
└── docker-<ContainerID_C2>.scope
CPU CGroup 配置
Kubelet 会将 Pod Spec 中定义的 CPU requests
和 limits
转换为对应的 Cgroup 参数。
CGroup 类型 | 参数 | QoS 类型 | 值 (示例) | 说明 |
---|---|---|---|---|
容器的 CGroup | cpu.shares |
BestEffort | 2 |
权重最低,在 CPU 资源竞争时获得最少时间片。 |
Burstable | requests.cpu * 1024 (转换为 millicores * 1.024,近似等比) |
权重根据 requests 按比例分配。requests 越高,权重越大。 |
||
Guaranteed | requests.cpu * 1024 |
同 Burstable,权重根据 requests (等于 limits ) 分配。 |
||
cpu.cfs_quota_us |
BestEffort | -1 |
-1 表示无限制,容器可以使用节点空闲的 CPU 资源,但权重最低。 |
|
Burstable | limits.cpu * 100000 (假定 period=100ms) |
在每个调度周期 (cfs_period_us, 通常 100ms) 内,最多使用 limits.cpu 对应的 CPU 时间。如果未设置 limit,则为 -1 (无限制)。 |
||
Guaranteed | limits.cpu * 100000 |
同 Burstable,严格限制 CPU 使用不超过 limits.cpu 。 |
||
Pod 的 CGroup | cpu.shares |
BestEffort | 2 |
Pod 整体权重最低。 |
Burstable | Pod 所有容器 (requests.cpu * 1024) 之和 |
Pod 整体权重是其下所有容器 requests 对应权重之和。 |
||
Guaranteed | Pod 所有容器 (requests.cpu * 1024) 之和 |
同 Burstable。 | ||
cpu.cfs_quota_us |
BestEffort | -1 |
Pod 整体无 CPU 硬限制。 | |
Burstable | Pod 所有容器 (limits.cpu * 100000) 之和 (如果容器设置了 limit) |
Pod 整体的 CPU 硬限制是其下所有设置了 limits 的容器的 CPU limit 之和。 |
||
Guaranteed | Pod 所有容器 (limits.cpu * 100000) 之和 |
Pod 整体严格限制 CPU 使用,等于其下所有容器的 limit 之和。 |
内存 CGroup 配置
Kubelet 会将 Pod Spec 中定义的 Memory limits
转换为内存 Cgroup 的 memory.limit_in_bytes
参数。Memory requests
主要用于调度和计算 oom_score_adj
,不直接设置 Cgroup 参数(但 Memory Manager 静态策略下会利用 request)。
CGroup 类型 | 参数 | QoS 类型 | 值 (示例) | 说明 |
---|---|---|---|---|
容器的 CGroup | memory.limit_in_bytes |
BestEffort | 9223372036854771712 (非常大的值, 约等于无限制) |
BestEffort 容器没有内存硬限制,但内存压力大时最先被 OOM Kill。 |
Burstable | limits.memory (转换为 bytes) |
如果设置了 limits.memory ,则容器内存使用不能超过此值。如果未设置,则同 BestEffort。 |
||
Guaranteed | limits.memory (转换为 bytes) |
严格限制内存使用不超过 limits.memory (等于 requests.memory )。 |
||
Pod 的 CGroup | memory.limit_in_bytes |
BestEffort | 9223372036854771712 |
Pod 整体没有内存硬限制。 |
Burstable | 所有设置了 limits.memory 的容器的 limits.memory (转换为 bytes) 之和 |
Pod 整体的内存硬限制是其下所有设置了 limits 的容器的 limit 之和。 |
||
Guaranteed | 所有容器的 limits.memory (转换为 bytes) 之和 |
Pod 整体严格限制内存使用,等于其下所有容器的 limit 之和。 |
OOM Killer 行为 (Out Of Memory Killer)
当节点内存严重不足时,Linux 内核的 OOM Killer 会介入,选择一个或多个进程杀死以释放内存。Kubernetes 通过设置 Cgroup 和调整进程的 oom_score_adj
值来影响 OOM Killer 的决策,以保护更重要的 Pod。
oom_score
: 内核为每个进程计算一个oom_score
(0-1000)。分数越高,越容易被 OOM Killer 选中。分数主要基于进程使用的物理内存占系统总内存的百分比。oom_score_adj
: Kubelet 可以为容器内的进程设置oom_score_adj
值 (-1000 到 1000)。这个值会调整内核计算出的oom_score
。-1000
: 完全禁止 OOM Killer 杀死该进程。1000
: 使进程非常容易被 OOM Killer 杀死。
计算逻辑: 进程的最终 OOM 评分大致为
(内存使用百分比 * 10) + oom_score_adj
(简化)。Kubernetes QoS 与
oom_score_adj
: Kubelet 根据 Pod 的 QoS 类型和内存requests
来设置容器进程的oom_score_adj
:Pod QoS 类型 oom_score_adj
说明 Guaranteed -998
非常不容易被 OOM Kill,受到最高保护。Kubelet 自身和其他关键系统进程通常有更低的 oom_score_adj
(如 -999)。BestEffort 1000
最容易被 OOM Kill,在内存不足时最先被牺牲。 Burstable min(max(2, 1000 - (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999)
动态计算,介于 2 和 999 之间。内存 requests
越接近节点总内存,oom_score_adj
越低,保护程度越高;requests
越小,越容易被 OOM Kill。
日志管理 (Log Management)
节点上系统服务和容器产生的大量日志如果不加管理,会迅速耗尽磁盘空间,触发 DiskPressure 甚至导致节点不可用。
- 系统日志: 节点操作系统层面的日志(如 systemd journal, /var/log/),需要配置
logrotate
或类似的工具进行*定期轮转 (rotate) 和 清理 (clean)。- 配置: 编辑
/etc/logrotate.conf
或/etc/logrotate.d/
下的配置文件,设置轮转周期(daily, weekly)、保留文件数 (rotate N
)、大小限制 (size M
)、压缩 (compress
) 等。 - 注意:
logrotate
的执行周期(通常是 cron 任务)不能过长,否则可能在周期到来前磁盘就被写满;也不能过短,以免过于频繁地操作影响性能。需要合理配置触发条件(如大小和时间结合)。
- 配置: 编辑
- 容器日志:
- Docker/Containerd: 容器运行时本身通常提供日志驱动 (logging driver),如
json-file
,可以配置每个容器日志文件的最大大小 (max-size
) 和 最大文件数 (max-file
)。容器运行时会在写入前检查大小并执行轮转。这是推荐的方式。- Docker 配置示例 (
/etc/docker/daemon.json
):1
2
3
4
5
6
7{
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
}
}
- Docker 配置示例 (
- Kubelet: Kubelet 也可以管理容器日志(主要是通过定期运行
du
检查大小)。可以通过 Kubelet 参数--container-log-max-size
和--container-log-max-files
来设置全局默认值(但优先级低于运行时自身的配置)。
- Docker/Containerd: 容器运行时本身通常提供日志驱动 (logging driver),如
Docker 卷管理 (Docker Volume Management - Legacy Context)
- Dockerfile
VOLUME
指令: 在 Dockerfile 中使用VOLUME
指令会创建一个由 Docker 管理的卷。在 Kubernetes 环境中,这种卷的管理和生命周期与 Kubernetes 的 Volume 机制不兼容,强烈建议不要在用于 Kubernetes 的镜像的 Dockerfile 中使用VOLUME
指令。应使用 Kubernetes 的 Volume 类型(如emptyDir
,hostPath
,persistentVolumeClaim
)来管理数据。 - 容器可写层与
emptyDir
: 如果容器向其可写层 (writable layer) 或挂载的emptyDir
卷大量写入数据,会消耗节点上的临时存储 (nodefs
)。这可能导致磁盘 I/O 过高,影响节点上其他 Pod 和系统进程的性能,并可能触发DiskPressure
和驱逐。 - I/O 限制: Docker 和 Containerd 运行时都基于 Cgroup v1 (在许多环境中仍然是主流) 对块设备 I/O 的限制支持有限,特别是对缓冲 I/O (Buffered I/O) 的支持不佳。虽然可以限制直接 I/O (Direct I/O),但大多数应用使用缓冲 I/O。这意味着很难精确地限制 Pod 的磁盘 I/O 速率。Cgroup v2 提供了更好的 I/O 控制能力,但需要操作系统和 Kubelet 配置 Cgroup v2 支持。对于有特殊 I/O 性能或隔离需求的场景,建议使用独立的物理卷或网络存储卷 (PersistentVolume),而不是依赖共享的节点临时存储。
网络资源 (Network Resources)
默认情况下,Kubernetes 不对 Pod 的网络带宽进行限制。但是,可以通过 CNI (Container Network Interface) 插件 来实现带宽控制。
- 机制: 支持带宽限制的 CNI 插件(如 Calico, Cilium, 或社区的
bandwidth
插件)通常利用 Linux Traffic Control (TC) 子系统来实现。TC 允许在网络接口上配置队列规则 (qdisc) 和 过滤器 (filter) 来整形 (shape) 或管制 (police) 流量。 - 配置: 可以通过在 Pod 的 annotations (注解) 中添加特定字段来声明期望的带宽限制。社区
bandwidth
插件使用的注解是:kubernetes.io/ingress-bandwidth
: Pod 的入向带宽限制。kubernetes.io/egress-bandwidth
: Pod 的出向带宽限制。
1
2
3
4
5
6
7
8
9
10
11apiVersion: v1
kind: Pod
metadata:
name: nginx-bw-limited
annotations:
kubernetes.io/ingress-bandwidth: 10M # 限制入向带宽为 10 Mbps
kubernetes.io/egress-bandwidth: 20M # 限制出向带宽为 20 Mbps
spec:
containers:
- name: nginx
image: nginx - 注意: 具体的注解和支持情况取决于所使用的 CNI 插件。需要查阅 CNI 插件的文档。
进程数 (Process Count / PIDs)
节点上的可用 PID (Process ID) 数量是有限的(由内核参数 kernel.pid_max
决定,通常在 /proc/sys/kernel/pid_max
查看)。如果节点上的总进程数(包括系统进程和所有 Pod 内的进程)过多,耗尽了 PID 资源,将无法创建新的进程,导致节点功能异常。
- 限制:
- Kubelet 默认不限制单个 Pod 可以创建的子进程数量。
- 可以通过 Kubelet 启动参数
--pod-max-pids
来限制每个 Pod 内可以运行的总 PID 数量。这是一个重要的稳定性保障措施,防止 “PID 泄漏” 或 “fork bomb” 类型的应用耗尽节点 PID 资源。 - 可以通过 Kubelet 参数
--system-reserved-pids
和--kube-reserved-pids
(如果支持)为系统和 Kubernetes 组件预留一部分 PID,确保它们在 PID 紧张时仍能正常工作。
- 监控与压力:
- Kubelet 会周期性地检查节点当前的 PID 使用量。
- 如果可用 PID 数量少于设定的阈值(通过
--eviction-hard
或--eviction-soft
配置,例如pid.available<1k
),Kubelet 会将节点的PIDPressure
Condition 标记为True
。 - kube-scheduler 会避免将新的 Pod 调度到处于
PIDPressure
状态的节点上(通过 Taint)。 - 如果达到硬驱逐阈值,Kubelet 会根据驱逐策略开始驱逐 Pod 以回收 PID 资源(通常优先驱逐 BestEffort 和 Burstable Pod)。
通过对 NUMA、状态上报、资源预留、Capacity/Allocatable、磁盘、内存、CPU、网络和 PID 等多方面的精细管理,可以显著提升 Kubernetes 生产集群的稳定性、性能和资源利用效率。
4. 节点异常检测
在 Kubernetes 集群的日常运维中,节点(Node)的健康状态是保障整个集群稳定运行和应用高可用的基石。一个节点可能因为硬件故障、内核问题、资源耗尽(内存、磁盘、PID)、网络配置错误或 Kubelet 自身异常等原因而变得不可用或状态不佳。Kubernetes 需要一套机制来及时发现这些异常节点,并根据情况采取相应的处理措施(如驱逐 Pod),这就是节点异常检测的核心目标。
Kubelet:节点状态的守卫者
每个 Kubernetes 工作节点上都运行着一个关键的代理进程:Kubelet。Kubelet 不仅负责管理本机上的 Pod 生命周期(如启动、停止容器),还承担着监控节点自身健康状况并将状态汇报给 Kubernetes API Server 的重要职责。
Kubelet 会定期收集节点的各种指标,例如 CPU 使用率、内存使用量、磁盘空间、网络连通性等。它通过与底层的 Linux 内核交互来获取这些信息,例如:
- 内存使用和压力:Kubelet 会检查
/proc/meminfo
文件以及 cgroups 提供的内存统计信息,来判断节点当前的内存使用情况以及是否存在内存压力。内核的 OOM Killer (Out Of Memory Killer) 日志也是判断内存问题的重要依据。 - 磁盘使用和压力:Kubelet 会监控其管理的关键文件系统(通常是根分区
/
和 Docker/containerd 使用的数据存储分区,如/var/lib/docker
或/var/lib/containerd
)的磁盘使用率和 inode 使用率。通过statfs
系统调用获取这些信息。当可用空间低于预设的阈值时,会标记磁盘压力。 - PID 压力:Linux 内核对进程 ID(PID)的数量是有限制的(可通过
/proc/sys/kernel/pid_max
查看)。如果节点上运行了过多的进程,耗尽了可用的 PID,将导致无法创建新的进程。Kubelet 会监控当前 PID 的使用情况,并与内核限制进行比较,判断是否存在 PID 压力。cgroups v1 和 v2 也提供了 PIDs 控制器来限制一个 cgroup 内可以创建的进程数。 - 网络可用性:Kubelet 会检查节点网络是否按预期配置完成。这通常依赖于 CNI (Container Network Interface) 插件是否成功初始化并配置了节点的网络,确保 Pod 可以获得 IP 地址并进行通信。如果 CNI 插件报告失败或网络路由、设备出现问题,Kubelet 会标记网络不可用。
Kubelet 将收集到的状态信息,特别是下面将要详述的 Node Conditions,定期(由 Kubelet 的启动参数 --node-status-update-frequency
控制,默认为 10 秒)通过 NodeStatus 对象上报给 API Server。
Node Conditions:节点健康状态的快照
NodeStatus
中最重要的部分是 Conditions 字段,它是一个列表,包含了描述节点当前状态的关键布尔型标志。这些 Conditions 是 Kubernetes 判断节点健康与否的主要依据。核心的 Node Conditions 包括:
Ready
:这是最核心的条件。- 状态意义:表示节点是否健康并且准备好接收、运行新的 Pod。如果
Ready
为True
,表示 Kubelet 正常运行,网络插件正常工作,节点可以承担工作负载。如果为False
或Unknown
,则表示节点存在问题,Scheduler 不会将新的 Pod 调度到该节点,并且控制平面(Node Controller)可能会在宽限期后驱逐该节点上的 Pod。 - 检测原理:Kubelet 综合评估自身健康、容器运行时状态以及网络插件状态来决定
Ready
条件。任何导致 Kubelet 无法正常工作或无法与容器运行时/CNI 插件通信的问题都可能导致Ready
变为False
。
- 状态意义:表示节点是否健康并且准备好接收、运行新的 Pod。如果
MemoryPressure
:- 状态意义:表示节点是否存在内存资源压力。如果为
True
,意味着节点可用内存不足,可能会影响现有 Pod 的运行,甚至触发内核的 OOM Killer。 - 检测原理:Kubelet 根据配置的**驱逐阈值(Eviction Thresholds)**来判断。例如,通过 Kubelet 启动参数
--eviction-hard=memory.available<1Gi
设置硬驱逐阈值,当可用内存低于 1Gi 时,Kubelet 会将MemoryPressure
标记为True
,并开始尝试驱逐 Pod 以回收内存。还有软驱逐阈值 (--eviction-soft
和--eviction-soft-grace-period
) 提供更灵活的驱逐策略。Kubelet 通过读取 cgroups V1 的memory.usage_in_bytes
和memory.limit_in_bytes
,或者 cgroups V2 的memory.current
和memory.max
,结合/proc/meminfo
来计算可用内存。
- 状态意义:表示节点是否存在内存资源压力。如果为
PIDPressure
:- 状态意义:表示节点是否存在 PID 资源压力。如果为
True
,意味着节点上可用 PID 数量紧张,可能无法创建新的进程,影响 Pod 甚至节点自身的稳定性。 - 检测原理:同样基于 Kubelet 配置的驱逐阈值,如
--eviction-hard=pid.available<1k
。Kubelet 会检查当前已分配的 PID 数量与系统或 cgroup 限制的差值。内核参数kernel.pid_max
定义了系统全局的 PID 上限,而 cgroups PIDs 控制器可以限制特定 cgroup(如 Kubelet 或 Pod)的 PID 数量。
- 状态意义:表示节点是否存在 PID 资源压力。如果为
DiskPressure
:- 状态意义:表示节点是否存在磁盘空间压力。如果为
True
,意味着 Kubelet 管理的关键磁盘分区(通常是nodefs
- 节点根文件系统,和imagefs
- 容器镜像和可写层存储文件系统)的可用空间不足。这可能导致无法写入日志、无法创建新的容器镜像层、甚至无法调度新的 Pod(如果需要写数据到这些分区)。 - 检测原理:基于 Kubelet 配置的驱逐阈值,如
--eviction-hard=nodefs.available<10%
,--eviction-hard=imagefs.available<15%
。Kubelet 使用statfs
系统调用检查对应文件系统的可用块和总块数,以及可用 inode 和总 inode 数,来判断是否达到压力阈值。
- 状态意义:表示节点是否存在磁盘空间压力。如果为
NetworkUnavailable
:- 状态意义:表示节点的网络配置是否不正确或未就绪。如果为
True
,意味着该节点的网络尚未被正确配置(通常由 CNI 插件负责),Pod 可能无法获得 IP 地址或与其他 Pod/服务通信。 - 检测原理:这个条件的设置通常由网络插件自己决定。Kubelet 会提供一个接口(例如,通过特定的文件或配置状态)让网络插件告知其配置状态。如果 Kubelet 未收到网络插件已就绪的信号,或者插件明确报告了错误,Kubelet 会将此条件设置为
True
。这通常只在特定的云提供商环境或网络配置场景下使用,并且需要 Kubelet 配置了相应的 cloud provider。对于大多数标准的 CNI 部署,Ready
条件已经隐含了网络的基本可用性。
- 状态意义:表示节点的网络配置是否不正确或未就绪。如果为
你可以通过以下命令查看一个节点的详细状态,包括它的 Conditions:
1 |
|
输出中会有一个 Conditions
的部分,清晰地展示了每个条件的状态 (True
, False
, Unknown
)、最后一次探测到状态的时间 (lastProbeTime
) 以及最后一次状态转变的时间 (lastTransitionTime
) 和原因 (reason
)、消息 (message
)。
1 |
|
Node Controller:异常节点的处理者
Kubernetes 控制平面中有一个重要的组件叫做 Node Controller(作为 kube-controller-manager
的一部分运行)。它的职责之一就是监控所有节点的状态,特别是它们的 Ready
Condition 和心跳。
心跳检测:Kubelet 会定期向 API Server 发送“心跳”来表明自己还活着并且能够通信。在早期版本中,这主要是通过更新 NodeStatus 实现。为了减少 API Server 的负载,Kubernetes 引入了 Lease 对象(位于
kube-node-lease
命名空间)。每个节点都有一个对应的 Lease 对象,Kubelet 会以更高的频率(由 Kubelet 参数--node-lease-duration-seconds
控制,默认为 40 秒,更新频率约为其 1/4)更新这个 Lease 对象。Node Controller 会同时监视 NodeStatus 和 Lease 对象。标记节点状态:Node Controller 会根据心跳情况更新节点的
Ready
Condition 状态为Unknown
。它使用--node-monitor-period
(默认为 5s)检查节点状态,如果在一个时间窗口(由--node-monitor-grace-period
控制,默认为 40s)内没有收到 Kubelet 的心跳(无论是 NodeStatus 更新还是 Lease 更新),Node Controller 会将该节点的Ready
Condition 标记为Unknown
。污点与驱逐:当 Node Controller 检测到节点状态变为
NotReady
(Ready
条件为False
)或Unreachable
(Ready
条件为Unknown
超过一定时间)时,它会自动给这个节点添加相应的污点(Taints):node.kubernetes.io/not-ready
: Node conditionReady
isFalse
.node.kubernetes.io/unreachable
: Node conditionReady
isUnknown
.
这些污点会阻止新的 Pod(除非 Pod 有相应的容忍度 Toleration)被调度到该节点。更重要的是,Node Controller 在添加这些污点后,会等待一个驱逐宽限期(由kube-controller-manager
的参数--pod-eviction-timeout
控制,默认为 5 分钟)。如果节点在此期间恢复正常(Ready
变为True
),污点会被移除。如果超时后节点状态仍未恢复,Node Controller 将会触发节点上所有 Pod 的驱逐(Eviction)流程,将它们从异常节点上删除,以便 Deployment、StatefulSet 等控制器可以在健康的节点上重新创建这些 Pod,实现应用的自愈。
Node Problem Detector (NPD):更主动、更细粒度的检测
虽然 Kubelet 能够检测到一些基本的节点问题(如资源压力),但对于更深层次或特定于环境的问题(如内核死锁、硬件故障、文件系统只读、网络黑洞等),Kubelet 可能无法直接感知。为了弥补这一不足,社区开发了 Node Problem Detector (NPD)。
解决了什么问题:NPD 旨在主动发现并报告 Kubelet 可能忽略的节点级别问题。它将这些问题转化为标准的 Kubernetes API 对象(Node Conditions 或 Events),使得集群管理员和自动化系统能够更容易地监控和响应这些问题。
是什么:NPD 通常以 DaemonSet 的形式部署在集群的每个(或部分)节点上。它是一个独立于 Kubelet 的程序。
工作原理:NPD 通过监控各种系统信号源来发现问题:
- 系统日志:监控
journald
、kern.log
、dmesg
等系统日志,通过预定义的正则表达式匹配错误或异常模式(例如,EXT4 文件系统错误、NMI watchdog 超时、硬件错误信息 MCE)。 - 系统状态文件:检查特定的系统文件或
/proc
,/sys
下的状态信息。 - 自定义插件/脚本:可以扩展 NPD,运行自定义的健康检查脚本来检测特定应用或硬件相关的问题。
当 NPD 检测到问题时,它会与 API Server 通信,执行以下操作之一: - 更新节点的 Condition:它可以添加自定义的 Node Condition(例如
KernelDeadlock=True
,FilesystemReadOnly=True
)到节点的status.conditions
字段。这些自定义 Condition 可以被监控系统捕获,或者被自定义的控制器用来触发特定操作。 - 创建 Event:为节点生成一个 Kubernetes Event,详细描述发现的问题。这对于事后分析和告警非常有用。
- 系统日志:监控
配置示例:NPD 的行为通过 ConfigMap 进行配置,定义了要监控的日志源、匹配规则以及发现问题时要报告的 Condition 或 Event 模板。例如,一个规则可能配置为:当在内核日志中检测到 “kernel BUG at” 字符串时,将节点的
KernelOops
Condition 设置为True
。
1 |
|
通过部署和配置 NPD,运维团队可以获得比 Kubelet 默认检查更广泛、更深入的节点健康视图,从而能够更早地发现并处理潜在的节点故障,进一步提升集群的稳定性和可靠性。
5. 常⽤节点问题排查⼿段
当 Kubernetes 集群中的某个节点出现异常(例如状态变为 NotReady
或 Unknown
,或者节点上的 Pod 持续失败)时,快速有效地定位并解决问题至关重要。这需要结合 Kubernetes 提供的工具和深入节点内部进行排查。
从 Kubernetes 控制平面视角检查
首先,我们应该从 Kubernetes 控制平面的视角来了解节点的宏观状态。
- 查看节点状态和 Conditions:使用
kubectl get node <node-name> -o wide
可以快速查看节点的基本状态、IP 地址、运行的 Kubelet 和 Kube-proxy 版本等。更详细的信息需要kubectl describe node <node-name>
。仔细检查Conditions
部分,确认哪个条件(Ready
,MemoryPressure
,DiskPressure
,PIDPressure
,NetworkUnavailable
)处于异常状态 (False
或Unknown
),并关注LastTransitionTime
,Reason
,Message
字段,它们通常会给出问题的初步线索。同时,检查节点的污点(Taints),确认是否有node.kubernetes.io/not-ready
或node.kubernetes.io/unreachable
等自动添加的污点。 - 检查节点事件:
kubectl describe node <node-name>
的输出底部会包含与该节点相关的事件(Events)。这些事件记录了 Kubelet 的关键活动(如启动、注册失败)、资源压力的触发、驱逐决策、以及 Node Problem Detector (如果部署) 报告的问题等。通过kubectl get events --field-selector involvedObject.kind=Node,involvedObject.name=<node-name> --sort-by='.lastTimestamp'
可以按时间顺序查看更完整的节点事件列表。 - 检查节点资源使用情况 (K8s 视角):使用
kubectl top node <node-name>
可以快速查看节点当前的 CPU 和内存使用量以及请求/限制的总和。这有助于判断是否存在整体资源不足。
深入节点内部进行诊断
如果控制平面的信息不足以定位问题,或者需要验证节点内部的实际情况,就需要登录到问题节点(通常通过 SSH)进行更深入的排查。
检查核心系统资源:
- CPU:使用
top
,htop
,vmstat 1
或mpstat -P ALL 1
检查 CPU 使用率。关注%us
(用户空间),%sy
(内核空间),%wa
(IO 等待),%si
(软中断)。高%wa
可能指向磁盘或网络 I/O 瓶颈,高%si
可能与网络流量处理有关。查看负载平均值 (Load Average) (uptime
或top
),持续高于 CPU 核心数可能表示系统过载。 - 内存:使用
free -h
查看总内存、已用内存、可用内存 (available
)。重点关注available
内存,它比free
更能反映实际可供应用程序使用的内存。检查 Swap 使用情况(free -h
或swapon -s
),过多的 Swap 使用通常表示内存严重不足。使用top
或htop
按内存使用排序进程。检查内核 OOM (Out Of Memory) Killer 日志:sudo dmesg | grep -i oom-killer
。 - 磁盘:使用
df -h
检查文件系统空间使用率,特别是根分区 (/
) 和容器存储分区(如/var/lib/docker
或/var/lib/containerd
)。使用df -i
检查inode 使用率,inode 耗尽也会导致无法创建新文件。如果怀疑 I/O 性能问题,使用iostat -xz 1
查看磁盘的读写速率、await
时间、%util
等指标。高await
和高%util
表明磁盘 I/O 存在瓶颈。检查内核日志中是否有文件系统错误:sudo dmesg | grep -iE "error|failed|corrupt|ext4|xfs"
。 - PID:使用
ps aux | wc -l
粗略估计当前进程数,并与系统 PID 上限比较:cat /proc/sys/kernel/pid_max
。如果使用了 cgroups PID 控制器,需要检查相应 cgroup 的 PID 限制和当前使用情况。
- CPU:使用
检查 Kubelet 服务:
- 状态:
systemctl status kubelet
查看服务是否正在运行,是否有错误日志片段。 - 日志:Kubelet 日志是排查节点问题的核心。通常使用
journalctl -u kubelet -f
实时跟踪日志,或journalctl -u kubelet --since "10 minutes ago"
查看近期日志。关注错误信息,例如:- 无法连接 API Server (
Failed to list *v1.Node: Get "https://<apiserver-ip>:6443/api/v1/nodes?..."
)。 - PLEG (Pod Lifecycle Event Generator) 问题 (
PLEG is not healthy
),通常表明 Kubelet 与容器运行时通信或获取 Pod 状态时遇到困难。 - 同步 Pod 状态错误 (
syncPod failed
)。 - Volume 挂载/卸载失败。
- CNI 网络插件调用失败。
- 资源驱逐相关的日志 (
eviction manager: attempting to reclaim...
)。
- 无法连接 API Server (
- 状态:
检查容器运行时 (Docker/Containerd):
- 状态:
systemctl status docker
或systemctl status containerd
。 - 日志:
journalctl -u docker
或journalctl -u containerd
。关注镜像拉取失败、存储驱动错误、创建 sandbox (网络命名空间) 失败、启动容器失败等信息。 - 列出容器:使用
crictl ps -a
(推荐,与 CRI 接口交互) 或docker ps -a
查看节点上所有容器(包括已停止的)的状态。crictl pods
查看 Pod sandbox。 - 检查特定容器日志:
crictl logs <container-id>
或docker logs <container-id>
。
- 状态:
检查网络连通性和配置:
- 基础连通性:
ping <apiserver-ip>
,ping <other-node-ip>
,ping <cluster-gateway>
。使用traceroute
或mtr
诊断网络路径问题。 - DNS 解析:在节点上尝试解析集群内部服务名 (
nslookup <service-name>.<namespace>.svc.cluster.local <coredns-ip>
) 和外部域名 (nslookup google.com
)。检查/etc/resolv.conf
配置是否正确。 - 网络接口和路由:
ip addr show
查看网络接口状态和 IP 配置。ip link show
查看接口 link 状态。ip route show
查看内核路由表。检查 CNI 创建的虚拟接口(如veth
,cali
,cilium_host
等)是否存在且状态正常。 - 防火墙/网络策略:检查
iptables
规则 (iptables-save
) 或nftables
规则 (nft list ruleset
) 是否阻止了必要的通信(例如,Kubelet 与 API Server,节点间通信,Pod 访问 Service)。如果是 IPVS 模式,检查ipvsadm -Ln
的输出。 - CNI 插件日志:查找 CNI 插件(如 Calico, Cilium, Flannel)自身的日志,位置可能在
/var/log/calico
,/var/log/cilium
或由 Kubelet 日志引导。 - 网络抓包:在极端情况下,使用
tcpdump
在节点上抓取特定端口或 IP 的网络包进行分析,例如tcpdump -i any host <apiserver-ip> and port 6443 -nn -vv
。
- 基础连通性:
检查内核日志:
sudo dmesg -T
查看完整的内核环形缓冲区日志。内核日志是诊断硬件问题、驱动程序故障、文件系统损坏、严重内存问题或内核 Bug 的最终信息来源。关注错误(Error)、失败(Fail)、警告(Warning)、崩溃(Oops)、死锁(Hung task)等关键字。检查 Node Problem Detector (如果部署):如果集群部署了 NPD,检查其 Pod 日志 (
kubectl logs -n kube-system -l k8s-app=node-problem-detector -f
) 看它是否检测到了特定问题。同时,kubectl describe node <node-name>
检查是否有 NPD 添加的自定义 Condition。
通过系统性地执行上述检查,通常可以定位到导致节点异常的根本原因,无论是资源耗尽、组件故障、网络问题还是底层系统问题。
6. 基于 extendedresource 扩展节点资源
Kubernetes 原生支持对 CPU、内存和临时存储(Ephemeral Storage)这些基本资源的调度和管理。然而,现代计算节点常常配备各种专用硬件,例如 GPU (图形处理单元)、FPGA (现场可编程门阵列)、高性能网络接口卡 (如 SR-IOV VF)、硬件加速器 (如 QAT) 等。Kubernetes 需要一种机制来发现、上报、调度和分配这些非原生、特定于硬件的资源,这就是 Extended Resources (扩展资源) 和 Device Plugins (设备插件) 机制要解决的问题。
解决了什么问题?
Extended Resources 机制使得 Kubernetes 能够感知并管理节点上的特殊硬件资源,就像管理 CPU 和内存一样。它解决了以下关键问题:
- 资源发现与广告:如何让 Kubernetes 集群知道某个节点拥有多少个特定类型的硬件设备(例如,2 个 NVIDIA GPU)?
- 资源请求与调度:如何让用户在 Pod 定义中请求这些特殊硬件资源(例如,我需要 1 个 GPU)?如何让 Kubernetes 调度器将这样的 Pod 只调度到拥有足够可用资源的节点上?
- 资源分配与隔离:当 Pod 被调度到节点后,如何确保它能够独占或共享地访问到被分配的硬件设备,并且与其他 Pod 使用的设备隔离?
是什么?
Extended Resources 是一种命名约定和 API 字段,允许节点报告其拥有的、非 Kubernetes 原生定义的资源。这些资源的名称通常采用 vendor-domain/resource-type
的格式,例如 nvidia.com/gpu
、intel.com/fpga
、amd.com/gpu
。资源数量必须是整数。
Device Plugin 是实现 Extended Resources 管理的核心机制。它是一种运行在节点上的、独立于 Kubelet 的 gRPC 服务(通常部署为 DaemonSet),负责:
- 发现节点上的特定硬件设备。
- 向 Kubelet 注册自身,并报告其管理的设备数量和类型(即 Extended Resources)。
- 监控设备健康状态。
- 在 Kubelet 请求时,为 Pod 分配具体的设备,并提供访问设备所需的信息(如设备文件路径、环境变量)。
工作原理
发现与注册:
- Device Plugin 启动后,扫描节点以发现其管理的硬件设备(例如,通过查询 NVIDIA 驱动获取 GPU 列表)。
- 它通过连接 Kubelet 暴露的 Unix Domain Socket (
/var/lib/kubelet/device-plugins/kubelet.sock
),调用 Kubelet 的Registration
gRPC 服务来注册自己,并告知 Kubelet 它能提供哪些 Extended Resource 以及对应的设备 ID 列表。 - Kubelet 作为设备插件的注册中心。
资源上报:
- Kubelet 收到 Device Plugin 的注册信息后,会将这些 Extended Resources 更新到该 Node 对象的
status.capacity
和status.allocatable
字段中。例如,status.allocatable
中会增加一项nvidia.com/gpu: 2
。 - API Server 存储这些信息,使得整个集群可见。
- Kubelet 收到 Device Plugin 的注册信息后,会将这些 Extended Resources 更新到该 Node 对象的
资源请求:
- 用户在 Pod 的容器规约 (
spec.containers[].resources
) 中,通过limits
字段来请求 Extended Resources。对于 Extended Resources,requests
字段会被忽略,并且limits
必须指定,且必须为整数。 - 例如,请求一个 GPU:
1
2
3
4
5
6
7
8
9
10
11
12
13
14apiVersion: v1
kind: Pod
metadata:
name: cuda-vector-add
spec:
containers:
- name: cuda-vector-add
image: "nvidia/cuda:11.0.3-base-ubuntu20.04"
resources:
limits:
# 请求一个类型为 nvidia.com/gpu 的资源
nvidia.com/gpu: 1
command: [ "/bin/bash", "-c", "--" ]
args: [ "while true; do sleep 1; done;" ]
- 用户在 Pod 的容器规约 (
调度:
- Kubernetes Scheduler 在调度 Pod 时,会检查 Pod 请求的 Extended Resources。
- 它会过滤掉
status.allocatable
中没有足够可用 Extended Resources 的节点。只有当节点拥有足够数量的、未被其他 Pod 占用的特定 Extended Resource 时,该 Pod 才可能被调度到这个节点。
分配:
- 当 Pod 被调度到某个节点后,Kubelet 在启动该 Pod 的容器之前,会调用相应 Device Plugin 的
Allocate
gRPC 方法。 - Kubelet 告诉 Device Plugin 需要为这个容器分配多少个(以及哪些,如果插件支持)设备。
- Device Plugin 执行实际的分配逻辑(例如,选择一个空闲的 GPU),并返回给 Kubelet 需要注入到容器环境的信息。这通常包括:
- 需要挂载的设备文件 (e.g.,
/dev/nvidia0
,/dev/nvidiactl
,/dev/nvidia-uvm
)。 - 需要挂载的驱动库/二进制文件目录 (e.g., NVIDIA driver libraries)。
- 需要设置的环境变量 (e.g.,
NVIDIA_VISIBLE_DEVICES=0
,告知容器内的 CUDA 应用使用哪个 GPU)。
- 需要挂载的设备文件 (e.g.,
- Kubelet 使用这些信息来配置容器的运行时环境(通过 CRI 接口传递给 Docker/Containerd),确保容器能够正确地访问和使用分配给它的硬件设备。
- 当 Pod 被调度到某个节点后,Kubelet 在启动该 Pod 的容器之前,会调用相应 Device Plugin 的
关键组件和配置
- Device Plugin 实现:通常由硬件供应商提供(如 NVIDIA Device Plugin, Intel Device Plugin Suite)或由社区/用户根据
k8s.io/kubelet/pkg/apis/deviceplugin/v1beta1
Go 包中定义的 gRPC API 自行开发。它们一般以 DaemonSet 的形式部署,确保在需要管理硬件的节点上运行。 - Kubelet 配置:需要确保 Kubelet 启动时能够找到 Device Plugin 注册的 Socket。默认路径是
/var/lib/kubelet/device-plugins/
。Device Plugin 功能门控 (DevicePlugins
) 在现代 Kubernetes 版本中通常是默认启用的。 - Node Status 查看:可以通过
kubectl get node <node-name> -o yaml
查看节点的status.capacity
和status.allocatable
部分,确认 Extended Resources 是否已正确上报。
1 |
|
通过这种机制,Kubernetes 实现了对异构硬件资源的管理和调度,使得需要特殊硬件加速的应用(如机器学习训练/推理、视频处理、高性能计算)能够无缝地部署和运行在 Kubernetes 集群中,充分利用节点的硬件能力。
7. 构建和管理高可用(HA)集群
在生产环境中,单个组件的故障可能导致整个服务中断,造成不可估量的损失。因此,构建和管理 高可用(High Availability, HA) 的 Kubernetes 集群是至关重要的。HA 的核心目标是 消除单点故障(Single Point of Failure, SPOF),确保集群在部分组件失效时仍能继续提供服务。
控制平面高可用
Kubernetes 的控制平面由多个关键组件构成,包括 etcd
、kube-apiserver
、kube-controller-manager
和 kube-scheduler
。实现控制平面高可用主要围绕这些组件的冗余部署和故障切换。
etcd 集群化部署:
- 解决了什么问题:
etcd
是 Kubernetes 集群的分布式键值存储,保存着集群的所有状态数据(如 Pods, Services, Secrets 等的配置和状态)。单点etcd
意味着一旦该节点故障,整个集群将瘫痪,无法读取状态,也无法进行任何变更操作。 - 是什么: 通过部署多个
etcd
实例组成一个集群,利用 Raft 一致性算法 来保证数据的一致性和容错性。 - 原理: Raft 算法确保了只要集群中超过半数(Quorum)的节点存活并能够通信,
etcd
集群就能正常工作并处理读写请求。例如,一个 3 节点的etcd
集群可以容忍 1 个节点故障;一个 5 节点的集群可以容忍 2 个节点故障。写入请求需要得到多数节点的确认,读取请求可以直接从 Leader 节点获取(或者通过 Quorum Read 保证线性一致性)。 - 部署模式:
- Stacked etcd:
etcd
实例与控制平面组件(API Server 等)部署在相同的节点上。管理相对简单。 - External etcd:
etcd
集群部署在独立的专用节点上。提供了更好的隔离性和资源保障,但管理复杂度稍高。
- Stacked etcd:
- 关键配置: 在启动
etcd
时,需要配置--initial-cluster
参数指定所有成员的地址和端口,--initial-cluster-state
(new
或existing
),以及相应的 TLS 证书以保证通信安全。同时,定期的 etcd 备份 是灾难恢复的最后防线,至关重要。
- 解决了什么问题:
kube-apiserver 冗余部署:
- 解决了什么问题:
kube-apiserver
是 Kubernetes API 的入口,所有用户请求(如kubectl
)、控制器以及kubelet
都需要与它交互。单点apiserver
故障将导致无法管理集群,新 Pod 无法调度,已有应用状态无法更新。 - 是什么: 部署多个
kube-apiserver
实例。 - 原理:
kube-apiserver
本身设计为 无状态 (Stateless) 或 近乎无状态,其状态主要存储在etcd
中。因此,可以水平扩展多个实例。在这些实例前放置一个 负载均衡器(Load Balancer),例如 Nginx、HAProxy,或者云厂商提供的 LB 服务。所有客户端(kubectl
,kubelet
, controllers)都配置为访问该负载均衡器的 虚拟 IP (VIP)。当某个apiserver
实例故障时,负载均衡器会自动将其从后端池中移除,将流量转发到健康的实例上,实现无缝切换。 - 配置示例 (概念性): 假设 VIP 为
192.168.1.100:6443
,负载均衡器将流量分发到后端的apiserver
实例(如10.0.0.1:6443
,10.0.0.2:6443
,10.0.0.3:6443
)。kubelet
和kubeconfig
文件中的server
地址应指向https://192.168.1.100:6443
。
- 解决了什么问题:
kube-controller-manager 和 kube-scheduler 高可用:
- 解决了什么问题:
controller-manager
负责运行各种控制器(如 Node Controller, Replication Controller 等)来维护集群状态。scheduler
负责将新创建的 Pod 调度到合适的 Node 上。单点故障会导致集群无法自动修复、副本数量无法维持、新 Pod 无法被调度。 - 是什么: 同时运行多个
controller-manager
和scheduler
实例。 - 原理: 这两个组件内部实现了 领导者选举(Leader Election) 机制。虽然可以运行多个实例,但在同一时刻,只有一个实例是 Active (Leader),负责执行实际的控制和调度逻辑。其他实例处于 Standby 状态,随时准备在 Leader 实例故障时接管。它们通过 Kubernetes API(通常是更新 Endpoints 或 Leases 资源对象上的特定 annotation)来竞争 Leader 身份并维持心跳。一旦当前 Leader 失联,某个 Standby 实例会成功获取 Leader 锁并成为新的 Leader。
- 关键配置: 启动
kube-controller-manager
和kube-scheduler
时,需要启用领导者选举,通常通过传递--leader-elect=true
命令行参数(或在组件配置文件中设置)。它们需要配置相同的--leader-elect-resource-name
和--leader-elect-resource-namespace
(通常在kube-system
命名空间下)。1
2
3
4
5# 示例:kube-controller-manager 启动参数片段
kube-controller-manager --leader-elect=true --leader-elect-resource-name=kube-controller-manager --leader-elect-resource-namespace=kube-system ...
# 示例:kube-scheduler 启动参数片段
kube-scheduler --leader-elect=true --leader-elect-resource-name=kube-scheduler --leader-elect-resource-namespace=kube-system ...
- 解决了什么问题:
工作节点与应用高可用
虽然控制平面的 HA 是基础,但最终用户的应用运行在工作节点上。工作节点的 HA 主要依赖于以下几点:
- 足够数量的节点: 确保有足够的计算资源,并且节点分布在不同的 故障域(Failure Domains),如不同的物理机架、数据中心可用区(Availability Zones)。
- 应用副本: 使用
Deployment
或StatefulSet
等控制器运行应用的多个副本(Pods)。 - 反亲和性(Anti-Affinity): 配置 Pod 反亲和性规则,强制要求同一应用的多个副本分散到不同的节点或故障域上,避免单节点故障导致所有副本同时失效。
- Pod Disruption Budgets (PDBs): PDBs 限制了在自愿性中断(如节点维护、升级)期间,一个应用同时可以有多少个 Pod 不可用,确保服务维持最低可用水平。
- 健康检查(Probes): 正确配置
livenessProbe
(探测容器是否存活,不健康则重启)、readinessProbe
(探测容器是否准备好接收流量,未就绪则从 Service Endpoints 中移除)和startupProbe
(用于启动时间较长的容器),确保 Kubernetes 能够准确判断 Pod 的健康状态并进行相应处理。
网络高可用
集群网络的 HA 也很重要,包括:
- 冗余网络路径: 物理网络层面应有冗余链路和设备。
- 高可用 CNI: 选择支持高可用部署的 CNI 插件。
- 高可用 Service 暴露: 如果使用
LoadBalancer
类型的 Service,依赖云厂商 LB 的 HA 能力。如果使用 Ingress,确保 Ingress Controller 有多个副本并配置反亲和性。对于裸金属环境,可以使用 MetalLB 等工具配合 BGP 实现 VIP 的高可用。
8. Cluster Autoscaler (CA)
Cluster Autoscaler (CA) 是 Kubernetes 中用于 自动调整集群节点数量 的核心组件。
解决了什么问题:
- 资源浪费: 静态配置的集群规模如果过大,在负载较低时会造成大量节点资源闲置,增加成本。
- 资源不足: 如果集群规模过小,当应用负载突增或部署新应用时,可能因资源不足导致 Pod 处于
Pending
状态无法调度,影响业务。 - 手动调整效率低且易错: 手动监控集群负载并增删节点费时费力,且响应不及时。
是什么: 一个运行在集群内的独立程序(通常是一个 Deployment),它监控集群状态,并根据需要与 云提供商(Cloud Provider) 的 API(如 AWS Auto Scaling Groups, GCP Managed Instance Groups, Azure Virtual Machine Scale Sets)交互,自动增加或减少集群中的工作节点数量。
原理:
- 监控: CA 定期(通过
--scan-interval
参数配置,默认 10 秒)扫描集群中处于Pending
状态的 Pod。它检查这些 Pod 无法被调度的原因是否是 资源不足(CPU、内存、GPU 或其他自定义资源)。同时,它也监控 节点利用率。 - Scale-Up (扩容): 如果发现有 Pod 因资源不足而
Pending
,并且 CA 模拟 发现如果增加一个来自某个 节点组(Node Group) 的新节点就能让这些 Pod 成功调度,CA 就会向关联的云提供商发出请求,要求增加该节点组的实例数量(通常是增加 1 个)。CA 会选择能够满足最多Pending
Pod 需求的节点组进行扩容。 - Scale-Down (缩容): CA 会检查哪些节点 持续一段时间(通过
--scale-down-delay-after-add
,--scale-down-unneeded-time
等参数配置)处于 低利用率 状态(CPU 和内存请求低于某个阈值,通过--scale-down-utilization-threshold
配置,默认 0.5 即 50%)。对于满足缩容条件的节点,CA 会 模拟 如果将该节点上的所有 Pod(除了系统关键 Pod 和配置了不能驱逐的 Pod)重新调度 到其他节点上是否可行。这个模拟过程会严格遵守 PodDisruptionBudgets (PDBs)、亲和性/反亲和性规则、节点选择器 (Node Selectors)、污点和容忍 (Taints and Tolerations) 等调度约束。如果模拟成功,CA 会首先 驱逐 (Drain) 该节点上的 Pod(将 Pod 安全地迁移到其他节点),然后向云提供商发出请求,要求 终止 (Terminate) 该节点实例并将其从节点组中移除。 - 节点组 (Node Groups): CA 的操作是基于节点组的。你需要为 CA 配置集群中可自动伸缩的节点组,通常对应云提供商的 Auto Scaling Group 或 VM Scale Set。每个节点组需要定义 最小节点数 (
minSize
) 和 最大节点数 (maxSize
)。CA 的伸缩活动会被限制在这个范围内。
- 监控: CA 定期(通过
关键配置:
- 部署: 通常以 Deployment 方式部署在
kube-system
命名空间。 - 云提供商集成: 需要为 CA Pod 配置访问云提供商 API 的权限(例如通过 IAM Role for Service Accounts on AWS, Workload Identity on GCP)。
- 启动参数: 通过 ConfigMap 或命令行参数配置,关键参数包括:
--cloud-provider
: 指定云提供商 (e.g.,aws
,gce
,azure
)。--nodes=<min>:<max>:<asg_name>
: 定义节点组及其规模限制 (可指定多个)。--scan-interval
: 扫描周期。--scale-down-enabled
: 是否启用缩容 (默认true
)。--scale-down-utilization-threshold
: 节点利用率低于此阈值才可能被缩容。--scale-down-unneeded-time
: 节点需要持续低于阈值多长时间才会被考虑缩容。--skip-nodes-with-local-storage
: 不缩容包含本地存储 Pod 的节点 (防止数据丢失)。--skip-nodes-with-system-pods
: 不缩容包含kube-system
命名空间下非 DaemonSet Pod 的节点。
- 示例 (概念性启动命令):
1
2
3
4
5
6
7
8
9
10
11
12./cluster-autoscaler \
--v=4 \
--stderrthreshold=info \
--cloud-provider=aws \
--skip-nodes-with-local-storage=false \
--expander=least-waste \
--node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/my-cluster-name \
--balance-similar-node-groups \
--skip-nodes-with-system-pods=false \
--scale-down-utilization-threshold=0.5 \
--scale-down-unneeded-time=10m \
--scale-down-delay-after-add=10m--node-group-auto-discovery
: 使用 AWS Tag 自动发现 ASG。--expander
: 选择扩容策略(如least-waste
优先选择浪费资源最少的节点组)。
- 部署: 通常以 Deployment 方式部署在
Golang 源码视角: Cluster Autoscaler 的核心逻辑在
cluster-autoscaler/core
包中。static_autoscaler.go
文件中的RunOnce
函数是主循环,它调用ScaleUp
和ScaleDown
函数。ScaleUp
会检查unschedulablePods
,并使用provisioning_simulator.go
中的逻辑来模拟节点添加。ScaleDown
则会识别candidates
(可能被缩容的节点),并通过drain.go
中的函数来执行驱逐,最后与具体的cloudprovider
接口交互来删除节点。它大量使用了 Kubernetesclient-go
库来与 API Server 交互,获取 Pods、Nodes 等资源的状态,并依赖informers
来高效地监听资源变化。
9. 集群管理实践案例分析
从提供的 PDF 片段(如 CI/CD 流程图、SaltStack 相关图示、CRD 示例 Cluster
, ComputeNode
, ClusterDeployment
)来看,这描绘了一个 自动化、声明式的集群生命周期管理 实践方案。这种方案通常结合了 GitOps、Operator Pattern 和 配置管理工具。
- 解决了什么问题: 手动创建、升级和管理 Kubernetes 集群(尤其是大规模集群)既复杂又容易出错。需要一个标准化、可重复、可审计的流程来管理从集群创建、节点配置、版本升级到最终退役的整个生命周期。
- 是什么: 这是一套集成的工具链和工作流,旨在实现 Kubernetes 集群的自动化管理。
- 核心组件与流程:
- Git Repository (Source of Truth): 所有集群的 期望状态 (Desired State),包括集群配置(版本、网络、区域等)、节点配置、控制平面参数、附加组件(如 CNI, CoreDNS, Ingress Controller)等,都以声明式文件(如 YAML)的形式存储在 Git 仓库中。
- CI/CD Pipeline:
- 当 Git 仓库中的配置发生变更(如 Pull Request 合并)时,触发 CI/CD 流水线。
- CI (Continuous Integration): 阶段包括代码风格检查、YAML 语法验证、配置策略检查(如使用 OPA Gatekeeper)、单元/集成测试(如果涉及自定义代码)。可能会构建自定义的 Kubernetes 组件镜像或 Operator 镜像。
- CD (Continuous Deployment/Delivery): 负责将验证过的配置变更应用到目标环境。这通常不是直接调用
kubectl apply
或配置管理工具,而是更新代表集群状态的 Custom Resource (CR) 对象。
- Kubernetes Operator / Custom Controller:
- 集群中运行着一个(或多个)自定义的 Operator。这个 Operator 负责 监听 (Watch) 特定类型的 CRD(如示例中的
Cluster
,ComputeNode
,ClusterDeployment
)。 - Reconciliation Loop: 当 Operator 检测到其管理的 CR 对象发生变化(创建、更新、删除)时,会触发 调和循环 (Reconciliation Loop)。
- 执行操作: 在调和循环中,Operator 读取 CR 中定义的期望状态,并与集群的 实际状态 (Actual State) 进行比较。如果两者不一致,Operator 会执行必要的操作来使实际状态趋向于期望状态。这些操作可能包括:
- 调用 云提供商 API 来创建/删除虚拟机、负载均衡器、网络等基础设施资源。
- 使用 配置管理工具 (如 SaltStack、Ansible) (如
salt highstate
命令所示) 来配置节点操作系统、安装 Docker/Containerd、Kubelet、Kube-proxy 等。salt-master
/salt-minion
的架构表明使用 SaltStack 进行节点级别的配置管理。 - 执行
kubeadm
命令或调用 Kubernetes API 来初始化控制平面、加入节点、执行版本升级。 - 部署或更新集群附加组件。
- CRD 定义:
Cluster
: 可能定义了集群的元数据(名称、区域)、Kubernetes 版本、网络配置(CNI 类型、Pod/Service CIDR)等。ComputeNode
: 定义了单个工作节点或控制平面节点的规格(实例类型/flavor、操作系统镜像、磁盘大小等)。ClusterDeployment
: 可能用于管理 集群升级 过程,定义了目标版本、升级策略(如滚动升级的批次大小和百分比strategy: "10,30,30,30"
表示分四批,分别升级 10%, 30%, 30%, 30% 的节点)、SaltStack 或其他部署工具所需资源的 URL (saltTarUrl
,serverTarUrl
) 等。
- 集群中运行着一个(或多个)自定义的 Operator。这个 Operator 负责 监听 (Watch) 特定类型的 CRD(如示例中的
- 管理工具:
kubectl
: 标准的 Kubernetes 命令行工具,用于与 API Server 交互,查看 CR 状态、Pod 日志等。tm-cli
(推测): 一个 自定义的命令行工具,可能封装了对上述 CRD 的操作(创建、获取、更新、删除 Cluster/Node/Deployment 等),提供更友好的用户接口,或者执行一些 Operator 无法覆盖的特定管理任务。kube-up
/kube-down
(推测): 可能是用于集群 初始引导 (Bootstrap) 或 销毁 (Teardown) 的脚本或工具,在 Operator 接管之前或之后使用。
- 运维界面 (Operator Interface): 可能提供了一个 Web UI 或 API,用于可视化集群状态、监控调和过程、触发特定操作等。
- 优势:
- 自动化: 大幅减少手动操作,提高效率,降低人为错误。
- 声明式: 只需定义期望状态,具体实现由 Operator 负责,易于理解和管理。
- 版本控制与审计: 所有变更记录在 Git 中,可追溯、可回滚。
- 一致性: 确保所有集群都按照相同的标准和流程进行管理。
- 可扩展性: 可以通过增加新的 CRD 和 Controller 来扩展管理能力。
- Linux/Golang 关联: Operator 通常使用 Go 语言 编写,利用
client-go
库与 Kubernetes API 交互,并使用controller-runtime
框架来简化 Operator 的开发。配置管理工具(如 SaltStack 的 Minion)运行在 Linux 节点 上,执行系统级的配置命令。整个流程涉及 Linux 系统管理、网络配置、容器运行时(Docker/Containerd)管理等底层操作。
10. 多租户(Multi-Tenancy)集群管理
在许多场景下,需要让多个不同的用户、团队或客户(即 租户 Tenants)共享同一个 Kubernetes 集群资源。多租户 的目标是在共享基础设施的同时,提供足够的 隔离性 (Isolation)、安全性 (Security) 和 资源公平性 (Fairness)。
解决了什么问题:
- 资源利用率: 共享集群通常比为每个租户单独部署集群更节省成本和资源。
- 管理开销: 集中管理一个(或少量)大型集群比管理大量小型集群更高效。
- 快速环境提供: 可以快速为新团队或项目分配隔离的环境。
是什么: 在单个 Kubernetes 集群内部署和运行属于不同租户的应用程序,同时确保它们之间互不干扰、资源使用受控、访问权限严格分离。
核心机制与实践: Kubernetes 本身提供了一些构建块来实现(通常是 软性 Soft)多租户:
Namespaces:
- 提供逻辑隔离: Namespace 是 Kubernetes 中资源(如 Pods, Services, Deployments, Secrets)的基本作用域。不同 Namespace 中的资源名称可以重复。这是实现多租户的 第一道屏障。
- 不提供安全隔离: 关键点:Namespace 本身并不阻止跨 Namespace 的网络访问,也不限制资源使用。默认情况下,一个 Namespace 中的 Pod 可以访问其他 Namespace 中的 Service (如果知道其 DNS 名称或 IP)。
- 配置示例:
1
2
3# 创建两个租户的 Namespace
kubectl create namespace tenant-a
kubectl create namespace tenant-b
RBAC (Role-Based Access Control):
- 提供访问权限控制: RBAC 用于精细化地控制 谁 (Subject: User, Group, ServiceAccount) 可以对 什么资源 (Resource: Pods, Services, Nodes) 执行 什么操作 (Verb: get, list, watch, create, update, patch, delete)。
- Namespace 作用域: 可以创建
Role
和RoleBinding
,其权限仅限于特定的 Namespace。这是限制租户用户只能管理自己 Namespace 资源的关键。 - Cluster 作用域:
ClusterRole
和ClusterRoleBinding
具有集群范围的权限,通常用于集群管理员或需要访问集群级别资源(如 Nodes, Namespaces)的组件。 - 配置示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26# role-tenant-a-admin.yaml: 定义租户 A 管理员角色
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: tenant-a
name: tenant-a-admin
rules:
- apiGroups: ["", "apps", "extensions", "batch", "networking.k8s.io"] # "" indicates core API group
resources: ["*"] # Allow access to all resources within the namespace
verbs: ["*"] # Allow all verbs
---
# rolebinding-tenant-a-admin.yaml: 将用户 "user-a" 绑定到租户 A 管理员角色
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: tenant-a-admin-binding
namespace: tenant-a
subjects:
- kind: User
name: user-a # Name is case sensitive
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: tenant-a-admin
apiGroup: rbac.authorization.k8s.io1
2kubectl apply -f role-tenant-a-admin.yaml
kubectl apply -f rolebinding-tenant-a-admin.yaml
Network Policies:
- 提供网络隔离: NetworkPolicy 允许定义 Pod 之间以及 Pod 与外部网络端点之间的 网络访问规则。这是实现租户间网络隔离的 核心机制。
- 依赖 CNI: 需要使用支持 NetworkPolicy 的 CNI 插件(如 Calico, Cilium, Weave Net)。
- 默认策略: 最佳实践是首先应用一个 默认拒绝 (Default Deny) 策略到每个租户 Namespace,阻止所有入站 (Ingress) 和/或出站 (Egress) 流量,然后根据需要显式地允许特定流量。
- Linux 内核关联: NetworkPolicy 的实现通常依赖于 Linux 内核的网络过滤机制,如 iptables 或更现代的 eBPF (Extended Berkeley Packet Filter)。CNI 插件会监听 NetworkPolicy 对象,并将规则转换为相应的 iptables 规则链或 eBPF 程序加载到内核中,从而在数据包路径上进行过滤。
- 配置示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31# netpol-default-deny-ingress.yaml: 拒绝所有进入 tenant-a 的流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: tenant-a
spec:
podSelector: {} # An empty podSelector selects all pods in the namespace
policyTypes:
- Ingress # Apply policy to Ingress traffic
---
# netpol-allow-nginx-ingress.yaml: 允许来自特定 namespace (e.g., monitoring) 的流量访问 nginx Pod
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-nginx-ingress
namespace: tenant-a
spec:
podSelector:
matchLabels:
app: nginx
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
team: monitoring # Allow from pods in namespaces labeled 'team=monitoring'
ports:
- protocol: TCP
port: 801
2kubectl apply -f netpol-default-deny-ingress.yaml -n tenant-a
kubectl apply -f netpol-allow-nginx-ingress.yaml -n tenant-a
Resource Quotas:
- 提供资源总量限制: ResourceQuota 用于限制一个 Namespace 能够消耗的资源总量,包括计算资源(CPU 请求/限制, 内存 请求/限制)、存储资源(持久卷声明数量, 总存储容量)以及对象数量(Pods, Services, Secrets, ConfigMaps 等)。
- 防止资源滥用: 确保单个租户不会耗尽整个集群的资源,影响其他租户。
- 配置示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# quota-tenant-a.yaml: 限制 tenant-a 的资源
apiVersion: v1
kind: ResourceQuota
metadata:
name: tenant-a-quota
namespace: tenant-a
spec:
hard:
requests.cpu: "10" # Total requested CPU across all pods cannot exceed 10 cores
requests.memory: 20Gi # Total requested Memory cannot exceed 20 GiB
limits.cpu: "20" # Total limited CPU cannot exceed 20 cores
limits.memory: 40Gi # Total limited Memory cannot exceed 40 GiB
pods: "50" # Maximum number of pods
services: "20" # Maximum number of services
persistentvolumeclaims: "10" # Maximum number of PVCs
requests.storage: "100Gi" # Maximum total requested storage by PVCs1
kubectl apply -f quota-tenant-a.yaml -n tenant-a
Limit Ranges:
- 提供容器级别的资源约束: LimitRange 用于为一个 Namespace 中的 每个 Pod 或 Container 设置默认、最小或最大的资源请求(requests)和限制(limits)。
- 规范资源使用: 确保 Pod 不会请求过大或过小的资源,同时强制所有 Pod 都设置资源请求/限制(这是 ResourceQuota 正常工作的前提)。
- 配置示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21# limits-tenant-a.yaml: 设置 tenant-a 中容器的默认和最大资源
apiVersion: v1
kind: LimitRange
metadata:
name: tenant-a-limits
namespace: tenant-a
spec:
limits:
- type: Container
max: # Maximum limits per container
cpu: "2"
memory: "4Gi"
min: # Minimum requests per container
cpu: "100m"
memory: "100Mi"
default: # Default limits if not specified by container
cpu: "500m"
memory: "1Gi"
defaultRequest: # Default requests if not specified by container
cpu: "200m"
memory: "256Mi"1
kubectl apply -f limits-tenant-a.yaml -n tenant-a
Pod Security Admission (PSA) / Pod Security Policies (PSP - Deprecated):
- 提供运行时安全控制: 限制 Pod 的安全相关行为,如是否能以 root 用户运行、是否能访问宿主机文件系统、是否能使用特权模式、允许加载哪些内核能力 (Capabilities) 等。PSA 是内置的 Admission Controller,提供了
privileged
,baseline
,restricted
三种策略级别,可以通过 Namespace Label 来强制实施。 - 增强安全性: 防止租户 Pod 破坏节点或其他租户的 Pod。
- 提供运行时安全控制: 限制 Pod 的安全相关行为,如是否能以 root 用户运行、是否能访问宿主机文件系统、是否能使用特权模式、允许加载哪些内核能力 (Capabilities) 等。PSA 是内置的 Admission Controller,提供了
Admission Controllers (尤其是 Validating/Mutating Webhooks):
- 提供自定义策略实施: 可以部署自定义的 Admission Webhook(通常结合 OPA Gatekeeper 或 Kyverno)来实施更复杂的、组织特定的策略,例如强制所有资源打上特定标签、禁止使用
latest
镜像标签、限制可以使用的 Ingress Hostname 等。
- 提供自定义策略实施: 可以部署自定义的 Admission Webhook(通常结合 OPA Gatekeeper 或 Kyverno)来实施更复杂的、组织特定的策略,例如强制所有资源打上特定标签、禁止使用
(高级) 运行时隔离: 使用如 gVisor 或 Kata Containers 等沙箱技术,为 Pod 提供更强的内核级隔离,但会带来一定的性能开销。
挑战:
- 复杂性: 正确配置所有隔离机制需要深入的 Kubernetes 知识。
- 控制平面共享: 所有租户共享 API Server, etcd 等控制平面组件,一个租户的恶意或错误行为(如 API Flood)可能影响整个集群(需要 API 优先级和公平性 APF)。
- 内核共享: 容器共享宿主机内核,内核漏洞可能导致隔离被破坏(除非使用沙箱技术)。
- “吵闹的邻居”问题: 即使有 Quota,一个租户的网络流量、磁盘 I/O 也可能间接影响其他租户的性能。
设计模式:
- Namespace as a Service: 最常见的多租户模式,使用上述 K8s 原生机制在共享集群中隔离租户。适用于信任度较高或对隔离性要求不是极高的场景。
- Cluster as a Service: 为每个租户提供独立的 Kubernetes 集群。隔离性最好,但成本和管理开销最高。
- Control Plane as a Service: 多个租户共享基础设施(节点),但拥有独立的控制平面(如使用 Virtual Kubelet 或 vCluster 等技术)。在隔离性和成本之间取得平衡。
多租户是一个复杂的主题,没有一刀切的解决方案。选择哪种模式和哪些隔离机制取决于具体的安全要求、信任模型、成本预算和管理能力。