🤖
AI审核中

Kubernetes1.37升级审计清单:网络、资源、安全、存储一次说清

Java 21分钟 107浏览 0评论

本文写于 2026 年 8 月 24 日。Kubernetes v1.37 计划于 2026 年 8 月 26 日发布,正式版本发布前,部分特性状态和时间表仍可能调整。(Kubernetes)

很多 Java 开发者关注 Kubernetes 版本升级时,第一反应往往是:

我的 Spring Boot 代码需要改吗?

大多数时候,业务代码确实不需要修改。

但这并不意味着升级没有风险。

Java 应用一旦运行在 Kubernetes 中,请求能否进入 Pod、JVM 能否正确识别资源限制、磁盘异常能否被及时发现、节点组件是否拥有过高权限,都已经不再由 Spring Boot 单独决定。

一条请求真正经过的链路更接近下面这样:

flowchart LR
    A["外部请求"] --> B["Service 网络<br/>kube-proxy / CNI"]
    B --> C["Java Pod"]
    C --> D["kubelet"]
    D --> E["cgroup 资源控制"]
    C --> F["PVC / CSI 存储"]
    D --> G["Metrics API"]
    H["SELinux / UserNS"] --> C

Kubernetes 1.37 的重要变化,恰好分布在这条链路的不同位置。

它没有引入一个能让业务代码立刻“少写几百行”的明星功能,但它释放出了一个非常明确的信号:

Kubernetes 正在清理早期遗留的基础设施路径,并把资源管理、网络转发、安全隔离和可观测性逐步收敛到更现代的基线上。

对于运行 Java 服务的团队来说,这类变化往往比新增一个 API 更值得关注。

一、先看结论:升级前需要审计什么

根据 Kubernetes 1.37 官方预览,这次需要重点关注以下变化:

变化 1.37 中的状态 主要影响对象 风险判断
kube-proxy 的 IPVS 模式 进入明确退场周期 使用 IPVS 的集群
cgroup v1 继续推进移除 老旧 Linux 节点、旧 JDK
Static Pod 引用 Secret、ConfigMap 严格禁止 控制平面、自建节点组件
SELinuxMount 预计 GA 并默认启用 SELinux 集群、共享卷 条件性高
Metrics API 预计进入稳定版 HPA、VPA、监控程序 中低
Rootless kubelet 预计进入 Beta 节点安全体系 中长期价值高
Volume Health Monitor 重新以 Alpha 推进 CSI、PVC、状态服务 观察阶段

上述状态均来自 Kubernetes 1.37 官方预览,其中 IPVS、cgroup v1 和 SELinux 变化更接近“升级前必须排查”,而 Rootless kubelet、Volume Health Monitor 更适合先评估和试验。(Kubernetes)

二、IPVS 开始退场,Service 网络基线需要重新选择

IPVS 曾经是大型 Kubernetes 集群常见的 kube-proxy 模式。

它最初被引入,是为了缓解传统 iptables 模式在大量 Service 和 Endpoint 场景下的同步效率问题。IPVS 使用内核哈希表,并提供轮询、最少连接、源地址哈希等多种调度算法,因此一度被视为比 iptables 更先进的方案。

但问题在于,IPVS 本身无法完整表达 Kubernetes Service 的全部语义,实际运行时仍然需要配合 iptables。随着 Kubernetes Service 能力不断扩展,这种底层模型不匹配的问题越来越明显。

Kubernetes 官方已经将 IPVS 标记为弃用,并推荐优先评估 nftables;对于无法使用 nftables 的老旧环境,也可以重新测试近几年已经大幅优化的 iptables 模式。nftables 模式目前要求 Linux 内核至少为 5.13。(Kubernetes)

在 Kubernetes 1.37 中,使用 IPVS 的 kube-proxy 预计会在启动时输出弃用警告。当前规划是:

  • Kubernetes 1.40 默认禁用 IPVS;
  • Kubernetes 1.43 完全移除 IPVS;
  • 具体时间仍可能随正式版本调整。(Kubernetes)

1. 先确认集群是否使用 IPVS

可以读取 kube-proxy 的配置:

kubectl -n kube-system get configmap kube-proxy \
  -o jsonpath='{.data.config\.conf}' \
  | grep 'mode:'

如果输出类似:

mode: ipvs

说明当前集群已经进入需要制定迁移计划的范围。该检查命令同样由 Kubernetes 1.37 官方预览给出。(Kubernetes)

2. 为什么 Java 服务也需要关注

从业务代码看,Spring Boot 应用可能只是访问:

http://order-service

但这个 Service 地址背后究竟如何被转换为 Pod 地址,是 kube-proxy 或 CNI 网络组件负责的。

切换 Service 代理模式后,至少需要重新验证:

  • ClusterIP 和 Headless Service;
  • NodePort 和 LoadBalancer;
  • externalTrafficPolicy
  • 会话保持;
  • 跨节点访问;
  • 长连接和连接重建;
  • Pod 扩缩容期间的流量切换;
  • DNS 与 NodeLocal DNSCache;
  • 服务端滚动发布时的连接中断情况。

对于使用 HikariCP、Redis Lettuce、Kafka Client、gRPC 或其他长连接客户端的 Java 服务,网络模式切换不一定会直接造成故障,但连接重置、连接重建时间和异常重试行为都应该重新压测。

3. 不要把网络迁移和版本升级塞进同一个窗口

更稳妥的方式是把两个动作拆开:

flowchart LR
    A["保持当前 Kubernetes 版本"] --> B["建立 nftables / iptables 测试节点池"]
    B --> C["迁移少量无状态 Java 服务"]
    C --> D["验证 Service、NodePort、长连接"]
    D --> E["逐步扩大节点范围"]
    E --> F["网络模式稳定"]
    F --> G["再进行 Kubernetes 版本升级"]

如果集群已经在 IPVS 上稳定运行多年,最危险的做法不是继续使用几个月,而是为了追赶版本,在一次变更中同时修改 Kubernetes、内核、容器运行时和网络代理模式。

基础设施变更应尽量保持单一变量。

三、cgroup v1 不再是可以继续拖延的技术债

cgroup 决定了 Linux 如何限制和统计容器的 CPU、内存及其他资源。

Kubernetes 已经稳定支持 cgroup v2。与 cgroup v1 相比,cgroup v2 使用统一层级,并提供更完整的资源统计、隔离和委派能力。Memory QoS 等新的资源管理能力也依赖 cgroup v2。官方建议优先使用默认启用 cgroup v2 的 Linux 发行版。(Kubernetes)

从 Kubernetes 1.35 开始,cgroup v1 已被标记为弃用,failCgroupV1 默认值为 true。这意味着 kubelet 默认不会在 cgroup v1 节点上启动。

虽然仍然可以临时设置:

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
failCgroupV1: false

但这只是过渡手段,不应该被当成长期解决方案。Kubernetes 1.37 仍保留该临时开关,但官方已经明确建议迁移到 cgroup v2。(Kubernetes)

1. 检查节点使用的 cgroup 版本

登录每台节点后执行:

stat -fc %T /sys/fs/cgroup/

如果输出:

cgroup2fs

说明节点使用 cgroup v2。

如果输出:

tmpfs

通常说明节点仍然使用 cgroup v1。该识别方法来自 Kubernetes 官方 cgroup 文档。(Kubernetes)

2. 对 Java 应用真正的影响是什么

JVM 会根据容器暴露的 cgroup 信息判断:

  • 容器可用 CPU;
  • 容器内存限制;
  • JVM 默认堆大小;
  • GC 线程数量;
  • ForkJoinPool 并行度;
  • 部分运行时和诊断指标。

如果 JDK 版本过旧,可能无法完整识别 cgroup v2 的资源边界,进而导致 JVM 对可用内存或 CPU 作出错误判断。

Kubernetes 官方当前建议,在 cgroup v2 环境中至少使用以下 OpenJDK 版本:

JDK 分支 建议最低版本
JDK 8 8u372
JDK 11 11.0.16
JDK 15 及以上 已具备完整支持

(Kubernetes)

这并不意味着 JVM 一定会把所有 Pod 内存都分配给 Java 堆。

一个 Java 容器的内存通常包括:

flowchart TB
    A["Pod Memory Limit"] --> B["Java Heap"]
    A --> C["Metaspace"]
    A --> D["线程栈"]
    A --> E["Direct Memory"]
    A --> F["Code Cache"]
    A --> G["JNI / Native Library"]
    A --> H["容器内其他进程"]

假设 Pod 的内存限制为 1 GiB,却直接配置:

-Xmx1g

那么堆外内存、线程栈和元空间将没有足够空间,最终仍可能被 Kubernetes 以 OOMKilled 终止。

3. 在容器里确认 JVM 看到了什么

对于支持该参数的现代 JDK,可以执行:

java -XshowSettings:system -version

它会输出 JVM 识别到的容器资源和系统信息,可用于对比迁移 cgroup v2 前后的 CPU、内存视图。OpenJDK 增加该参数的目的,就是展示系统或容器配置;Oracle 的 Kubernetes 升级指南也建议用它检查 JVM 识别到的资源边界。(Oracle 文档)

在 Kubernetes 中可以直接检查某个 Java Pod:

kubectl exec -it deployment/order-service -- \
  java -XshowSettings:system -version

迁移 cgroup v2 时,不要只验证 Pod 能否启动,还应对比:

  • JVM 识别到的 CPU 数量;
  • 默认最大堆大小;
  • Pod 实际内存;
  • GC 行为;
  • OOMKilled 数量;
  • Pod 重启次数;
  • 节点 MemoryPressure
  • HPA 扩缩容行为。

四、Static Pod 引用 Secret 和 ConfigMap 将被严格禁止

Static Pod 不是通过 API Server 创建的,而是由某个节点上的 kubelet直接读取本地清单并启动。

使用 kubeadm 部署的控制平面组件,例如 kube-apiserver、kube-scheduler 和 kube-controller-manager,通常就以 Static Pod 形式运行。

由于 Static Pod 并不是由 API Server 管理,它本来就不应该依赖 ServiceAccount、ConfigMap 或 Secret 等 API 对象。官方文档一直将这些能力列为 Static Pod 的限制。(Kubernetes)

此前的实现漏洞导致部分引用方式仍可能被接受。Kubernetes 1.37 修复这一问题后,Static Pod 中的 Secret、ConfigMap 引用将被严格拒绝,原来用于临时关闭限制的特性开关也会被移除。(Kubernetes)

1. 检查控制平面清单

在控制平面节点上执行:

sudo grep -RInE \
  'serviceAccountName|configMapRef|configMapKeyRef|secretRef|secretKeyRef|configMap:|secret:' \
  /etc/kubernetes/manifests

如果自建的节点组件或 Java Agent 以 Static Pod 运行,并依赖 Secret、ConfigMap,升级后可能无法启动。

2. 应该如何调整

可以根据场景选择:

  • 将配置文件提前写入节点,再通过 hostPath 挂载;
  • 使用节点环境变量或启动参数;
  • 将工作负载迁移为 DaemonSet、Deployment;
  • 通过外部配置中心获取配置;
  • 通过受控的 Secret 管理系统在启动前写入节点文件。

不建议为了绕过限制,直接把数据库密码、Token 或证书明文写进 Static Pod YAML。

如果一个工作负载需要动态配置、权限身份和 Secret,本质上它通常就不适合继续作为 Static Pod。

五、SELinuxMount 默认行为变化,可能让共享卷上的 Pod 卡住

对于启用了 SELinux 的节点,Kubernetes 过去通常会递归修改卷内文件的 SELinux 标签。

这种方式兼容性较好,但当卷中包含大量文件时,递归修改会拖慢挂载和 Pod 启动。

新的 SELinuxMount 路径倾向于通过挂载参数直接设置上下文,避免递归遍历整个卷。Kubernetes 1.37 预计会将该能力推进到 GA,并默认启用,但前提是对应 CSI Driver 声明支持。(Kubernetes)

问题在于:

同一个挂载点只能使用一个 SELinux 上下文。

如果两个使用不同 SELinux 标签的 Pod 被调度到同一个节点,并共享同一个卷,其中一个 Pod 可能持续停留在 ContainerCreating 状态。

官方文档给出的典型错误类似:

conflicting SELinux labels of volume

对于确实需要兼容旧行为的工作负载,可以在 Pod 中设置:

apiVersion: v1
kind: Pod
metadata:
  name: example
spec:
  securityContext:
    seLinuxChangePolicy: Recursive
  containers:
    - name: app
      image: example/app:1.0

Recursive 会让该工作负载继续使用递归修改标签的方式。官方同时建议,使用 SELinux 的集群在切换前启用 SELinuxWarningController,提前发现共享卷上的标签冲突。(Kubernetes)

哪些 Java 工作负载更容易受到影响

需要重点排查:

  • Java 主服务与独立备份 Pod 共享 PVC;
  • 日志采集 Pod 与业务 Pod 共享目录;
  • 特权运维 Pod 临时挂载业务卷;
  • StatefulSet 与数据迁移 Job 共用卷;
  • 不同安全域的 Pod 共用同一个存储;
  • 同一卷同时被普通容器和特权容器访问。

没有启用 SELinux 的集群不受这一变化影响。(Kubernetes)

因此不要为了“兼容升级”,给所有 Pod 批量添加 Recursive。正确做法是先识别真正存在标签冲突的共享卷,再针对性处理。

六、Metrics API 终于准备结束长期 Beta

Kubernetes 的 metrics.k8s.io API 为节点和 Pod 提供基础 CPU、内存指标。

它是以下能力的重要数据来源:

  • kubectl top
  • Horizontal Pod Autoscaler;
  • Vertical Pod Autoscaler;
  • 部分资源推荐和自动扩缩容工具。

Metrics API 只提供自动扩缩容所需的最小资源指标,并不能替代 Prometheus、OpenTelemetry、Micrometer 或 JVM 监控体系。(Kubernetes)

在经过接近九年的 Beta 阶段后,Metrics API 预计在 Kubernetes 1.37 进入稳定版。迁移期间,v1v1beta1 预计会同时可用。(Kubernetes)

1. 检查集群提供了哪些版本

kubectl api-versions | grep metrics.k8s.io

也可以查看 API 发现信息:

kubectl get --raw /apis/metrics.k8s.io

如果某个内部工具直接把地址写死为:

/apis/metrics.k8s.io/v1beta1

应当检查它是否支持 API Discovery,或者是否能够兼容新的 v1

可以在代码仓库中搜索:

grep -RIn "metrics.k8s.io/v1beta1" .

2. 不要因为 Metrics API 稳定,就把它当成 JVM 监控

Metrics API 看到的内存主要是容器工作集,它无法直接告诉你:

  • Java 堆使用量;
  • 老年代占用;
  • GC 暂停;
  • Metaspace;
  • Direct Memory;
  • 活跃线程数;
  • 数据库连接池;
  • HTTP 请求耗时;
  • 线程池队列长度。

更合理的分层是:

flowchart LR
    A["Metrics API"] --> A1["Pod CPU / Memory"]
    A --> A2["HPA / VPA"]

    B["Micrometer / OpenTelemetry"] --> B1["请求耗时"]
    B --> B2["线程池"]
    B --> B3["连接池"]

    C["JFR / JVM Metrics"] --> C1["Heap"]
    C --> C2["GC"]
    C --> C3["线程与锁"]

Metrics API 的 GA 解决的是资源指标接口稳定性,而不是应用可观测性的全部问题。

七、Rootless kubelet 进入 Beta,节点组件也开始减少 root 权限

传统 Kubernetes 节点上的 kubelet、容器运行时和部分网络组件通常拥有较高的宿主机权限。

一旦这些节点组件存在漏洞,攻击者可能获得较大的宿主机影响范围。

Kubernetes 1.37 预计把 Kubelet in User Namespace,也就是 Rootless Mode,推进到 Beta。其核心思想是:

  • kubelet 在宿主机上以普通用户运行;
  • 在 Linux User Namespace 内表现为 root;
  • 通过用户映射限制宿主机权限;
  • 降低节点组件漏洞的潜在影响范围。(Kubernetes)

这里需要区分三个不同层次:

能力 作用层次
runAsNonRoot 限制业务容器内的运行用户
hostUsers: false 为单个 Pod 启用用户命名空间
Rootless kubelet 降低节点组件在宿主机上的权限

Pod User Namespace 可以通过 hostUsers: false 让容器内 UID 与宿主机 UID 进行隔离映射,容器中的能力也只在对应 User Namespace 内有效。(Kubernetes)

但 Rootless kubelet 并不意味着 Java 应用可以放松自身的安全配置。

一个基本的 Java Deployment 仍然应该考虑:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      securityContext:
        runAsNonRoot: true
      containers:
        - name: app
          image: example/order-service:1.0
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop:
                - ALL
            seccompProfile:
              type: RuntimeDefault

节点组件降权、Pod 用户命名空间和业务容器安全上下文,是三层不同的纵深防御,不能互相替代。

八、存储健康开始从“猜故障”走向机器可读

在传统 Kubernetes 存储故障中,CSI 或底层磁盘出现问题后,应用侧通常只会看到:

  • 文件读写突然变慢;
  • 线程阻塞;
  • 请求超时;
  • Pod 挂载失败;
  • 数据库或消息组件报 I/O 错误。

平台人员需要同时查看 Pod 事件、PVC、节点日志和存储厂商控制台,才能判断问题究竟来自应用还是存储。

Kubernetes 1.37 的 Volume Health Monitor 重新以 Alpha 形态推进,计划允许 CSI Driver 报告机器可读的存储状态,例如 InaccessibleDegraded,并分别反映到 PVC、Pod 和 CSINode 的状态中。(Kubernetes)

预览设计包括:

PersistentVolumeClaim.status.healthStatus
Pod.status.volumeHealth
CSINode.status.storageHealth

对于 Java 状态服务,这能够建立更完整的排障链路:

flowchart LR
    A["接口响应变慢"] --> B["检查 JVM 与线程池"]
    B --> C["检查 Pod Event"]
    C --> D["检查 PVC Health"]
    D --> E["检查 Node / CSI Health"]
    E --> F["区分应用、节点与存储故障"]

但它目前仍属于 Alpha,不适合直接成为自动故障转移的唯一判断依据。

更合理的做法是先将它作为辅助信号,与以下信息进行关联:

  • Java 请求延迟;
  • 线程阻塞数量;
  • 文件系统 I/O;
  • Pod Event;
  • PVC 状态;
  • CSI 日志;
  • 节点磁盘压力;
  • 存储平台告警。

九、升级前可以直接执行的审计清单

1. 集群级检查

# 查看客户端与服务端版本
kubectl version

# 查看 kube-proxy 模式
kubectl -n kube-system get configmap kube-proxy \
  -o jsonpath='{.data.config\.conf}' \
  | grep 'mode:'

# 查看 Metrics API 版本
kubectl api-versions | grep metrics.k8s.io

# 查看 Metrics API 发现信息
kubectl get --raw /apis/metrics.k8s.io

# 查看节点操作系统、内核和容器运行时
kubectl get nodes \
  -o custom-columns='NAME:.metadata.name,OS:.status.nodeInfo.osImage,KERNEL:.status.nodeInfo.kernelVersion,RUNTIME:.status.nodeInfo.containerRuntimeVersion'

2. 节点级检查

# 检查 cgroup 版本
stat -fc %T /sys/fs/cgroup/

# 检查 Static Pod 是否引用 API 对象
sudo grep -RInE \
  'serviceAccountName|configMapRef|configMapKeyRef|secretRef|secretKeyRef|configMap:|secret:' \
  /etc/kubernetes/manifests

# 检查是否启用 SELinux
getenforce 2>/dev/null || true

3. Java 工作负载检查

# 查看 JVM 识别到的系统与容器资源
kubectl exec -it deployment/order-service -- \
  java -XshowSettings:system -version

# 查看 Pod 是否发生 OOMKilled
kubectl get pods -A \
  -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{range .status.containerStatuses[*]}{.lastState.terminated.reason}{" "}{end}{"\n"}{end}' \
  | grep OOMKilled

# 查看资源配置
kubectl get deployment order-service -o yaml \
  | grep -A 12 'resources:'

4. 代码仓库检查

# 检查是否写死旧 Metrics API
grep -RIn "metrics.k8s.io/v1beta1" .

# 检查是否存在旧 Kubernetes API 版本
grep -RInE \
  'extensions/v1beta1|apps/v1beta1|networking.k8s.io/v1beta1' \
  .

十、推荐的升级顺序

Kubernetes 官方不支持直接跳过多个次版本进行 kubeadm 升级,因此版本跨度较大的集群应逐个次版本推进。(Kubernetes)

完整流程可以设计为:

flowchart LR
    A["资产盘点"] --> B["开发集群升级"]
    B --> C["兼容性与压力测试"]
    C --> D["生产金丝雀节点池"]
    D --> E["迁移少量无状态服务"]
    E --> F["升级控制平面"]
    F --> G["分批升级工作节点"]
    G --> H["观察业务与基础设施指标"]
    H --> I["扩大范围或回滚"]

升级期间尤其不要同时完成以下所有动作:

  • Kubernetes 版本升级;
  • 操作系统大版本升级;
  • cgroup v1 切换到 v2;
  • containerd 或 CRI-O 大版本升级;
  • kube-proxy 从 IPVS 切换到 nftables;
  • JDK 大版本升级;
  • JVM 参数重构;
  • CNI 插件替换;
  • CSI Driver 替换。

同时修改的变量越多,出现问题后越难定位根因。

更合理的策略是:

先让节点、容器运行时和 JDK 具备新基线的兼容能力,再升级 Kubernetes;网络模式、存储驱动等高风险组件单独迁移。

结语

Kubernetes 1.37 看起来不像一次充满“炫酷功能”的版本升级。

但它正在推动几项非常关键的基础设施变化:

  • IPVS 开始明确退场;
  • cgroup v1 继续走向移除;
  • Static Pod 的边界更加严格;
  • SELinux 卷挂载行为开始改变;
  • Metrics API 走向稳定;
  • kubelet 开始探索更彻底的非 root 运行;
  • 存储健康逐步变成机器可读状态。

对于 Java 开发团队来说,真正需要升级的并不只是 Kubernetes 版本号,而是整个运行基线:

flowchart LR
    A["旧运行基线"] --> B["现代 Linux 内核"]
    B --> C["cgroup v2"]
    C --> D["受支持的容器运行时"]
    D --> E["现代 JDK"]
    E --> F["稳定的网络代理"]
    F --> G["分层可观测性"]
    G --> H["更小权限的安全边界"]

Spring Boot 应用可能一行代码都不用改。

但只有当网络、资源、安全、存储和监控都完成验证时,这个“一行代码不用改”的升级,才真正称得上平滑。

0 条评论
如果你觉得文章对你有帮助,那就请作者喝杯咖啡吧☕
微信
支付宝
  0 条评论