-今日
-本周
-本月
-累计
独立访客
项目工作台‹运维 个人项目与实践记录

运维体系

站点各部分的实现方式与选择理由。

部署架构

/var/www/releases/<时间戳>-<标签>/   不可变发布物,每个版本独立
/var/www/personal-site              软链接 → 当前生效版本
/var/www/releases/.current          当前版本指针
/var/www/releases/.history          版本历史(回滚依据)

用软链接而不是直接覆盖文件,因为覆盖是破坏性的: 切一半失败会得到半新半旧的状态。软链接切换是 原子的(mv -T 的 rename 语义), 要么全新要么全旧,没有中间态。

发布流程(9 步,每步失败都不留半成品)

步骤动作失败时
1校验源目录(必须有 index.html)拒绝发布
2记录回滚点 + 备份 nginx 配置中止
3创建新 release 目录(cp -a)中止
4权限预检(以 nginx 用户试读)删除新目录,中止
5nginx -t 语法校验删除新目录,中止
6原子切换软链接—
7reload(非 restart)—
8健康检查 5 次自动回滚
9清理旧版本(保留 5 个)跳过生效版本

为什么是 reload 而不是 restart

方式总请求失败master 进程
reload25,4960保留
restart25,612408更换

restart 的失败数是 408 个(393 个 connection refused + 15 个 connection reset)。这一组是同时跑的对照:它说明压测方法本身 能检测到服务中断,因此 reload 那一组的零失败才可采信。

流量防护(按 20 GB/月 额度设计)

CDT 免费额度按地域区分:中国内地 20 GB/月, 非中国内地 200 GB/月。本机在青岛,所以额度是 20 GB ≈ 682.7 MB/天。

limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_req  zone=general burst=20 nodelay;
limit_conn perip 20;
limit_req_status 429;

取值依据:真人浏览瞬时峰值约 10–20 请求/秒,自动化工具通常 100+ 请求/秒, 所以 10r/s + burst=20 只挡后者。

双向验证(只测"能拦住"是不完整的——过严的限流赶走真人更糟):

实验负载结果
A 正常用户2 请求/秒 × 1010/10 成功,零误伤
B 攻击负载8 并发,2320 QPS11,547 个被 429 拦截

监控与告警

lib/traffic-monitor.py            日志分析引擎(纯标准库)
bin/traffic-check.sh              阈值判定 + 告警
/etc/cron.d/ops-lab-traffic       每小时巡检
/etc/cron.d/ops-lab-traffic-daily 每日存档(不发邮件)
logs/traffic/                     历史报告(自动保留 48 份)
logs/alerts/                      告警记录
bin/db/verify-backup.sh           每天 03:40 在一次性容器里试恢复最新备份
logs/db-backup/                   备份文件与每日校验日志

阈值来自额度换算而非拍脑袋:20GB ÷ 30 天 = 682.7 MB/天, 50% 警告、80% 严重。

测量局限(这点很重要):/proc/net/dev 的计数 包含内网流量——阿里云 agent 大量发往 100.64.0.0/10 内部服务网,不出机房、不计费。 实测站点出流量只占 eth0 发送量的 5.58%,真实计费数字要看控制台 CDT。

日常巡检

巡检的目的是在问题变成故障之前发现它。下面是每天实际执行的顺序, 从头做一遍就能确认整台服务器的状态。

第一步:跑一次全量快照

healthcheck.sh 把负载、内存、磁盘、inode、失败服务、监听端口、 占用最高的进程、网卡累计流量、fail2ban、备份数量、练习环境与已知偏差一次打全, 并落成一份带时间戳的快照:

cd /root/ops-lab
bash bin/healthcheck.sh
ls -t logs/health-*.txt | head -n 1

快照文件名是 health- 加年月日时分秒,历史快照留着用于对比。

第二步:看自动巡检的结果

不用手动跑也不会漏:每天 08:00 由 cron 触发 lib/daily-check.py,把 11 项检查跑一遍,结果写进日志。 读三个文件就能掌握现状:

cat logs/daily-check/latest.txt          # 最近一次巡检全文
ls -t logs/alerts/ | head -n 5            # 最近的告警文件
tail -n 50 logs/daily-check/cron.log      # 定时任务的执行记录

11 项检查依次是:磁盘、inode、服务、心跳、内存、负载、流量、数据库、Tomcat 代理、目录占用、脚本自洽。任何一项越界都会抬高整体等级。

告警不会重复轰炸:脚本把上一次的等级写在 logs/daily-check.state,只有等级发生变化时才发邮件。 所以邮箱里没有新信,不代表巡检没跑 —— 也可能是问题还在、等级没变。

第三步:核对阈值

判断越界用的是固定阈值,不是感觉:

磁盘使用率      80%
inode 使用率    80%
可用内存        200 MB
系统负载        4.0
心跳文件年龄    180 秒
流量占日均预算  50% 警告 / 80% 严重
数据库备份年龄  48 小时

这些值写成常量放在 lib/daily-check.py 顶部,要调只改那一处。 心跳那一项看的是 /var/log/quest02.log 的修改时间,而不是进程是否存在 —— 进程活着但不再干活,也是一种故障。

第四步:分项排查

整体巡检报出异常后,用对应的单项工具定位:

bash bin/f2b-status.sh                # fail2ban 是否在跑、封了多少、增长快不快
bash bin/deviation-check.sh           # 对照 docs/KNOWN-DEVIATIONS.md 审计已知偏差
python3 lib/daily-check.py --verbose  # 显示 OK 项的完整巡检

--verbose 把通过的检查也打印出来,用来确认「没报错」是真的通过, 而不是检查项根本没被执行。

第五步:单独看流量口径

服务器按流量计费,所以流量单独看一眼。每小时与每天各跑一次 bin/traffic-check.sh,报告自动保留最近 48 份:

ls -t logs/traffic/traffic-*.txt | head -n 1 | xargs tail -n 20

报告里的「占日均预算」按 20 GB ÷ 30 天 = 682.7 MB/天 折算, 50% 警告、80% 严重。注意 /proc/net/dev 的计数包含内网流量, 真实计费口径要看阿里云控制台。

技术选型理由

选择理由
nginx 直跑静态文件内存 14.8 MB;Node/Python 后端起步 50–100 MB
不用 Docker 跑网站多一层 overlayfs 与网络命名空间开销,2 GB 内存要留给其他练习
不用宝塔面板本项目目的是理解各组件的手动配置过程。面板自动生成的配置不透明,出问题时需先弄清它的生成逻辑才能定位;这类工具更适合快速建站场景,与练习目的不同
自研 Markdown 转换器服务器软件越少攻击面越小;且零依赖可审计
CSS 内联进 HTML省一次 HTTP 请求,弱网下体感明显,也少一次被限流的可能

工具集

工具用途
backup-config.sh改配置前自动备份(保留 30 份)
healthcheck.sh一键系统巡检
deploy.sh原子发布(9 步 + 自动回滚)
rollback.sh秒级回滚
traffic-check.sh流量巡检 + 分级告警
db/verify-backup.sh每天在一次性容器里试恢复最新数据库备份并校验
test-narrow-path.sh窄 PATH 下跑两个回滚脚本,缺命令必须拒绝执行
lib/loadtest.py标准库并发压测器(支持按时长)
lib/md2html.pyMarkdown 转换器(含 XSS 防护、图片与有序列表起始编号,61 项测试)