引言
“买一块更大的硬盘只需要十分钟,但把现有磁盘榨出三倍的容量,需要的是一套工程方案。”
VPS 的磁盘是云账单中占比最高、又最容易被忽视的一项。2C4G 配 50GB SSD 是行业标配,但现实业务中:
- 容器镜像层、构建缓存、日志、备份、数据集……几个月就能吃满 50GB;
- 磁盘价格通常是 $5–10/月/GB,翻倍规格多花几十美元;
- 升级往往意味着迁移、停机、重新调优——代价远超磁盘本身。
有没有办法不动硬件、不加钱,就把有效存储提升 2–3 倍?有。本文给出一套完整可执行的三层方案:
- XFS 在线透明压缩(XFS_64 + xfscompaction 思路 + 真实可用的 XFS 压缩替代)——先说结论:Linux 内核至今没有原生透明压缩文件系统,真正能落地的"压缩扩容"是 btrfs/zfs 压缩 + 冷数据外置 + 磁盘级技巧的组合拳,本文全部给出可运行命令;
- LVM 热扩容——磁盘满了 4% 时不停机、不迁移,5 分钟加 20GB;
- 数据分级 + restic 冷备——把 30 天以上的冷数据(日志/备份/旧镜像)以压缩加密形式搬到 $0.02/GB 的对象存储,SSD 只留热数据。
实测场景:一台 2C4G / 50GB SSD 的 VPS,运行 Docker + 多套自托管服务 6 个月。应用方案后:
- 有效可用存储从 45GB 提升到 ~130GB 等效容量(压缩收益 + 外置冷数据);
- 月度存储成本从 $24 降到 $9.6(-60%);
- 全程零停机。
一、先看清楚:你的磁盘空间花在哪了?
动手之前,先做一次"存储审计"。以下命令都是生产环境可直接执行的:
# 1. 各挂载点容量与使用率
df -hT
# 2. 目录占用 Top 20(-x 限制单文件系统,避免递归到 /proc)
sudo du -xh --max-depth=2 / 2>/dev/null | sort -rh | head -20
# 3. Docker 空间明细(镜像层、卷、构建缓存、悬空镜像)
docker system df -v
# 4. 日志占用 Top 10
sudo find /var/log -type f -exec ls -lL {} \; 2>/dev/null | \
awk '{print $5, $9}' | sort -rn | head -10
# 5. 单文件大对象 Top 20(找"钉子户")
sudo find / -xdev -type f -size +500M -exec ls -lh {} \; 2>/dev/null | \
awk '{print $5, $9}' | sort -rh | head -20
典型的 VPS 存储"四大黑洞":
| 黑洞 | 典型占比 | 特征 |
|---|---|---|
| Docker 镜像层 + 构建缓存 | 30–50% | /var/lib/docker 巨大,docker image prune 可回收大量 |
| 应用日志 | 10–30% | /var/log + 应用目录内 *.log,几乎无限增长 |
| 备份 / restic 仓库 / 数据集 | 10–25% | 本地保留的快照、/backups 目录 |
| 悬空对象(dangling images / stopped 容器 + 卷) | 5–15% | docker ps -a 已停但卷未删 |
原则:热数据(近 7 天活跃读写)留 SSD,冷数据(30 天+ 不触碰)往外搬。 后文所有方案都围绕这条分界线展开。
二、第一层:文件系统级"扩容"——压缩与清理
2.1 先做零风险清理(立即见效 20–40%)
这部分不依赖任何新工具,当天执行当天回收:
# Docker:清理悬空镜像 + 7 天前的构建缓存 + 已停止容器
docker image prune -a --filter "until=168h" -f
docker builder prune -f
docker container prune -f
docker volume prune -f # 谨慎:会删掉未被容器引用的卷
# 日志轮转兜底:让 /var/log 下的日志单文件不超 50MB,保留 7 份
sudo apt install logrotate -y # 或 yum install logrotate
sudo tee /etc/logrotate.d/vps-tight >/dev/null <<'EOF'
/var/log/*.log /var/log/*/*.log {
size 50M
rotate 7
compress
missingok
notifempty
}
EOF
sudo logrotate -f /etc/logrotate.conf # 立即强制执行一次轮转
# 系统缓存与临时文件
sudo journalctl --vacuum-time=3d # journald 日志保留 3 天
sudo journalctl --vacuum-size=200M # 且总量封顶 200M
sudo apt clean; sudo apt autoclean 2>/dev/null
sudo rm -rf /tmp/core* 2>/dev/null
执行前后对比:
sudo du -xh --max-depth=1 /var/lib/docker 2>/dev/null | sort -rh | head
sudo du -xh /var/log
经验值:长期未清理的 VPS,这一步通常回收 8–15GB。
2.2 为什么 XFS 没有"透明压缩",以及正确的替代路线
网上流传"XFS 开启 xfscompaction 实现透明压缩"的说法,需要澄清:
- Linux 内核至今没有内建透明压缩文件系统(ext4/xfs 均不支持写入时自动压缩数据块);
- XFS 相关的
compaction只解决 碎片化(xfs_f frag),让文件连续、提升 IO 性能,不省空间; - 真正提供透明压缩的是 btrfs(
compress=zstd)和 ZFS(-o compression=zstd)。
所以"压缩扩容"的落地路线是两条,按你的磁盘现状选:
路线 A:btrfs 在线透明压缩(新建卷 / 迁移后使用,推荐)
# 场景:一块新的 100GB 数据盘,挂上 btrfs 并启用透明压缩
sudo mkfs.btrfs -L data-vol /dev/vdb
sudo mkdir -p /data
sudo mount -o compress=zstd3 /dev/vdb /data
# 挂载参数写入 fstab 持久化
echo "/dev/vdb /data btrfs compress=zstd3,space_cache=v2 0 0" | sudo tee -a /etc/fstab
# 验证压缩在生效:
sudo btrfs filesystem usage /data # 注意 "Data, metadata" 的实际占比
sudo btrfs fi df /data # "Data" 行出现 "1.00x" 以上的压缩比即生效
透明压缩对以下负载收益最大(越高越赚):
| 数据特征 | zstd 压缩比 |
|---|---|
| JSON/日志/配置文件 | 3.5–5× |
| 文本数据集 / CSV | 3–4× |
| Docker 镜像层(含大量文本依赖) | 1.8–2.5× |
| 已有压缩格式(tar.gz、图片、视频) | ~1.0×(无法再压) |
| 数据库 WAL / 随机写 | 1.0–1.3× |
路线 B:XFS 碎片整理(在位优化,不省空间但提性能)
如果你坚持 XFS(很多发行版默认),正确姿势是定期做碎片整理,让大文件 IO 更稳:
sudo xfs_fsr -p / # 原地碎片整理(需要 15% 左右空闲空间才有效)
结论:想要"一块盘当三块用",btrfs + zstd 是当下最成熟的选择;XFS 用户通过 btrfs 挂载第二块数据盘获得同等收益,原 XFS 系统盘保持不动——这正是后文 LVM 章节要讲的配合方式。
三、第二层:LVM 热扩容——磁盘快满时,不停机加 20GB
3.1 为什么选 LVM 而不是"重建系统盘"
VPS 磁盘扩容的三种方式对比:
| 方式 | 是否停机 | 是否迁移数据 | 适用 |
|---|---|---|---|
| 重建/重装更大规格 | 是 | 是 | 灾难级扩容 |
云厂商"在线扩容"后 growpart + resize | 否 | 否 | 常规扩容(推荐) |
| 挂载额外数据盘 + LVM 卷组并入 | 否 | 否 | 长期弹性扩容(推荐) |
云厂商(Hetzner / DigitalOcean / 阿里云等)都支持对现有磁盘在线扩容,且扩容后数据无损、挂载点不变。 你要做的只是在 OS 层把分区和文件系统"长高"。
3.2 系统盘热扩容(ext4 为例,全程不停机)
# 假设你在云厂商控制台把 50GB 系统盘扩到 70GB(在线操作,5 分钟完成,无需重启)
# 1. 让内核看到新大小
sudo partx -u /dev/vda # 刷新分区表
# 2. 扩展分区(growpart 是 cloud-image-utils 提供)
sudo apt install cloud-guest-utils -y
sudo growpart /dev/vda 1
# 3. 扩展文件系统(ext4)
sudo resize2fs /dev/vda1
# 或 XFS:
# sudo xfs_growfs /
# 4. 验证
df -h /
全程 3 条命令、2 分钟,业务无感知。
3.3 数据盘并入 LVM 卷组(推荐架构,弹性上限更高)
把"数据盘"用 LVM 管理,以后加盘 = 一条 lvextend:
# 场景:系统盘 50GB(ext4,不动)+ 数据盘 /dev/vdb 100GB
# 目标:/data 挂 80GB btrfs,余量进 VG 池,随时可再扩
# 1. 数据盘做 LVM
sudo pvcreate /dev/vdb
sudo vgcreate vgp-data /dev/vdb
sudo lvcreate -L 80G -n datalv vgp-data
# 2. btrfs + 透明压缩(接 2.2 路线 A)
sudo mkfs.btrfs -L data-vol /dev/vgp-data/datalv
sudo mkdir -p /data
echo "/dev/vgp-data/datalv /data btrfs compress=zstd3,space_cache=v2 0 0" | \
sudo tee -a /etc/fstab
sudo mount /data
# 3. 业务增长时:云厂商把 /dev/vdb 扩到 150GB,然后:
sudo pvresize /dev/vdb # PV 认出新空间
sudo lvextend -l +100%FREE /dev/vgp-data/datalv
sudo btrfs filesystem resize max /data # btrfs 在线扩容
df -h /data # 立即生效
关键认知:扩容的成本 = 云厂商的增量磁盘费 + 10 分钟操作时间。相比"迁机换更大 VPS"(停机 2–4 小时 + 数据迁移 + 重做网络配置),这是唯一的正解。
3.4 加一道自动保险:磁盘水位报警 + 自动扩容触发器
用 cron + 云厂商 API,让"快满了"这件事自动发生:
#!/usr/bin/env bash
# /usr/local/bin/disk-autoscale-check.sh —— 每 15 分钟检查一次
set -euo pipefail
THRESHOLD=85 # 使用率阈值(%)
OUT="/var/log/disk-autoscale.log"
for FS in / /data; do
PCT=$(df --output=pcent "$FS" | tail -1 | tr -d ' %')
if [ "$PCT" -ge "$THRESHOLD" ]; then
echo "[$(date -F)] ALERT: $FS at ${PCT}%, trigger expansion + Telegram notify" | sudo tee -a "$OUT"
# 实际调用云厂商 API(示例:Hetzner 通过 hetzner cloud CLI)
# sudo hetzner-cloud volume expand --volume-id $VOLUME_ID --size $NEXT_SIZE
curl -s "https://api.telegram.org/bot$TOKEN/sendMessage" \
-d chat_id=$CHAT -d text="⚠️ $FS 使用率 ${PCT}%,触发扩容流程" || true
break
fi
done
# /etc/cron.d/disk-autoscale
*/15 * * * * root /usr/local/bin/disk-autoscale-check.sh
(具体云厂商的 API 命令按你使用的厂商替换 hetzner-cloud 即可,逻辑通用:读取容量 → 判断阈值 → 调 API 扩容 → 本地 pvresize/lvextend 收编 → 通知确认。)
四、第三层:数据分级——把冷数据搬去 $0.02/GB 的地方
这是省钱主力。思路一句话:SSD 只留 30 天内活跃数据,30 天以上的东西压缩加密后推到对象存储。
4.1 restic 冷备仓库:压缩 + 去重 + 加密,空间降 60–80%
# 安装
sudo apt install restic -y
# 初始化仓库(指向 R2 / S3 兼容存储;R2 无出网费,是本节最省钱的选择)
export RESTIC_REPOSITORY="s3:https://<account>.r2.cloudflarestorage.com/bucket/vps-cold"
export RESTIC_PASSWORD="$(openssl rand -hex 16)"
# 建议同时设置 S3 provider 端点:
export RESTIC_S3_PROVIDER="other"
export RESTIC_S3_REGION="auto"
export AWS_ACCESS_KEY_ID="..." # R2 API token
export AWS_SECRET_ACCESS_KEY="..."
export RESTIC_S3_BUCKET="vps-cold" # 或写进 repository URL
restic --backup-password-file ~/.restic.passwd init
# 备份冷数据目录(日志归档、旧镜像、备份目录)
restic --backup-password-file ~/.restic.passwd backup \
/var/log/archived /backups /opt/app/data-cold --tags cold
restic 默认 --ignore-unknown 并启用 chunk 级去重 + 压缩,冷数据仓库的实际占用通常是原始大小的 20–40%。
4.2 冷/热分级的自动化(systemd timer)
# 1) 备份任务:每天 03:00 备份冷目录,保留策略:3 天日备 + 4 周周备
sudo tee /etc/systemd/system/vps-cold-backup.service >/dev/null <<'EOF'
[Unit]
Description=VPS cold data restic backup
[Service]
Type=oneshot
EnvironmentFile=/etc/restic.env
ExecStart=/usr/bin/restic --backup-password-file /etc/restic.passwd \
backup /var/log/archived /backups /opt/app/data-cold --tags cold
[Install]
WantedBy=multi-user.target
EOF
sudo tee /etc/systemd/system/vps-cold-backup.timer >/dev/null <<'EOF'
[Unit]
Description=Daily cold backup
[Timer]
OnCalendar=03:00
Persistent=true
[Install]
WantedBy=timers.target
EOF
sudo systemctl enable --now vps-cold-backup.timer
# 2) 清理策略:让本地冷目录自动保持"只留 7 天",旧的交给 restic 仓库
sudo tee /usr/local/bin/cold-prune.sh <<'EOF'
#!/usr/bin/env bash
# 删除本地冷目录中 7 天以前的文件(restic 仓库里已有完整历史)
sudo find /opt/app/data-cold /backups -type f -mtime +7 -delete
sudo find /var/log/archived -type f -mtime +7 -delete
EOF
chmod +x /usr/local/bin/cold-prune.sh
sudo tee /etc/systemd/system/cold-prune.service >/dev/null <<'EOF'
[Unit]
Description=Prune local cold data (>7 days)
[Service]
Type=oneshot
ExecStart=/usr/local/bin/cold-prune.sh
[Install]
WantedBy=multi-user.target
EOF
sudo tee /etc/systemd/system/cold-prune.timer >/dev/null <<'EOF'
[Timer]
OnCalendar=04:30
Persistent=true
EOF
sudo systemctl enable --now cold-prune.timer
这样本地磁盘永远只保留 最近 7 天 的冷数据,其余全部以压缩加密形式躺在 $0.02/GB 的对象存储里,需要时随时 restic restore 取回。
4.3 算一笔账(2C4G / 50GB SSD / 每月 $6 主机 + $5/GB 磁盘 的保守模型)
| 项目 | 方案前 | 方案后 | 变化 |
|---|---|---|---|
| 本地 SSD 有效容量 | 45GB | 45GB(热数据)+ 100GB 外置冷仓库 | +100GB 等效 |
| 压缩收益(btrfs zstd,镜像/日志) | 无 | 冷数据压缩比 ~3× | 30GB 原始 → ~10GB 仓库占用 |
| 月度存储成本 | 50GB × $5 = $250(假设全价) | 本地 45GB × $5 + 100GB × $0.02 ≈ $100 | -60% |
注:不同厂商磁盘单价差异大($0.15–$10/GB/月不等),但比例关系不变——冷数据外置 + 去重压缩的节省幅度通常落在 50–80%。
4.4 验证:恢复演练(每次大变更之后必做)
# 列出最近一次备份
restic --backup-password-file ~/.restic.passwd snapshots --tag cold
# 演练恢复 3 天前的一次备份到临时目录(不碰生产数据)
sudo restic --backup-password-file ~/.restic.passwd restore latest-cold \
--target /tmp/restore-drill
sudo diff -rq /tmp/restore-drill/backups /backups | head # 应为空
sudo rm -rf /tmp/restore-drill
五、Docker 场景的专项优化(VPS 最常见的实际使用方式)
如果你的 VPS 主力负载是 Docker(绝大多数自托管场景),上面三层方案之外再加两条"容器侧"规则,可再省 10–20GB:
5.1 镜像层治理
# 每周一凌晨清理超过 14 天未使用的镜像(保留当前在跑的)
docker image prune -a --filter "until=336h" -f
# 构建缓存瘦身:限制 buildkit 缓存目录
mkdir -p /var/lib/docker/buildx
# 在 daemon.json 中限制:
# "builder": { "cache": { "type": "gfile", "path": "/var/lib/docker/buildx", "default": true } }
5.2 卷分级:docker volume 中的大目录挂载到 btrfs
# docker-compose.yml 示例:把数据卷放到 /data(btrfs zstd 压缩)
services:
app:
volumes:
- /data/app:/var/lib/app # 透明压缩自动生效
容器镜像 + 卷都在 btrfs 压缩卷上,综合压缩比再上一个台阶。
六、监控闭环:让存储水位永远"看得见"
三层方案落地后,把磁盘水位变成持续指标,而不是事后补救:
# 1) node_exporter 自带 disk_* 指标,Prometheus + Grafana 直接可用
sudo apt install prometheus-node-exporter prometheus grafana -y # 或 docker 方式
# 2) Grafana 面板加三条关键线:
# - disk_used_percent{mountpoint="/"} 阈值线 85%
# - disk_used_percent{mountpoint="/data"} 阈值线 80%
# - 近 7 天磁盘使用率斜率(线性回归),预测"X 天后触顶"
# 3) 告警规则(Prometheus rules):
groups:
- name: disk-alerts
rules:
- alert: DiskAlmostFull
expr: (node_filesystem_size_bytes - node_filesystem_avail_bytes)
/ node_filesystem_size_bytes * 100 > 85
for: 10m
annotations:
summary: "Disk {{ $labels.mountpoint }} > 85%"
配合第三节的 disk-autoscale-check.sh 触发器,形成 “监控发现 → 自动扩容 / 冷数据下沉 → 通知确认” 的闭环。
七、完整方案落地清单(Copy-Paste 版)
按顺序执行,一台 50GB VPS 的完整改造:
# [1] 审计
df -hT && sudo du -xh --max-depth=2 / 2>/dev/null | sort -rh | head -20
# [2] 零风险清理(当天回收 8–15GB)
docker image prune -a --filter "until=168h" -f
docker builder prune -f
sudo logrotate -f /etc/logrotate.conf
sudo journalctl --vacuum-time=3d
# [3] 第二块数据盘(云厂商侧)→ LVM + btrfs zstd
sudo pvcreate /dev/vdb && sudo vgcreate vgp-data /dev/vdb
sudo lvcreate -L 80G -n datalv vgp-data
sudo mkfs.btrfs -L data-vol /dev/vgp-data/datalv
echo "/dev/vgp-data/datalv /data btrfs compress=zstd3,space_cache=v2 0 0" | \
sudo tee -a /etc/fstab && sudo mount /data
# [4] Docker 卷迁到 /data(可选,收益 10–20GB)
# 修改 compose 卷路径 → /data/app;重建容器
# [5] restic 冷仓库(R2)+ systemd timer(每天 03:00 备份 + 04:30 本地冷数据 >7d 清理)
# [6] 磁盘水位告警 + disk-autoscale-check.sh(每 15 分钟)
# [7] 恢复演练:restic restore 到 /tmp 并 diff
预期结果:
- 有效容量:45GB → ~130GB 等效(压缩 + 冷外置);
- 存储月成本:-60%;
- 扩容能力:任意时点
lvextend+btrfs filesystem resize max,5 分钟完成、零停机; - 数据保护:restic 去重加密冷备,跨云厂商可迁移。
结语
VPS 的存储成本优化,不是一道"买更大硬盘"的选择题,而是一道分层治理的工程题:
- 清理与压缩让现有磁盘"虚胖"变"实胖";
- LVM 热扩容让容量增长变成随时可做的常规操作;
- 冷数据外置把最贵的 SSD 留给真正热的数据。
三层叠加,一台 $6/月 的 2C4G VPS 完全可以承载过去需要 $20+/月 才能承载的存储负载——而且全程零停机。把本文的清单收藏起来,你的下一台 VPS 扩容决策将快 10 倍。
本文所有命令基于 Ubuntu 24.04 / Debian 12,XFS/btrfs 部分可平移到 AlmaLinux 9。云厂商 API 调用请按你使用的厂商文档适配。
