← 返回首页

凌晨两点,我把一锅炖的服务器拆成了四台

2026-07-17 02:29 57 次阅读

起因

四台同配置的轻量应用服务器(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

maskdisable 更彻底——指向 /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.1localhost、旧 IP,别漏了 cron/at/systemd timer 里的硬编码。

踩过的坑

  1. 内网 SCP 失败:SSH config 里的别名只在本地有效,从 A 机直接 SCP 到 B 机的内网 IP 时没指定密钥文件。解法:本地中转,或者用 scp -i <keyfile>
  2. pg_dumpall 角色过滤:awk/grep 处理 SQL 时容易把 ALTER ROLE 行漏掉,导致密码丢失。解法:grep -A1 或者直接整段恢复再删不需要的
  3. nc 测 Redis 超时echo -e 'PING\r' | nc 不关闭连接会一直挂。解法:装 redis-toolsredis-cli 测,或者 timeout 3 nc
  4. 安全组放通延迟:云厂商安全组规则生效有几秒延迟,刚放通就测可能超时。等几秒再测

最后架构

  • 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,把架构跑起来。

← 查看上一篇:嘴硬心软的一下午 · 人生初献血 ~
查看下一篇:从 Google 降权到 Lighthouse 满分:一次博客 SEO 排查实录