文中所有命令、配置与流程均经过作者真实环境验证,但因服务器环境、软件版本、网络条件等差异,读者在复现时仍可能遇到本文未覆盖的问题。请结合自身实际情况谨慎操作,并自行承担相关风险。
本文记录在一台 1C 0.5G 配置的 KVM VPS 上配置 Swap 的完整过程,包含 btrfs 文件系统下的踩坑解决方案,以及针对代理/端口转发场景的内核参数优化建议。
背景
最近新开了一台 1 核 512MB 内存的 KVM 小鸡,主要用途是做代理和端口转发。这种低内存机器为了防止 OOM,配置 Swap 是必要操作。本以为是五分钟搞定的事,结果在 btrfs 文件系统上翻车了,记录一下完整流程。
一、Swap 大小怎么选
常见的经验法则是 swap = RAM × 2,但更实用的是按用途来定:
| 用途 | 推荐 Swap 大小 |
|---|---|
| 轻量 web / 代理 / bot | 512MB ~ 1GB |
| Node.js / Python 应用 | 1GB |
| 偶尔编译代码 | 1 ~ 2GB |
| 数据库(MySQL/Postgres) | 1 ~ 2GB |
0.5G 内存机器推荐 1GB Swap,够用且不会太占磁盘空间。
二、标准配置流程
通用步骤
# 1. 创建 1GB 的 swap 文件
sudo fallocate -l 1G /swapfile
# 2. 设置权限(必须只能被 root 读写)
sudo chmod 600 /swapfile
# 3. 格式化为 swap
sudo mkswap /swapfile
# 4. 立即启用
sudo swapon /swapfile
# 5. 添加到 /etc/fstab 开机自动挂载(先备份原文件)
sudo cp /etc/fstab /etc/fstab.bak
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# 6. 验证是否生效
sudo swapon --show
free -h
正常情况下,执行到 swapon 这一步就能看到 Swap 生效。但这次翻车了。
三、踩坑:swapon failed: Invalid argument
问题现象
root@server:~# sudo swapon /swapfile
swapon: /swapfile: swapon failed: Invalid argument
mkswap 能正常执行,但 swapon 直接拒绝。
排查步骤
第一步:确认虚拟化类型
systemd-detect-virt
# 输出:kvm
KVM 是完整虚拟化,支持 swap,排除 OpenVZ/LXC 容器限制的可能。
第二步:换用 dd 创建文件
有时候 fallocate 在某些文件系统上会创建稀疏文件,导致 mkswap 能过但 swapon 拒绝。但这次换成 dd 依然报错。
第三步:查看内核日志
df -T /
# Filesystem Type ...
# /dev/vda3 btrfs ...
sudo dmesg | tail -20
# [141925.195702] BTRFS warning (device vda3): swapfile must not be copy-on-write
真相大白:根分区是 btrfs,而 btrfs 的 swapfile 不能带 copy-on-write (CoW) 属性。
为什么 btrfs 有这个限制
btrfs 默认所有文件都启用 CoW,即修改数据时会写到新的块位置,原数据块不变。但 swapfile 需要固定的物理块位置,内核要能稳定地往这些块里读写页数据。CoW 会让块地址动态变化,swap 机制就无法工作,所以内核直接拒绝。
正确做法
必须在文件为空时用 chattr +C 禁用 CoW,然后再写入数据。
# 1. 清理之前失败的文件
sudo swapoff /swapfile 2>/dev/null
sudo rm -f /swapfile
# 2. 创建空文件(注意必须是 touch,不能用 dd/fallocate)
sudo touch /swapfile
# 3. 关键步骤:禁用 CoW(必须在文件为空时执行)
sudo chattr +C /swapfile
# 4. 现在才能写入数据
sudo dd if=/dev/zero of=/swapfile bs=1M count=1024 status=progress
# 5. 后续常规步骤
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# 6. 验证
free -h
swapon --show
执行成功后的输出:
total used free shared buff/cache available
Mem: 457Mi 116Mi 22Mi 6.4Mi 336Mi 340Mi
Swap: 1.0Gi 0B 1.0Gi
NAME TYPE SIZE USED PRIO
/swapfile file 1024M 0B -2
小贴士:如何判断自己的根分区是不是 btrfs?执行
df -T /看 Type 列即可。常见的ext4和xfs不需要这步操作。
四、swappiness 调优
什么是 swappiness
vm.swappiness 控制内核将内存页换到 swap 的倾向。
- 值越大:越倾向于把内存页换到 swap
- 值越小:越倾向于保留在 RAM,优先回收文件 cache
Linux 默认值是 60,适合通用服务器,但不适合低内存 VPS。
推荐值
| 场景 | 推荐值 | 原因 |
|---|---|---|
| 桌面 / 通用服务器 | 60 | 默认值,通用平衡 |
| 低内存 VPS(≤1G) | 10 ~ 20 | swap 在磁盘,频繁换页拖慢响应 |
| 代理 / 端口转发 | 10 | 对延迟敏感,swap 只做保险 |
| 数据库服务器 | 1 ~ 10 | 避免热数据被换出 |
关于 swappiness 和 OOM 的常见误解
误解:swappiness 越高越不容易 OOM。
真相:swappiness 不决定"是否 OOM",只决定"日常回收倾向"。当 RAM 真的快满时,不管 swappiness 是 10 还是 60,内核都会强制使用 swap。
也就是说:
- swappiness=10:RAM 用到 ~85% 才开始换
- swappiness=60:RAM 用到 ~60% 就开始主动换页
真正防 OOM 的是 swap 总大小,而不是 swappiness。
配置方法
# 临时生效
sudo sysctl vm.swappiness=10
# 永久生效
echo "vm.swappiness=10" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
# 验证
cat /proc/sys/vm/swappiness
五、代理 / 端口转发场景的完整内核优化
对于代理、转发这类对延迟和并发敏感的场景,除了 swappiness,还有几个参数值得一起调:
sudo tee -a /etc/sysctl.conf <<EOF
# === 内存管理 ===
vm.swappiness=10
vm.vfs_cache_pressure=50
# === 网络转发(必开) ===
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
# === TCP 优化 ===
net.ipv4.tcp_fastopen=3
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr
# === 连接数上限 ===
net.core.somaxconn=4096
net.ipv4.tcp_max_syn_backlog=8192
EOF
sudo sysctl -p
参数说明
| 参数 | 作用 |
|---|---|
vfs_cache_pressure=50 | 降低内核回收文件 cache 的倾向,减少重复读盘 |
ip_forward | 内核级启用 IPv4 转发,做端口转发/NAT 必开 |
ipv6.conf.all.forwarding | IPv6 版本的转发开关 |
tcp_fastopen=3 | 启用 TFO,减少握手延迟(客户端+服务端) |
default_qdisc=fq + tcp_congestion_control=bbr | 启用 Google BBR 拥塞控制,代理场景吞吐提升明显 |
somaxconn | listen 队列上限,代理并发连接多时防止丢连接 |
tcp_max_syn_backlog | SYN 半连接队列,抗突发连接 |
BBR 需要内核 ≥ 4.9,现代主流发行版(Ubuntu 20.04+、Debian 10+)都已支持。
六、完整脚本
把前面的步骤整合成一个一键脚本,适用于 btrfs/ext4/xfs:
#!/bin/bash
# 1GB Swap 配置脚本(兼容 btrfs)
SWAPFILE="/swapfile"
SIZE_MB=1024
# 如已存在则清理
[ -f "$SWAPFILE" ] && swapoff "$SWAPFILE" 2>/dev/null && rm -f "$SWAPFILE"
# 创建空文件
touch "$SWAPFILE"
# 若是 btrfs 则禁用 CoW
FS_TYPE=$(df -T / | awk 'NR==2 {print $2}')
if [ "$FS_TYPE" = "btrfs" ]; then
echo "检测到 btrfs,禁用 CoW..."
chattr +C "$SWAPFILE"
fi
# 写入数据
dd if=/dev/zero of="$SWAPFILE" bs=1M count=$SIZE_MB status=progress
chmod 600 "$SWAPFILE"
mkswap "$SWAPFILE"
swapon "$SWAPFILE"
# 添加开机自动挂载
if ! grep -q "$SWAPFILE" /etc/fstab; then
echo "$SWAPFILE none swap sw 0 0" >> /etc/fstab
fi
# swappiness 优化
sysctl vm.swappiness=10
grep -q "vm.swappiness" /etc/sysctl.conf || echo "vm.swappiness=10" >> /etc/sysctl.conf
echo "=== 配置完成 ==="
free -h
swapon --show
总结
- 低内存 VPS 必配 Swap,1GB 内存推荐 1~2GB Swap,0.5GB 内存推荐 1GB。
- **btrfs 下必须先
touch再chattr +C**,最后才写数据,否则swapon必报Invalid argument。 - swappiness 不防 OOM,只影响日常换页倾向。代理机建议设为 10。
- 代理场景顺便开启 BBR,吞吐提升立竿见影。
踩坑不可怕,可怕的是踩了坑没记录。希望这篇文章能帮到同样遇到 btrfs 问题的朋友。

Comments NOTHING