Kutt 3.2.6 日常运维与升级指南
Kutt 3.2.6 生产运维实录:三服务栈日常巡检清单、pg_dump 备份与恢复演练、健康探针与日志轮转、v3.2.5↔v3.2.6 升级回滚实测(迁移幂等)、过期链接 cron 自动清理验证与资源水位基线。
Kutt 3.2.6 日常运维与升级指南
目标:覆盖 Kutt v3.2.6 生产栈(应用 + PostgreSQL 16 + Redis 7)的日常巡检、备份恢复、监控告警、扩缩容、升级回滚与故障处置主线;所有操作已在 Ubuntu 24.04 宿主机实测栈(端口 8153)上执行并留存回显(2026-11-01)。栈的搭建过程见 《Kutt 3.2.6 Docker Compose 部署指南》。
一、环境信息
- 操作系统:Ubuntu 24.04.5 LTS(x86_64),Docker 29.9.2 / Compose v5.6.1
- 应用:
kutt/kutt:v3.2.6(容器kutt-3.2.6-server,宿主机端口 8153→3000) - 数据库:
postgres:16-alpine(容器kutt-3.2.6-postgres,库名kutt,不对外暴露) - 缓存/队列:
redis:7-alpine(容器kutt-3.2.6-redis,不对外暴露) - 部署目录:
/opt/kutt(compose 项目 +.env+data/pg、data/redis、custom三处 bind mount) - 管理员:
admin@kutt.local/q1w2e3r4;API key 走x-api-key头 - 实测资源水位:应用 52.9MiB、Postgres 96.8MiB、Redis 33.4MiB(空闲态)
二、日常巡检
日常巡检回答四个问题:服务活着吗、数据在涨吗、队列堵了吗、磁盘够吗。以下命令按序执行即为一次完整巡检。
2.1 服务状态与健康检查
cd /opt/kutt && docker compose ps --format '{{.Name}} {{.Image}} {{.Status}}'
kutt-3.2.6-postgres postgres:16-alpine Up 12 minutes (healthy)
kutt-3.2.6-redis redis:7-alpine Up 12 minutes
kutt-3.2.6-server kutt/kutt:v3.2.6 Up 10 seconds
curl -s http://localhost:8153/api/v2/health; echo
OK
/api/v2/health 返回纯文本 OK 即应用就绪(该端点无认证,专为探针设计);Postgres 的 (healthy) 来自 compose 内置 pg_isready 健康检查。
2.2 业务数据水位
docker exec kutt-3.2.6-postgres psql -U postgres -d kutt -t -c \
"SELECT (SELECT count(*) FROM links) AS links, (SELECT count(*) FROM users) AS users, (SELECT count(*) FROM visits) AS visit_rows;"
3 | 1 | 1
links 为现存短链数(过期与已删不计)、visits 按"每链每天一行"聚合增长,长期远大于 365×链数说明异常(可查 countries/referrers 是否被爬虫灌入)。
2.3 队列与缓存
docker exec kutt-3.2.6-redis redis-cli keys '*'
s:1
u:7sGm14IdbxoknkpnItuKQVRTMBaucEQscN7kAXsJ
l:kutt-proj:
bull:visit:id
键含义:s:<链接ID> 统计缓存(TTL 60s)、u:<apikey> 用户缓存、l:<address>: 跳转缓存、bull:visit:* 访问事件队列。巡检重点看 bull:visit 积压:
docker exec kutt-3.2.6-redis redis-cli llen bull:visit:wait
(integer) 0
持续大于百级说明消费端(应用进程)异常,结合 2.4 日志定位。
2.4 日志与磁盘
docker compose logs server --since 10m | grep -iE "error|exit" | head
du -sh data/pg data/redis custom
(无输出——10 分钟内无错误)
63M data/pg
8.0K data/redis
4.0K custom
日志轮转实测由宿主机 Docker daemon 默认策略接管:
docker inspect kutt-3.2.6-server --format '{{.HostConfig.LogConfig.Type}} {{.HostConfig.LogConfig.Config}}'
json-file map[max-file:3 max-size:10m]
即单容器日志最多 3×10MB,无需额外配置 logrotate;若宿主机 daemon 无此默认值,应在 compose 的 server 服务显式加 logging: { driver: json-file, options: { max-size: "10m", max-file: "3" } }。
💡 说明:
data/pg63MB 中大部分是 Postgres 初始集群骨架,Kutt 自身数据(3 链 1 用户)占比极小;短链业务天然写多读少但行极小,磁盘增长主要来自visits表,按年清理旧行即可(见 7.4)。
三、备份与恢复
Kutt 栈的全部状态 = Postgres 库 + custom 静态目录(Redis 仅缓存与瞬态队列,可不备)。
3.1 备份三件套
cd /opt/kutt
mkdir -p backup/$(date +%F)
docker exec kutt-3.2.6-postgres pg_dump -U postgres -d kutt > backup/$(date +%F)/kutt.sql
tar czf backup/$(date +%F)/custom.tar.gz custom/
cp .env docker-compose.yml backup/$(date +%F)/
ls -lh backup/$(date +%F)/
-rw-r--r-- 1 root root 46K Nov 1 16:45 kutt.sql
-rw-r--r-- 1 root root 167 Nov 1 16:45 custom.tar.gz
-rw------- 1 root root 176 Nov 1 16:45 .env
-rw-r--r-- 1 root root 1.5K Nov 1 16:45 docker-compose.yml
实测说明两点:① pg_dump 默认输出 COPY 格式(本次 796 行、8 个 COPY 数据段),恢复速度快、体积小,无需 --inserts;② 备份 .env 时注意其含 JWT_SECRET 与库口令,chmod 600 并纳入密管,泄露等同泄露全部登录态。
3.2 恢复演练(实测通过)
恢复到新库验证备份可用性(不影响生产库):
docker exec kutt-3.2.6-postgres psql -U postgres -c "CREATE DATABASE kutt_restore;"
docker exec -i kutt-3.2.6-postgres psql -U postgres -d kutt_restore < backup/$(date +%F)/kutt.sql
docker exec kutt-3.2.6-postgres psql -U postgres -d kutt_restore -t -c "SELECT count(*) FROM links;"
CREATE DATABASE
ALTER TABLE
ALTER TABLE
ALTER TABLE
3
链接数与生产一致,演练成功后清理:
docker exec kutt-3.2.6-postgres psql -U postgres -c "DROP DATABASE kutt_restore;"
整栈迁移(换机)流程:新机建好 /opt/kutt 目录结构 → 放入 docker-compose.yml、.env、custom/ → docker compose up -d postgres redis → docker exec -i <pg容器> psql -U postgres < kutt.sql(首次启动空库自动建表结构由 compose 的 POSTGRES_DB 保证,恢复前需确认 kutt 库存在且为空)→ docker compose up -d server。
⚠️ 注意:不要用"直接拷贝
data/pg目录"跨版本迁移——Postgres 数据目录与镜像小版本绑定,逻辑备份(pg_dump)才是可移植路径。同版本整目录拷贝则可行(本文 bind mount 即为此设计)。
3.3 SQLite 形态备份
若采用单容器 SQLite 形态(DB_CLIENT=better-sqlite3),备份退化为复制数据文件(实测位于容器 /kutt/db/data,106KB 起步;官方 compose 挂载路径为 /var/lib/kutt):
docker exec kutt-server sqlite3 /kutt/db/data ".backup '/tmp/kutt-backup.sqlite'"
docker cp kutt-server:/tmp/kutt-backup.sqlite backup/$(date +%F)/
用 .backup 而非 cp:前者走 SQLite 在线备份协议,避免写入中途拷贝出撕裂页。
四、监控告警
最小可用监控三件套(均可直接落到 Prometheus/Blackbox 或 crontab 脚本):
- 存活探针:
GET http://<host>:8153/api/v2/health期望 200 + 正文OK,建议 30s 间隔、连续 3 次失败告警; - 队列积压:
redis-cli llen bull:visit:wait持续 >100 告警(消费端异常); - 证书/域名:短链服务域名与证书过期是最高危故障(所有跳转失效),纳入现有域名到期监控即可。
无专用 exporter;如需指标化,Postgres/Redis 用通用 exporter(postgres_exporter、redis_exporter)采集连接数与内存,Kutt 应用层指标以 health + 日志关键字(error)为准。
五、扩容与缩容
Kutt 应用进程无本地会话状态(登录态为签名 JWT Cookie,API key 存库),天然支持水平多副本:
- 将
server服务副本数提升(Postgres/Redis 保持单实例共享):
docker compose up -d --scale server=2
⚠️ 注意:
--scale与固定container_name互斥,多副本前需从 compose 中删除server的container_name行,并把ports改为交给前置 Nginx 负载均衡(宿主机 8153 只能被一个副本占用)。
- 多副本共享同一
DEFAULT_DOMAIN与同一JWT_SECRET(必须一致,否则 A 节点签发的 JWT 在 B 节点验签失败); - 官方 compose 的本地构建镜像内含
custom挂载,各副本共享同一custom卷。
缩容即反向操作(--scale server=1)。单机场景无需扩缩——实测空闲内存水位不足 200MB(三容器合计),垂直扩容(加 CPU/内存)即可覆盖到数十万链接量级;真正的容量瓶颈在 Postgres 单库写入,届时优先清理 visits 历史行(见 7.4)而非加副本。
六、升级与回滚
6.1 升级流程(实测 v3.2.5 → v3.2.6)
Kutt 的镜像启动命令自带 knex migrate:latest,升级 = 换 tag + 重建应用容器,数据库迁移自动完成且幂等。实测步骤:
cd /opt/kutt
docker pull kutt/kutt:v3.2.6
sed -i 's|kutt/kutt:v3.2.5|kutt/kutt:v3.2.6|' docker-compose.yml
cp .env backup/pre-upgrade-$(date +%F).env # 配置留档
docker compose up -d server
docker compose logs server --since 1m | tail -5
> kutt@3.2.6 start
> node server/server.js --production
> Ready on http://localhost:3000
验证升级成功且数据无损:
curl -s http://localhost:8153/api/v2/health && echo
curl -s "http://localhost:8153/api/v2/links" -H "x-api-key: <APIKEY>" | python3 -c "import json,sys; print('links total:', json.load(sys.stdin)['total'])"
OK
links total: 2
若目标版本含新迁移,日志会出现 Batch N run: M migrations(首启实测 Batch 1 run: 10 migrations);无新迁移时输出 Already up to date(实测 v3.2.5/v3.2.6 间迁移列表一致,均为 10 个迁移、最新 20250106070444_remove_cooldown.js)。
6.2 回滚
knex 迁移只前进不回退(镜像 CMD 无 migrate:rollback),因此回滚策略取决于新迁移是否改表:
- 3.2.x 补丁版之间(如 v3.2.6 → v3.2.5):迁移集合相同,直接改 tag 重建即可,实测双向切换均正常、数据完整;
sed -i 's|kutt/kutt:v3.2.6|kutt/kutt:v3.2.5|' docker-compose.yml
docker compose up -d server
Already up to date
> kutt@3.2.5 start
> Ready on http://localhost:3000
- 跨小版本回滚(新迁移已执行):先
pg_dump恢复升级前备份(3.2 流程),再换旧镜像——这就是 6.1 中"配置留档 + 备份先行"的原因。
6.3 版本节奏与渠道
GitHub Releases 实测近 5 版:v3.2.6(2026-07-05)、v3.2.5 / v3.2.4(2026-05-17)、v3.2.3(2025-02-06)、v3.2.2(2025-01-24)。3.2.x 为稳定维护线;不要从 2.x 直升 3.x——2.x 用 MongoDB、3.x 用关系库,数据模型完全不同,需按官方迁移指引重建数据(或选 D4 类迁移方案)。镜像 tag 与 Release 同名(带 v 前缀),生产固定版本号、避免 latest。
七、故障排查手册
按"现象 → 定位 → 处置"速查(完整问答见下一章与常见问题篇):
7.1 应用容器反复重启
docker compose logs server | grep -A3 "Missing environment"
================================
Missing environment variables:
JWT_SECRET: undefined
================================
Exiting with error code 1
生产模式缺 JWT_SECRET 即退出(实测)。补齐 .env 后 docker compose up -d。
7.2 短链跳转 502/挂起
依次检查:docker compose ps(server 是否 Up)→ pg_isready(库健康)→ redis-cli ping。实测故障矩阵:REDIS_ENABLED=true 时 Redis 宕机会让跳转与建链请求直接挂起直至客户端超时(ioredis 重试阻塞请求线程,实测 8 秒超时无响应),Postgres 宕机同样挂起——即开启 Redis 后它是跳转链路的强依赖而非软依赖。应急两种:恢复 Redis;或临时把 REDIS_ENABLED 置 "false" 重建 server(跳转退回纯 Postgres 路径,统计失去队列)。两库任一恢复后应用自动自愈,无需重启(实测 stop/start postgres、stop/start redis 后跳转恢复 302)。
7.3 统计计数不动
三查:① 访客 UA 是否被 isbot 过滤(curl 默认 UA 不计数,实测 5 次访问计数为 0);② 队列积压(llen bull:visit:wait);③ stats 接口 60 秒缓存。处置:带浏览器 UA 复测 → 重启 server 消费积压 → 等 1 分钟复查。
7.4 visits 表膨胀
docker exec kutt-3.2.6-postgres psql -U postgres -d kutt -t -c \
"SELECT pg_size_pretty(pg_total_relation_size('visits'));"
超阈值时清理 N 天前聚合行(不删 links,仅历史统计):
docker exec kutt-3.2.6-postgres psql -U postgres -d kutt -c \
"DELETE FROM visits WHERE created_at < now() - interval '365 days';"
7.5 过期链接未清理
内置 cron 每 30 秒删除 expire_in 已过期的链接(实测 1 分钟有效期链接在 90 秒后自动消失,见 8.3)。若未生效,检查 server 进程是否存活(cron 在应用进程内,无独立服务)与日志报错。
八、常见问题
8.1 更换 JWT_SECRET 的后果
所有已登录会话与已签发 JWT 立即失效(用户需重新登录),API key 不受影响(存库)。仅密钥泄露时更换,并同步所有副本。
8.2 忘记密码 / 找回
POST /api/v2/auth/reset-password 依赖 SMTP(MAIL_ENABLED=true)。未配邮件时唯一路径:直接改库——
docker exec kutt-3.2.6-postgres psql -U postgres -d kutt -c \
"UPDATE users SET password='\$2a\$12\$<新bcrypt哈希>' WHERE email='admin@kutt.local';"
哈希在应用容器内生成(bcryptjs 已在依赖中):
docker exec kutt-3.2.6-server node -e "require('bcryptjs').hash('q1w2e3r4', 12).then(h=>console.log(h))"
8.3 有效期链接的自动删除实测
curl -s -X POST http://localhost:8153/api/v2/links -H "x-api-key: <APIKEY>" \
-H 'Content-Type: application/json' \
-d '{"target":"https://example.com/expire-test","expire_in":"1m"}' | \
python3 -c "import json,sys; d=json.load(sys.stdin); print(d['address'], d['expire_in'])"
sleep 90
curl -s "http://localhost:8153/api/v2/links" -H "x-api-key: <APIKEY>" | \
python3 -c "import json,sys; d=json.load(sys.stdin); print('total:', d['total']); [print(x['address']) for x in d['data']]"
8ecnU2 2026-11-01T16:47:58.085Z
total: 2
kutt-proj
6ZErgk
8ecnU2 已不在列表——30 秒周期 cron 生效。另实测 expire_in 下限 1 分钟:"30s" 返回 Expire time should be more than 1 minute.(HTTP 400)。
8.4 备份需要停服吗
不需要。pg_dump 使用一致性快照;Redis 数据为缓存+瞬态队列,可接受重建;custom 为静态文件。建议低峰期每日一次全量 pg_dump,保留 7–30 天。
九、参考链接
- Kutt 仓库(Releases / compose 变体):https://github.com/thedevs-network/kutt
- 官方环境变量样例:https://github.com/thedevs-network/kutt/blob/main/.example.env
- knex 迁移文档:https://knexjs.org/guide/migrations.html
- PostgreSQL pg_dump 文档:https://www.postgresql.org/docs/16/app-pgdump.html
- 部署与验收:《Kutt 3.2.6 Docker Compose 部署指南》
- 选型与架构:《Kutt 3.2.6 介绍与选型指南》