WEBKT

告别虚高的 Load Average:在传统虚拟机集群中玩转 PSI 压力预警与轻量级调度

7 0 0 0

在云原生时代,大家都在谈论 Kubernetes 的资源隔离和自动扩缩容,但实际上,仍有大量公司的业务跑在传统的虚拟机(VM)或物理机集群上。

在这种环境下,很多运维同学会遇到一个经典痛点:Load Average 飘高,但系统响应似乎还行;或者 Load Average 不高,但某个关键 I/O 操作已经卡死了。 这是因为传统的负载均值混淆了 CPU、I/O 甚至不可中断进程。

其实,只要你的内核版本在 4.20+,完全可以抛开 K8s,利用 Linux 内核自带的 PSI(Pressure Stall Information) 实现极其精准的资源预警和动态调度。

1. 为什么在虚拟机里也要用 PSI?

PSI 将压力分为两种状态:

  • some:表示部分任务因为该资源(如内存)被阻塞,但 CPU 还能干点别的。
  • full:表示所有非空闲任务都因为该资源被卡住了,这是真正的系统瓶颈。

在虚拟机环境下,PSI 的价值在于:

  1. 精准区分瓶颈:一眼看出是磁盘 I/O 慢了,还是内存页回收太频繁。
  2. 提前介入自愈:在 OOM 发生前,根据 memory full 指标主动杀掉低优先级进程或清理缓存。
  3. 轻量级调度策略:如果本机压力过大,脚本自动修改 Nginx 权重或停止 Cron Job。

2. 核心原理:解析 /proc/pressure/

在 Linux 中,PSI 信息直接通过文件暴露:

  • /proc/pressure/cpu
  • /proc/pressure/io
  • /proc/pressure/memory

文件内容示例:

some avg10=0.10 avg60=0.05 avg300=0.01 total=123456
full avg10=0.02 avg60=0.01 avg300=0.00 total=12345

我们关注的是 avg10(过去 10 秒的停滞百分比)。如果 memory full avg10 超过了 10,说明系统已经有 10% 的时间完全卡在内存申请上了。

3. 轻量级调度与预警脚本示例

下面是一个基于 Python 的轻量级守护脚本示例。它的逻辑非常简单:监控 PSI 指标,一旦超过阈值,执行自定义的“降级”操作(如停止本地日志压缩任务或发送告警)。

import time
import os

# 配置阈值:10秒内的平均停滞百分比
THRESHOLD = {
    'cpu_some': 50.0,    # CPU 压力超过 50%
    'mem_full': 5.0,     # 内存完全阻塞超过 5% (高危)
    'io_full': 20.0      # I/O 完全阻塞超过 20%
}

def get_psi_metric(resource, type='some'):
    """解析 /proc/pressure 文件"""
    path = f"/proc/pressure/{resource}"
    if not os.path.exists(path):
        return 0.0
    
    with open(path, 'r') as f:
        for line in f:
            if line.startswith(type):
                # 提取 avg10 的值
                parts = line.split()
                avg10 = parts[1].split('=')[1]
                return float(avg10)
    return 0.0

def handle_overload(reason):
    """
    触发过载处理逻辑
    这里可以根据实际需求改为:
    1. 修改 Nginx upstream 状态
    2. 杀掉吃内存的临时进程
    3. 发送钉钉/企业微信告警
    """
    print(f"[ALERT] 系统资源过载: {reason} | 执行降级策略...")
    # 示例:如果是生产环境,可以执行 os.system("systemctl stop some-low-priority-job")

def main():
    print("PSI Monitor Started. Target Kernel: 4.20+")
    while True:
        # 检测内存压力
        mem_stall = get_psi_metric('memory', 'full')
        if mem_stall > THRESHOLD['mem_full']:
            handle_overload(f"Memory Stall Full ({mem_stall}%)")

        # 检测 CPU 压力
        cpu_stall = get_psi_metric('cpu', 'some')
        if cpu_stall > THRESHOLD['cpu_some']:
            handle_overload(f"CPU Stall Some ({cpu_stall}%)")

        # 每 5 秒轮询一次
        time.sleep(5)

if __name__ == "__main__":
    main()

4. 进阶玩法:结合 Systemd 资源控制

如果你想在虚拟机上实现类似 K8s 的效果,可以将上述脚本与 cgroup v2 结合。

  1. 设置 cgroup 监控:在 /sys/fs/cgroup/ 下,每个子系统也有自己的 cpu.pressure。你可以监控特定业务进程所属的 cgroup。
  2. 配合触发器(Inotify):Linux PSI 实际上支持 poll() 系统调用。你可以通过监听文件描述符,在压力超过阈值的毫秒级内获得内核通知,而不是像上面脚本那样循环轮询。这正是 K8s oom-kill 逻辑的底层实现原理。

5. 总结

即便没有 K8s,PSI 也是现代 Linux 运维的“神兵利器”。

  • 如果你有 Prometheus:可以直接使用 node_exporter,它默认已经采集了 PSI 指标,你只需要在 Grafana 上配置 node_pressure_cpu_waiting_percent 等告警规则即可。
  • 如果你需要本地自愈:参考上面的 Python 逻辑,写一个 100 行以内的守护进程,就能比传统的 top/uptime 监控快得多、准得多。

避坑指南:如果你的 /proc/pressure 目录下是空的,请检查内核版本,并确保启动参数中没有禁用 PSI(部分发行版需要检查 CONFIG_PSI=y)。

码农老王 Linux内核性能优化运维自动化

评论点评