linux内核参数再调优

Tip

本文是 vps-kernel-sysctl 的后续审计报告。原始 blog 阐述了如何从 shell 参考脚本移植一套动态 sysctl 生成框架;本文记录对该框架的一次全面上机验证与修正

之所以有这次 Re-Tuning,因为前两天看到 6神 on X: "买完VPS,搭建节点后,为什么一定要进行TCP调优才行?" / X 这个tweet,想着参考一下他的shell,做个处理。最终结果是,他的shell能提供的借鉴并不多,但是让 AI去做 Re-Tuning,也确实查到很多之前配置有问题的配置项。在此做个记录。


1. 方法论:验证方法

判断准不准,不能靠猜,上机跑一遍最实在。这次用了好几种手段来交叉验证——直接 SSH 上 VPS 逐 key 读写、对比部署态与生效态、翻原始设计文档追溯决策意图、以及对自己的预判做反向验证。下面逐个说。

1.1 SSH 逐 key 读测试

lib/vps-sysctl.nix 中每个有疑问的 sysctl key,登录目标 VPS 执行:

sysctl -n <key>
echo "exit: $?"

exit 0 表示 key 存在且可读,exit 1(No such file or directory)表示 key 不存在。

1.2 对比部署态与实际生效值

读取 NixOS 生成的 /etc/sysctl.d/60-nixos.conf 确认实际写入了什么;再读 /proc/sys/ 对应文件确认 NixOS activation script 是否在 sysctl.d 之上又做了覆盖(例如 networking.natip_forward 覆盖)。

# 部署态配置
cat /etc/sysctl.d/60-nixos.conf
# 实际生效值
cat /proc/sys/net/ipv4/ip_forward

1.3 溯源设计文档

重读原始 blog 的 Changelog,理解每个参数的移植意图:

  • "全量补齐脚本参数" → 哪些来自 shell 脚本的平移
  • "动态公式覆盖" → 哪些做了定制化
  • "风险点与兼容性处理" → 哪些是已知风险

1.4 内部交叉验证

对存疑的判断方向做反向验证。例如:

  • 认为 vm.page-cluster(连字符)是 typo → 不仅读,还试了另一种形式 vm.page_cluster(下划线)
  • 认为调度参数太激进 → 检查它们是否真的存在于 sysctl -a 输出中
  • 认为 ip_forward=0 会覆盖 NAT → 对比实际 /proc/sys/

2. 判断证实与证伪

上机之前,我心里有一些预判。等实际跑完一遍 sysctl 逐 key 读之后,才发现有些判对了,有些判错了——而最有意思的恰恰是那些错判,因为它们暴露了我对内核行为的理解盲区。把正误都列出来,以后回头看就知道当时踩过哪些坑。

2.1 证伪(判断错了)

预判 原理由 实测真相
vm.page-cluster 连字符是 typo 标准形式是下划线 page_cluster 连字符正常工作sysctl CLI 自动规整化 -_,读写都无问题。下划线形式反而报 No such file
sched_*_granularity_ns 等值太激进 10μs 调度粒度远超通用服务器默认值 (~3ms) 这些 key 在 kernel 6.18 (EEVDF) 上根本不存在。CFS 已被 EEVDF 替换,这些旧控制参数被移除。写入 sysctl.d 后一直被 silent ignore
ip_forward=0 会盖过 NAT baseline 裸值优先级高于 NAT 模块的 mkDefault 实际 ip_forward=1。NixOS 的 NAT activation script 在 sysctl.d 应用之后直接写 /proc/sys/,覆盖了文件中的 0
transparent_hugepage.* 会丢 THP 配置 sysctl.d 中删了就没地方设 THP=madvise 了 THP 已在 [madvise] 状态,由其他 NixOS 机制保障(kernel boot params / systemd tmpfiles),sysctl 条目从始至终没有生效过

2.2 证实(判断对了)

预判 实测
busy_poll/busy_read 当前值 = 50 且活跃生效 ✅ 确认值 50,sysctl -n 返回正常
vm.nr_hugepages = 156 永久锁定 ~312MB ✅ 确认 156 hugepages(×2MB = 312MB)
vm.panic_on_oom = 1 OOM 触发整机重启 ✅ 确认值 1
kernel.tsc_reliable 不存在 sysctl -n 报 No such file
net.ipv4.tcp_init_cwnd 不存在 sysctl -n 报 No such file
vm.transparent_hugepage.enabled/defrag 不合法 ✅ 均报 No such file(在 sysfs 不在 procfs)
rttMs = 1 低估 BDP lib/inventory/data.nix 确认写死 1ms
Thin / overlay IP 包在 rp_filter=1 下会被丢 conf.all.rp_filter = 1,Tailscale/flannel 使用非对称路由

3. 最终修改与预期效果

验证完就该动手改了。下面按类型分五组,每组说清楚:原来写了什么、改成了什么、为什么改、改完预期会有什么变化。这样以后回溯时能一眼对上账。

3.1 删除不存在的 sysctl key(9 个)

来源:kernel 6.18 (EEVDF) 已移除的 CFS 参数、不合法路径、已废弃的全局 sysctl。

删除项 原值 原所在行 原因
kernel.sched_child_runs_first 0 lib/vps-sysctl.nix EEVDF 移除
kernel.sched_min_granularity_ns 10000 同上 同上
kernel.sched_wakeup_granularity_ns 15000 同上 同上
kernel.sched_latency_ns 60000 同上 同上
kernel.sched_migration_cost_ns 50000 同上 同上
kernel.tsc_reliable 1 同上 需特殊 kernel config
vm.transparent_hugepage.enabled "madvise" 同上 sysfs 路径,非 sysctl
vm.transparent_hugepage.defrag "never" 同上 同上
net.ipv4.tcp_init_cwnd 动态公式 同上 kernel 5.10+ 已移除全局 sysctl
tcpInitCwnd 变量 let 块 随删除项一同移除
nrHugepages 变量 动态公式 let 块 改为硬编码 0

预期效果:无行为变化(这些 key 本来就被 systemd-sysctl 静默忽略)。/etc/sysctl.d/60-nixos.conf 干净 ~10 行。

3.2 修正 Proxy 场景有害值(4 个)

参数 旧值 新值 原因 预期效果
net.core.busy_poll 50 0 参考脚本面向 HFT/通用网络场景。Proxy 高并发 epoll 下忙轮询白耗 CPU CPU idle 占用降低,连接数越高越明显
net.core.busy_read 50 0 同上 同上
vm.nr_hugepages 156(312MB) 0 Proxy 不需要 hugepage,永久锁 312MB 物理 RAM 是浪费 回收 5% 物理内存(6GB VPS),可被 page cache、socket buffer、conntrack 表复用
vm.panic_on_oom 1(panic→reboot) 0(OOM killer) Proxy 是持续在线服务。OOM 杀掉肇事进程即可,整机重启中断所有连接更坏 OOM 时服务继续运行,仅失掉一个进程而非全部连接

3.3 架构参数调整(3 个)

参数 旧值 新值 原因 预期效果
net.ipv4.conf.all.rp_filter 1(strict) 2(loose) Tailscale + flannel overlay 使用非对称路由。strict 模式下此类包被误判为"源地址欺骗"而丢弃 多网卡 overlay 场景网络连通性保障
net.ipv4.conf.default.rp_filter 1(strict) 2(loose) 同上 同上
net.ipv4.ip_forward + conf.*.forwarding(共 3 行) 0 移出 baseline 交由 NAT、k3s 等功能模块自行管理。NixOS 组合式架构下 baseline 不应硬设 消除模块优先级冲突,下次加新模块不必再面对覆盖问题

3.4 策略参数修正(1 个)

参数 旧值 新值 原因 预期效果
vm.overcommit_memory 0(启发式) 1(始终 overcommit) 启发式 overcommit 在 Nix 大构建 fork 大量子进程时保守拒绝,导致 "Cannot allocate memory"。改为 1 后 fork 永远成功 Nix 构建稳定性提升。物理内存真正耗尽时由 OOM killer 处理(配合 panic_on_oom=0,仅杀进程不重启)

同步移除 hosts/nixos-vps/default.nix 中原先的 mkForce 0 覆盖(该覆盖是应对 mode 2→mode 0 的 workaround,lib 改 1 后不再需要)。

3.5 数据输入修正(1 个)

文件 参数 旧值 新值 原因 预期效果
lib/inventory/data.nix nixos-vps-dev rttMs 1 150 原值来自 Speedtest.net 到最近节点的 RTT(0.867ms)。BDP 计算应用于 proxy 场景时应使用用户路径真实 RTT(LA→China ~150ms) 单 TCP 连接跨境吞吐从瓶颈 ~223Mbps 恢复到线速能力(~800Mbps)。4MB socket buffer → ~30MB 上限

附录

A. 根源分析:Shell 移植到 NixOS 的思维差异

这次审计暴露的核心问题不是 "参数值选错了",而是移植方法论的错位

Shell 脚本方式 NixOS 模块方式
配置写入 一次性写入 /etc/sysctl.d/ 多模块叠加到同一份 boot.kernel.sysctl
优先级 最后一个文件写 = 最终值 声明式优先级(裸值 > mkDefault > mkForce)
参数来源 参考脚本的全量列表,直接使用 应只声明本模块关心的项,其他交给其他模块
kernel 兼容 仅面向当前机器 相同配置可能在多个 kernel 版本间共享

此次出问题的三类情况分别对应:

  • 不存在的 key:shell 脚本收集于特定 kernel 版本,平移到新版本后发现 key 不存在
  • 不匹配的值:shell 脚本的调优目标(HFT/DB/通用网络)≠ proxy 场景的调优目标
  • 架构冲突:shell 脚本假设自己是唯一配置来源,NixOS 中模块间需要协作

修正方向:lib/vps-sysctl.nix 保持计算框架不变,但收缩 baseline 边界——只承诺与该库直接相关的参数,将网络功能开关类参数(ip_forward、forwarding)交还给各功能模块。

B. 保留待审项

以下参数有争议但本次未修改,标注出来供日后回溯:

参数 当前值 来源 潜在风险
kernel.sched_rt_runtime_us 980000(98%) 原始脚本全量补齐 RT 线程可占用 98% CPU,若某个 RT 线程死循环,系统接近锁死。默认值 950000
kernel.sched_cfs_bandwidth_slice_us 3000 原始脚本全量补齐 EEVDF 下该参数的语义可能与 CFS 不同,未验证
vm.overcommit_ratio 50 原始脚本 overcommit=1 时此参数无效。若将来切回 mode 2 需重新审视
vm.dirty_ratio / vm.dirty_background_ratio 15 / 5 SSD 策略默认值 对大内存 VPS(>16GB)百分比值意味着 GB 级脏页,可能引发延迟尖峰

C. 参考命令集

验证过程中使用的命令,便于复现:

# 确认某个 sysctl key 是否存在
sysctl -n vm.page-cluster
echo "exit: $?"        # 0=存在,1=不存在

# 列出所有可用的调度参数
sysctl -a | grep "^kernel.sched"

# 查看当前 kernel 版本
uname -r

# 查看 NixOS 生成的 sysctl 配置
cat /etc/sysctl.d/60-nixos.conf

# 查看实际生效值(有时 ≠ sysctl.d 中的值)
cat /proc/sys/net/ipv4/ip_forward

# 检查 sysfs 路径(非 procfs/sysctl)的参数
cat /sys/kernel/mm/transparent_hugepage/enabled

# 内核 config 是否编译了某选项
zgrep CONFIG_X86_TSC /boot/config-$(uname -r) 2>/dev/null

# systemd-sysctl 服务状态与日志
systemctl status systemd-sysctl
journalctl -u systemd-sysctl --no-pager | tail -20