Kubernetes 1.35核心特性解析:从容器编排到AI负载操作系统的演进
1. 从编排平台到“泛在操作系统”的蜕变
最近Kubernetes 1.35版本正式发布,社区里讨论的热度又上来了。但这次大家聊的焦点,已经不再是“如何部署一个无状态应用”或者“Service和Ingress怎么配”这些老生常谈的话题了。一个更根本性的问题被反复提及:Kubernetes是不是正在变成另一种东西?它似乎正在从一个纯粹的“容器编排系统”,演变成一个更为底层的、抽象的“泛在计算资源操作系统”。而这次更新中,对AI负载(AI Workload)的显著增强,恰恰是这一演变趋势中最具代表性的信号,但它可能仅仅是个开始。
我接触Kubernetes算比较早的,从它和Docker Swarm、Mesos争锋的时代就开始用了。那时候的K8s,核心诉求非常明确:把我这一堆容器,用我声明的方式,在集群里跑起来,并且保持健康。它的API对象,像Pod、Deployment、Service,都是围绕这个核心目标设计的。但如果你看看现在1.35版本里引入或变得成熟的特性,比如InPlacePodVerticalScaling(原地垂直扩缩容)进入稳定版,ReadWriteOncePod持久卷访问模式进入稳定版,以及对Sidecar容器生命周期的精细化管控,你会发现它的触角正在伸向更底层、更具体的资源调度和生命周期管理。
这感觉就像什么呢?早期的Linux内核主要管进程调度、内存管理和文件系统,后来它开始要直接管理GPU、NPU、FPGA,要处理RDMA网络,要协调跨异构硬件的任务。Kubernetes也在经历类似的“内核化”过程。它不再满足于只当“容器的调度员”,它想成为数据中心里所有计算任务——无论是传统的Web服务、批处理作业,还是现在火热的AI训练推理、科学计算、边缘设备上的实时处理——的统一抽象层和调度平台。AI Workload因其对算力(尤其是异构算力)、网络、存储的极端和独特需求,成为了推动Kubernetes完成这次蜕变的第一块,也是最重要的一块试金石。理解了这个背景,我们再去看1.35的具体更新,就不会只停留在“哦,又加了几个API”的层面,而是能看清它背后整个系统演进的方向盘在往哪打。
2. 1.35核心更新:为“操作系统”夯实地基
每次K8s版本更新,都会有一长串的变更日志。对于大多数开发者或运维而言,没必要逐条细究,但必须抓住那些标志着“能力边界拓展”或“范式转变”的特性。1.35版本中,以下几个特性值得我们深入解读,因为它们直接服务于更复杂、更多样化的工作负载,尤其是AI负载。
2.1 InPlacePodVerticalScaling:原地垂直伸缩的终局
这个特性从Alpha走到Stable,花了相当长的时间,但其意义重大。在它出现之前,如果你想调整一个Pod的CPU或内存限制(resources.limits/requests),唯一的办法是重建Pod。这意味着IP地址可能会变,存储可能会重新挂载,对于有状态服务或者对网络连续性有要求的服务(比如一些长连接的网关、数据库)来说,这是不可接受的。
它解决了什么问题?想象一个AI推理服务。白天请求量小,2个CPU核心、4GB内存可能就够了。但到了晚上流量高峰,或者突然需要处理一批离线推理任务,我们需要临时把资源扩展到4核8G。传统的Horizontal Pod Autoscaler(HPA)通过增减Pod副本数来应对,但这不一定适合所有场景:
- 有状态服务:模型本身可能很大,每个Pod都加载一份,内存和GPU显存浪费严重。理想状态是单个Pod承载,动态调整其资源。
- 资源绑定型应用:某些应用与特定硬件(如特定的GPU卡)或网络身份(如特定的IP)强绑定,无法轻易迁移。
- 快速响应:创建新Pod并等待其启动、加载模型,时间成本可能比直接扩展现有Pod的资源要高。
InPlacePodVerticalScaling允许你直接修改Pod的resources字段,kubelet在节点上原地调整容器的cgroup限制,无需重启容器。这对于AI模型服务这种“重”进程来说,是革命性的。你可以根据队列深度,动态地为同一个模型服务Pod分配更多CPU或内存来处理突发请求。
实操要点与避坑指南:
# 1. 首先,你的Pod必须设置resources,并且指定允许更新的策略 apiVersion: v1 kind: Pod metadata: name: ai-inference-pod spec: containers: - name: model-server image: my-ai-model:latest resources: requests: memory: "4Gi" cpu: "2" limits: memory: "8Gi" cpu: "4" # 关键:启用原地更新策略 resizePolicy: - resourceName: "cpu" restartPolicy: "NotRequired" # 核心!表示CPU调整无需重启容器 - resourceName: "memory" restartPolicy: "NotRequired" # 2. 更新时,直接patch Pod的resources字段 kubectl patch pod ai-inference-pod --type='merge' -p '{"spec":{"containers":[{"name":"model-server","resources":{"requests":{"cpu":"4", "memory":"8Gi"},"limits":{"cpu":"8", "memory":"16Gi"}}}]}}'注意:
resizePolicy是容器级别的字段,不是Pod级别。restartPolicy设置为NotRequired是原地伸缩的关键。目前,对于内存的原地扩容,Linux内核需要cgroup v2且开启cgroup内存控制器的memory.high和memory.max功能。对于CPU,依赖cpuset cgroup控制器。在实际生产环境启用前,务必在测试环境验证节点内核和cgroup配置是否支持。
2.2 ReadWriteOncePod:存储访问的精准隔离
持久卷(PersistentVolume, PV)的访问模式(Access Modes)大家都很熟悉:ReadWriteOnce(RWO,单节点读写)、ReadOnlyMany(ROX,多节点只读)、ReadWriteMany(RWX,多节点读写)。但传统的RWO存在一个模糊地带:它只保证“同时只能被一个节点挂载为读写模式”,但并没有阻止同一个PV被同一个节点上的多个Pod挂载。
这在AI场景下会出大问题。假设你有一个存储了大型预训练模型文件(如几百GB的Checkpoint)的PV,以RWO模式创建。你启动了一个训练任务Pod-A挂载它。理论上,另一个调度到同一节点的推理任务Pod-B,也可能挂载同一个PV。如果两个Pod同时写入(比如训练任务保存中间状态,推理任务缓存数据),就会导致数据损坏,而且这种错误静默发生,极难排查。
ReadWriteOncePod(简称RWOP)访问模式进入Stable,彻底解决了这个问题。它保证一个PV在同一时间只能被一个Pod挂载,提供了最强的存储隔离性。这对于需要独占访问模型文件、数据集或重要中间状态的AI负载是刚需。
配置示例与场景:
# 1. 创建支持RWOP的StorageClass(需要CSI驱动支持) apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: csi-rwop provisioner: pd.csi.storage.gke.io # 示例:GCP PD CSI驱动 parameters: type: pd-ssd # 关键:在allowedTopologies或由驱动决定,但模式由PVC指定 --- # 2. 创建PVC时指定accessModes为ReadWriteOncePod apiVersion: v1 kind: PersistentVolumeClaim metadata: name: model-storage-pvc spec: accessModes: - ReadWriteOncePod # 注意这里是单数Pod! resources: requests: storage: 500Gi storageClassName: csi-rwop --- # 3. Pod挂载此PVC后,其他任何Pod(即使在同一节点)都无法再挂载它 apiVersion: v1 kind: Pod metadata: name: exclusive-training-pod spec: containers: - name: trainer image: pytorch:latest volumeMounts: - name: model-storage mountPath: /models volumes: - name: model-storage persistentVolumeClaim: claimName: model-storage-pvc实操心得:不是所有CSI驱动都立即支持RWOP。主流的云厂商驱动(如AWS EBS CSI, GCP PD CSI, Azure Disk CSI)的新版本通常已支持。在自建环境中,需要确认你使用的CSI驱动(如Ceph RBD, NFS)是否实现了该功能。启用RWOP后,存储卷的“身份”更像是一个Pod的独占附属资源,这在设计有状态AI应用架构时,思路需要从“共享存储”转向“专有存储”。
2.3 Sidecar容器生命周期管理:启动与终止的秩序
Sidecar模式在Kubernetes中无处不在:日志收集代理(如Fluentd)、服务网格边车(如Istio Envoy)、监控代理等。在AI场景下,Sidecar可能是一个模型预热器、一个特征数据加载器,或者一个GPU监控上报组件。
长期以来,Kubernetes对Pod内容器的启动和停止顺序是“平等对待”的,这导致了一个经典问题:主容器可能依赖Sidecar容器提供的服务(比如Envoy配置就绪),但主容器先启动,就会连接失败。1.35版本对Sidecar容器的生命周期管理提供了更明确的支持(通过restartPolicy字段和特定的生命周期钩子)。
虽然完整的、声明式的Sidecar类型容器还在演进中,但当前版本的最佳实践已经可以更好地处理顺序问题。核心在于利用容器生命周期钩子postStart和preStop,以及readinessProbe,来协调启动和关闭流程。
一个AI模型服务Pod的Sidecar协调示例:
apiVersion: v1 kind: Pod metadata: name: model-serving-with-sidecar spec: initContainers: - name: download-model image: busybox command: ['sh', '-c', 'wget -O /models/latest.pt https://model-repo.com/latest.pt'] volumeMounts: - name: model-store mountPath: /models containers: # Sidecar容器:负责模型预热和监控 - name: model-warmup-sidecar image: warmup-agent:latest lifecycle: postStart: exec: command: ["/bin/sh", "-c", "echo '开始预热模型...' && warmup --model-path /models/latest.pt && touch /tmp/warmup.done"] readinessProbe: exec: command: ["cat", "/tmp/warmup.done"] initialDelaySeconds: 5 periodSeconds: 2 volumeMounts: - name: model-store mountPath: /models - name: tmp-dir mountPath: /tmp # 主容器:模型推理服务 - name: model-server image: triton-server:latest lifecycle: postStart: exec: command: ["/bin/sh", "-c", "while [ ! -f /tmp/warmup.done ]; do sleep 1; done; echo 'Sidecar预热完成,启动主服务'"] ports: - containerPort: 8000 volumeMounts: - name: model-store mountPath: /opt/models - name: tmp-dir mountPath: /tmp volumes: - name: model-store emptyDir: {} - name: tmp-dir emptyDir: {}设计逻辑解析:
initContainer负责下载模型到共享卷,这是所有容器启动前的准备。model-warmup-sidecar启动后,立即执行postStart钩子进行模型预热(加载到GPU显存等),预热完成后创建标志文件/tmp/warmup.done。- Sidecar的
readinessProbe检查标志文件是否存在,只有存在后才报告“就绪”。这会影响Service的端点发现,但更重要的是为后续协调提供信号。 - 主容器
model-server的postStart钩子会循环等待标志文件出现,确保在Sidecar完成预热后才真正启动推理服务进程。
注意事项:
postStart钩子并不保证在容器ENTRYPOINT之前执行,它只是异步触发。因此,上述模式中主服务进程的启动是通过在postStart中等待,然后可能通过一个启动脚本才启动实际进程来实现的,并非完美方案。社区正在推动真正的sidecar容器类型,使其能明确地在主容器之前启动、之后停止。目前,利用initContainers做准备工作,结合readinessProbe和共享卷状态文件进行协调,是最可靠的土办法。
3. 面向AI负载的Kubernetes架构演进思考
Kubernetes要承载好AI负载,光靠这几个特性更新是不够的。它需要在整个架构层面进行思考和调整。从我的经验来看,一个面向AI的K8s集群,需要在以下几个层面做好准备。
3.1 异构资源管理与设备插件
AI的核心是算力,而算力今天已经高度异构化:NVIDIA GPU、AMD GPU、Google TPU、华为昇腾、各种AI推理芯片等。Kubernetes通过设备插件框架来管理这些资源。但原生的设备插件模型比较基础,对于AI场景下复杂的设备拓扑(如NVLink连接的GPU组)、设备内存分页、多实例GPU(MIG)的细粒度切分,支持起来很吃力。
当前实践与挑战:
- NVIDIA GPU Operator:这几乎是生产环境的标准选择。它自动化了节点上GPU驱动、容器运行时(如
nvidia-container-runtime)、监控组件(DCGM)以及K8s设备插件的部署。它同时支持时间片共享模式和MIG模式。 - 资源请求与限制:在Pod中请求GPU资源时,传统方式是
nvidia.com/gpu: 1。但对于MIG,你需要指定具体的实例类型,如nvidia.com/mig-1g.5gb: 1。这要求调度器能理解这些新的资源类型。 - 调度器扩展:默认调度器对GPU的“感知”仅限于数量。但在实际AI训练中,你可能希望两个需要高速互联的Pod调度到有NVLink连接的GPU上,或者避免将多个高负载训练任务调度到同一张物理GPU的不同MIG实例上(可能共享显存带宽)。这就需要使用像
NodeResourceTopologyAPI、Scheduler Plugins或者直接使用像kube-batch、Volcano这样的批处理调度器,它们对AI任务(如Gang Scheduling——组调度,保证所有任务同时成功运行)有更好的支持。
配置示例(使用GPU Operator和MIG):
# 节点上配置MIG策略(通常由GPU Operator或节点初始化脚本完成) # 例如,将一张A100 80GB GPU切分成7个1g.5gb实例 # 然后在Pod中请求特定的MIG实例 apiVersion: v1 kind: Pod metadata: name: mig-pod spec: containers: - name: cuda-container image: nvidia/cuda:12.1.0-base command: ["sleep", "infinity"] resources: limits: nvidia.com/mig-1g.5gb: 2 # 请求两个1g.5gb的MIG实例3.2 存储与数据编排
AI负载对存储的需求是海量且高性能的。训练数据集动辄TB级,模型Checkpoint也很大,而且要求高吞吐、低延迟。K8s的PV/PVC抽象是第一步,但远远不够。
核心方案:
- 高性能共享存储:对于需要多Pod读取的训练数据,
ReadWriteMany(RWX)模式的存储是必须的。这通常指向网络文件系统,如NFS、CephFS,或云上的托管服务(如AWS EFS, Azure Files, GCP Filestore)。它们的性能是关键瓶颈,需要根据IOPS和吞吐量需求仔细选型。 - 本地临时存储加速:为了缓解共享存储的延迟,常见的模式是使用
InitContainer将数据从远程存储(如S3)下载到节点的本地emptyDir或hostPath卷,或者使用像Fluid这样的数据编排系统。Fluid可以将远程数据缓存在集群节点的本地存储(如SSD)中,并以PV的形式暴露给Pod,自动保持数据一致性,大幅提升数据访问速度。 - 数据集管理Operator:更高级的做法是使用如
KubeDL、Kubeflow的Pipelines等框架中的组件,它们能管理数据集的生命周期,自动完成数据下载、预处理、版本控制和缓存。
3.3 网络与通信优化
分布式训练(如PyTorch DDP, Horovod)对网络的要求极高,需要高带宽、低延迟的节点间通信。普通的Kubernetes集群网络(如Flannel的VXLAN模式)可能无法满足需求。
优化方向:
- 高性能网络CNI插件:选择支持
RDMA(如SR-IOV)或eBPF加速的CNI插件,如Calico(开启eBPF数据平面)、Cilium、Multus等。Multus允许Pod拥有多张网卡,可以将数据面流量(如GPU间的梯度同步)通过一张高性能网卡(如InfiniBand)传输,而控制面流量走默认的集群网络。 - 节点亲和性与拓扑感知调度:通过
PodAntiAffinity避免将多个通信密集的Pod调度到同一个节点(争抢网络带宽),或者反过来,通过PodAffinity将需要频繁通信的Pod调度到同一个节点(利用节点内的高效通信,如NVLink)。更精细的调度需要Topology Manager配合,它尝试将CPU、内存、GPU、网络设备等资源在同一个NUMA节点内对齐,以获得最佳性能。
4. 通用故障排查思路在AI场景下的应用
Kubernetes的故障排查通常遵循从Pod到Service到Ingress,从应用日志到事件到资源状态的路径。但在AI负载下,有些问题具有特殊性。
4.1 Pod状态异常排查清单
| 现象 | 可能原因 | 排查命令与步骤 |
|---|---|---|
| Pending | 资源不足(特别是GPU)、节点Selector不匹配、PVC未绑定、污点容忍问题。 | kubectl describe pod <pod-name>查看Events。重点看Warning事件,如Insufficient nvidia.com/gpu。检查kubectl get nodes看节点资源分配情况。 |
| CrashLoopBackOff | 容器启动后立即退出。AI场景常见:模型文件缺失/路径错误、GPU驱动/库版本不兼容、权限问题(如无法写入共享存储)。 | kubectl logs <pod-name> --previous查看上一次崩溃的日志。检查容器启动命令和参数。确认容器镜像是否包含正确的CUDA版本和依赖库。检查挂载卷的权限(fsGroup)。 |
| Running但服务不可用 | 应用内部错误(如模型加载失败)、Sidecar未就绪、端口监听错误、GPU内存不足(OOM)。 | kubectl logs <pod-name> -c <container-name>查看指定容器日志。kubectl exec -it <pod-name> -- nvidia-smi检查GPU状态和显存占用。检查readinessProbe配置。 |
| GPU相关错误 | nvidia-container-cli初始化失败、MIG配置冲突、CUDA版本不匹配。 | 查看kubectl describe node <node-name>,在Capacity和Allocatable部分确认GPU资源是否正常上报。登录节点,检查/var/log/messages或journalctl中与nvidia相关的日志。 |
4.2 性能问题排查
AI任务跑得慢,可能不是代码问题,而是环境问题。
- 数据读取慢:使用
kubectl top pod查看Pod的IO情况。如果怀疑是存储性能,可以进入Pod,用dd或fio工具测试挂载点的读写速度。考虑引入数据缓存层如Fluid。 - GPU利用率低:通过
kubectl exec进入Pod运行nvidia-smi,观察GPU-Util和显存占用。如果Util很低,可能是:- CPU成为瓶颈:数据预处理跟不上GPU计算。检查Pod的CPU请求/限制是否足够,使用
kubectl top pod看CPU使用率。 - IO等待:数据加载慢导致GPU空闲。优化数据管道,使用更快的存储或缓存。
- 通信瓶颈:分布式训练中,节点间梯度同步耗时过长。检查网络带宽和延迟,考虑使用高性能网络插件或调整训练任务的
batch size、通信频率。
- CPU成为瓶颈:数据预处理跟不上GPU计算。检查Pod的CPU请求/限制是否足够,使用
- 内存不足(OOM):不仅是系统内存,更要关注GPU显存。显存OOM通常会导致进程直接被杀死,日志可能不完整。务必在Pod资源限制中准确设置GPU内存限制(如果使用MIG,则是实例的固定大小),并监控
nvidia-smi中的显存使用趋势。对于PyTorch,可以使用torch.cuda.memory_summary()来跟踪显存分配。
4.3 一个典型的分布式训练故障排查案例
场景:一个使用PyTorch DDP进行分布式训练的Job,有4个Worker Pod。其中一个Pod一直处于Pending状态,其他3个运行正常。
排查流程:
- 描述Pod:
kubectl describe pod train-job-xxxx。在Events中发现一条关键信息:0/4 nodes are available: 4 Insufficient nvidia.com/gpu. - 检查节点资源:
kubectl get nodes -o custom-columns=NAME:.metadata.name,GPU:.status.allocatable.'nvidia\.com/gpu'。发现集群只有3个节点有GPU,且每个节点只有1张GPU卡。而我们的Job请求了replicas: 4,每个Pod需要nvidia.com/gpu: 1。 - 问题根因:资源不足。DDP训练要求所有Worker同时启动(Gang Scheduling),缺一个都无法开始。
- 解决方案:
- 方案A(扩容):增加一个GPU节点。
- 方案B(修改需求):如果模型较小,可以尝试使用更少的GPU(如
replicas: 3),或者使用MIG将一张物理GPU切分给多个Pod使用(但需注意性能影响)。 - 方案C(使用批调度器):使用
Volcano等调度器,它支持minAvailable策略,可以配置为“所有Pod都调度成功才真正绑定”,避免部分Pod占用资源而其他Pod饿死的情况。同时,它也能更好地处理这种资源不足时的队列等待和优先级。
这个案例说明,在AI场景下,资源调度不再是简单的“有或无”,而是涉及到复杂的拓扑、共享和协同需求。Kubernetes正在通过引入新的API和扩展点,让自己具备处理这些复杂需求的能力,这正是它向“泛在操作系统”演进的核心体现。AI Workload只是一个开始,未来边缘计算、高性能计算、量子计算模拟等更多样化的负载,都将推动Kubernetes在这条路上走得更远。对于我们使用者来说,理解这个趋势,意味着我们需要从更高的维度去思考集群的规划、应用的架构和故障的排查,不再仅仅局限于“容器”本身。