返回博客

用三个 Bash 脚本收敛日常运维:状态总览、健康检查与依赖顺序重建

Lazybones Follow 0 2026年09月07日 运维与监控 102 次阅读

一台服务器上跑十几个 Docker 容器加几个裸机服务时,日常运维不能靠记忆敲命令。本文讲如何用三个 Bash 脚本把「看状态、查健康、重建服务」收敛成三条命令,以及过程中容易踩到的 Bash 坑。

问题:命令散落、记忆依赖

容器化完成后,服务散在 /srv/stacks/ 下十几个子目录里,每个都有自己的 docker-compose.yml。日常运维面临三个痛点:

  • 状态查看:要依次 cd 到各目录跑 docker compose ps,或者 docker ps -a 看满屏输出,分不清哪些是业务容器、哪些是基础设施。
  • 健康检查:端口是否在监听、域名是否可达、SSL 证书还剩几天、磁盘有没有快满——全靠人肉逐项查,容易漏。
  • 服务重建:改了配置或代码后要 cd /srv/stacks/xxx && docker compose up -d --build,十几个服务敲十几遍,还不能搞错依赖顺序。

于是可以把这些操作收敛成三个脚本,放在 /srv/stacks/scripts/ 下:

scripts/
├── service-manager.sh    # 统一服务管理(status/start/stop/restart/logs)
├── health-check.sh       # 一键健康检查(46 项)
├── quick-deploy.sh       # 快速部署/重建
├── backup.sh             # 自动备份
└── restore.sh            # 从备份恢复

service-manager.sh:统一服务管理

核心思路是建立一个服务注册表——用 Bash 数组描述每个服务的 compose 目录、容器名、端口、所属层级:

# 格式: "服务名|compose目录|容器名(逗号分隔)|端口|层级"
# 层级: 0=基础设施  1=数据层  2=业务层
DOCKER_SERVICES=(
    "traefik|/srv/stacks/traefik|traefik|80,443|0"
    "authelia|/srv/stacks/authelia|authelia|443|0"
    "mariadb|/srv/stacks/app|app_mariadb|3306|1"
    "redis|/srv/stacks/app|app_redis|6379|1"
    "web|/srv/stacks/web|web|443|2"
    "worker|/srv/stacks/web|worker,worker-cron|443|2"
    # ... 共 14 个 Docker 服务 + 3 个裸机服务
)

按层级分组展示

status 命令按 基础设施层 → 数据层 → 业务层 三层分组展示,输出一张彩色表格:

╔══════════════════════════════════════════════════════════════════╗
║                    服务器服务状态总览                            ║
╚══════════════════════════════════════════════════════════════════╝

+----------------+----------+--------------+----------+----------------------+
| 服务           | 类型     | 状态         | 端口     | 运行时间             |
+----------------+----------+--------------+----------+----------------------+
|── 基础设施层 ────────────────────────────────────────────────────
| traefik        | Docker   | ● 运行中     | 80,443   | 2d 5h 30m            |
| authelia       | Docker   | ● 运行中     | 443      | 2d 5h 30m            |
|── 数据层 ────────────────────────────────────────────────────────
| mariadb        | Docker   | ● 运行中     | 3306     | 2d 5h 30m            |
| redis          | Docker   | ● 运行中     | 6379     | 2d 5h 30m            |
|── 业务层 ────────────────────────────────────────────────────────
| web            | Docker   | ● 运行中     | 443      | 2d 5h 30m            |
| worker         | Docker   | ● 运行中     | 443      | 2d 5h 30m            |
|   └worker      |          | ● 运行中     |          |                      |
|   └worker-cron |          | ● 运行中     |          |                      |
+----------------+----------+--------------+----------+----------------------+

多容器服务(如某个应用有 web / worker / cron 多个容器)会自动展开子行,一眼就能看出哪个子容器掉了。

依赖顺序启动/停止

启动和停止不是随机顺序,而是按依赖拓扑:

# 启动顺序:基础设施 → 数据层 → 业务层
START_ORDER=(traefik authelia mariadb redis worker web ...)

# 停止顺序:反向
STOP_ORDER=(web worker ... redis mariadb authelia traefik)

这样确保数据库先于应用启动、反向代理先于业务服务启动,避免启动时连接失败。

日志查看

./service-manager.sh logs traefik      # 最近 100 行
./service-manager.sh logs -f web       # 实时跟随

Docker 服务走 docker compose logs,裸机服务走 journalctl,接口统一。

health-check.sh:46 项一键健康检查

这是三个脚本里最「值钱」的一个。完整模式跑 46 项检查,分 10 个检查组:

检查组 项目数 检查内容
Docker 容器状态 16 逐个检查所有容器是否 running
裸机服务状态 3 例如几个尚未容器化的 systemd 服务
关键端口监听 6 22、80、443 与三个自定义高位端口
域名可达性 9 所有域名 HTTPS 返回码
磁盘使用率 2 根分区 + 各挂载点(阈值 85%)
内存使用率 1 总体使用率(阈值 85%)
数据库连接 2 MariaDB ping + Redis PONG
备份状态 2 24h 内配置备份 + 数据卷备份
SSL 证书有效期 9 所有域名证书剩余天数(30天告警)
快速模式 2 --quick 仅查容器/服务状态

检查结果格式

每项检查结果用 emoji 标记状态,底部输出汇总:

  ── Docker 容器状态 ──
  ✅ traefik                          运行中
  ✅ authelia                         运行中
  ✅ web                              运行中

  ── 磁盘使用率 ──
  ✅ 磁盘使用率                       59%
  ✅ 分区 /home                       12%

  ── SSL 证书有效期 ──
  ✅ www.example.com                  剩余 72 天
  ⚠️ proxy.example.com               剩余 18 天(30 天内告警)

  ════════════════════════════════════════
  检查汇总
  ════════════════════════════════════════
  总计: 46 项
  通过: 43
  告警: 2
  失败: 1

快速模式

--quick 模式只检查容器和服务状态,跳过端口探测、域名 curl、SSL 证书检查等网络操作,适合频繁巡检或网络不通时使用。

quick-deploy.sh:按依赖顺序重建

解决「改了配置要重建十几个服务」的问题。支持三种用法:

./quick-deploy.sh web              # 重建指定服务
./quick-deploy.sh all              # 按依赖顺序重建全部
./quick-deploy.sh --pull all       # 拉取最新镜像 + 重建全部

--pull 适合版本升级场景:先 docker compose pull 拉新镜像,再 up -d --build 重建。

踩坑一:Bash ((var++)) 在 var=0 时中断脚本

这是写这些脚本时遇到的最隐蔽的坑。

现象:health-check.sh 跑到某处就莫名退出,退出码非零,但没有报错信息。

根因:脚本启用了 set -euo pipefail(严格模式),里面有计数器递增:

PASS_CHECKS=0
# ... 某个检查通过后
((PASS_CHECKS++))   # ← 这里出问题了

((expr)) 算术扩展的退出码取决于求值结果——结果为 0 返回退出码 1。((PASS_CHECKS++)) 是后置递增:先返回原值(此时为 0),再自增。原值 0 → 退出码 1 → set -e 判定为错误 → 立即退出脚本。

修复:改用赋值形式,赋值操作恒成功:

# ❌ 危险:var=0 时 ((var++)) 退出码为 1
((PASS_CHECKS++))

# ✅ 安全:赋值恒成功
PASS_CHECKS=$((PASS_CHECKS + 1))

# ✅ 也可以用前置递增:((++var)) 先自增再返回,var=0→1,退出码 0
((++PASS_CHECKS))

可复用经验:set -e 脚本里绝不用 ((var++)) 做计数器递增。这是 Bash 的一个经典陷阱——算术扩展的退出码语义与直觉相反。

踩坑二:时长格式化函数的字符串测试

现象:fmt_duration 函数在某些容器名不存在时输出乱码。

根因:函数内部用 -z 测试字符串是否为空,但变量未初始化时在算术上下文中展开会报语法错误:

# 原始写法有问题
fmt_duration() {
    local sec="$1"
    if [[ -z "$sec" ]]; then ...  # ← 当 $1 为空时,后续算术运算会炸
}

修复:加默认值 + 正则校验,确保输入是合法数字:

fmt_duration() {
    local sec="${1:-0}"
    if ! [[ "$sec" =~ ^[0-9]+$ ]] || [[ "$sec" -le 0 ]]; then
        echo "< 1m"; return
    fi
    # ... 正常计算
}

踩坑三:systemd 时间戳解析兼容性

获取裸机服务的运行时长时,systemctl show -p ActiveEnterTimestamp --value 返回的时间戳格式是 Mon 2026-09-07 16:54:10 CST。这个格式在不同 locale 下可能变化,date -d 解析偶尔失败。

修复:始终加 2>/dev/null || echo 0 降级,解析失败就返回 0(显示 "< 1m"),不让一个服务的时间解析失败拖垮整个脚本。

别名与软链接

为了让日常使用更顺手,可以在 ~/.bashrc 里加别名:

alias svc='/srv/stacks/scripts/service-manager.sh'
alias health='/srv/stacks/scripts/health-check.sh'
alias deploy='/srv/stacks/scripts/quick-deploy.sh'

日常操作变成:

svc status          # 看状态
health              # 跑健康检查
deploy --pull web   # 拉新镜像重建 Web 服务

小结

三个脚本的设计原则:

  1. 注册表驱动:服务清单集中在数组里,增删服务只改注册表,不改逻辑。
  2. 层级感知:启动/停止/展示都按依赖层级排序,避免启动顺序错误。
  3. 严格模式 + 优雅降级:set -euo pipefail 保证脚本不静默失败,但单个服务的检查失败用 || warn 降级,不拖垮整体。
  4. 输出面向人:彩色、表格、emoji,一眼看出哪里有问题,不需要 grep 日志。

这三个脚本配合多层安全加固,基本就能覆盖日常运维的主要场景。

评论 (0)

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

登录 后可以发表评论