Cairn
← 返回博客

网站上线不是结束:备份、日志、更新与下一阶段

网站上线不是结束:备份、日志、更新与下一阶段

网站第一次通过 HTTPS 打开时,很容易产生一种错觉:完成了。

准确地说,只是它开始需要被照顾了。

程序会更新,数据库会增长,证书会续期,磁盘会变满,错误会挑你吃饭时出现。上线后的工作不必复杂,但必须能回答四个问题:

  1. 服务现在活着吗?
  2. 出错后去哪里找原因?
  3. 数据丢了能恢复吗?
  4. 更新失败能退回去吗?

下面是一套适合单机 Go 网站的最低运维方案。

一、先建立固定检查顺序

网站打不开时,不要随机重启所有服务。按照请求经过的顺序检查:

DNS -> 防火墙 -> Caddy -> Go -> PostgreSQL

1. 域名是否解析正确

getent ahostsv4 example.com

如果 IP 不对,先改 DNS。应用重启十次也修不好域名解析。

2. Caddy 是否运行

sudo systemctl status caddy --no-pager
sudo journalctl -u caddy -n 100 --no-pager

证书申请、端口占用、Caddyfile 语法错误都会出现在这里。

3. Go 服务是否运行

sudo systemctl status cairn --no-pager
sudo journalctl -u cairn -n 100 --no-pager

绕过 Caddy 直接检查:

curl -i http://127.0.0.1:8080/healthz

如果本机健康检查成功、域名访问失败,问题多半在 Caddy、证书、DNS 或防火墙,不在 Go。

4. 数据库是否健康

cd /opt/cairn
sudo docker compose --env-file /etc/cairn/cairn.env ps
sudo docker compose --env-file /etc/cairn/cairn.env logs --tail=100 db

不要忘记 --env-file。生产环境的 Compose 文件引用了 DB_PASSWORD,缺少环境文件时连 ps 都可能先报变量错误。

二、让日志能回答问题

Go 服务使用结构化日志输出到标准输出,systemd 会统一收集:

sudo journalctl -u cairn

查看最近日志:

sudo journalctl -u cairn -n 100 --no-pager

持续跟踪:

sudo journalctl -u cairn -f

查看某段时间:

sudo journalctl -u cairn \
  --since "2026-06-21 14:00:00" \
  --until "2026-06-21 15:00:00"

有用的应用日志至少应该包含:

  • 请求或操作发生的时间;
  • 日志级别;
  • 发生错误的模块;
  • 可以定位问题的资源标识;
  • 原始错误信息。

不要把密码、Session token、完整 Cookie 或数据库连接串写入日志。日志是排查工具,不是秘密收藏夹。

对于 HTTP 请求,建议记录方法、路径、状态码和耗时,但不要无差别记录请求体。登录表单和评论内容都可能包含不适合长期保存的信息。

三、备份成功不等于可以恢复

PostgreSQL 可以通过 pg_dump 生成逻辑备份:

set -a
source /etc/cairn/cairn.env
set +a

timestamp="$(date -u +%Y%m%dT%H%M%SZ)"
PGPASSWORD="$DB_PASSWORD" pg_dump \
  --host 127.0.0.1 \
  --port 5432 \
  --username cairn \
  --dbname cairn \
  --no-owner \
  --no-privileges |
  gzip > "/var/backups/cairn/cairn-${timestamp}.sql.gz"

备份目录应归专用服务用户所有,并限制权限:

sudo install -d -o cairn -g cairn -m 0750 /var/backups/cairn

可以用 systemd timer 定时执行。备份服务运行完成后显示:

inactive (dead)

Type=oneshot 服务来说,这是正常状态:任务执行成功后退出,并不是服务挂了。真正要看的是退出码和日志:

sudo systemctl status cairn-backup.service --no-pager
sudo journalctl -u cairn-backup.service -n 50 --no-pager
sudo systemctl list-timers cairn-backup.timer

定期做恢复演练

只有真正恢复过的备份,才值得信任。

可以创建临时数据库:

sudo docker exec cairn-db-1 \
  psql -U cairn -d postgres \
  -c "CREATE DATABASE cairn_restore_test;"

恢复最近一次备份:

latest_backup="$(ls -1t /var/backups/cairn/cairn-*.sql.gz | head -n 1)"

gunzip -c "$latest_backup" |
  sudo docker exec -i cairn-db-1 \
  psql -U cairn -d cairn_restore_test

检查核心表:

sudo docker exec cairn-db-1 \
  psql -U cairn -d cairn_restore_test \
  -c "\dt"

确认后删除临时数据库:

sudo docker exec cairn-db-1 \
  psql -U cairn -d postgres \
  -c "DROP DATABASE cairn_restore_test;"

恢复演练可能暴露很多问题:压缩包损坏、权限不对、备份脚本没读到密码、磁盘里只剩空文件。比起真正丢数据时才发现,提前尴尬划算得多。

四、建立可回退的更新流程

更新应用不要直接覆盖正在运行的二进制。一个简单而可靠的流程是:

上传源码
-> 编译新二进制
-> 备份旧二进制
-> 执行迁移
-> 原子替换
-> 重启
-> 健康检查

在服务器上编译临时文件:

cd /opt/cairn
go build -trimpath -ldflags="-s -w" -o bin/cairn.new ./cmd/server

先确认它确实是可执行文件:

file bin/cairn.new
ls -lh bin/cairn.new

备份当前版本并替换:

sudo cp bin/cairn "bin/cairn.$(date -u +%Y%m%dT%H%M%SZ)"
sudo mv bin/cairn.new bin/cairn
sudo chown cairn:cairn bin/cairn
sudo chmod 755 bin/cairn
sudo systemctl restart cairn

立即验收:

sudo systemctl status cairn --no-pager
curl -fsS http://127.0.0.1:8080/healthz
curl -fsS https://example.com/healthz

如果启动失败:

sudo journalctl -u cairn -n 100 --no-pager

确认是新版本问题后,再把最近的旧二进制恢复并重启。不要看到错误就先删数据库、清卷或重装服务器,那属于用拆房子的方式修门锁。

五、迁移必须考虑新旧版本兼容

数据库迁移比替换二进制更难回退,因为数据结构已经发生变化。

较稳妥的做法是采用“先兼容,再切换,最后清理”:

  1. 第一版迁移只增加新列或新表,旧程序仍能运行;
  2. 新程序开始同时支持旧结构和新结构;
  3. 数据回填完成;
  4. 确认没有旧程序运行;
  5. 后续版本再删除旧列。

例如把评论作者从文本用户名改成 user_id,不要在同一次发布里直接删除用户名列并要求所有数据立刻完美。可以先增加 user_id,完成关联和回填,验证查询稳定后,再用下一条迁移清理旧字段。

这是数据库领域里很朴素的一条经验:加东西通常容易回退,删东西通常很有主见。

六、监控先从最小可用开始

个人网站不一定需要立刻搭建一套监控平台,但至少要有外部健康检查。

外部服务可以定时请求:

https://example.com/healthz

不过健康检查不能只返回“Go 进程还活着”。如果页面依赖数据库,接口最好执行一次轻量数据库探测,并设置超时。

同时要注意:健康检查不应该泄露数据库地址、版本、错误堆栈或服务器信息。对外只返回必要状态,详细原因写入服务端日志。

本机也可以定期检查磁盘:

df -h
du -sh /var/backups/cairn
sudo journalctl --disk-usage

数据库备份和日志都很擅长悄悄吃满磁盘。备份脚本应设置保留周期,例如删除 30 天前的文件:

find /var/backups/cairn \
  -type f \
  -name 'cairn-*.sql.gz' \
  -mtime +30 \
  -delete

删除策略上线前先把 -delete 换成 -print,确认匹配结果正确。

七、秘密也需要维护

生产环境至少要定期检查:

  • 数据库密码是否足够强;
  • 环境文件是否只有必要用户可读;
  • SSH 是否关闭密码登录并使用密钥;
  • 系统和 Docker 是否及时安装安全更新;
  • 不再使用的账号和密钥是否已经撤销;
  • 备份文件是否被错误暴露到 Web 目录。

修改数据库密码时,要同时更新 PostgreSQL 和应用环境文件,并安排一次可控重启。只改一边,得到的通常不是“更安全”,而是“连接失败”。

八、下一阶段应该先做什么

网站上线后,很容易进入功能许愿池:搜索、标签、归档、后台、对象存储、访问统计、邮件通知,好像每个都应该马上出现。

排序标准可以简单一点:

  1. 数据是否安全;
  2. 读者能否顺利阅读;
  3. 作者能否稳定发布;
  4. 功能是否解决了反复出现的问题。

按这个标准,一个内容型网站更合理的顺序通常是:

备份恢复
-> 错误日志与外部健康检查
-> 发布体验
-> 标签和归档
-> 搜索
-> 图片管理
-> 访问统计

Redis、搜索引擎、对象存储当然都有价值,但应该在问题出现以后引入。没有性能瓶颈时先部署缓存,往往只是多得到一个需要备份、监控和排错的服务。

结尾

上线前,开发关注“功能能不能工作”;上线后,关注点会悄悄变成:

  • 它能不能一直工作;
  • 出问题能不能看懂;
  • 数据能不能找回来;
  • 更新能不能安心进行。

这四件事没有炫目的界面,却决定了一个网站究竟是偶尔能打开的作品,还是可以长期生长的系统。

评论

暂无评论,来说点什么吧。

请先登录后再发表评论

去登录