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 默认检查更广泛、更深入的节点健康视图,从而能够更早地发现并处理潜在的节点故障,进一步提升集群的稳定性和可靠性。
NPD 的核心职责:检测与报告
根据图片内容,NPD 的核心职责非常明确:它只负责检测节点上发生的异常事件,并将这些事件作为 Node Condition
更新到对应节点的 Node
对象状态中。这一点至关重要,意味着 NPD 本身是一个信息收集器和报告器,它不会直接干预节点的调度状态或驱逐 Pod。
NPD 上报的 Node Condition
示例:
1 |
|
这个示例清晰地展示了 NPD 如何工作。它监测到了一个内核级别的事件(通过分析内核日志,例如 dmesg
或 journald
),具体表现为 Docker 相关的任务(PID 20744)被阻塞超过 120 秒。NPD 将此问题归类为 KernelDeadlock
类型,并将状态设置为 True
,附带了详细的内核日志信息。这些信息被更新到该节点对象的 .status.conditions
字段。
从 Linux 内核角度看,task blocked for more than X seconds
这类消息通常由内核的 RCU (Read-Copy Update) 子系统或者 watchdog
机制检测到。当一个任务长时间处于不可中断的睡眠状态(TASK_UNINTERRUPTIBLE
,通常是等待 I/O 或某些锁)时,内核会打印这类警告,这往往预示着潜在的驱动程序错误、硬件问题或内核死锁。NPD 通过配置好的日志监控规则(例如,使用正则表达式匹配 /var/log/dmesg
或 journalctl -k
的输出)捕获这类关键信息。
处理 NPD 报告:需要额外的控制器
由于 NPD 自身不影响调度,为了让 Kubernetes 集群能够对 NPD 检测到的问题做出反应(例如,阻止新的 Pod 调度到故障节点),通常需要一个额外的自定义控制器(Custom Controller)。这个控制器会 监听 Node
对象的变化,特别是关注由 NPD 添加或更新的特定 Node Condition
类型(如 KernelDeadlock
, FilesystemReadOnly
等)。
当控制器检测到一个节点出现了 NPD 报告的严重问题(例如 status: "True"
),它会采取行动。最常见的行动是 给该节点添加 Taint(污点)。Taint 是 Kubernetes 的一种机制,用于阻止 Pod 被调度到具有不匹配 Taint 的节点上。
例如,控制器可以执行类似以下的命令来给问题节点添加一个 Taint:
1 |
|
这个命令给 my-faulty-node
节点添加了一个 Taint,其 key
是 npd.kubernetes.io/problem
,value
是 KernelDeadlock
,effect
是 NoSchedule
。这意味着,除非 Pod 明确声明了对这个 Taint 的 Toleration(容忍),否则 Kubernetes 调度器不会将新的 Pod 调度到这个节点上。这样就有效地将故障节点隔离,防止问题扩大。
从 Golang 和 Kubernetes 控制器实现的角度看,这个自定义控制器通常使用 client-go
库来 Watch Node
资源。当检测到 Node
对象的 .status.conditions
中出现需要处理的 NPD Condition 时,控制器会构造一个 Node
对象的 Patch 请求,将相应的 Taint 添加到 .spec.taints
字段中,然后通过 client-go
将更新发送给 Kubernetes API Server。
问题修复与状态清理
当节点上的底层问题(例如内核死锁解除、文件系统恢复读写)被管理员修复后,需要清理 NPD 报告的错误状态以及控制器添加的 Taint。
问题修复后,可以通过重启 NPD Pod 来清理错误事件。重启 NPD Pod 会使其重新执行初始化检查。如果此时节点问题已不存在,NPD 将不会再上报对应的 Node Condition
,或者会将其状态更新为 False
。随后,之前添加 Taint 的自定义控制器也应该检测到 Node Condition
的变化(问题 Condition 消失或状态变为 False
),并自动移除之前添加的 Taint,从而使节点恢复正常调度。
需要注意的是,重启 NPD Pod 是一种相对直接但可能不是最优的清理方式。更理想的情况是,NPD 能够周期性地重新检查问题状态,并在问题消失时自动更新 Node Condition
的状态为 False
。同样,自定义控制器也应该具备移除 Taint 的逻辑。重启 NPD 是一种确保状态刷新的有效手段,尤其是在 NPD 或控制器逻辑不够完善的情况下。
总结来说,NPD 扮演着节点健康状况的“侦察兵”角色,负责深入挖掘并报告 Kubelet 无法覆盖的底层问题。但它需要与自定义控制器(通常负责添加 Taint)协同工作,才能将这些报告转化为实际的调度决策,最终实现对故障节点的有效隔离和管理。整个流程体现了 Kubernetes 通过组合不同组件、利用声明式 API 和控制器模式来解决复杂问题的设计哲学。
5. 常⽤节点问题排查⼿段
在管理和维护 Kubernetes 集群时,快速定位并解决问题至关重要。无论是应用程序故障、调度异常还是节点本身的问题,有效的排查手段都离不开对集群内部状态的深入了解。图片中提到的两种核心方法——访问内部节点和查看日志,是日常排查工作的基础。
访问集群内部节点 (Accessing Internal Cluster Nodes)
- 创建一个支持 SSH 的 Pod:这通常意味着部署一个带有 SSH 服务端和必要工具(如
net-tools
,tcpdump
等)的特殊 Pod,这个 Pod 可以被调度到集群中的任意节点(或通过nodeSelector
/affinity
指定到特定节点)。然后,你可以通过kubectl exec
进入这个 Pod,或者如果 Pod 的 SSH 端口通过 Service (如 NodePort 或 LoadBalancer) 暴露出来,你可以直接 SSH 到这个 Pod。这种方式提供了一个受控的、临时的节点环境访问入口,避免直接暴露节点 SSH。 - 通过负载均衡器转发 SSH 请求:如果需要从集群外部访问某个特定节点(或前面提到的 SSH Pod),可以通过创建一个
LoadBalancer
类型的 Service,将外部流量引导至节点的 SSH 端口(通常是 22)或 SSH Pod 的服务端口。这在云环境或需要固定入口点时比较有用,但需要注意安全风险,务必配置严格的网络策略和认证。
实践考量与安全
虽然直接 SSH 到生产环境的 K8s Worker Node 是最直接的方式,但这通常不被推荐,因为它破坏了基础设施即代码和不可变基础设施的原则,且有安全风险。创建临时的调试 Pod 是更符合云原生理念的方法。例如,可以使用 kubectl debug
命令(较新版本 K8s 提供)快速在节点上启动一个具有特权和访问节点文件系统的 Pod。
1 |
|
然后通过 kubectl exec -it debug-pod -- bash
进入该 Pod 进行操作。
查看 Systemd 管理的服务日志 (Viewing Logs for Services Managed by Systemd)
在许多 Linux 发行版中,K8s 的核心节点组件(如 kubelet
、容器运行时 containerd
或 dockerd
)通常由 systemd
管理。它们的日志会被 systemd-journald
服务捕获并存储在 systemd journal 中。
原理:systemd-journald
是一个系统服务,它从内核、系统服务(包括 systemd
启动的服务)、syslog
等多个来源收集日志,并以结构化的二进制格式存储。journalctl
是查询这些日志的命令行工具。
命令示例与解析:
journalctl -u kubelet
: 查看kubelet
服务的所有日志。-u
(unit) 参数指定要查询的systemd
单元。kubelet
是运行在每个 Node 上的关键代理,负责管理 Pod 的生命周期,因此其日志对于排查 Pod 相关问题至关重要。journalctl -f -u kubelet
: 实时跟踪(tail)kubelet
的最新日志。-f
(follow) 类似于tail -f
。journalctl -u kubelet -S "2019-08-26 15:00:00"
: 查看从指定时间点 (--since
,-S
) 开始的kubelet
日志。对于分析特定时间段内发生的问题非常有用。journalctl -afu kubelet
: 结合-a
(show all, 显示所有字段,即使包含不可打印字符或过长),-f
(follow) 和-u kubelet
,提供详细的实时日志流。
配置示例:通常无需特殊配置,journalctl
直接可用。若要调整 journald
的存储策略(如持久化存储、大小限制),可编辑 /etc/systemd/journald.conf
。
查看容器日志 (Viewing Container Logs)
容器化应用的最佳实践是将日志输出到标准输出 (stdout) 和标准错误 (stderr)。Kubernetes 会自动捕获这些输出流,并通过 kubectl logs
命令提供访问。
原理:容器运行时(如 Docker 或 containerd)负责捕获容器的 stdout
和 stderr
输出,并将它们存储在节点上的特定位置(通常是 /var/log/pods/...
或由运行时管理的日志驱动程序处理)。当执行 kubectl logs
时,请求会通过 API Server 转发给目标 Pod 所在节点的 Kubelet,Kubelet 再从容器运行时获取相应的日志数据并返回给用户。
命令示例与解析:
kubectl logs <podname>
: 获取指定 Pod 中第一个容器的日志。如果 Pod 只有一个容器,这是最常用的命令。kubectl logs -c <containername> <podname>
: 当一个 Pod 包含多个容器时(例如,应用容器 + sidecar 容器),必须使用-c
参数指定要查看哪个容器的日志。这是排查多容器 Pod 问题时的关键。kubectl logs -f <podname>
或kubectl logs -f -c <containername> <podname>
: 实时跟踪指定容器的日志流。-f
(follow) 非常适合观察正在运行的应用的行为或调试实时问题。kubectl logs --all-containers <podname>
: 获取 Pod 内所有容器的日志。注意,这会将所有容器的日志混合在一起输出,可能较难阅读,但有时可用于快速概览。kubectl logs <podname> --previous
: 获取 Pod 中上一个已终止的容器实例的日志。这对于诊断反复崩溃重启 (CrashLoopBackOff) 的 Pod 至关重要,因为当前运行的容器可能刚启动,日志很少,而错误信息在上次崩溃的容器日志中。
Golang 与日志:在 Golang 后端开发中,推荐使用标准库 log
或更强大的第三方库(如 logrus
, zap
)将日志直接输出到 os.Stdout
或 os.Stderr
,这样就能无缝对接 K8s 的日志收集机制。避免将日志写入容器内的文件,除非有特殊理由。
1 |
|
查看重定向到文件的容器日志 (Viewing Container Logs Redirected to Files)
有时,应用程序(特别是遗留系统或某些第三方软件)可能被配置为将日志写入容器内的文件,而不是 stdout/stderr
。
原理:kubectl logs
无法直接读取这些文件。此时需要使用 kubectl exec
命令在容器内部执行命令来查看文件内容。kubectl exec
通过 API Server -> Kubelet -> 容器运行时 的路径,在目标容器的命名空间内启动一个指定的进程(如 sh
, bash
, cat
, tail
)。
命令示例与解析:
kubectl exec -it <podname> -- tail -f /path/to/log/file
: 在指定 Pod 的默认容器内(或用-c <containername>
指定容器)执行tail -f
命令,实时查看位于/path/to/log/file
的日志文件。-i
(stdin): 保持标准输入打开。-t
(tty): 分配一个伪终端。对于交互式 shell (bash
,sh
) 是必需的,对于tail -f
也是常用的。--
: 分隔符,用于区分kubectl
的参数和要在容器内执行的命令及其参数。
配置示例:无需特殊配置 kubectl
,但需要知道日志文件在容器内的确切路径。
总结
掌握节点访问技术和熟练运用日志查看命令是 K8s 问题排查的基础技能。理解这些命令背后的原理(如 systemd journal
, stdout/stderr
捕获, kubectl
与 K8s 各组件的交互流程)能帮助你更有效地定位问题。在实践中,通常会结合 kubectl describe pod <podname>
, kubectl get events --sort-by='.lastTimestamp'
, 以及监控系统(如 Prometheus + Grafana)的信息,形成一个完整的排查视图。
6. 基于 extendedresource 扩展节点资源
在 Kubernetes 集群中,计算资源的管理是核心功能之一。除了内建的 CPU 和 Memory 资源外,Kubernetes 提供了一种 扩展资源(Extended Resources) 的机制,允许集群管理员和开发者定义、通告和使用节点级别的、非内建的特殊资源。这极大地增强了 Kubernetes 在异构硬件和特定场景下的调度与管理能力。
解决的问题与应用场景
标准的 CPU 和 Memory 资源无法满足所有调度需求。例如:
- 特殊硬件调度:需要将 Pod 调度到配备了特定硬件(如 NVIDIA GPU、FPGA、高性能网卡(SmartNICs)、加密卡等)的节点上。
- 逻辑资源或配额:管理一些并非物理硬件但需要按节点或集群进行计数的资源,例如软件许可证、特定服务的并发连接数、或者是像图中示例的
reclaimed-cpu
这样的自定义逻辑资源。 - 精细化资源隔离:为特定类型的负载确保独占或定量的特殊资源访问。
Extended Resources 解决了 Kubernetes 原生资源模型无法描述这些特殊节点能力的问题,使得调度器能够基于这些自定义资源约束来放置 Pod。
扩展资源的定义与通告
扩展资源必须遵循 domain/resource-name
的命名格式(kubernetes.io
域为 Kubernetes 核心组件保留)。有两种主要方式在节点上通告扩展资源:
设备插件(Device Plugins):
- 这是管理特定硬件设备(如 GPU、FPGA、SR-IOV VFs 等)的标准方式。
- Device Plugin 是一个运行在节点上的独立进程(通常是 DaemonSet),负责:
- 发现节点上的特定硬件设备。
- 向 Kubelet 注册,并汇报可用设备资源及其健康状况。Device Plugin 通过 gRPC 与 Kubelet 的 Device Manager 进行通信(监听在
/var/lib/kubelet/device-plugins/kubelet.sock
)。 - 分配设备给请求资源的容器。
- 当 Device Plugin 向 Kubelet 注册并汇报资源后,Kubelet 会自动更新该 Node 对象的
.status.capacity
和.status.allocatable
字段,将这些设备资源(例如nvidia.com/gpu: 2
)通告给 API Server。kube-scheduler 后续会读取这些信息进行调度决策。
节点级扩展资源(Operator Managed):
对于非硬件或者不由 Device Plugin 管理的资源(例如图中
cncamp.com/reclaimed-cpu
),集群管理员或运维人员可以通过直接修改 Node 对象的方式来通告。具体操作是向 API Server 发送 HTTP PATCH 请求,更新目标 Node 的
.status.capacity
字段。示例命令 (如图片所示,用于向节点
cadmin
添加cncamp.com/reclaimed-cpu=2
的容量):1
2
3
4
5curl --key admin.key --cert admin.crt --cacert ca.crt \
-H "Content-Type: application/json-patch+json" \
-X PATCH \
--data '[{"op": "add", "path": "/status/capacity/cncamp.com~1reclaimed-cpu", "value": "2"}]' \
https://<api-server-ip>:<port>/api/v1/nodes/cadmin/status- 注意: JSON Patch 路径中的
/
需要转义为~1
,所以cncamp.com/reclaimed-cpu
变成了cncamp.com~1reclaimed-cpu
。
- 注意: JSON Patch 路径中的
在
.status.capacity
被更新后,节点上的 Kubelet 会异步地检测到这个变化,并相应地更新.status.allocatable
字段。kube-scheduler
在进行 Pod 调度时,主要依据的是.status.allocatable
中的资源量。因此,从capacity
更新到资源实际可被调度(allocatable
更新完成)之间可能存在短暂的延迟。
Pod 使用扩展资源
Pod 在其规约(Spec)中可以像请求 CPU 和 Memory 一样请求扩展资源。
声明方式:在容器的
resources
字段下的limits
和requests
中声明扩展资源。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17apiVersion: v1
kind: Pod
metadata:
name: extended-resource-pod
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
cncamp.com/reclaimed-cpu: 3 # 请求 3 个单位的扩展资源
memory: "200Mi"
cpu: "0.5"
requests:
cncamp.com/reclaimed-cpu: 3 # 请求必须等于 Limits
memory: "200Mi"
cpu: "0.5"重要约束:对于扩展资源,
requests
必须等于limits
。这是因为 Kubernetes 无法对扩展资源进行超售(Overcommit)。扩展资源通常代表离散的、不可压缩的单元(如一个 GPU、一个许可证),不像 CPU 时间可以被多个进程分时共享。因此,请求多少就必须预留多少,不允许 Limit > Request 的情况。
调度器与扩展资源
kube-scheduler 在调度 Pod 时,会执行以下步骤相关的扩展资源:
- 过滤(Filtering):检查节点列表,只保留那些
.status.allocatable
中声明的扩展资源满足 Podrequests
需求的节点。如果 Pod 请求了nvidia.com/gpu: 1
,则只有allocatable
中nvidia.com/gpu
大于等于 1 的节点才会通过过滤。 - 打分(Scoring):虽然默认的打分策略可能不直接基于扩展资源的数量(除非配置了特定优先级函数),但满足扩展资源需求是调度的硬性条件。
集群层面的扩展资源与调度器扩展(Scheduler Extenders)
有时,扩展资源可能代表的不是物理设备,而是更抽象的概念,或者其调度逻辑非常复杂,超出了默认调度器的能力。
Scheduler Extenders 是一种允许你插入自定义调度逻辑的机制。你可以部署一个外部服务(Extender),
kube-scheduler
在调度决策的特定点(如 Filter 或 Prioritize)会调用这个外部服务。管理特定扩展资源:Extender 可以用来管理某些特定的扩展资源。例如,某个扩展资源可能代表软件许可证,Extender 需要检查全局许可证的可用性,而不仅仅是节点上的容量。
忽略默认调度器处理:可以配置
kube-scheduler
的 Policy,使其忽略某些扩展资源。这样,这些资源的调度决策就完全委托给了 Extender。示例配置 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16{
"kind": "Policy",
"apiVersion": "v1",
"extenders": [
{
"urlPrefix": "<extender-endpoint>", // Extender 服务的 URL
"bindVerb": "bind", // 指定 Extender 负责绑定操作
"managedResources": [
{
"name": "example.com/foo", // Extender 管理的扩展资源
"ignoredByScheduler": true // 指示默认调度器忽略此资源
}
]
}
]
}在这个配置中,
example.com/foo
这个扩展资源的调度(过滤、打分、绑定)将不会由默认调度器处理,而是完全依赖于配置的 Extender。这对于实现复杂的资源管理(如跨节点配额、拓扑感知调度等)非常有用。
GPU 作为扩展资源:核心机制 - 设备插件
直接通过 PATCH
Node 对象来手动管理 GPU 资源是不切实际的,原因在于:
- 动态性:节点上的 GPU 状态(是否健康、是否可用)可能变化。
- 发现:需要自动检测节点上有多少个、哪些型号的 GPU。
- 分配:需要精确地将特定的物理 GPU 设备映射给请求它的容器。
- 健康检查:需要监控 GPU 的健康状况,并将故障设备从可调度资源中移除。
因此,管理 GPU 资源的标准方法是使用 设备插件(Device Plugin)。
什么是 GPU 设备插件?
- 它是一个遵循 Kubernetes Device Plugin API 规范的、独立于 Kubelet 运行的进程(通常部署为 DaemonSet,确保在需要 GPU 的节点上运行)。
- 供应商特定:不同的 GPU 供应商(如 NVIDIA、AMD)会提供各自的设备插件实现。最常见的是 NVIDIA Device Plugin for Kubernetes。
- 职责:
- 发现 (Discovery):启动时,插件会检测节点上存在的 GPU 设备(例如,NVIDIA 插件会使用
nvidia-ml
库或类似工具来发现物理 GPU)。 - 注册 (Registration):通过 gRPC 与 Kubelet 内建的 Device Manager 通信(监听在 Unix 套接字
/var/lib/kubelet/device-plugins/kubelet.sock
),注册自己能管理的资源类型。对于 NVIDIA GPU,这个资源名通常是nvidia.com/gpu
。 - 汇报资源 (Reporting):向 Kubelet 汇报节点上可用 GPU 的数量和设备 ID。Kubelet 收到这些信息后,会自动更新该 Node 对象的
.status.capacity
和.status.allocatable
字段,例如增加nvidia.com/gpu: 2
表示该节点有 2 个可用的 GPU。这一步将 GPU 资源通告给了 Kubernetes 集群。 - 分配 (Allocation):当一个 Pod 被调度到该节点并请求 GPU 资源时,Kubelet 的 Device Manager 会调用 Device Plugin 的
Allocate
gRPC 方法。插件负责选择一个或多个具体的、当前未被分配的 GPU 设备,并将它们的设备文件路径(例如/dev/nvidia0
,/dev/nvidiactl
,/dev/nvidia-uvm
等)和必要的环境变量(如NVIDIA_VISIBLE_DEVICES=0
)返回给 Kubelet。Kubelet 随后会把这些设备挂载到 Pod 的容器内,并设置相应的环境变量,使得容器内的应用程序能够访问到指定的 GPU。 - 健康检查 (Health Checking):插件可以监控 GPU 的健康状况。如果检测到 GPU 故障,它可以通知 Kubelet 将该 GPU 标记为不可用,Kubelet 会相应地更新节点的
allocatable
资源。
- 发现 (Discovery):启动时,插件会检测节点上存在的 GPU 设备(例如,NVIDIA 插件会使用
操作步骤:启用和使用 GPU 资源
前提条件:
- 节点安装 GPU 驱动:在所有需要运行 GPU 任务的 Kubernetes Worker Node 上,必须预先正确安装相应供应商的 GPU 驱动程序(例如,NVIDIA 驱动)。这是运行 Device Plugin 和 GPU 应用的基础。
1. 部署 GPU 设备插件:
通常使用 DaemonSet 来部署设备插件,确保它在所有(或标记了特定标签的)具备 GPU 硬件的节点上运行。
以 NVIDIA Device Plugin 为例,你需要从 NVIDIA 的官方仓库获取其部署 YAML 文件。
示例部署命令(假设你已获取
nvidia-device-plugin.yml
):1
2kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.1/nvidia-device-plugin.yml
# 注意:请使用适合你 Kubernetes 版本和 GPU 驱动版本的官方推荐 YAML这个 YAML 文件会创建一个
DaemonSet
对象,可能还包括必要的ServiceAccount
、ClusterRole
、ClusterRoleBinding
等 RBAC 配置,赋予 Device Plugin Pod 与 Kubelet 通信所需的权限。
2. 验证资源通告:
等待 Device Plugin Pod 在 GPU 节点上成功运行后,检查节点的资源状态。
示例命令:
1
2
3
4
5
6
7
8
9
10
11
12# 查看某个安装了 GPU 且运行了插件的节点
kubectl describe node <your-gpu-node-name>
# 在输出中查找 Capacity 和 Allocatable 部分,应该能看到类似信息:
# Capacity:
# cpu: ...
# memory: ...
# nvidia.com/gpu: 2 # 假设该节点有 2 块 GPU
# Allocatable:
# cpu: ...
# memory: ...
# nvidia.com/gpu: 2nvidia.com/gpu: 2
表明 Kubelet 已成功接收到 Device Plugin 的汇报,并将 2 个 GPU 资源通告给了 API Server。
3. 在 Pod 中请求 GPU 资源:
在 Pod 的容器规格(
spec.containers[]
)中,通过resources.limits
字段来请求 GPU。关键点:对于 GPU 这类扩展资源,
requests
必须省略 或者 等于limits
。Kubernetes 不支持 GPU 资源的超售。示例 Pod YAML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16apiVersion: v1
kind: Pod
metadata:
name: cuda-vector-add
spec:
restartPolicy: OnFailure
containers:
- name: cuda-vector-add
# 使用包含 CUDA 工具包和驱动兼容的镜像
image: "nvidia/cuda:11.4.0-base-ubuntu20.04"
resources:
limits:
# 请求 1 个 NVIDIA GPU
nvidia.com/gpu: 1
command: ["/bin/sh", "-c"]
args: ["nvidia-smi && sleep 3600"] # 简单运行 nvidia-smi 验证 GPU 可见性工作流程:
- 用户创建这个 Pod。
- kube-scheduler 查找
allocatable
中nvidia.com/gpu
至少为 1 的节点。 - Pod 被调度到满足条件的 GPU 节点。
- 该节点上的 Kubelet 看到 Pod 请求 GPU,调用本地运行的 NVIDIA Device Plugin 的
Allocate
接口。 - Device Plugin 返回一个可用 GPU 的设备信息(如设备 ID
0
)。 - Kubelet 配置容器的 cgroup、挂载
/dev/nvidia0
等设备文件,并设置环境变量NVIDIA_VISIBLE_DEVICES=0
。 - 容器启动,其内部运行的
nvidia-smi
命令应该能看到并访问到被分配的那个 GPU。
高级特性(NVIDIA 相关)
NVIDIA Device Plugin 还支持一些高级特性,例如:
- 时间片共享 (Time-Slicing):允许非关键任务以时间片方式共享同一个 GPU,通过 ConfigMap 配置。资源请求仍然是
nvidia.com/gpu: 1
,但实际是共享使用。 - 多实例 GPU (Multi-Instance GPU, MIG):对于支持 MIG 的 GPU(如 A100),可以将一个物理 GPU 分割成多个独立的、硬件隔离的 GPU 实例。Device Plugin 可以配置为将这些 MIG 设备作为不同的资源类型(例如
nvidia.com/mig-1g.5gb: 1
)进行通告和调度,实现更细粒度的 GPU 资源分配和隔离。
总结来说,要在 Kubernetes 中使用 GPU,标准且推荐的做法是部署对应 GPU 供应商(如 NVIDIA)提供的 Device Plugin。这个插件负责 GPU 的发现、注册、资源汇报、分配和健康检查,通过与 Kubelet 的 Device Manager 交互,将 GPU 作为名为 vendor.com/gpu
(如 nvidia.com/gpu
)的 Extended Resource 暴露给 Kubernetes 系统。用户只需在 Pod 的 resources.limits
中声明对该资源的需求即可,调度器和 Kubelet 会协同 Device Plugin 完成后续的调度和设备分配。
资源浪费与成本优化
在典型的 Kubernetes 集群中,为了保证核心在线服务的 SLA(服务等级协议),我们通常会根据其峰值需求来设置 Pod 的 requests
和 limits
,并配置足够的节点容量。然而,在非高峰时段(波谷),这些核心服务的实际资源使用率可能远低于其请求值,导致节点上存在大量已分配但未使用的 CPU 和内存资源。这些闲置资源,尤其是在公有云环境中,意味着持续的成本支出却没有产生价值。
直接在这些节点上运行低优先级任务,如果不加控制,可能会因为资源争抢而干扰到高优先级服务(“ noisy neighbor” 问题)。使用标准的 CPU requests
为低优先级任务预留资源又违背了利用“空闲”资源初衷,且可能导致节点资源不足。
Extended Resources 提供了一种机制,可以将这种动态变化的、机会性的“空闲资源”显式化,并纳入 Kubernetes 的调度体系。
解决方案:基于 Agent 的空闲资源扩展
1. 定义扩展资源:
我们首先定义一种自定义的扩展资源,用于代表节点上可被低优先级任务使用的、回收来的 CPU 资源。例如,我们称之为 example.com/reclaimed-cpu
。这里的 example.com
是你的组织或项目的域名,reclaimed-cpu
清晰地表达了资源的来源和性质。单位通常沿用 Kubernetes 的 CPU 单位,如 m
(milliCPU)。
2. 开发并部署 Idle Resource Agent:
我们需要在希望回收资源的节点上部署一个 Agent(通常以 DaemonSet 形式运行,确保覆盖所有目标节点)。这个 Agent 的核心职责是:
监控节点资源使用情况:Agent 需要持续监控当前节点的实际 CPU 使用率。更精确的做法是,监控节点总 CPU 容量,并减去关键 Pod(高优先级应用)的实际 CPU 使用量(可以通过 Kubelet 的
/metrics/cadvisor
端点获取容器级别的指标,或集成 Prometheus 等监控系统)。计算可回收资源量:基于监控数据,Agent 计算出当前有多少 CPU 资源是“空闲”的,可以被安全地“回收”给低优先级任务使用。
1
ReclaimedCPU = NodeTotalCPU - Sum(CriticalPodsActualCPUUsage) - SafetyBuffer
NodeTotalCPU
: 节点的总 CPU 容量。CriticalPodsActualCPUUsage
: 通过标签选择器等方式识别出的关键 Pod 的实时 CPU 使用总和。SafetyBuffer
: 非常重要!设置一个安全缓冲 CPU 量,防止低优先级任务在关键应用 CPU 使用量突然增加时造成严重干扰。这个 Buffer 可以是固定值,也可以是节点容量的一个百分比。
动态更新 Node Status:Agent 定期(例如每 30-60 秒)通过 Kubernetes API PATCH 请求,更新其所在 Node 对象的
.status.capacity
字段,将计算出的example.com/reclaimed-cpu
的值写入。Agent 内部 Go 代码片段(使用 client-go 示意):
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65import (
"context"
"fmt"
"os"
"time"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/json" // For JSON Patch
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1 "k8s.io/api/core/v1" // For Node type, though patch might not need it directly
)
func updateNodeReclaimedCPU(clientset *kubernetes.Clientset, nodeName string, reclaimedCPUValue string) error {
patchPayload := []map[string]interface{}{
{
"op": "add", // Use "replace" if the path might already exist, or check first
"path": "/status/capacity/example.com~1reclaimed-cpu", // ~1 is JSON Pointer escape for /
"value": reclaimedCPUValue, // e.g., "1500m" or "2"
},
}
patchBytes, _ := json.Marshal(patchPayload)
_, err := clientset.CoreV1().Nodes().Patch(context.TODO(), nodeName, types.JSONPatchType, patchBytes, metav1.PatchOptions{}, "status")
if err != nil {
// Handle error (e.g., log, retry logic)
return fmt.Errorf("failed to patch node %s status: %v", nodeName, err)
}
fmt.Printf("Successfully patched node %s with reclaimed-cpu: %s\n", nodeName, reclaimedCPUValue)
return nil
}
// In the main loop of the Agent:
func agentLoop(clientset *kubernetes.Clientset, nodeName string) {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for range ticker.C {
// 1. Monitor CPU usage (implementation depends on metrics source)
actualUsageCPU := getCriticalPodsUsage() // Placeholder function
nodeCapacityCPU := getNodeCapacity() // Placeholder function
safetyBuffer := getSafetyBuffer() // Placeholder function
// 2. Calculate reclaimed CPU (ensure non-negative)
reclaimedMilliCPU := nodeCapacityCPU*1000 - actualUsageCPU*1000 - safetyBuffer*1000
if reclaimedMilliCPU < 0 {
reclaimedMilliCPU = 0
}
reclaimedCPUValue := fmt.Sprintf("%dm", reclaimedMilliCPU)
// 3. Get current value to decide if update is needed (optional optimization)
// currentNode, _ := clientset.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{})
// currentReclaimed, _ := currentNode.Status.Capacity[corev1.ResourceName("example.com/reclaimed-cpu")]
// if currentReclaimed.String() == reclaimedCPUValue { continue }
// 4. Update Node Status via PATCH
err := updateNodeReclaimedCPU(clientset, nodeName, reclaimedCPUValue)
if err != nil {
fmt.Fprintf(os.Stderr, "Error updating node status: %v\n", err)
}
}
}
// Note: The Agent needs RBAC permissions to Get and Patch Node Status
// Ensure its ServiceAccount is bound to a Role/ClusterRole with these permissions.
Agent 所需 RBAC (示例 ClusterRole):
1 |
|
- 处理零资源:当计算出的可回收 CPU 小于等于零时,Agent 应将 Node Status 中的
example.com/reclaimed-cpu
更新为0
或完全移除该条目,防止新的低优先级 Pod 被调度到该节点。
3. 配置低优先级 Pod 请求扩展资源:
现在,对于那些可以容忍资源波动、优先级较低的“波谷”工作负载(例如,一个批处理 Job),在定义它们的 Pod Spec 时:
不请求或请求极少量的标准
cpu
资源。在
resources.limits
中请求自定义的example.com/reclaimed-cpu
资源。记住,对于扩展资源,requests
必须等于limits
(或者省略requests
,它会默认等于limits
)。示例 Job YAML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24apiVersion: batch/v1
kind: Job
metadata:
name: batch-processing-job
spec:
template:
spec:
containers:
- name: processor
image: my-batch-processor:latest
resources:
limits:
# Only request the reclaimed CPU, not standard CPU
example.com/reclaimed-cpu: "1000m" # Request 1 reclaimed CPU core
memory: "2Gi" # Still request memory as usual
requests:
# Requests must equal limits for extended resources
example.com/reclaimed-cpu: "1000m"
memory: "2Gi"
restartPolicy: Never # Or OnFailure, typical for Jobs
# Important: Add tolerations or node selectors if the agent only runs on specific nodes
# topologySpreadConstraints might also be useful
# Consider setting a low priorityClassName
priorityClassName: low-priority # Assuming you have defined this class
4. 工作流程与调度:
- Agent 持续运行,根据实际负载动态更新各节点上
example.com/reclaimed-cpu
的capacity
(Kubelet 会相应更新allocatable
)。 - 当用户提交上述
batch-processing-job
时,kube-scheduler 会寻找满足其资源请求的节点。 - 调度器只会考虑那些当前
.status.allocatable
中example.com/reclaimed-cpu
大于或等于 1000m 的节点。 - Job 的 Pod 会被调度到有足够“空闲”资源的节点上运行。
- 如果某个节点上的高优先级应用负载上升,Agent 会减少或清零该节点的
reclaimed-cpu
容量,新的低优先级 Pod 就不会再被调度到该节点。
如何实现降本增效
- 提高资源利用率:原本闲置的 CPU 周期被有效利用起来运行额外的(低优先级)工作负载,提高了整个集群的资源利用率。
- 避免过度配置:可以更精细地控制资源分配,减少为应对峰值而预留的、大部分时间闲置的资源量。
- 降低成本:
- 在公有云上,更高的资源利用率意味着可以用更少或更小的节点实例来承载相同的工作负载总量,直接降低虚拟机费用。
- 在私有云或物理部署中,可以支持更多的业务,延缓硬件采购需求。
- 保障核心业务:通过
SafetyBuffer
和仅让低优先级任务使用reclaimed-cpu
的方式,最大限度地减少了对高优先级在线服务性能的影响。
7. 构建和管理高可用(HA)集群
Kubernetes 高可用层级

基础架构管理 (Infrastructure Management)
这是 Kubernetes 集群运行的物理或虚拟基础。高可用的基石在于底层基础设施的稳定性与冗余性。如果底层硬件、网络或操作系统频繁故障,上层的 Kubernetes 也难以保证高可用。
- 主机上架 (Server Provisioning): 需要有多台物理或虚拟机作为 Kubernetes 的 Master 和 Worker 节点。在关键集群中,这些主机应分布在不同的故障域 (Failure Domains),例如不同的机架、不同的可用区(Availability Zones, AZs),以防止单点物理故障。
- OS 管理 (OS Management): 运行稳定且经过安全加固的 Linux 发行版(如 Ubuntu, CentOS, RHEL)。内核版本、系统参数(如
fs.file-max
,net.core.somaxconn
)需要根据集群规模和负载进行优化。操作系统的补丁管理和升级策略也需要考虑对集群服务的影响。 - 安全策略 (Security Policies): 网络隔离(如使用安全组、防火墙
iptables
/nftables
)、主机访问控制、镜像安全扫描等,虽然不直接等同于 HA,但安全事件可能导致服务中断,因此也是广义可用性的一部分。 - 主机网络 (Host Networking): 网络冗余是基础设施 HA 的关键。通常会配置多块网卡进行绑定 (Bonding),例如使用 Linux 内核的
bonding
模块,配置active-backup
或LACP (802.3ad)
模式,确保单块网卡或交换机端口故障时不影响节点网络连接。底层网络架构也应具备冗余性(如冗余交换机、路由器)。 - Container Runtime: Docker、containerd 或 CRI-O 等容器运行时本身需要稳定可靠。虽然运行时本身通常不直接做 HA 集群,但其稳定运行是 Pod 能否正常启动和运行的前提。它们的配置(如存储驱动、网络插件接口)会影响性能和稳定性。
集群管理 (Cluster Management)
这一层关注 Kubernetes 集群本身的搭建、维护和管理,确保集群作为一个整体是可用的。
- 集群安装 (Cluster Installation): 使用如
kubeadm
、k3s
、RKE
或商业发行版安装 K8s。安装过程需要规划好控制平面节点 (Control Plane Nodes) 和工作节点 (Worker Nodes) 的数量和分布。 - 节点管理 (Node Management): 集群管理员需要监控节点状态(
kubectl get nodes
),处理NotReady
状态的节点,执行节点维护(如内核升级、硬件更换)时的排空 (Drain) 操作 (kubectl drain <node-name>
),以优雅地迁移 Pod。 - 认证授权 (Authentication & Authorization): RBAC (Role-Based Access Control) 等机制虽然主要关注安全,但也关系到哪些用户或服务账号可以执行可能影响可用性的操作(如删除 Deployment)。
- 网络 (Networking): CNI (Container Network Interface) 插件的选择和配置至关重要。例如 Calico、Flannel、Cilium 等。一些 CNI 插件(如 Calico BGP 模式)自身可以配置路由冗余。网络策略 (NetworkPolicy) 用于隔离 Pod 间流量,防止故障扩散。
- 存储 (Storage): CSI (Container Storage Interface) 驱动的选择和配置直接关系到有状态应用的数据持久性和可用性。需要使用支持动态卷分配 (Dynamic Provisioning) 和跨节点挂载的存储解决方案(如 Ceph、NFS、云厂商块存储)。存储系统自身的 HA 是有状态应用 HA 的前提。
- 配额管理 (Resource Quotas): 通过 ResourceQuota 和 LimitRange 限制命名空间或 Pod 的资源使用,防止某个应用耗尽节点资源导致其他应用甚至节点本身不可用。
- 备份恢复 (Backup & Restore):
etcd
是 Kubernetes 的状态存储核心,其备份和恢复机制是集群灾难恢复的关键。通常需要定期对etcd
进行快照备份,并演练恢复流程。
控制平面 (Control Plane)
这是 Kubernetes 的“大脑”,负责集群决策和状态管理。控制平面的高可用是 Kubernetes 集群 HA 的核心。
核心组件 (Core Components):
etcd
: 分布式键值存储,保存集群的所有状态数据。必须部署为集群模式(通常 3 或 5 个节点),利用 Raft 协议保证数据一致性和容错性。只要超过半数的etcd
节点存活,etcd
集群就能正常工作。1
2# 查看 etcd 集群成员状态 (需在 etcd Pod 或有 etcdctl 的地方执行)
ETCDCTL_API=3 etcdctl --endpoints=https://[ETCD_IP]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key member listkube-apiserver
: 集群的入口,处理所有 API 请求。需要部署多个实例,并在它们前面放置一个负载均衡器 (Load Balancer)(可以是云厂商的 ELB,也可以是自建的 HAProxy/Nginx 或 Keepalived+LVS)。所有其他组件(kubectl
,kubelet
, controllers)都通过这个负载均衡器的 VIP 或 DNS 地址访问 API Server。kube-controller-manager
: 运行各种控制器(如 Deployment Controller, Node Controller)。同一时间只有一个实例是 active (leader),其他实例处于 standby 状态。它们通过租约 (Lease) 对象或 Endpoint 对象进行领导者选举 (Leader Election)。如果当前 leader 故障,其他实例会竞选成为新的 leader。这个机制通常是利用 Kubernetes API 实现的,例如 Golang 的client-go
库提供了leaderelection
包。kube-scheduler
: 负责将 Pod 调度到合适的 Node 上。与 Controller Manager 类似,也需要部署多个实例并进行领导者选举。
插件 (Plugins) & 用户空间控制器 (Custom Controllers): 部署在集群中用于扩展功能的组件(如 metrics-server, ingress controller, cert-manager 等)也需要考虑自身的 HA,通常也是通过部署多个副本和 Leader Election(如果需要)来实现。
Assertion: 这可能指的是集群健康检查和断言机制,确保集群状态符合预期。
数据平面 (Data Plane)
这是用户应用负载实际运行的地方。数据平面的高可用关注的是应用本身在节点故障、Pod 故障等情况下的持续服务能力。
Pod: 运行应用的最小单元。本身是短暂的 (Ephemeral)。高可用不依赖单个 Pod 的存活,而是依赖于多个 Pod 副本 (Replicas)。
PVC (PersistentVolumeClaim): 应用对持久化存储的请求。需要由可靠的、支持跨节点访问的 PV (PersistentVolume) 来满足,如前述的网络存储。确保在 Pod 漂移到新节点后,数据卷能够被重新挂载。
Service: 提供了一个稳定的访问入口(ClusterIP, NodePort, LoadBalancer)来访问一组 Pod。它通过
kube-proxy
(运行在每个 Node 上)在 Linux 内核中利用iptables
或IPVS
规则实现负载均衡。当一个 Pod 故障并被控制器替换后,Service 会自动将流量转发到健康的 Pod 上。1
2
3
4
5
6
7
8
9
10
11
12
13# Service 示例:为 app=my-app 的 Pod 提供服务
apiVersion: v1
kind: Service
metadata:
name: my-app-svc
spec:
selector:
app: my-app # 选择标签为 app=my-app 的 Pod
ports:
- protocol: TCP
port: 80 # Service 暴露的端口
targetPort: 8080 # Pod 容器监听的端口
type: ClusterIP # 或 NodePort, LoadBalancerIngress: 管理对集群内部 Service 的外部访问(通常是 HTTP/HTTPS)。Ingress Controller (如 Nginx Ingress, Traefik) 本身也需要部署多个副本,并通常通过 Service of type LoadBalancer 或 NodePort 对外暴露。Ingress Controller 的 HA 确保了外部流量入口的可用性。
分级部署 (Staged Deployment - Application Focus)
这部分更侧重于应用开发者如何利用 Kubernetes 机制来保障其应用的高可用。
资源需求 (Resource Requests/Limits): 正确设置 Pod 的 CPU 和 Memory Requests/Limits,确保 Pod 获得必要的资源,避免因资源不足而被 OOMKilled 或影响性能,同时也帮助 Scheduler 做出更合理的调度决策。
接入需求 (Ingress Needs): 应用需要被外部访问时,定义 Ingress 规则。
亲和性 (Affinity/Anti-Affinity): 使用 Pod (Anti-)Affinity 规则来控制 Pod 的部署位置。例如,使用
podAntiAffinity
要求一个 Deployment 的多个 Pod 分散到不同的节点或可用区,提高容错性。1
2
3
4
5
6
7
8
9
10
11# Pod Anti-Affinity 示例:尽量不将同一应用的 Pod 调度到同一节点
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 或 preferredDuringSchedulingIgnoredDuringExecution
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- my-app # 与自身的 app 标签匹配
topologyKey: "kubernetes.io/hostname" # 拓扑域为节点主机名横向扩展 (Horizontal Scaling): 使用 Deployment 或 StatefulSet 管理 Pod 副本,并配置 HorizontalPodAutoscaler (HPA) 根据 CPU、内存使用率或其他自定义指标自动增减 Pod 数量,应对负载变化,保证服务能力。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19# HPA 示例:根据 CPU 使用率自动伸缩 Deployment
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app-deployment # 要伸缩的目标 Deployment
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 80 # CPU 使用率达到 80% 时扩容服务注册/发现 (Service Registration/Discovery): Kubernetes 内置了基于 DNS (CoreDNS/KubeDNS) 和 Service 的服务发现机制。应用可以通过 Service 名称(如
my-app-svc.my-namespace.svc.cluster.local
)来访问其他服务,无需关心 Pod 的具体 IP 地址。健康检查 (Health Checks): 配置 Liveness Probe 和 Readiness Probe。Liveness Probe 用于检测容器是否存活,如果不存活,
kubelet
会杀死并重启容器。Readiness Probe 用于检测容器是否准备好接收流量,如果未就绪,会被从 Service 的 Endpoints 列表中移除。这是保证 Service 流量只打到健康 Pod 上的关键。1
2
3
4
5
6
7
8
9
10
11
12
13# Probe 示例
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10滚动更新与中断预算 (Rolling Updates & PodDisruptionBudget): Deployment 默认使用滚动更新策略,保证更新过程中服务不中断。配置 PodDisruptionBudget (PDB) 可以确保在自愿性中断(如节点维护)期间,至少有多少个 Pod 副本是可用的。
1
2
3
4
5
6
7
8
9
10# PDB 示例:确保 my-app 至少有 2 个副本可用
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-pdb
spec:
minAvailable: 2 # 或 maxUnavailable: 1
selector:
matchLabels:
app: my-app
应用开发 (Application Development)
开发者自身在编码层面也需要考虑高可用。
- 应用管理 (Application Management): 设计为无状态 (Stateless) 服务是最佳实践,易于水平扩展和替换。如果是有状态服务,需要妥善处理状态的持久化和一致性。
- 服务开发 (Service Development): 应用需要能够处理优雅终止 (Graceful Shutdown)。当收到
SIGTERM
信号时(Pod 删除前会发送),应用应完成当前请求,释放资源,然后退出。否则强制终止 (SIGKILL
) 可能导致请求失败或数据不一致。 - 持续集成 (Continuous Integration): CI/CD 流程的稳定性和自动化程度也影响着应用部署和更新的可靠性。
Kubernetes 公共服务 & 企业公共服务
这些是集群运行所依赖的支撑服务,它们自身也需要高可用。
- 日志/监控/告警: Prometheus/Thanos、Elasticsearch/Fluentd/Kibana (EFK)、Loki/Grafana/Promtail (PLG) 等组件通常都需要部署为 HA 模式(多副本、数据冗余)。
- 本地/企业镜像仓库: Harbor 等镜像仓库需要保证高可用,否则应用无法部署和更新。
- 四层/七层代理/Service Mesh: 除了 Ingress Controller,Service Mesh (如 Istio, Linkerd) 的控制平面和数据平面也需要高可用配置。
- KubeDNS/CoreDNS: 作为集群内部服务发现的核心,必须部署多个副本,并利用 Kubernetes Service 对外提供服务。
- Jenkins/CI/CD: CI/CD 平台的高可用保证了开发和部署流程的顺畅。
- 企业 DNS, ELB, 用户管理: 这些外部依赖(通常由企业 IT 部门或云厂商提供)的可用性直接影响 Kubernetes 集群和应用的对外服务能力。
高可用的数据中心 (Highly Available Data Center)
这部分内容描述了实现 Kubernetes 或任何关键业务系统高可用的物理基础设施层面的要求。它是整个高可用体系的物理基石。如果数据中心本身存在单点故障,那么上层所有的 HA 机制都可能失效。
多地部署 (Multi-Site Deployment): 这是最高级别的物理容灾策略。将系统部署在地理位置分散的多个数据中心,可以抵御区域性的灾难(如地震、洪水、大规模断电)。每个数据中心都是一个独立的故障域。虽然 Kubernetes 本身可以通过 Federation v2 或其他多集群管理工具(如 Karmada)实现跨数据中心的管理和应用分发,但这通常带来了更高的复杂性、成本和潜在的网络延迟问题。对于大多数组织而言,在单个地理区域内的多个可用区部署是更常见的实践。
高可用区 (Availability Zones - AZs): 这是在单个地理区域(Region)内实现高可用的核心概念。每个数据中心需要划分成多个具有独立供电、制冷、网络设备的高可用区(AZ)。这里的“独立”意味着一个 AZ 的基础设施故障(如断电、空调故障、核心交换机故障)不应影响到其他 AZ。AZ 的设计目标是隔离物理故障,使得单个基础设施组件的失败不会导致整个数据中心或区域的服务中断。在公有云环境中,如 AWS、Azure、GCP,AZ 是标准的物理隔离单元。在自建数据中心,也需要参照此原则进行规划。
独立硬件资产 (Independent Hardware Assets): 每个高可用区管理独立的硬件资产,这是实现 AZ 隔离的具体手段。这包括:
- 机架 (Racks): 服务器应分布在不同 AZ 的不同机架上。
- 计算节点 (Compute Nodes): 即运行 Kubernetes Master和 Worker 角色的物理或虚拟机。关键的控制平面节点(
etcd
,api-server
等)和应用负载的工作节点都应跨 AZ 部署,以确保在一个 AZ 完全故障时,集群和其他 AZ 中的应用仍然可用。Kubernetes 通过节点标签 (Node Labels) 和拓扑感知调度 (Topology Aware Scheduling) 来支持这种跨 AZ 部署。例如,节点通常会被打上topology.kubernetes.io/zone=us-east-1a
这样的标签,调度器和控制器(如 StatefulSet 的podAntiAffinity
)可以利用这些标签来分散 Pod。 - 存储 (Storage): 持久化存储系统本身需要高可用,并且最好是 AZ 感知的。例如,使用 Ceph 并配置 CRUSH 规则将数据副本分布到不同的 AZ;或者使用云厂商提供的跨 AZ 复制的块存储或文件存储服务。这确保了当一个 AZ 的存储不可用时,数据仍然可以从其他 AZ 访问(可能需要应用或 K8s 层面的故障转移)。
- 负载均衡器 (Load Balancers): 无论是硬件 F5 还是软件 Nginx/HAProxy,或者是云厂商的 ELB/ALB,都需要有冗余部署,并且能够跨 AZ 分发流量。入口流量的负载均衡器是外部用户访问集群服务的关键路径。
- 防火墙 (Firewalls): 网络边界的安全设备也需要冗余部署,通常是主备或集群模式,分布在不同的 AZ 或至少有冗余的网络路径接入。
总而言之,高可用的数据中心设计通过物理层面的冗余和故障域隔离(主要是 AZ),为上层的 Kubernetes 集群和应用提供了稳定可靠运行的基础环境。没有这一层,软件层面的 HA 措施效果将大打折扣。
Node 的生命周期管理 (Node Lifecycle Management)
这部分描述了 Kubernetes 集群运维中的一个核心任务:管理集群中计算节点(Node)从加入到退出的整个过程。运营 Kubernetes 集群不仅仅是初始搭建,更重要的是持续地、自动化地管理节点的生命周期,以保证集群的健康、弹性和资源充足。核心是 Node 的生命周期管理,而不是简单的扩容和缩容。
- 集群搭建 (Cluster Setup): 这是生命周期的起点,但不是管理的全部。
- 集群扩容/缩容 (Cluster Scaling): 这是生命周期中最常见的操作之一,涉及节点的加入 (Onboard) 和移除 (Offboard)。
- 集群销毁 (Cluster Decommissioning): 很少发生,但涉及所有节点的 Offboard。
生命周期管理主要包含以下阶段:
Onboard (节点加入/扩容): 将一个新的物理机或虚拟机添加到 Kubernetes 集群作为工作节点的过程。
物理资产上架 (Physical Asset Provisioning): 准备好服务器硬件。
操作系统安装 (OS Installation): 安装符合要求的 Linux 操作系统(内核版本、依赖包等)。
网络配置 (Network Configuration): 配置服务器 IP 地址、网关、DNS,确保它可以访问 Kubernetes 控制平面和其他节点,并满足 CNI 插件的网络要求(例如,可能需要特定的 MTU 设置或路由配置)。
Kubernetes 组件安装 (K8s Component Installation): 安装容器运行时 (Container Runtime)(如 containerd, Docker)、
kubelet
(负责管理节点上的 Pod 和容器,与 API Server 通信)和kube-proxy
(负责实现 Kubernetes Service 的网络规则,通常通过iptables
或IPVS
。IPVS
利用 Linux 内核的 Netfilter 和 IP Virtual Server 提供更高效的负载均衡)。创建 Node 对象 (Node Object Creation): 通常使用
kubeadm join
命令将节点加入集群。这个过程包括 TLS 引导(安全地获取证书与 API Server 通信)、kubelet
向 API Server 注册自身。成功注册后,API Server 中会创建一个 Node 对象,代表这个新加入的机器。集群管理员可以通过kubectl get nodes
查看到这个新节点,并且其状态应为Ready
。1
2
3
4
5# 示例:一个新节点加入集群(使用 kubeadm)
# 在 Master 节点上生成 join 命令
# kubeadm token create --print-join-command
# 在新 Worker 节点上执行 Master 节点生成的 join 命令
# sudo kubeadm join <control-plane-host>:<control-plane-port> --token <token> --discovery-token-ca-cert-hash sha256:<hash>此时,该节点变为可调度状态(除非被标记为
Unschedulable
),kube-scheduler
可以将新的 Pod 分配到这个节点上。
故障处理 (Fault Handling): 节点在运行过程中可能会遇到问题。
- 检测: Kubernetes Node Controller(运行在
kube-controller-manager
中)负责监控节点状态。kubelet
会定期向 API Server 发送心跳 (Heartbeat) 更新 NodeStatus。如果 Node Controller 在一定时间(--node-monitor-grace-period
,默认为 40s)内未收到心跳,会将节点状态标记为Unknown
,如果持续无法联系(总超时--node-monitor-period
+ grace period),则标记为NotReady
。 - 临时故障 (Temporary Fault): 例如节点重启。如果节点恢复并
kubelet
重新启动,它会再次开始发送心跳,节点状态会恢复为Ready
。运行在该节点上的 Pod(如果是被 Deployment 等控制器管理的)通常会在节点恢复后继续运行(如果 Pod 本身没问题),或者如果之前被判定为故障节点上的 Pod 已被驱逐,则控制器会在其他节点上重建它们。 - 永久故障 (Permanent Fault): 例如硬件损坏导致机器无法启动。节点会长时间处于
NotReady
状态。Kubernetes 会在超时后(--pod-eviction-timeout
,默认为 5 分钟)驱逐 (Evict) 该节点上的 Pod。这意味着 API Server 会删除这些 Pod 对象,对应的控制器(如 Deployment Controller)会监测到 Pod 数量少于期望值,并在其他健康节点上创建新的 Pod 来替代它们。这是 Kubernetes 实现应用自愈和高可用的关键机制之一。对于永久故障的节点,需要进行 Offboard 操作。
- 检测: Kubernetes Node Controller(运行在
Offboard (节点移除/缩容/退役): 从集群中移除一个节点。这可能是因为缩容、硬件维护、升级或退役。
驱逐 Pod (Drain): 在移除节点前,必须先执行
kubectl drain <node-name>
操作。drain
命令会首先将节点标记为不可调度 (Unschedulable/Cordoned),阻止新的 Pod 被调度上来;然后优雅地驱逐 (Evict) 节点上现有的所有 Pod(除了不受控制器管理的 Pod 和 DaemonSet 的 Pod,可以通过参数--ignore-daemonsets
和--delete-local-data
控制)。驱逐过程会遵循 Pod 的优雅终止期 (Graceful Termination Period) 和 PodDisruptionBudgets (PDB),确保服务的平稳过渡。1
2
3# 示例:优雅地移除节点上的 Pod 并标记为不可调度
kubectl drain <node-name> --ignore-daemonsets --delete-local-data --force
# 注意:--force 通常用于处理不受控制器管理的 Pod,需谨慎使用删除 Node 对象 (Delete Node Object): 在所有 Pod 都被成功驱逐后,可以从 Kubernetes API 中删除该 Node 对象:
kubectl delete node <node-name>
。这会通知 Kubernetes 不再管理这台机器。物理资产下架/送修/报废 (Physical Asset Decommissioning): 最后,关闭服务器电源,进行物理下架、维修或报废处理。
Node 的生命周期管理是 Kubernetes 运维的核心,涉及到集群的弹性伸缩、故障恢复和日常维护。自动化这些流程(例如使用 Cluster Autoscaler 自动扩缩容,或结合监控告警自动触发 drain
和节点替换流程)对于大规模集群的稳定运行至关重要。
主机管理 (Host Management)
主机选型与规划
- 选定系统内核版本与发行版 (Select Kernel Version and Distribution): 内核版本直接影响系统的性能、稳定性和对新硬件、新功能(如 eBPF, cgroups v2)的支持。选择一个经过广泛测试、与 Kubernetes 版本兼容、并且有长期支持(LTS)的 Linux 发行版(如 Ubuntu LTS, CentOS Stream, RHEL)和内核版本至关重要。例如,较新的 K8s 版本可能依赖特定内核功能才能完全发挥 CNI 或 CSI 插件的性能。不恰当的内核版本可能导致性能瓶颈、驱动不兼容甚至内核恐慌 (Kernel Panic)。
- 安装工具集 (Install Toolsets): 主机上需要预装一些必要的工具,如
socat
,conntrack
,ipset
等,这些是kubelet
,kube-proxy
或某些 CNI 插件正常运行所依赖的。标准化这些基础工具集可以确保节点行为的一致性。 - 主机网络规划 (Host Network Planning): 需要规划节点的 IP 地址分配、子网划分、网关设置、DNS 配置。确保主机网络有足够的带宽和低延迟,并且具备冗余性(如前述的网卡 Bonding)。网络规划还需考虑与 Kubernetes CNI 插件的兼容性,例如,某些 CNI(如 Calico BGP 模式)可能对底层网络架构有特定要求。合理的网络规划可以避免 IP 冲突、网络隔离问题,并优化 Pod 间的通信效率。
主机镜像升级与风险
图片指出了一个关键的运维挑战:日常的主机镜像升级更新也可能是造成服务不可用的因素之一。这里的“主机镜像”通常指预先制作好的包含操作系统、固定内核、必要工具和配置的操作系统模板(VM 模板或物理机安装镜像)。
即使是常规的安全补丁更新或内核升级,也可能引入问题:
- 兼容性问题: 新内核或系统库可能与
kubelet
、容器运行时或 CNI/CSI 插件不兼容,导致节点无法加入集群或 Pod 运行异常。 - 配置漂移: 更新过程可能意外修改了关键配置文件(如
sysctl.conf
、网络配置、防火墙规则),影响节点功能。 - 更新失败: 更新过程本身可能失败,导致主机处于不稳定状态。
- 重启中断: 大多数内核或关键系统库的更新都需要重启主机才能生效。在 Kubernetes 环境下,这意味着节点需要被排空 (Drain),上面的 Pod 需要被迁移,这会暂时减少集群的可用容量,并可能对有状态应用或配置了严格 PDB 的应用造成影响。如果更新或重启过程耗时过长,或者多个节点同时更新,可能会对集群整体可用性产生显著影响。
A/B 系统 OTA 升级方式
为了降低主机操作系统升级带来的风险和停机时间,图片介绍了一种先进的升级策略:A/B 系统 OTA (Over-The-Air) 升级。这种方式常见于嵌入式设备、移动操作系统(如 Android),也被一些现代 Linux 发行版(如 CoreOS/Flatcar Linux, Fedora Silverblue/Kinoite)或专门的服务器管理方案所采用。
其核心思想是:
- 双分区系统 (Dual Partition System): 主机的存储设备(通常是根文件系统所在的磁盘或分区)被划分为两个独立的系统分区,我们称之为 A 分区和 B 分区。在任何时候,只有一个分区是活动 (Active) 的,系统从该分区启动并运行。另一个分区是非活动 (Inactive) 的。
- 共享数据 (Shared Data): 用户数据、应用程序数据、持久化配置等通常存储在独立于 A/B 分区的、共享的数据分区上(例如挂载到
/var
或/home
)。这样,无论系统从 A 启动还是从 B 启动,都能访问到相同的数据。 - 后台升级 (Background Update): 当需要进行系统升级时,升级包会被下载并应用到当前的非活动分区 (Inactive Partition)。例如,如果当前系统运行在 A 分区,那么新的操作系统镜像会被完整地写入 B 分区。这个过程在后台进行,不影响当前正在运行的系统(即 A 分区)。
- 切换启动目标 (Switch Boot Target): 升级完成后,系统会修改引导加载程序 (Bootloader,通常是 GRUB) 的配置,将下一次启动的目标指向刚刚更新完成的那个分区(在这个例子中是 B 分区)。
- 重启生效 (Reboot to Apply): 只需要一次重启,系统就会从新的、已更新的分区(B 分区)启动。由于新系统镜像已经提前完整写入,启动过程通常很快,并且减少了在启动过程中应用补丁可能带来的风险。
- 故障回滚 (Rollback Capability): 这是 A/B 升级的关键优势。如果在从新分区(B 分区)启动后发现任何问题(例如,系统不稳定、关键服务无法启动),管理员可以简单地再次修改引导加载程序配置,让系统下次从原来的、未经修改的分区(A 分区)启动。这就实现了快速且可靠的回滚,将系统恢复到升级前的状态。
在 Kubernetes 环境中应用 A/B 升级的优势:
- 减少维护窗口: 升级的主要工作(下载和写入镜像)在后台完成,真正需要中断节点服务的时间仅限于一次重启。配合
kubectl drain
,可以将对业务的影响降到最低。 - 提高升级可靠性: 避免了在运行中的系统上执行复杂的原地升级(in-place upgrade)脚本可能带来的风险。新系统是作为一个整体被验证和部署的。
- 快速回滚: 如果升级后的节点出现问题(例如
kubelet
无法启动,或者性能下降),可以快速重启回滚到之前的稳定版本,大大缩短故障恢复时间。
实现方式:
- 需要操作系统和引导加载程序支持 A/B 分区方案。例如,使用支持该模式的 Linux 发行版,或者通过工具(如
ostree
,Mender.io
,RAUC
)和自定义分区布局来实现。 - 需要配套的 OTA 更新服务器和客户端机制来管理镜像的分发和升级流程。
生产化集群管理
如何设定单个集群规模 (Setting Single Cluster Size)
这是一个基础且关键的决策,涉及到对集群管理复杂性、故障影响范围(Blast Radius)以及资源利用率的权衡。
社区规模声明与现实挑战: Kubernetes 社区通常声明单个集群理论上可支持高达 5000 个节点。然而,这更多是一个基准或上限参考,实际可达到的稳定规模强依赖于控制平面的资源配置、etcd 的性能、网络质量以及集群上运行的工作负载类型和密度。在超大规模集群(数千节点)中,管理和运维会面临诸多挑战:
- 控制平面压力:
kube-apiserver
需要处理来自大量kubelet
、用户和其他组件的请求,可能成为瓶leneck。etcd
作为状态存储核心,其读写性能和存储容量会面临巨大压力,对延迟非常敏感,大规模集群的事件风暴可能导致其性能下降甚至不稳定。Linux 内核的网络连接跟踪表 (conntrack
) 也可能在高并发连接下耗尽。 - 故障域扩大 (Increased Blast Radius): 单个大型集群意味着任何控制平面的故障、核心组件(如 CNI、CoreDNS)的全局性问题或安全漏洞,其影响范围会波及整个集群的所有应用,可能导致大规模服务中断。
- 升级与维护困难: 对大型集群进行版本升级或重大变更风险更高,耗时更长,回滚也更复杂。协调数千个节点的维护窗口是一项艰巨的任务。
- 资源公平性与隔离: 在超大集群中,确保不同团队或应用之间的资源公平分配、避免“吵闹邻居”问题(一个行为不当的应用影响其他应用)变得更加困难,需要更精细的 ResourceQuota、LimitRange 和可能的 NetworkPolicy 配置。
- 控制平面压力:
更多还是更少?如何权衡?
- 倾向于更少、更大的集群:
- 优点: 管理开销相对较低(管理一个控制平面 vs 多个),集群内服务间通信简单直接(无需跨集群服务发现和网络连接)。
- 缺点: 如上所述,故障域大,升级风险高,可能存在扩展瓶颈。
- 倾向于更多、更小的集群:
- 优点: 显著减小故障域 (Reduced Blast Radius),单个集群故障影响范围有限。更容易实现基于业务线、环境或团队的隔离。升级和维护可以分批进行,降低风险。可能更容易满足特定的合规性或数据本地性要求。
- 缺点: 运维开销增加(需要管理多个控制平面、etcd 集群、监控系统等)。需要解决跨集群的服务发现、网络连接和身份认证问题,增加了架构复杂性(可能需要 Service Mesh 或多集群管理平台如 Karmada, Open Cluster Management)。
权衡决策通常基于: 组织的运维能力、应用架构特点、对故障域的容忍度、安全和合规要求、地理分布需求等。没有绝对的“最佳”规模,需要根据具体场景定制。
- 倾向于更少、更大的集群:
如何根据地域划分集群 (Dividing Clusters by Region)
这关系到如何处理跨地理位置的节点部署,核心在于网络延迟对 Kubernetes 控制平面(尤其是 etcd
)的影响。
不同地域的计算节点划分到同一集群 (Anti-Pattern): 强烈不推荐将跨越广域网(WAN)的不同地理区域(例如北京和上海)的节点加入到同一个 Kubernetes 集群。原因在于:
- 高延迟和不稳定性:
etcd
依赖 Raft 协议进行数据同步和领导者选举,该协议对网络延迟非常敏感。跨地域的高延迟(通常几十到几百毫秒)会导致 Raft 超时、频繁的领导者选举失败,最终破坏etcd
集群的稳定性和数据一致性。 kubelet
心跳问题:kubelet
需要定期向kube-apiserver
发送心跳并更新节点状态。高延迟可能导致心跳超时,Node Controller 会将节点标记为NotReady
,导致 Pod 被错误地驱逐。- API 访问缓慢: 远端节点访问中心化的
kube-apiserver
会非常缓慢,影响 Pod 启动、配置更新等操作效率。 - CNI 性能问题: 跨地域的 Pod 间通信性能会急剧下降,且许多 CNI 插件的设计并未考虑或优化 WAN 场景。
- 高延迟和不稳定性:
将同一地域的节点划分到同一集群 (Best Practice): 这是标准的、推荐的做法。应该为每个需要部署资源的地理区域(Region)创建一个独立的 Kubernetes 集群。在同一个地理区域内,节点应部署在不同的可用区 (Availability Zones - AZs) 以实现高可用,因为 AZ 之间的网络延迟通常足够低(个位数毫秒),能够满足
etcd
和其他控制平面组件的要求。如果业务需要在多个地理区域存在,就应该部署和管理多个独立的 Kubernetes 集群。
如何规划集群的网络 (Planning Cluster Networking)
网络规划是生产集群管理中的复杂环节,直接关系到安全、性能和隔离性。
企业办公环境、测试环境、预生产环境和生产环境应如何进行网络分离:
- 目标: 防止开发/测试活动影响生产环境,保障生产环境的安全性和稳定性。
- 实现方式:
- 物理/逻辑隔离: 最彻底的方式是为不同环境使用完全独立的 Kubernetes 集群,部署在不同的网络段(VLANs/Subnets),并通过防火墙进行严格访问控制。
- 集群内隔离 (如果必须混合部署): 如果资源限制或管理需求导致必须在同一集群内容纳不同环境(通常不推荐用于生产环境与其他环境混合),则需要利用 Kubernetes 的内置机制:
- Namespaces: 为不同环境(如
dev
,test
,prod
)创建独立的命名空间。 - NetworkPolicy: 使用 NetworkPolicy 对象来定义严格的网络访问规则。例如,可以配置 NetworkPolicy 默认拒绝所有跨命名空间的流量,然后显式允许必要的通信。这需要 CNI 插件(如 Calico, Cilium)支持 NetworkPolicy。
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# 示例:默认拒绝所有进入 'prod' 命名空间的流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: prod
spec:
podSelector: {} # 选择命名空间下的所有 Pod
policyTypes:
- Ingress
---
# 示例:允许 'monitoring' 命名空间的 Pod 访问 'prod' 命名空间的 Pod
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-monitoring-ingress
namespace: prod
spec:
podSelector: {} # 应用到 prod 下的所有 Pod
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring # 允许来自 monitoring 命名空间的流量
# 可以进一步根据 Pod Label 或端口进行限制
policyTypes:
- Ingress - RBAC: 结合基于角色的访问控制(RBAC)限制用户或服务账号只能访问其被授权的命名空间和资源。
- Namespaces: 为不同环境(如
不同租户之间应如何进行网络隔离 (Multi-tenancy Network Isolation):
- 目标: 在共享集群中为不同用户、团队或客户(租户)提供安全的网络边界,防止他们相互干扰或访问对方的应用。
- 实现方式: 与环境隔离类似,主要依赖 Namespaces + NetworkPolicy + RBAC。每个租户分配一个或多个 Namespace。NetworkPolicy 用于强制执行租户间的网络隔离,确保一个租户的 Pod 默认无法访问另一个租户的 Pod。RBAC 则限制租户只能管理自己 Namespace 内的资源。此外,还需要配合 ResourceQuota 和 LimitRange 来限制每个租户的资源使用,防止资源滥用。对于需要更强隔离保证的场景,可以考虑使用虚拟集群 (Virtual Clusters) 技术(如 vCluster)。
如何自动化搭建集群 (Automating Cluster Setup)
在生产环境中,手动搭建和管理 Kubernetes 集群是不可行且极易出错的。自动化是必须的。
- 如何自动化搭建和升级集群:
- 目标: 实现集群部署、配置、扩缩容和升级的一致性、可重复性和高效性。
- 实现方式:
- 基础设施即代码 (Infrastructure as Code - IaC): 使用 Terraform, Pulumi 等工具自动化创建底层基础设施(虚拟机、网络、负载均衡器、存储等)。
- 集群部署工具: 使用
kubeadm
(结合脚本或 Ansible 等配置管理工具)、kubespray
(基于 Ansible)、RKE
(Rancher Kubernetes Engine)、k3s
(轻量级发行版,易于自动化) 或云厂商提供的托管服务 (EKS, GKE, AKS) 的 API/CLI 来自动化部署 Kubernetes 控制平面和数据平面核心组件。 - 配置管理: 使用 Ansible, Chef, Puppet 或 SaltStack 自动化配置节点操作系统、安装依赖、设置内核参数等。
- GitOps: 采用 GitOps 理念,将集群的期望状态(包括 Kubernetes 版本、组件配置、部署的应用等)存储在 Git 仓库中,使用 Argo CD 或 Flux 等工具自动同步 Git 仓库的状态到集群中。升级集群也通过修改 Git 中的配置并由工具自动执行完成。这是目前自动化管理集群状态和应用部署的主流最佳实践。
与企业认证平台集成 (Integration with Enterprise Authentication Platform)
在企业环境中,通常已经存在一套成熟的统一认证平台 (Identity Provider, IdP),例如基于 LDAP、Active Directory (AD)、SAML 或 OpenID Connect (OIDC) 的系统(如 Keycloak, Okta, Azure AD 等)。Kubernetes 集群不应再独立维护一套用户账号体系。与企业认证平台集成,可以让企业用户使用他们熟悉的账号密码或单点登录 (SSO) 方式访问 Kubernetes 集群资源(例如通过 kubectl
、Dashboard 或其他依赖 K8s API 的应用)。
- 解决了什么问题? 避免了账号管理的碎片化,简化了用户管理流程,提高了安全性(密码策略、多因素认证等由统一平台管理),并且方便了权限审计。
- 是什么? Kubernetes API Server 支持通过 OIDC 或 Webhook Token Authentication 等方式对接外部认证系统。管理员可以配置 API Server 启动参数,指定 IdP 的信息。
- 原理:
- 用户尝试访问 K8s API(例如执行
kubectl get pods
)。 - 如果用户未认证,
kubectl
或客户端库会根据kubeconfig
中的 OIDC 配置,将用户重定向到企业的 IdP 进行登录。 - 用户在 IdP 成功登录后,IdP 会签发一个 ID Token (JWT)。
- 客户端将 ID Token 发送给
kube-apiserver
。 kube-apiserver
使用配置的 OIDC 信息(Issuer URL, Client ID)验证 ID Token 的签名和声明 (Claims)。- 验证通过后,API Server 从 Token 中提取用户信息(用户名、用户组),然后结合 Kubernetes 的 RBAC (Role-Based Access Control) 规则进行授权判断。
- 用户尝试访问 K8s API(例如执行
- 配置示例 (API Server 启动参数):或者使用像 Dex 或 Pinniped 这样的中间件来简化多种 IdP 的集成。
1
2
3
4
5kube-apiserver --oidc-issuer-url=https://idp.example.com/auth/realms/myrealm \
--oidc-client-id=kubernetes \
--oidc-username-claim=email \
--oidc-groups-claim=groups \
... # 其他 OIDC 相关参数
集成企业域名服务 (DNS) 与负载均衡 (ELB)
Kubernetes 集群内部的服务发现(如 CoreDNS)通常只解析集群内部的 Service 名称。但集群内的应用可能需要访问集群外部的企业服务,同时,集群内发布的服务也需要通过企业级的 DNS 和负载均衡器暴露给外部用户。
- 集成 DNS:
- 场景: Pod 需要解析企业内网的域名(如
database.internal.mycorp.com
)。 - 原理: 配置 Kubernetes 的 CoreDNS,使其将对特定域(如
internal.mycorp.com
)的查询转发 (forward) 给企业的 DNS 服务器。这可以通过修改 CoreDNS 的 ConfigMap 实现。 - 配置示例 (CoreDNS Corefile):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18internal.mycorp.com:53 {
errors
cache 30
forward . 10.100.0.5 10.100.0.6 # 转发给企业 DNS 服务器地址
}
.:53 {
errors
cache 30
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
upstream # 使用宿主机的 /etc/resolv.conf 作为上游
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . /etc/resolv.conf # 默认转发
loop
reload 5s
}
- 场景: Pod 需要解析企业内网的域名(如
- 集成负载均衡 (ELB):
- 场景: 将 Kubernetes 中运行的服务(通常通过 Ingress 或 Service type=LoadBalancer)安全、可靠地暴露给企业网络或公网用户。
- 原理: 使用企业现有的硬件负载均衡器 (如 F5) 或软件负载均衡器 (如 HAProxy, Nginx),或者云厂商提供的 ELB 服务。这些负载均衡器将流量导向 Kubernetes Ingress Controller 的 Pod 所在节点的 NodePort,或者直接与 Service type=LoadBalancer 创建的云厂商 LB 对接。企业 ELB 提供了统一的流量入口、SSL 卸载、更复杂的路由规则和高可用保障。
- 配置: 这通常涉及在企业 ELB 上配置 VIP (Virtual IP Address)、后端服务器池(指向 K8s Node 或 Ingress Controller 暴露的端口),以及健康检查。
依赖服务的可靠性考量
当 Kubernetes 集群中的应用依赖于外部的企业公共服务(如数据库、消息队列、支付网关、遗留系统 API 等)时,这些外部服务的可靠性会直接影响到 K8s 应用的整体可用性。
- 问题: 外部服务可能发生故障、性能下降或维护。如果 K8s 应用没有做好容错处理,外部服务的抖动会传递到 K8s 应用,导致应用本身不可用或性能严重下降。
- 应对策略:
- 明确依赖关系和 SLA: 清晰梳理应用对外部服务的依赖,了解这些服务的服务水平协议 (SLA)。
- 设计容错模式: 在应用代码层面实现断路器 (Circuit Breaker) 模式。当检测到对某个外部服务的调用连续失败或超时达到阈值时,断路器“跳闸”,暂时停止调用该服务,可以直接返回错误或返回预设的降级响应 (Fallback),避免雪崩效应。断路器会定期尝试恢复(半开状态)。在 Golang 中,可以使用如
sony/gobreaker
或mercari/go-circuitbreaker
等库。 - 异步化与解耦: 对于非核心、可延迟处理的依赖,尽量采用异步消息队列(如 Kafka, RabbitMQ)进行解耦。
- 监控与告警: 对外部服务的调用进行细致的监控(延迟、错误率),并设置告警。
同步调用与超时管理
对于必须同步调用 (Synchronous Call) 的外部服务请求(即发起请求后必须等待响应才能继续执行),设置合理的超时时间至关重要。
- 问题: 如果不设置超时,或者超时时间设置过长,当外部服务响应缓慢或卡死时,调用方的线程(或 Goroutine)会被长时间阻塞,耗尽资源(如连接池、内存、线程数),最终导致调用方服务自身也变得不可用,无法处理新的请求。过长的等待时间还会显著增加整个请求链路的端到端延迟,从而降低系统的吞吐量 (TPS - Transactions Per Second)。
- 解决方案:
- 客户端超时: 必须在调用方(客户端)设置超时。不要依赖于被调用方(服务端)的超时。在 Golang 中,进行 HTTP 调用时,可以在
http.Client
中设置Timeout
字段,它涵盖了从连接、请求发送到接收响应头的整个过程。对于更细粒度的控制(如单独设置连接超时、读超时、写超时),可以配置http.Transport
。对于 gRPC 调用,可以通过context.WithTimeout
传递带超时的上下文。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import (
"net/http"
"time"
"context"
)
// 示例 1: 设置整体超时
client := http.Client{
Timeout: 500 * time.Millisecond, // 设置 500ms 超时
}
resp, err := client.Get("http://external.service/api")
// 示例 2: 使用带超时的 Context (更推荐,尤其在复杂调用链中)
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel() // 确保资源释放
req, _ := http.NewRequestWithContext(ctx, "GET", "http://external.service/api", nil)
resp, err := http.DefaultClient.Do(req) - 合理设置: 超时时间应根据对外部服务 P99 响应时间的了解来设定,并留有少量 buffer。宁可超时失败,快速返回错误,也不能无限期等待。
- 客户端超时: 必须在调用方(客户端)设置超时。不要依赖于被调用方(服务端)的超时。在 Golang 中,进行 HTTP 调用时,可以在
智能重试策略 (Intelligent Retry Strategy)
网络是不可靠的,瞬时性的网络抖动 (Network Jitter) 或服务临时过载可能导致调用失败。对于这类短暂的、偶然的 (Transient) 失败,进行重试是合理的,可以提高系统的韧性。但是,盲目重试所有失败,尤其是那些必然 (Definitive) 的失败(如认证失败、请求参数错误、资源不存在),或者在下游服务已经崩溃时持续重试,会适得其反。
- 问题:
- 重试风暴 (Retry Storm): 如果大量客户端同时对一个已经有问题的服务进行重试,会进一步放大请求量,加剧服务的负载,甚至导致服务彻底崩溃或恢复时间延长。
- 无效重试: 对逻辑错误(如
4xx
错误码)或永久性错误进行重试是无效的,只会浪费资源。
- 解决方案:
- 区分错误类型: 只对明确可重试的错误进行重试。通常包括:
- 网络错误(连接超时、读取超时等)。
- 特定的 HTTP 状态码,如
503 Service Unavailable
,504 Gateway Timeout
,429 Too Many Requests
(有时需要配合Retry-After
头部),以及某些情况下的500 Internal Server Error
(如果 API 明确约定某些 500 是临时的)。 - 绝不重试
4xx
客户端错误(除了429
)。
- 实现指数退避和抖动 (Exponential Backoff and Jitter): 重试时不应立即进行,而是等待一段时间。每次重试的等待时间应指数级增长(如 100ms, 200ms, 400ms…),以避免短时间内大量重试。同时,在等待时间上增加一个随机的抖动 (Jitter),防止多个客户端在完全相同的时间点重试,打散重试请求。
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// 简化的指数退避+抖动逻辑示意
import (
"math"
"math/rand"
"time"
)
func calculateBackoff(attempt int) time.Duration {
baseDelay := 100 * time.Millisecond
maxDelay := 5 * time.Second
backoff := time.Duration(float64(baseDelay) * math.Pow(2, float64(attempt)))
if backoff > maxDelay {
backoff = maxDelay
}
// 添加 Jitter (例如,+/- 10% 的随机抖动)
jitter := time.Duration(rand.Float64()*float64(backoff)*0.2) - (backoff / 10)
return backoff + jitter
}
// 在重试循环中使用
// for attempt := 0; attempt < maxRetries; attempt++ {
// err := callExternalService()
// if err == nil { break }
// if !isRetryable(err) { break }
// time.Sleep(calculateBackoff(attempt))
// } - 限制重试次数: 设置一个最大重试次数或总重试时间上限,避免无限重试。
- 利用库或框架: 使用成熟的库(如 Golang 的
hashicorp/go-retryablehttp
)或 Service Mesh (如 Istio, Linkerd) 提供的重试功能,它们通常内置了健壮的重试逻辑。
- 区分错误类型: 只对明确可重试的错误进行重试。通常包括:
控制平面高可用

理解控制平面及其重要性
在深入探讨高可用策略之前,我们首先要明确 Kubernetes 控制平面包含哪些核心组件以及它们的作用。主要组件通常包括:
- kube-apiserver: 集群的统一入口,处理所有 REST API 请求,负责认证、授权、准入控制以及 API 注册和发现。它是无状态的,可以水平扩展。
- etcd: 一个一致性、高可用的键值存储,作为 Kubernetes 所有集群数据的后台数据库(例如 Pods, Services, Secrets 的状态)。etcd 的可用性和数据一致性是整个集群可用性的基石。
- kube-scheduler: 监视新创建的、未指定运行节点的 Pods,并根据资源需求、策略限制、亲和性/反亲和性规则、数据局部性等因素,选择一个最佳节点来运行它们。
- kube-controller-manager: 运行控制器进程。逻辑上,每个控制器是一个独立的进程,但为了降低复杂性,它们都被编译到同一个可执行文件
kube-controller-manager
中,并在一个进程中运行。这些控制器包括节点控制器、副本控制器(ReplicaSet)、端点控制器(Endpoints)、服务账户和令牌控制器等。它们负责驱动集群状态从当前状态趋向期望状态。 - cloud-controller-manager: (可选,在云环境中)运行与底层云提供商交互的控制器。例如,管理云路由、负载均衡器、存储卷等。将这部分逻辑分离出来,使得 Kubernetes 核心与特定云平台的耦合度降低。
控制平面的高可用性意味着即使部分组件或其所在的节点发生故障,整个集群的管理功能仍然可用,或者只有短暂中断。
多 Master 节点与物理分布
解决的问题:单点故障。如果控制平面所有组件都运行在单一节点上,该节点的任何硬件故障(CPU、内存、磁盘、网络)、操作系统崩溃或维护都会导致整个控制平面瘫痪。
是什么:高可用控制平面部署的核心思想是运行多个控制平面实例,并将它们分布在不同的物理基础设施上。通常这意味着至少有 3 个节点专门用于运行控制平面组件(kube-apiserver, etcd, kube-scheduler, kube-controller-manager)。
原理:
- etcd 集群:etcd 本身设计为分布式系统,使用 Raft 一致性算法来保证数据一致性和容错。一个 etcd 集群通常需要奇数个成员(例如 3 或 5 个),因为它需要**多数派(Quorum)**才能进行写操作和选举 Leader。对于 N 个成员的集群,它可以容忍 (N-1)/2 个成员的故障。例如,一个 3 节点的 etcd 集群可以容忍 1 个节点故障,一个 5 节点的集群可以容忍 2 个节点故障。etcd 的高可用是整个控制平面 HA 的基础和关键。
- API Server 负载均衡:多个
kube-apiserver
实例是无状态的,它们都连接到同一个 etcd 集群。在这些 API Server 前端需要放置一个负载均衡器(Load Balancer)。集群内部组件(如 kubelet, kube-proxy)以及外部用户(如kubectl
)都通过这个负载均衡器的虚拟 IP (VIP) 或 DNS 名称访问 API Server。如果某个 API Server 实例失败,负载均衡器会将其从可用池中移除,请求会被路由到其他健康的实例。 - Scheduler 和 Controller Manager 的 Leader Election:
kube-scheduler
和kube-controller-manager
内部实现了**领导者选举(Leader Election)**机制。虽然你可以运行多个实例,但在任何时候只有一个实例是 active (leader),负责执行实际的调度或控制循环。其他实例处于 standby 状态。如果当前 leader 失败,其中一个 standby 实例会通过竞争成为新的 leader,接管工作。这个机制通常利用 Kubernetes API(创建/更新 Lease 或 Endpoint 对象)来实现分布式锁。你可以通过查看kube-system
命名空间下的lease
对象来观察 leader 选举情况,例如kubectl get lease -n kube-system
。
物理分布:为了防止单一物理故障(如整个机架断电、交换机故障)影响所有控制平面节点,这些节点应分布在不同的故障域(Failure Domains)中。在本地数据中心,这可能意味着不同的物理服务器、不同的机架、甚至不同的房间。在云环境中,这通常指不同的可用区(Availability Zones, AZs)。云提供商的 AZ 设计就是为了隔离故障。
专用节点与资源保障
解决的问题:资源争抢和干扰。如果控制平面组件与普通业务应用 Pod 混合部署在同一节点上,业务应用可能消耗过多资源(CPU、内存、磁盘 I/O),导致控制平面组件性能下降、响应缓慢甚至 OOM (Out Of Memory) 被杀死。反之,控制平面组件的峰值负载也可能影响业务应用。此外,业务应用的潜在安全漏洞也可能威胁到控制平面组件。
是什么:为控制平面组件划分专用的节点,不让普通用户的业务 Pod 调度到这些节点上。同时,确保这些专用节点拥有充足且有保障的资源。
原理与实践:
- 节点污点(Taints)和容忍(Tolerations):这是 Kubernetes 实现节点隔离的主要机制。可以给控制平面节点打上污点(Taint),表明这些节点不接受不能“容忍”该污点的 Pod。控制平面组件的 Pod 定义中需要包含对这个 Taint 的容忍(Toleration),这样它们才能被调度到这些专用节点上。例如,
1
2
3
4# 给 master-node1 添加一个 Taint,效果是 NoSchedule (不允许新 Pod 调度上来,除非有对应 Toleration)
kubectl taint nodes master-node1 node-role.kubernetes.io/master:NoSchedule
# 或者使用更通用的 control-plane role
kubectl taint nodes master-node1 node-role.kubernetes.io/control-plane:NoSchedulekube-apiserver
的 Pod YAML 中会有类似配置:1
2
3
4
5
6
7
8spec:
tolerations:
- key: "node-role.kubernetes.io/master"
operator: "Exists"
effect: "NoSchedule"
- key: "node-role.kubernetes.io/control-plane"
operator: "Exists"
effect: "NoSchedule" - 资源请求(Requests)和限制(Limits):为控制平面组件的 Pod 设置合理的资源请求(
requests
)和限制(limits
)至关重要。requests
保证了 Pod 至少能获得这些资源,影响调度决策和节点的资源预留。limits
则限制了 Pod 能使用的资源上限,防止其过度消耗。对于关键的控制平面组件,特别是etcd
和kube-apiserver
,通常建议将requests
和limits
设置为相同的值,使其获得Guaranteed QoS Class,这样它们在节点资源紧张时被 OOM Kill 的优先级最低。过于严苛的资源限制会导致系统效率低下,降低集群可用性。例如,API Server 内存不足会频繁重启,etcd 磁盘 I/O 不足会导致请求超时和集群不稳定。1
2
3
4
5
6
7
8
9
10spec:
containers:
- name: kube-apiserver
resources:
requests:
cpu: "1" # 请求 1 个 CPU 核心
memory: "2Gi" # 请求 2 GiB 内存
limits:
cpu: "1" # 限制最多使用 1 个 CPU 核心
memory: "2Gi" # 限制最多使用 2 GiB 内存
减少外部依赖与解耦
解决的问题:外部系统故障导致 Kubernetes 控制平面不可用。在早期 Kubernetes 版本中,一些核心功能(如云存储卷管理、负载均衡器创建)直接内置在 kube-controller-manager
或 kube-apiserver
中,并直接调用 Cloud Provider API。如果 Cloud Provider API 出现故障或响应缓慢,会直接影响 Kubernetes 控制平面的稳定性和性能。
是什么:将与特定环境(尤其是云平台)相关的逻辑从 Kubernetes 核心组件中剥离出来,通过**标准接口(如 CSI, CNI, CPI)**与外部组件或驱动进行交互。
原理与实践:
- Cloud Provider Interface (CPI):
cloud-controller-manager
的引入就是为了解耦。它独立运行,负责与云平台 API 交互。如果云 API 故障,影响主要局限在与云资源相关的操作,而不是整个kube-controller-manager
。 - Container Storage Interface (CSI): 定义了一套标准的存储卷管理接口。存储提供商(云厂商或存储供应商)可以开发符合 CSI 规范的驱动程序,作为独立的 Pod 部署在集群中。Kubernetes 核心组件通过调用 CSI 接口来管理存储卷,不再需要内置特定存储的逻辑。这使得存储功能的更新和维护独立于 Kubernetes 版本。
- Container Network Interface (CNI): 定义了容器网络配置的标准接口。网络插件(如 Calico, Flannel, Cilium)作为独立组件部署,负责 Pod 网络设置和策略。
kubelet
在创建和销毁 Pod 时调用 CNI 插件。
通过这种解耦,减少了 Kubernetes 控制平面核心组件对外部系统的直接强依赖。即使某个外部系统(如云存储 API)暂时不可用,只要不涉及相关操作,控制平面的核心功能(如 Pod 调度、状态同步)仍然可以工作。
控制平面与数据平面解耦
解决的问题:控制平面故障是否会导致运行中的业务中断。
是什么:Kubernetes 架构设计上天然地将控制平面(管理)和数据平面(运行业务负载)进行了分离。
原理:
- 数据平面主要由集群中的**工作节点(Worker Nodes)**以及运行在这些节点上的
kubelet
、kube-proxy
和容器运行时(如 Docker, containerd)组成。业务 Pod 运行在数据平面上。 kubelet
负责管理本节点上的 Pod 生命周期,与容器运行时交互。kube-proxy
负责维护网络规则(如 iptables 或 IPVS),实现 Service 的负载均衡。- 当控制平面(主要是
kube-apiserver
)不可用时:- 现有 Pod 会继续运行:
kubelet
会继续保持当前运行 Pod 的状态,容器运行时也会继续工作。 - Service 网络通常继续工作:
kube-proxy
基于它最后一次从kube-apiserver
获取的信息来维护网络规则。只要kube-proxy
自身和底层网络设施(如 CNI 插件管理的部分)正常,现有的 Service 访问通常不受影响。 - 无法进行管理操作:无法创建、删除、更新 Pod、Service、Deployment 等资源。自动伸缩(HPA/VPA/Cluster Autoscaler)会停止工作。
- 节点状态更新和心跳停止:
kubelet
无法向kube-apiserver
报告节点和 Pod 状态。长时间失联后,kube-controller-manager
(如果它还能运行并访问 etcd)可能会将失联节点标记为NotReady
,并可能触发 Pod 驱逐(如果配置了)。 - 新 Pod 无法调度:
kube-scheduler
无法工作。
- 现有 Pod 会继续运行:
这意味着控制平面的短暂故障通常不会立即影响正在运行的业务,为修复控制平面赢得了时间。然而,长时间的控制平面故障会逐渐影响集群的健康和管理能力。确保控制平面组件出现故障时,将业务影响降到最低是 HA 设计的重要目标。
核心插件的可用性
解决的问题:一些以普通 Pod 形式运行的、对集群功能至关重要的核心插件(Add-ons),如 DNS(CoreDNS)、网络插件(CNI daemons)、Ingress 控制器等,它们的故障也会严重影响集群的可用性。
是什么:这些插件虽然不是狭义上的控制平面组件,但它们提供了基础服务,其可用性同样关键。它们通常作为 Deployment
或 DaemonSet
部署在集群中。
原理与实践:
- 副本和调度策略:对于像 CoreDNS 或 Ingress 控制器这样的服务,应使用
Deployment
并配置多个副本(replicas)。利用**Pod 反亲和性(Pod Anti-Affinity)**规则,确保这些副本分布在不同的节点甚至不同的故障域上,避免单点故障。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22spec:
replicas: 3
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: k8s-app
operator: In
values:
- kube-dns # For CoreDNS example
topologyKey: "kubernetes.io/hostname" # Ensure pods are on different nodes
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: k8s-app
operator: In
values:
- kube-dns
topologyKey: "topology.kubernetes.io/zone" # Prefer pods in different AZs - 资源保障:同样,为这些核心插件 Pod 配置合理的资源请求和限制,确保它们有足够的资源稳定运行。
- 调度位置:这些插件 Pod 默认可能会被调度到任何工作节点上,与普通应用竞争资源。在某些情况下,可能需要考虑将它们调度到特定的节点池,或者确保它们有足够的优先级(PriorityClass)来抢占资源。这些插件是否正常运行也决定了集群的可用性。例如,如果 CoreDNS 全部实例失败,集群内部的服务发现就会瘫痪。
集群安装方法比较

二进制安装 (“The Hard Way” / 从零构建)
是什么: 这是最基础、最原始的安装方式。它涉及到手动下载 Kubernetes 各个核心组件的二进制文件(例如 kube-apiserver
, kube-controller-manager
, kube-scheduler
, kubelet
, kube-proxy
)以及 etcd
的二进制文件,然后手动配置每一个组件,并通过 Linux 的服务管理系统(通常是 systemd)来启动和管理这些进程。
原理与工作方式: 你需要从头开始构建整个集群的控制平面和数据平面。这要求你对每个组件的角色、配置选项、依赖关系以及它们之间的交互有深刻的理解。具体步骤通常包括:
准备基础设施: 配置 Linux 虚拟机或物理服务器,设置网络。
生成证书: 手动创建所有必要的 TLS 证书和密钥,用于保障组件之间(如 API Server 与 etcd、kubelet 与 API Server)的安全通信。这通常使用
openssl
或cfssl
等工具完成,这个过程能让你深刻理解 PKI(公钥基础设施) 在分布式系统安全中的核心作用。部署 etcd 集群: 手动配置并启动一个高可用的 etcd 集群(通常需要 3 或 5 个节点)。这涉及到配置节点间发现、TLS 加密通信,并且需要理解 etcd 依赖的 Raft 一致性算法 来保证数据可靠性。
配置 Kubernetes 组件: 为
kube-apiserver
,kube-controller-manager
,kube-scheduler
等控制平面组件编写配置文件(通常是 YAML 格式或传递给 systemd 单元的环境变量文件),指定诸如 etcd 服务器地址、证书路径、特性门控 (feature gates)、网络参数(Pod CIDR, Service CIDR)、云服务商集成配置(如果需要)等大量参数。创建 systemd Unit 文件: 为每个组件编写
.service
文件,以便 systemd 能够管理它们的生命周期(启动、停止、重启、开机自启)。你需要理解 systemd 的 target、依赖关系(如Wants
,After
)以及执行参数(ExecStart
)。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
32
33
34
35
36
37# /etc/systemd/system/kube-apiserver.service 文件示例片段
[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/kubernetes/kubernetes
# 确保网络和 etcd 服务先启动
After=network.target etcd.service
Wants=etcd.service
[Service]
ExecStart=/usr/local/bin/kube-apiserver \\
--advertise-address=${INTERNAL_IP} \\
--allow-privileged=true \\
--apiserver-count=3 \\ # HA 配置需要
--audit-log-maxage=30 \\
--audit-log-maxbackup=3 \\
--audit-log-maxsize=100 \\
--audit-log-path=/var/log/audit.log \\
--authorization-mode=Node,RBAC \\
--bind-address=0.0.0.0 \\
--client-ca-file=/var/lib/kubernetes/ca.pem \\
--enable-admission-plugins=... \\
--etcd-cafile=/var/lib/kubernetes/ca.pem \\
--etcd-certfile=/var/lib/kubernetes/etcd-server.pem \\
--etcd-keyfile=/var/lib/kubernetes/etcd-server-key.pem \\
# 连接到 etcd 集群
--etcd-servers=https://ETCD1_IP:2379,https://ETCD2_IP:2379,https://ETCD3_IP:2379 \\
# ... 此处省略大量其他必要参数 ...
--service-account-key-file=/var/lib/kubernetes/service-account.pem \\
--service-cluster-ip-range=10.32.0.0/24 \\
--tls-cert-file=/var/lib/kubernetes/kube-apiserver.pem \\
--tls-private-key-file=/var/lib/kubernetes/kube-apiserver-key.pem \\
--v=2
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target配置工作节点: 在工作节点上配置和启动
kubelet
和kube-proxy
,确保它们能通过 bootstrap token 或证书向 API Server 注册。安装网络插件 (CNI): 在核心组件运行起来之后,手动安装和配置一个 CNI 网络插件(如 Calico, Flannel, Cilium),这是 Pod 之间通信的基础。
优点:
- 极高的灵活性和控制力: 你可以完全控制每个组件的版本、每一个配置参数,适合进行深度定制或集成。
- 最深入的理解: 这种方式强迫你学习 Kubernetes 各组件之间错综复杂的依赖和交互、证书管理、Linux 服务管理以及网络知识。这对于后续排查复杂问题非常有价值。
缺点:
- 极其复杂: 整个过程非常繁琐、耗时,且极易出错。需要对细节有极高的关注度。正如图片所说,复杂,需要关心每一个组件的配置。
- 高昂的维护成本: 集群的升级、证书轮换、配置变更等操作完全是手动的,非常复杂且难以保证一致性。
- 重复造轮子: 你在手动执行大量已被其他工具自动化的任务。对系统服务的依赖性过多,因为你需要手动确保所有底层依赖(如时间同步、内核模块)都已就绪。
Kubeadm
是什么: Kubeadm 是 Kubernetes 官方(SIG Cluster Lifecycle 小组)维护的一个命令行工具,旨在 简化创建符合最佳实践的 Kubernetes 集群的引导(bootstrap)过程。它自动化了二进制安装中的许多复杂步骤,但仍将一些基础设施准备和附加组件(Add-ons)的安装留给用户。
原理与工作方式:
kubeadm init
: 在第一个控制平面节点上运行。- 执行一系列预检检查(验证系统状态、内核模块、端口可用性等)。
- 生成必要的 TLS 证书和密钥(默认存储在
/etc/kubernetes/pki
目录下)。 - 可以选择性地设置一个 stacked etcd 实例(即 etcd 作为静态 Pod 运行在控制平面节点上),或者配置连接到一个外部的 etcd 集群。
- 在
/etc/kubernetes/manifests/
目录下为控制平面组件(kube-apiserver
,kube-controller-manager
,kube-scheduler
)生成 静态 Pod (Static Pods) 的 YAML 清单文件。这些 Pod 由节点上的kubelet
直接管理,而不是通过 API Server,这是 Kubernetes 自举的一个关键机制。 - 生成用于集群管理的
kubeconfig
文件 (admin.conf
) 以及供内部组件使用的配置文件 (controller-manager.conf
,scheduler.conf
)。 - 部署一些基础的插件,如
kube-proxy
(以 DaemonSet 形式)和CoreDNS
(以 Deployment 形式)。 - 输出一个
kubeadm join
命令,其中包含用于添加其他节点的引导令牌 (bootstrap token)。
kubeadm join
: 在其他节点(可以是控制平面节点或工作节点)上运行。- 使用
init
命令提供的引导令牌和发现信息(API Server 地址和 CA 证书哈希)安全地连接到集群。 - 执行 TLS 引导流程,为本节点的
kubelet
获取唯一的凭证。 - 配置本地
kubelet
与 API Server 通信。如果使用--control-plane
参数加入作为控制平面节点,它也会在本节点上设置控制平面组件的静态 Pod。
- 使用
配置: 通过
--config
参数传递一个ClusterConfiguration
YAML 文件来进行定制(例如,指定 Pod/Service 的 CIDR 网段、Kubernetes 版本、控制平面端点、特性门控等)。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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50# 使用配置文件执行 init 命令示例
sudo kubeadm init --config kubeadm-config.yaml --upload-certs
# kubeadm-config.yaml 文件示例片段
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
bootstrapTokens:
- groups:
- system:bootstrappers:kubeadm:default-node-token
token: abcdef.0123456789abcdef # 示例 Token
ttl: 24h0m0s
usages:
- signing
- authentication
localAPIEndpoint:
advertiseAddress: "192.168.1.100" # 节点 IP
bindPort: 6443
nodeRegistration:
criSocket: unix:///var/run/containerd/containerd.sock # 或 Docker shim
imagePullPolicy: IfNotPresent
name: master-1
taints: # 给控制平面节点打上污点
- effect: NoSchedule
key: node-role.kubernetes.io/master
- effect: NoSchedule
key: node-role.kubernetes.io/control-plane
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
apiServer:
timeoutForControlPlane: 4m0s
certSANs: # 如果需要,添加额外的证书主题备用名 (SANs)
- "my-loadbalancer-dns.com"
certificatesDir: /etc/kubernetes/pki
clusterName: my-cluster
# 为 HA 设置控制平面端点 (VIP 或 DNS)
controlPlaneEndpoint: "my-loadbalancer-dns.com:6443"
controllerManager: {}
dns: {}
etcd:
local: # 使用内置的 stacked etcd
dataDir: /var/lib/etcd
kubernetesVersion: v1.28.2
networking:
dnsDomain: cluster.local
podSubnet: "10.244.0.0/16" # Pod 网络 CIDR
serviceSubnet: 10.96.0.0/12 # Service 网络 CIDR
scheduler: {}
---
# 可选: KubeletConfiguration, KubeProxyConfiguration 等
优点:
- 简化引导过程: 相比二进制安装,极大地降低了初始搭建的复杂度,自动化了证书生成、etcd 基本设置(可选)、控制平面静态 Pod 创建等繁琐任务。正如图片所说,控制面板组件的安装和配置被封装起来了。
- 封装了最佳实践: 遵循社区推荐的标准方式来配置集群。
- 生命周期管理: 提供了用于集群升级 (
kubeadm upgrade
)、证书续期 (kubeadm certs renew
)、令牌管理等命令。
缺点:
- 自动化不完整: 它不会自动安装 CNI 网络插件。这是在
kubeadm init
成功后必须手动完成的一步(例如kubectl apply -f calico.yaml
)。它通常也不处理操作系统层面的配置自动化(如安装containerd
/docker
、禁用 swap、配置内核参数如net.bridge.bridge-nf-call-iptables=1
)。 - 高可用(HA)需要额外步骤: 搭建多主高可用集群需要手动设置外部负载均衡器(或使用带内建 keepalived/haproxy 的实验性
--control-plane-endpoint
功能),可能需要管理外部 etcd 集群,并在init
时使用--upload-certs
参数、join
时使用--control-plane
参数。运行时安装配置等复杂步骤依然是必须的。
Kubespray
是什么: Kubespray 是一个使用 Ansible playbooks 来部署和管理 Kubernetes 集群的工具。它旨在提供比 Kubeadm 更“开箱即用”的体验,能够处理操作系统准备、依赖安装以及可选附加组件的部署。
原理与工作方式:
它依赖于 Ansible,一个无代理的配置管理和自动化工具。你需要在 Ansible 的 inventory 文件 和 变量文件 (
group_vars
) 中定义你的集群拓扑和配置。Ansible playbooks(特别是 Kubespray 中的
cluster.yml
playbook)通过 SSH 连接到你的目标 Linux 主机,并执行一系列预定义的任务:- 操作系统准备(安装软件包、调整内核参数)。
- 安装和配置容器运行时(Docker, containerd)。
- 下载 Kubernetes 二进制文件或容器镜像。
- 生成证书。
- 设置 etcd 集群。
- 配置并启动 Kubernetes 组件(注意:它可能在内部利用了 kubeadm 来完成部分引导工作,但它负责编排整个过程)。
- 部署你选择的 CNI 网络插件。
- 可选地部署其他附加组件(如 metrics-server, ingress controller 等)。
整个过程由变量文件中定义的配置驱动,允许进行大量的定制(选择 CNI 插件、Kubernetes 版本、启用特性等)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19# Ansible inventory 文件示例片段 (inventory/mycluster/inventory.ini)
[all]
master1 ansible_host=192.168.1.101 ip=192.168.1.101
node1 ansible_host=192.168.1.102 ip=192.168.1.102
node2 ansible_host=192.168.1.103 ip=192.168.1.103
[kube_control_plane]
master1
[etcd]
master1
[kube_node]
node1
node2
[k8s_cluster:children]
kube_control_plane
kube_node1
2
3
4
5
6
7
8
9# 变量文件示例片段 (inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml)
kube_version: v1.28.2
kube_network_plugin: calico # 可选 flannel, weave, cilium 等
# 设置 Pod 和 Service CIDR
kube_pods_subnet: 10.233.64.0/18
kube_service_addresses: 10.233.0.0/18
# 启用附加组件
metrics_server_enabled: true
ingress_nginx_enabled: true
优点:
- 高度自动化: 自动化了从操作系统准备到依赖安装、Kubernetes 核心组件、CNI 插件乃至附加组件的整个部署流程。
- 高度可定制: 通过丰富的变量配置,支持多种 Linux 发行版、容器运行时、CNI 插件和 Kubernetes 版本。
- 幂等性 (Idempotent): Ansible 的设计保证了重复运行 playbook 会收敛到期望的状态,这对于更新和保持配置一致性很有用。
- 基础设施无关性: 只要 Ansible 能通过 SSH 连接到主机,就可以在裸金属、虚拟机、公有云或私有云上部署。
缺点:
- 需要 Ansible 知识: 使用者需要理解 Ansible 的基本概念(inventory, playbooks, variables, roles)。
- 复杂性: 大量的配置变量和层层嵌套的 playbook 结构,在出现问题时可能难以调试。
- 命令式本质: 虽然 Ansible 定义了期望状态,但其执行过程是一系列的任务步骤(命令式)。这与 KOPS 或 Cluster API 的完全声明式方法不同。图片中提到缺少基于声明式 API 的支持,指的就是它不像 Cluster API 那样提供一个 K8s CRD 风格的 API 来管理集群生命周期。
KOPS (Kubernetes Operations)
是什么: KOPS 是一个 Kubernetes 官方 SIG 项目,用于创建、销毁、升级和维护生产级别、高可用的 Kubernetes 集群。它主要面向云环境(对 AWS 的支持最好,对 GCP、Azure、OpenStack 等也有不同程度的支持),并采用声明式方法。
原理与工作方式:
声明式状态: 你在一个 YAML 清单文件中定义集群的期望状态。这包括 Kubernetes 版本、节点实例类型和数量、网络配置(VPC/子网选择、CNI)、高可用设置(跨可用区的多主节点)、IAM 角色、负载均衡器等。这些在 KOPS 内部被表示为
Cluster
和InstanceGroup
等自定义资源。云 API 集成: KOPS 直接与目标云服务商的 API 交互,来自动置备所需的基础设施(如虚拟机、VPC、子网、安全组、IAM 角色、自动伸缩组 Auto Scaling Groups、负载均衡器 Load Balancers 等)。
kops create cluster
: 读取你的定义(或命令行参数)并生成详细的配置清单。kops update cluster
: 预览为了使云端基础设施与清单匹配所需做的变更。kops update cluster --yes
: 应用这些变更,在云上创建或修改资源,并在实例上安装和配置 Kubernetes 组件。它会自动处理 HA 设置、etcd 配置(通常使用云厂商管理的 etcd 或自动设置 etcd 集群)以及基础的集群引导。生命周期管理: 提供用于集群升级 (
kops upgrade cluster
)、伸缩节点组 (kops edit ig nodes
)、管理附加组件等的命令。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20# 在 AWS 上定义并创建集群的示例命令
export NAME=mycluster.k8s.local
# 将 KOPS 状态存储在 S3 bucket 中
export KOPS_STATE_STORE=s3://my-kops-state-bucket
kops create cluster \
--zones=us-west-2a,us-west-2b,us-west-2c \
--node-count=3 \
--node-size=t3.medium \
--master-zones=us-west-2a,us-west-2b,us-west-2c \
--master-size=t3.medium \
--dns-zone=my-hosted-zone-id \ # 或者使用 --dns private 进行基于 gossip 的发现
--networking=calico \
${NAME}
# 预览变更计划
kops update cluster ${NAME}
# 应用变更 (置备基础设施并安装 K8s)
kops update cluster ${NAME} --yes
优点:
- 声明式与云原生: 与 Kubernetes 的声明式模型高度一致。与云服务商的深度集成简化了基础设施管理,非常适合利用云平台的能力。
- 生产级 HA: 设计时就将高可用作为核心目标,自动化了多主节点、跨可用区(AZ)的部署。
- 强大的生命周期管理: 通过更新清单文件,对集群升级、伸缩和配置变更提供稳健的支持。
- 遵循社区标准 (Cluster API): 尽管有自己的历史 API,KOPS 正在逐步向 Cluster API 标准靠拢。Cluster API 旨在提供一个跨云服务商的、统一的声明式 API 来管理集群生命周期。正如图片所说,基于社区标准的 Cluster API 进行集群管理,节点的操作系统安装等全自动化。
缺点:
- 侧重于云服务商/存在锁定风险: 与云环境深度集成意味着它在特定的云平台上(尤其是 AWS)表现最好。对于本地(on-premise)部署或支持较少的云平台,其灵活性不如 Kubespray 或二进制安装。
- 主观性强 (Opinionated): KOPS 对基础设施的设置(如 VPC 布局、实例配置)做了很多默认决策,可能不适用于所有场景。在基础设施层面的灵活性低于 Kubespray。
- 抽象泄漏: 调试问题时,可能既需要理解 KOPS 的内部工作原理,也需要了解它所管理的底层云服务商资源。
总结:
选择哪种安装方法取决于你的目标和环境:
- 二进制安装: 追求最极致的控制和最深入的学习,不介意极高的复杂性和维护成本。
- Kubeadm: 想要一个相对简单的方式来引导符合最佳实践的核心集群,同时愿意手动处理 OS 配置和 CNI 安装,是在学习和生产之间的一个良好平衡点。
- Kubespray: 需要在包括本地环境在内的各种基础设施上实现高度自动化的、可定制的集群部署,并且熟悉或愿意学习 Ansible。
- KOPS: 主要在主流公有云上部署生产级、高可用的集群,并希望利用声明式方法和云集成来简化基础设施和集群生命周期管理。
对于系统性学习,从 Kubeadm 开始可以让你理解核心的引导过程,同时避免二进制安装的过度复杂。之后,根据你的目标环境(云或本地)和对自动化程度的需求,可以进一步学习 KOPS 或 Kubespray。
Kubespray:解决 Kubernetes 集群部署的复杂性
部署一个生产级别的 Kubernetes 集群,尤其是高可用集群,是一项复杂的任务。你需要考虑诸多因素:
- 多节点配置:控制平面节点、工作节点、etcd 节点的角色分配和初始化。
- 组件安装与版本管理:选择合适的容器运行时 (Docker/Containerd)、kubelet、kubeadm、apiserver、scheduler、controller-manager、etcd 等核心组件,并确保它们的版本兼容性。
- 网络配置:选择和部署 CNI (Container Network Interface) 插件,如 Calico、Flannel 等,以实现 Pod 间的通信。
- 高可用性 (HA):确保控制平面和 etcd 集群的冗余和故障切换能力。
- 证书管理:生成和分发集群所需的各种 TLS 证书。
- 可扩展性和可升级性:集群后续的节点增删和版本升级。
手动执行这些步骤不仅耗时,而且极易出错。Kubespray 的核心价值在于它通过 Ansible 自动化了整个 Kubernetes 集群的部署、配置、扩展和升级过程。它提供了一套经过社区验证和广泛测试的 Ansible Playbook 和 Role,允许用户通过声明式配置来定义集群的期望状态,然后由 Ansible 负责在目标机器上执行所有必要的任务,以达到这个状态。
Kubespray 的核心原理与工作流程
Kubespray 本质上是一系列 Ansible Playbook 和 Role 的集合。Ansible 是一款强大的自动化工具,它通过 SSH(默认)连接到目标主机,执行预定义的任务,无需在目标主机上安装 Agent。
从图中我们可以看到 Kubespray 的主要工作流程:
定义集群清单 (Inventory):
- 用户首先需要定义集群的拓扑结构。这通常通过一个
hosts.yml
文件(或者直接是 Ansible 的inventory.ini
格式文件)来完成。 hosts.yml
中会定义主机组,例如:all
: 包含所有参与集群的主机。kube_control_plane
: 指定哪些主机将作为 Kubernetes 的控制平面节点(运行 apiserver, scheduler, controller-manager)。kube_node
: 指定哪些主机将作为 Kubernetes 的工作节点(运行 kubelet 和容器运行时,承载 Pod)。etcd
: 指定哪些主机将组成 etcd 集群(存储 Kubernetes 的所有状态数据)。通常建议 etcd 节点与控制平面节点部署在一起,或者作为独立的集群。对于高可用,etcd 至少需要3个节点。k8s_cluster
: 这是一个组合组,通常包含了kube_control_plane
和kube_node
。calico_rr
: (可选) 如果使用 Calico CNI 并且需要 BGP Route Reflector,会定义这个组。
- 图中显示的
inventory_builder
是一个辅助脚本,它可以基于更简洁的hosts.yml
(如示例中只列出 node1, node2, node3) 来动态生成符合 Ansible 要求的inventory.ini
文件。这个inventory.ini
文件会更详细地描述每个主机属于哪些组。
- 用户首先需要定义集群的拓扑结构。这通常通过一个
配置集群变量 (Variables):
- Kubespray 提供了大量的可配置变量,允许用户定制化集群的各个方面。这些变量通常定义在
group_vars
目录下的文件中,例如all/all.yml
,k8s_cluster/k8s-cluster.yml
,etcd.yml
等。 - 图中特别指出了
vars.yml
(这可能是一个概括性的说法,实际上变量会分散在多个文件中)。关键的配置项包括:gcr_image_repo
/kube_image_repo
: Kubernetes 的核心组件镜像默认存储在k8s.gcr.io
(Google Container Registry)。在国内或其他访问 Google 服务受限的区域,需要将其替换为国内的镜像仓库地址,例如图中的registry.aliyuncs.com/google_containers
。这是确保在国内顺利部署的关键配置。etcd_download_url
/cni_download_url
: 类似地,etcd 的二进制文件和 CNI 插件(如 Calico, Flannel)的二进制文件或镜像可能也需要从可访问的源下载。ghproxy.com
(Github 代理): Kubespray 在部署过程中可能会从 Github 下载一些资源(例如 CNI 插件的 manifest 文件)。如果直接访问 Github 速度慢或不稳定,可以通过配置代理来加速下载。
- 其他重要变量还包括:Kubernetes 版本 (
kube_version
)、CNI 插件选择 (kube_network_plugin
)、容器运行时选择 (container_manager
,可以是docker
或containerd
)、高可用模式配置等。
- Kubespray 提供了大量的可配置变量,允许用户定制化集群的各个方面。这些变量通常定义在
执行 Ansible Playbook:
- 一旦清单和变量配置完毕,用户就可以通过
ansible-playbook
命令来执行 Kubespray 的主 playbook (通常是cluster.yml
)。 ansible-playbook -i inventory/mycluster/hosts.yml cluster.yml -b -v --private-key=~/.ssh/id_rsa
-i inventory/mycluster/hosts.yml
: 指定清单文件路径。cluster.yml
: Kubespray 的主 playbook 文件,它会调用一系列 roles 来完成集群部署。-b
(--become
): 表示以特权用户 (通常是 root) 执行任务,因为安装系统组件需要高权限。-v
: 详细输出模式。--private-key
: 指定用于 SSH 免密登录的私钥。Kubespray 要求部署节点能够免密登录到所有集群节点。
- 一旦清单和变量配置完毕,用户就可以通过
在目标节点上安装和配置组件:
- Ansible Playbook 会在清单中定义的各个节点上执行一系列任务,包括:
- 操作系统预配置:安装依赖包、配置内核参数 (如
net.bridge.bridge-nf-call-iptables=1
)、禁用 SELinux (如果需要)、配置防火墙规则等。 - 安装容器运行时:根据
container_manager
的配置安装 Docker 或 Containerd。 - 下载和分发 Kubernetes 二进制文件和镜像。
- 配置和启动 etcd 集群。
- 使用 kubeadm (Kubespray 在底层会利用 kubeadm 的一些功能) 初始化控制平面节点,生成证书,配置 kube-apiserver, kube-scheduler, kube-controller-manager。
- 在工作节点上配置和启动 kubelet 和 kube-proxy。
- 部署 CNI 插件,使得 Pod 网络能够正常工作。
- 配置高可用:例如,如果部署了多个控制平面节点,Kubespray 会配置负载均衡器(可以是外部的,也可以是基于 keepalived+haproxy/nginx 的内部负载均衡方案)来分发对 API Server 的请求。
- 操作系统预配置:安装依赖包、配置内核参数 (如
- Ansible Playbook 会在清单中定义的各个节点上执行一系列任务,包括:
图中右侧的“计算节点集群”展示了最终在每个 Node (包括控制平面节点和工作节点) 上运行的核心组件。例如,Docker/Containerd 作为容器运行时,kubelet 作为节点代理与控制平面通信并管理 Pod 生命周期,kubeadm 在初始化阶段被使用,Etcd 存储集群状态,Apiserver 提供 API 接口,Scheduler 负责 Pod 调度。
Kubespray 的高可用性 (HA) 实现
Kubespray 对高可用性的支持是其核心特性之一。
- Etcd HA: 通过部署奇数个 (通常是 3 或 5 个) etcd 节点组成集群。Raft 协议保证了数据的一致性和容错性。
- Control Plane HA:
- 部署多个控制平面节点 (apiserver, scheduler, controller-manager)。
- API Server 负载均衡: 需要一个负载均衡器(LB)在多个 API Server 实例前。这个 LB 可以是云提供商的 LB、硬件 LB,或者 Kubespray 可以帮你部署一个基于 nginx/haproxy + keepalived 的本地高可用负载均衡方案。Keepalived 通过 VRRP 协议提供一个虚拟 IP (VIP),当主 LB 节点故障时,VIP 会漂移到备用节点。
- Scheduler 和 Controller Manager: Kubernetes 内部通过 leader election 机制确保在任何时候只有一个 scheduler 和一个 controller-manager 实例处于活动状态,其他实例处于备用状态。
配置示例 (Inventory 片段)
一个简化的 inventory/mycluster/hosts.ini
(或由 hosts.yml
生成) 可能如下所示:
1 |
|
在 group_vars/all/all.yml
中配置镜像仓库:
1 |
|
在 group_vars/k8s_cluster/k8s-cluster.yml
中配置 CNI:
1 |
|
声明式 API 与集群管理

在云原生场景中,我们追求的目标是让系统按照我们 期望的状态 (Desired State) 运行。Kubernetes 本身就是基于声明式 API 构建的:你通过 YAML 文件描述你想要的应用部署、服务配置等,然后 Kubernetes 的控制器会不断地工作,将 当前状态 (Current State) 调整到你声明的期望状态。
然而,在 Cluster API 出现之前,Kubernetes 集群本身的创建、升级、扩缩容等生命周期管理操作,往往依赖于各种不同的工具(如 kubeadm
, kops
, kubespray
,或云厂商的控制台/CLI),这些工具很多是 命令式 (Imperative) 的。这意味着你需要执行一系列步骤来达到某个状态,而不是声明一个最终状态让系统去实现。这种方式在自动化、一致性和跨平台管理上存在挑战。
Cluster API 项目的核心目标就是将 Kubernetes 的声明式 API 模型扩展到集群自身的生命周期管理上。 这意味着你可以像管理应用一样,用 Kubernetes API(通过自定义资源 CRD)来创建、配置和管理 Kubernetes 集群。
Cluster API 详解
Cluster API 允许用户通过在“管理集群 (Management Cluster)”上创建 Kubernetes 自定义资源 (CRDs) 来声明“工作负载集群 (Workload Clusters)”的期望状态。然后,一系列的控制器会监听这些资源的变化,并驱动底层的云服务商或基础设施提供商来创建和调整实际的集群资源。
解决的问题
- 标准化集群部署:提供一套标准的 API 来定义和管理集群,无论底层是 AWS、Azure、GCP、vSphere 还是裸金属。
- 自动化集群生命周期管理:通过控制器模式,自动化集群的创建、扩缩容、升级和删除。
- GitOps for Clusters:集群的定义可以存储在 Git 仓库中,通过 GitOps 工作流来管理集群的变更,实现可追溯和可审计的集群运维。
- 可插拔的基础设施提供商:通过定义良好的接口,允许不同的基础设施提供商(Infrastructure Providers)接入,实现跨云和本地环境的统一管理。
- 提升开发者和运维体验:对于熟悉 Kubernetes API 的用户来说,学习成本更低,管理方式更统一。
核心架构与组件
管理集群 (Management Cluster):
- 这是一个预先存在的 Kubernetes 集群,用于运行 Cluster API 的控制器。
- 你在这个集群上安装 Cluster API 的 CRDs 和相关的控制器。
- 你通过
kubectl
与这个管理集群交互,来定义和控制你的工作负载集群。
工作负载集群 (Workload Cluster / Target Cluster):
- 这些是你希望通过 Cluster API 创建和管理的 Kubernetes 集群。它们是真正运行你的应用程序的地方。
- 这些集群可以是运行在任何受支持的基础设施上。
提供商 (Providers):
- Cluster API Controller (Core Provider):这是 Cluster API 的核心,提供了与基础设施无关的集群生命周期管理逻辑,例如
Machine
,MachineSet
,MachineDeployment
等 CRD 和对应的控制器。 - Infrastructure Provider (基础设施提供商):负责与特定的基础设施(如 AWS, Azure, GCP, vSphere, OpenStack, Bare Metal 等)交互,以创建和管理该基础设施上的资源(如虚拟机、网络、负载均衡器等)。例如,
cluster-api-provider-aws
(CAPA),cluster-api-provider-azure
(CAPZ)。每个基础设施提供商都会定义自己的 CRD,如AWSCluster
,AWSMachine
。- 例如,当你想在 AWS 上创建一个集群,你需要安装 CAPA。CAPA 会提供
AWSCluster
CRD 来描述 AWS 相关的集群配置 (如 VPC, Region) 和AWSMachine
CRD 来描述 EC2 实例的配置 (如实例类型, AMI ID)。
- 例如,当你想在 AWS 上创建一个集群,你需要安装 CAPA。CAPA 会提供
- Bootstrap Provider (引导提供商):负责将一个基础设施上的机器(如 VM)转变为一个 Kubernetes 节点。它通常使用
cloud-init
或类似的机制来安装kubelet
,kubeadm
,containerd
等,并执行kubeadm init
或kubeadm join
。默认且最常用的是cluster-api-bootstrap-provider-kubeadm
(CABPK)。它会提供如KubeadmConfig
这样的 CRD。 - Control Plane Provider (控制平面提供商):负责管理工作负载集群的控制平面。它通常与引导提供商协同工作,确保控制平面的高可用性、升级等。默认且最常用的是
cluster-api-control-plane-provider-kubeadm
(KCP)。它会提供如KubeadmControlPlane
这样的 CRD。
- Cluster API Controller (Core Provider):这是 Cluster API 的核心,提供了与基础设施无关的集群生命周期管理逻辑,例如
关键自定义资源 (CRDs)
这些 CRDs 是你用来声明集群状态的 “API 对象”:
Cluster
: 顶层资源,代表一个 Kubernetes 集群。它通常引用一个基础设施提供商的集群资源 (如AWSCluster
) 和一个控制平面提供商的资源 (如KubeadmControlPlane
)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
name: my-cluster
namespace: default
spec:
clusterNetwork:
pods:
cidrBlocks: ["192.168.0.0/16"]
controlPlaneRef:
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
name: my-cluster-control-plane
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 # 假设是 AWS
kind: AWSCluster
name: my-cluster-awsInfrastructureCluster
(例如AWSCluster
,AzureCluster
,VSphereCluster
): 特定于基础设施的集群级配置。由相应的基础设施提供商定义。1
2
3
4
5
6
7
8
9apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 # 示例为 AWS
kind: AWSCluster
metadata:
name: my-cluster-aws
namespace: default
spec:
region: "us-east-1"
sshKeyName: "my-ssh-key"
# ...其他 AWS 特定配置,如 VPC ID, Subnet IDsControlPlane
(例如KubeadmControlPlane
): 定义工作负载集群控制平面的期望状态,如副本数、Kubernetes 版本。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
metadata:
name: my-cluster-control-plane
namespace: default
spec:
replicas: 3 # 期望3个控制平面节点
version: "v1.28.3" # 期望的 Kubernetes 版本
machineTemplate:
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 # 假设是 AWS
kind: AWSMachineTemplate
name: my-cluster-control-plane-template
kubeadmConfigSpec:
# kubeadm init 和 join 的配置
initConfiguration:
nodeRegistration:
kubeletExtraArgs:
cloud-provider: "aws"
joinConfiguration:
nodeRegistration:
kubeletExtraArgs:
cloud-provider: "aws"Machine
: 代表一个单独的节点(物理机或虚拟机)。它通常引用一个引导配置 (如KubeadmConfig
) 和一个基础设施机器配置 (如AWSMachine
)。通常你不会直接创建Machine
,而是通过MachineSet
或MachineDeployment
。MachineSet
: 确保指定数量的Machine
副本正在运行,类似于 Kubernetes 的ReplicaSet
对于Pod
。MachineDeployment
: 为MachineSet
和Machine
提供声明式的更新能力,类似于 Kubernetes 的Deployment
对于Pod
。这是管理工作节点 (worker nodes) 的推荐方式。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineDeployment
metadata:
name: my-cluster-md-0
namespace: default
spec:
clusterName: my-cluster
replicas: 3 # 期望3个工作节点
selector:
matchLabels:
cluster.x-k8s.io/cluster-name: my-cluster
# ...
template:
spec:
clusterName: my-cluster
version: "v1.28.3" # 期望的 Kubernetes 版本
bootstrap:
configRef:
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
name: my-cluster-md-0-bootstrap-template
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 # 假设是 AWS
kind: AWSMachineTemplate
name: my-cluster-md-0-infra-templateBootstrapConfigTemplate
(例如KubeadmConfigTemplate
): 为Machine
定义引导配置的模板。1
2
3
4
5
6
7
8
9
10
11
12
13
14apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
metadata:
name: my-cluster-md-0-bootstrap-template
namespace: default
spec:
template:
spec:
# kubeadm join 的配置
joinConfiguration:
nodeRegistration:
kubeletExtraArgs:
cloud-provider: "aws"
# ...InfrastructureMachineTemplate
(例如AWSMachineTemplate
,AzureMachineTemplate
): 为Machine
定义基础设施特定配置的模板。1
2
3
4
5
6
7
8
9
10
11
12
13apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 # 示例为 AWS
kind: AWSMachineTemplate
metadata:
name: my-cluster-md-0-infra-template
namespace: default
spec:
template:
spec:
instanceType: "t3.medium"
ami:
id: "ami-xxxxxxxxxxxxxxxxx" # AWS AMI ID
sshKeyName: "my-ssh-key"
# ... 其他 AWS 实例配置MachineHealthCheck
: 定义节点健康检查的条件和修复策略(例如,当节点状态为NotReady
超过一定时间,则删除并重建该Machine
)。
工作流程(以创建集群为例)
初始化管理集群: 使用
clusterctl init --infrastructure aws
(以 AWS 为例) 在你的管理集群上安装 Cluster API 核心组件、AWS 基础设施提供商、Kubeadm 引导提供商和 Kubeadm 控制平面提供商。1
2# 示例:初始化管理集群以支持 AWS
clusterctl init --infrastructure aws这个命令会下载并应用所需的 CRDs 和部署对应的控制器 Pods。
定义集群: 创建上述 YAML 文件(
Cluster
,AWSCluster
,KubeadmControlPlane
,MachineDeployment
,AWSMachineTemplate
,KubeadmConfigTemplate
等)。
你可以使用clusterctl generate cluster
命令来帮助生成这些 YAML 文件的模板。1
2
3
4
5
6
7
8# 示例:生成一个名为 my-cluster 的集群配置文件模板
# 你需要设置一些环境变量,如 AWS_REGION, AWS_SSH_KEY_NAME 等
# KUBERNETES_VERSION, CONTROL_PLANE_MACHINE_COUNT, WORKER_MACHINE_COUNT
clusterctl generate cluster my-cluster \
--kubernetes-version v1.28.3 \
--control-plane-machine-count=3 \
--worker-machine-count=3 \
> my-cluster.yaml应用集群定义: 将这些 YAML 文件
kubectl apply -f <your-cluster-definition>.yaml
到管理集群。控制器协同工作:
Cluster
控制器监听到新的Cluster
对象。- 它会根据
infrastructureRef
找到对应的AWSCluster
对象。AWS 基础设施提供商的控制器会开始创建 VPC、子网、安全组等(如果需要)。 - 它会根据
controlPlaneRef
找到对应的KubeadmControlPlane
对象。Kubeadm 控制平面提供商的控制器开始协调控制平面节点的创建。 KubeadmControlPlane
控制器会为其定义的replicas
创建相应数量的Machine
对象,并关联AWSMachineTemplate
和KubeadmConfigTemplate
。- 对于每个
Machine
对象:- 引导提供商 (CABPK) 的控制器会根据
KubeadmConfigTemplate
生成cloud-init
脚本(包含kubeadm init
或kubeadm join
命令及证书等)。 - 基础设施提供商 (CAPA) 的控制器会根据
AWSMachineTemplate
在 AWS 上创建 EC2 实例,并将cloud-init
脚本作为用户数据传递给实例。 - EC2 实例启动后,执行
cloud-init
脚本,安装 Kubernetes 组件,然后初始化控制平面或加入现有控制平面。
- 引导提供商 (CABPK) 的控制器会根据
MachineDeployment
控制器会类似地为其定义的replicas
创建工作节点的Machine
对象。- 所有控制器会持续监控其管理的资源,确保实际状态与期望状态一致。例如,如果一个 EC2 实例意外终止,相应的控制器会尝试创建一个新的实例来替代它。
集群生命周期管理示例
集群扩缩容:
控制平面扩容: 修改
KubeadmControlPlane
对象的spec.replicas
字段。KCP 控制器会自动创建或删除控制平面节点。1
kubectl patch kcp my-cluster-control-plane --type='json' -p='[{"op": "replace", "path": "/spec/replicas", "value": 5}]'
工作节点扩容: 修改
MachineDeployment
对象的spec.replicas
字段。MachineDeployment
控制器会自动创建或删除工作节点。1
kubectl scale machinedeployment my-cluster-md-0 --replicas=5
节点健康检查和自动修复:
创建一个MachineHealthCheck
对象,指定不健康条件(例如,NodeStatus 为Unknown
超过 5 分钟)和对应的MachineDeployment
或KubeadmControlPlane
。如果节点满足不健康条件,MachineHealthCheck
控制器会删除对应的Machine
对象,然后其所属的MachineSet
(由MachineDeployment
或KubeadmControlPlane
管理) 会创建一个新的Machine
来替换它。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineHealthCheck
metadata:
name: my-cluster-mhc
spec:
clusterName: my-cluster
selector: # 选择要监控的机器
matchLabels:
cluster.x-k8s.io/cluster-name: my-cluster # 通常会监控所有 worker
unhealthyConditions:
- type: Ready
status: Unknown
timeout: 300s
- type: Ready
status: "False"
timeout: 300s
# remediationTemplate: (可选,定义修复操作,默认为删除Machine)Kubernetes 升级:
控制平面升级: 修改
KubeadmControlPlane
对象的spec.version
字段。KCP 控制器会执行滚动升级,一次替换一个控制平面节点。1
kubectl patch kcp my-cluster-control-plane --type='json' -p='[{"op": "replace", "path": "/spec/version", "value": "v1.29.0"}]'
工作节点升级: 修改
MachineDeployment
对象的spec.template.spec.version
字段。MachineDeployment
控制器会执行滚动升级,根据其更新策略(如RollingUpdate
)逐个替换工作节点。1
2
3
4# 获取 MachineDeployment 的名称
MD_NAME=$(kubectl get machinedeployment -l cluster.x-k8s.io/cluster-name=my-cluster -o jsonpath='{.items[0].metadata.name}')
# Patch MachineDeployment 的版本
kubectl patch machinedeployment $MD_NAME --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/version", "value": "v1.29.0"}]'
操作系统升级:
这通常通过更新InfrastructureMachineTemplate
(例如AWSMachineTemplate
) 中的镜像 ID (如spec.template.spec.ami.id
对于 AWS) 来实现。然后,MachineDeployment
(对于工作节点) 或KubeadmControlPlane
(对于控制平面节点) 会检测到其关联模板的变更(这取决于提供商的实现,通常需要手动触发或等待下一次协调),并执行滚动更新,用新的操作系统镜像创建新的节点来替换旧节点。
例如,更新AWSMachineTemplate
中的ami
:1
2
3# 假设你的 AWSMachineTemplate 名为 my-cluster-md-0-infra-template
NEW_AMI_ID="ami-newxxxxxxxxxxxxxx"
kubectl patch awsmachinetemplate my-cluster-md-0-infra-template --type='json' -p="[{'op': 'replace', 'path': '/spec/template/spec/ami/id', 'value': '${NEW_AMI_ID}'}]"之后,相关的
MachineDeployment
会开始滚动更新其管理的Machine
。
Cluster API 核心角色
Cluster API (CAPI) 是一个 Kubernetes 子项目,它通过声明式 API 来创建、配置和管理 Kubernetes 集群的生命周期。它的核心思想是将 Kubernetes 集群本身也作为一种资源,使用 Kubernetes 的方式来管理 Kubernetes (Kubernetes-ception)。
Cluster API 的运作依赖于几个关键的角色,它们协同工作以实现集群的自动化管理。
管理集群 (Management Cluster)
- 核心职责:这是 Cluster API 控制器运行的地方,它负责托管和管理一个或多个 Workload 集群的生命周期。
- 关键特征:管理集群本身是一个已经存在的 Kubernetes 集群。所有描述 Workload 集群的 Cluster API 对象(自定义资源,CRDs)都存储在管理集群的 etcd 中。例如,
Cluster
、Machine
、MachineDeployment
等 CRD 对象都存在于此。 - 运行机制:管理集群中的 Cluster API 控制器会监听这些 CRD 对象的变化,并根据其声明的状态来驱动 Workload 集群的创建、更新和删除。
Workload 集群 (Workload Cluster)
- 核心职责:这是最终用户用来部署和运行其应用程序的 Kubernetes 集群。
- 关键特征:Workload 集群的整个生命周期,从基础设施的准备、Kubernetes 组件的安装与配置,到节点的加入和集群的伸缩,都由管理集群通过 Cluster API 的声明式 API 进行管理。
- 用户视角:对于最终用户而言,Workload 集群就是一个标准的 Kubernetes 集群,他们可以直接使用
kubectl
与之交互,部署应用。
Infrastructure Provider (基础设施提供者)
- 核心职责:负责与特定的底层基础设施(如 AWS, Azure, GCP, vSphere, Bare Metal 等)进行交互,以管理 Workload 集群所需的计算、网络和存储资源。
- 关键特征:每个 Infrastructure Provider 都实现了一套 Cluster API 定义的接口,将 Cluster API 中通用的声明(如“我需要一个拥有 3 个节点的集群”)转化为特定云平台的 API 调用(如在 AWS 创建 3 个 EC2 实例,配置 VPC、安全组等)。
- 实现机制:通常以一组自定义控制器和 CRD 的形式存在,例如
AWSMachine
、AzureCluster
等。当一个通用的Machine
对象被创建时,对应的 Infrastructure Provider 控制器会监听到,并创建其特定平台的资源。 - 例如:
cluster-api-provider-aws
(CAPA),cluster-api-provider-azure
(CAPZ)。
Bootstrap Provider (引导提供者)
- 核心职责:负责在新创建的基础设施节点上进行 Kubernetes 的初始化和引导工作。
- 关键特征:一旦 Infrastructure Provider 创建了计算节点(如虚拟机),Bootstrap Provider 就接管后续工作。
- 具体任务包括:
- 证书生成:为新的 Kubernetes 集群生成所需的 PKI 证书和密钥。
- 控制面组件安装和初始化:在指定的节点上安装和配置 Kubernetes 控制平面组件(如 etcd, kube-apiserver, kube-controller-manager, kube-scheduler)。这通常通过云初始化脚本(cloud-init)或类似机制,结合
kubeadm
来完成。 - 节点加入:生成
kubeadm join
命令或配置,使新的控制平面节点和工作节点能够正确加入到 Workload 集群中。
- 常用实现:最常见的 Bootstrap Provider 是基于
kubeadm
的 (CABPK
- Cluster API Bootstrap Provider Kubeadm)。
Control Plane Provider (控制平面提供者)
- 核心职责:负责管理 Workload 集群控制平面的生命周期和配置。
- 关键特征:它与 Bootstrap Provider 紧密协作,但更侧重于控制平面作为一个整体的编排和升级。
- 例如:
KubeadmControlPlaneProvider
(KCP) 是一个常见的实现,它使用kubeadm
来管理控制平面节点的集合,支持声明式的控制平面升级、伸缩等操作。它通常会创建一组Machine
对象来代表控制平面节点。
Cluster API 核心模型 (CRDs)
Cluster API 通过一系列自定义资源定义 (CRDs) 来描述和管理 Kubernetes 集群。这些模型是声明式 API 的基础。
Machine
- 定义:
Machine
是 Cluster API 中的一个核心 CRD,它代表一个计算资源单元,通常是一个虚拟机或物理机。这个计算资源将被用来运行 Kubernetes 组件,并最终成为 Workload 集群中的一个 KubernetesNode
。 - 与 Kubernetes
Node
的差异:Machine
是 Cluster API 的抽象,存在于管理集群中,描述的是基础设施层面的“机器”或“实例”的期望状态。- Kubernetes
Node
是 Kubernetes 内部的逻辑概念,存在于 Workload 集群中,代表一个已经成功加入集群并准备好运行 Pod 的工作单元。 - 一个
Machine
对象最终会通过 Infrastructure Provider 和 Bootstrap Provider 的协作,在 Workload 集群中具化为一个Node
对象。
- 生命周期:
- 创建:当一个新的
Machine
对象被创建时,Cluster API 的相关控制器(通常是 Infrastructure Provider 的控制器)会与底层基础设施(如 AWS)交互,创建一个实际的计算实例(如 EC2 实例)。随后,Bootstrap Provider 会在该实例上安装操作系统和 Kubernetes 组件,并使其加入 Workload 集群。Machine
对象的状态会随之更新以反映其实际状态。 - 删除:当一个
Machine
对象被删除时,对应的控制器会从 Workload 集群中优雅地移除对应的Node
(驱逐 Pod),然后调用 Infrastructure Provider 回收底层的计算资源。 - 更新与不可变性 (Machine Immutability):
- 核心理念:Cluster API 强烈推荐并默认采用不可变基础设施 (Immutable Infrastructure) 的模式来管理
Machine
。 - In-place Upgrade vs. Replace:当
Machine
的某些属性需要更新时(例如,指定新的 Kubernetes 版本、更改实例类型或 AMI),Cluster API 不会尝试在现有机器上进行“原地升级”(In-place Upgrade),而是采用“替换”(Replace) 策略。 - 替换流程:一个新的
Machine
实例会根据新的配置被创建出来。一旦新Machine
准备就绪并加入集群,旧的Machine
实例才会被删除。 - 优势:这种不可变性大大简化了升级和回滚的复杂性,提高了系统的可预测性和可靠性,避免了因原地修改引入的配置漂移或潜在的失败状态。
- 核心理念:Cluster API 强烈推荐并默认采用不可变基础设施 (Immutable Infrastructure) 的模式来管理
- 创建:当一个新的
- 定义:
MachineDeployment
- 定义:
MachineDeployment
类似于 Kubernetes 原生的Deployment
对象,但它管理的是MachineSet
和Machine
的集合。 - 功能:它为一组相同的
Machine
(通常是 Workload 集群的工作节点) 提供了声明式的更新和管理能力。你可以定义期望的Machine
模板、副本数量以及更新策略(如滚动更新RollingUpdate
)。 - 例如:当你想升级 Workload 集群工作节点的 Kubernetes 版本时,你会修改
MachineDeployment
中定义的Machine
模板(如更新version
字段)。MachineDeployment
控制器会按照指定的策略(例如,一次替换一个节点)逐步创建新的Machine
(使用新版本) 并删除旧的Machine
。
- 定义:
MachineSet
- 定义:
MachineSet
类似于 Kubernetes 原生的ReplicaSet
对象,但它管理的是Machine
。 - 功能:它的主要目标是确保在任何给定时间都有指定数量的
Machine
副本在运行。如果实际运行的Machine
数量少于期望数量,MachineSet
控制器会根据其模板创建新的Machine
;如果数量过多,则会删除多余的Machine
。 - 关系:
MachineDeployment
通过管理MachineSet
来实现复杂的部署和更新策略。通常用户直接操作MachineDeployment
,而不是MachineSet
。
- 定义:
MachineHealthCheck
- 定义:
MachineHealthCheck
是一种机制,用于监控Machine
(及其对应的 KubernetesNode
) 的健康状况。 - 功能:用户可以定义一组条件,当
Machine
或Node
满足这些条件时,该Machine
就会被标记为不健康。例如,Node
状态长时间处于NotReady
。 - 自动修复:当
MachineHealthCheck
控制器检测到一个Machine
不健康时,它可以触发自动修复流程。最常见的修复动作是删除这个不健康的Machine
对象。随后,拥有该Machine
的MachineSet
(或由MachineDeployment
管理的MachineSet
) 会检测到副本数量不足,并自动创建一个新的、健康的Machine
来替代它,从而实现节点的自愈。
- 定义:
通过这些角色和模型的协同工作,Cluster API 提供了一个强大且灵活的框架,用于以声明式、Kubernetes 原生的方式管理 Kubernetes 集群的整个生命周期,极大地简化了多集群管理和自动化运维的复杂性。
实践
Create host cluster
1 |
|
Generate cluster specs
1 |
|
Check
1 |
|
1 |
|


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 等技术)。在隔离性和成本之间取得平衡。
多租户是一个复杂的主题,没有一刀切的解决方案。选择哪种模式和哪些隔离机制取决于具体的安全要求、信任模型、成本预算和管理能力。