凌晨两点,我把一锅炖的服务器拆成了四台
起因
四台同配置的轻量应用服务器(2C2G/40G),之前图省事把所有服务塞在一台上:Gitea、博客、PostgreSQL、Nginx 全家桶。跑了一阵发现问题不少——git push 大仓库时博客跟着卡,数据库和 Web 抢 IO,一台炸了全炸。
今晚决定拆开。
目标架构
四台机器同账号同地域,内网互通。按职责拆分:
| 角色 | 服务 | 暴露方式 |
|---|---|---|
| Web 站 | Nginx + MariaDB + 几个 Flarum/静态站 | 公网 80/443 |
| Git + 博客 | Gitea + 个人博客 + Nginx | 公网 80/443,Gitea SSH 单独端口 |
| 缓存 | Redis | 仅内网 |
| 数据库 | PostgreSQL 16 | 仅内网 |
原则:数据库和缓存绝不暴露公网,只走内网通信。
过程
1. PostgreSQL 迁移
源机器本地 PSQL 跑着两个库(Gitea + 博客),目标是一台干净的机器。
# 源端导出
sudo -u postgres pg_dumpall --roles-only > roles.sql
sudo -u postgres pg_dump -Fc giteadb > giteadb.dump
sudo -u postgres pg_dump -Fc jaypub > jaypub.dump
# 目标端恢复
sudo -u postgres psql -f roles.sql
sudo -u postgres createdb -O gitea giteadb
sudo -u postgres createdb -O jaypub jaypub
sudo -u postgres pg_restore -d giteadb giteadb.dump
sudo -u postgres pg_restore -d jaypub jaypub.dump
几个坑:
pg_dumpall --roles-only默认含密码(SCRAM-SHA-256),但 grep 过滤时要保留ALTER ROLE行,否则密码丢了- SCP 走内网 IP 失败,因为目标机 SSH 用的是密钥文件,内网直连没指定 IdentityFile——最后本地中转拉过去再推
- 恢复时用
--no-owner --role=xxx避免 owner 不匹配
2. 数据库调优
2G 内存机器跑 PSQL,默认配置太保守(shared_buffers 才 128MB)。写了独立调优文件:
# /etc/postgresql/16/main/conf.d/99-tuning.conf
shared_buffers = 512MB # 总内存 25%
effective_cache_size = 1536MB # 总内存 75%
work_mem = 8MB
maintenance_work_mem = 128MB
wal_buffers = 16MB
max_connections = 50 # 专用机不需要 100
max_parallel_workers = 2 # 匹配 2 核
checkpoint_completion_target = 0.9
max_wal_size = 1GB
配置文件放 conf.d/ 而不是改主配置,便于回滚——删文件即可。
3. zswap 启用
两台专用机(DB + Redis)都开了 zswap,配置一致:
# 内核参数
zswap.enabled=1
zswap.compressor=zstd
zswap.max_pool_percent=50
runtime 启用 + 写入 GRUB 持久化:
echo 1 | sudo tee /sys/module/zswap/parameters/enabled
sudo sed -i 's|GRUB_CMDLINE_LINUX_DEFAULT="\(.*\)"|GRUB_CMDLINE_LINUX_DEFAULT="\1 zswap.enabled=1 zswap.compressor=zstd zswap.max_pool_percent=50"|' /etc/default/grub
sudo update-grub
为什么用 zswap 而不是 zram?zswap 有后端 swap 兜底,冷数据能落盘;zram 是纯内存压缩,满了就 OOM。数据库机器更稳。
4. Redis 部署
bind <内网IP> 127.0.0.1 -::1
protected-mode yes
requirepass <强密码>
maxmemory 1536mb # 留 384M 给系统
maxmemory-policy allkeys-lru
appendonly yes
appendfsync everysec
重点:
- 必须设密码,即使只放内网。protected-mode 在无密码时会拒绝外部连接,但不要为了图方便关掉它——设密码才是正解
- maxmemory 不是预占,是上限。Redis 实际占用取决于数据量
- AOF everysec 是性能和安全的折中,最多丢 1 秒数据
5. 服务切换
改配置指向新 DB,重启服务:
# Gitea
sudo sed -i 's|^HOST = 127.0.0.1:5432|HOST = <新DB内网IP>:5432|' /etc/gitea/app.ini
sudo systemctl restart gitea
# 博客
sudo sed -i 's|^DB_HOST=127.0.0.1|DB_HOST=<新DB内网IP>|' /var/www/blog/.env
sudo systemctl restart blog
切之前先备份原配置(.bak.日期),方便一键回滚。
6. 旧库停止但保留
源机器的本地 PSQL 没直接卸,而是 stop + mask:
sudo systemctl stop postgresql@16-main
sudo systemctl mask postgresql@16-main
mask 比 disable 更彻底——指向 /dev/null,任何方式都启动不了。数据目录保留,观察几天稳定后再删。
7. 清理无用服务
源机器上还发现一个跑着的 NodeBB(Docker 部署),但已经 503 好久了。打包备份后清理:
docker compose down
docker volume rm <volumes...>
docker rmi <images...>
rm -rf /home/ubuntu/nodebb-docker
释放了 1.4GB 磁盘。MongoDB 数据用 mongodump --archive --gzip 做逻辑导出(133K),比直接拷 WiredTiger 文件靠谱——原始文件权限不够还容易损坏。
另外还卸了一台机器上空跑的 PostgreSQL(只有默认库,没业务用),apt purge + 删数据目录。
8. 安全加固
四台统一禁用密码 SSH:
# 高优先级配置覆盖 cloud-init
echo 'PasswordAuthentication no' | sudo tee /etc/ssh/sshd_config.d/99-disable-password.conf
# 禁止 cloud-init 重启时改回
echo 'ssh_pwauth: false' | sudo tee /etc/cloud/cloud.cfg.d/99-disable-pwauth.cfg
sudo systemctl reload ssh
cloud-init 的 50-cloud-init.conf 会在重启时覆盖你的 sshd 配置,必须同时禁用 cloud-init 的 ssh_pwauth。
9. 漏网之鱼:at 定时任务
切完数据库才发现有个 at 定时任务(8 月 21 日定时发布文章),里面还连着旧的 127.0.0.1:
# 查看任务
atq
at -c <job_id>
# 修改:导出脚本 → 改 DB 地址 → 删旧任务 → 重新提交
at -c 1 > /tmp/job.sh
sed -i 's|127.0.0.1|<新DB内网IP>|' /tmp/job.sh
atrm 1
echo 'bash /tmp/job.sh' | at 00:00 08/21/2026
教训:迁移后全局搜一遍 127.0.0.1、localhost、旧 IP,别漏了 cron/at/systemd timer 里的硬编码。
踩过的坑
- 内网 SCP 失败:SSH config 里的别名只在本地有效,从 A 机直接 SCP 到 B 机的内网 IP 时没指定密钥文件。解法:本地中转,或者用
scp -i <keyfile> - pg_dumpall 角色过滤:awk/grep 处理 SQL 时容易把
ALTER ROLE行漏掉,导致密码丢失。解法:grep -A1或者直接整段恢复再删不需要的 - nc 测 Redis 超时:
echo -e 'PING\r' | nc不关闭连接会一直挂。解法:装redis-tools用redis-cli测,或者timeout 3 nc - 安全组放通延迟:云厂商安全组规则生效有几秒延迟,刚放通就测可能超时。等几秒再测
最后架构
- Web 站机:Nginx + PHP + MariaDB,跑几个 Flarum 和静态站,公网 80/443
- Git + 博客机:Gitea + 个人博客 + Nginx,公网 80/443,Gitea SSH 单独端口
- Redis 机:1.5G maxmemory + AOF,仅内网 6379
- PostgreSQL 机:shared_buffers 512MB + zswap,仅内网 5432
Git/博客机通过内网连 PSQL 和 Redis,Web 站机自带 MariaDB 自包含。每台职责单一,互不干扰,数据库和缓存只走内网,公网暴露面最小化。
数字总结
- 迁移数据库:2 个库,120+ 张表
- 部署 Redis:1.5G 上限,AOF 持久化
- 清理释放:~1.4GB 磁盘(NodeBB + 空 PSQL)
- 服务停机:每次重启 < 5 秒,用户无感
- 凌晨耗时:约 1.5 小时
一点心得
- 先备份再动手,配置文件
.bak.日期,数据库pg_dump,宁滥勿缺 - 分阶段切换,每步可回滚,不要一步到位
- mask 比 disable 彻底,防止意外启动
- cloud-init 会覆盖你的配置,改 SSH 一定要同时处理 cloud-init
- 迁移后全局搜硬编码,cron/at/systemd timer 容易漏
- 2G 机器也能跑数据库,关键是调优 + zswap + 专用不混部
下次打算重写其中一个站,到时候直接用上 PSQL + Redis,把架构跑起来。