公司动态
The Caretakers:打造自动化系统看护与自愈运维体系
The Caretakers 这个名字听起来像是一个固定项目但它更像是一类“系统看护自动化”方案的统称。简单说健康检查、服务拉起、日志清理、磁盘告警、任务失败重试这些零散能力组合在一起就是一套基础版 caretaker 体系。它解决的实际问题很具体服务在凌晨三点挂了没人知道、磁盘被日志写满导致批处理失败、任务中断后没有自动重试、告警发了一堆却不知道先处理哪个。适合看这篇文章的人也比较明确想给自己机器、测试环境或小团队服务加一层“自动值守”能力的开发者刚开始接触运维的初级工程师以及正在把单机脚本往任务队列化、可观察方向改造的人。下面这些例子偏 Linux 服务器和命令行但判断标准和排查思路是通用的。有人会觉得这类工作交给监控平台不就行了。实际上大部分场景的瓶颈不是“没有监控工具”而是没有一个自己能掌控、能改参数、能快速验证的看护脚本。监控平台解决的是“看到问题”Caretakers 要解决的是“看到之后怎么处理”以及“处理完怎么知道真的恢复了”。下面按我实际落地习惯的顺序拆开讲。1. 先搞清楚它到底是一套项目还是一类运维思路1.1 名字在不同语境里的几种理解先说结论如果你去搜索引擎搜 The Caretakers会发现同名信息分布得很散。它可能是某个团队的项目代号可能是作品名、频道名也可能只是内部文档里用来描述自动值守模块的占位名称。至少我没有找到一套被所有人统一引用的官方源码仓库。所以在技术落地时不要先去找“官方源码”而是先确认你手里那套要解决的问题是什么。我通常会先做三件事把 The Caretakers 当作工作代号而不是具体依赖避免默认某个仓库存在。把需求拆成健康检查、自动修复、通知告警、定期清理四类。先确认当前环境有没有 systemd、cron、supervisor 这类基础组件再决定要不要引入额外脚本或依赖。这样处理后你会发现它更像一个“运维思路”而不是某个必然存在的项目。看清这一点能避免很多无效搜索和方向性错误。1.2 大多数场景真正需要的四种能力按真实系统的优先级排序大多数场景需要这四种能力可观察服务活着吗依赖正常吗日志有没有异常增长。可恢复监测到异常后能不能自动重启、重试或跳过。可通知恢复成功或反复失败后是否有人能看到。可清理临时文件、旧日志、过期任务结果能否自动清理。整套方案本身不复杂但很考验边界控制。“自动重启”很有效可如果服务因为配置错误重启一百次反而比不重启更糟糕。所以后面会专门讲参数和熔断。这一段的实操意义是把标题里的名词翻译成可执行需求。与其问“The Caretakers 怎么部署”不如问“我能接受的自动操作边界是什么”。边界想清楚之后工具选什么反而没那么重要。2. 落地前先确认环境、权限和通知通道2.1 最小环境清单在做任何自动化之前先确认三件事机器能不能跑脚本、脚本是否长期运行、通知能否发出去。以下是我常用的检查项检查项说明最低要求操作系统本文示例基于 LinuxDebian/Ubuntu/CentOS 均可运行方式脚本、systemd timer、cron至少支持 bash内存200MB 以上更稳妥视服务数量而定磁盘预留 /var/log 空间1GB 以上通知通道webhook 或邮件能收到测试消息即可不同系统差异比较大。macOS 没有 systemd很多场景用 launchdWindows 则用计划任务。所以不要照抄 systemd 示例先看自己平台支持什么。如果目标平台是 Windows尽量把逻辑写在 PowerShell 或 Python 里再用任务计划程序调度。2.2 权限和账号边界我不建议所有任务都用 root 跑。原因很简单root 权限下脚本一旦写错路径或删除条件影响面会被放大。更稳妥的方式是给脚本单独建一个专用用户或者使用具备目标服务重启权限的最小账号。判断标准很简单看脚本要操作什么。如果只读取状态并发送通知普通用户即可如果需要 systemctl restart 某个服务就需要权限组或 sudo 白名单如果要删除日志和临时文件还要确认目录属主。如果权限不够优先选择“提权白名单”而不是直接切到 root。这样即使脚本逻辑出现 bug破坏范围仍然可控。很多人在本地测试时用 root 一切正常换到服务器上就开始报 permission denied基本都是没有提前规划权限边界。2.3 先准备一个能确认送达的通知通道通知是 caretaker 方案里最容易忽略的环节。我先解释为什么脚本跑完了如果通知环节失败那整个看护仍然是无声的。我一般会先做一个最简单的测试手动向 webhook 或邮件发送一条测试消息确认通道可用再进入脚本编写。例如用 curl 测试 webhook 时通常会先发一条固定内容curl -fsS -X POST https://your-notify-server.example.com/hook \ -H Content-Type: application/json \ -d {text: caretaker test message}只要这一步能收到消息后面脚本里的通知逻辑就只需要处理“服务名不一样、内容不一样”这些变量。通知服务尽量选支持重试和可追溯的比如响应里带请求 ID或者把发送结果落盘保存方便后续排查“为什么没收到告警”。3. 先写一个最小可运行的“看护脚本”3.1 第一版只做三件事检查、拉起、记录我最开始写这类脚本不是从网上找几十个参数的高级模板而是先保证一条链路能通。最小版本只需要做三件事检查目标服务是否健康。不健康时执行预先定义好的重启命令。把结果追加到日志文件。一个简单的 bash 版本可以这样写注意这里只列出关键逻辑路径和命令需要按自己环境调整#!/usr/bin/env bash set -euo pipefail SERVICE_NAMEwebapp CHECK_URLhttp://127.0.0.1:8080/healthz LOG_DIR/var/log/caretaker if curl -fsS --max-time 5 $CHECK_URL /dev/null 21; then echo $(date %F %T) $SERVICE_NAME healthy $LOG_DIR/$SERVICE_NAME.log exit 0 fi echo $(date %F %T) $SERVICE_NAME down, restart $LOG_DIR/$SERVICE_NAME.log systemctl restart $SERVICE_NAME看到这个版本先不要急着追求优雅。它的价值在于把健康检查和重启这两个动作绑定到了一个可观察、可重复运行的文件里。之后每增加一个能力都是在这条主链路上添加分支。第一次跑的时候建议手动停掉服务测试一次确认脚本真的能拉起服务而不是只写了“看起来能重启”的代码。3.2 用 systemd timer 还是 cron脚本写完之后需要一个调度器每 30 秒或每分钟跑一次。这直接取决于你的环境cron 最简单但最小粒度是分钟没有持久化记录和依赖管理。systemd timer 支持秒级调度日志由 journald 管理配置略多。supervisor 适合守护持续运行的进程不适合一次性检查任务。系统里已有 systemd 时我更推荐 systemd timer。原因是它能把执行记录、失败状态、标准输出统一交给系统管理。下面是一个最小示例[Unit] DescriptionRun caretaker check [Timer] OnCalendar*:0/1 Persistenttrue [Install] WantedBytimers.target这里解释一下关键参数。OnCalendar*:0/1表示每分钟的第 0 秒执行Persistenttrue表示如果机器曾经关机错过执行时间重启后会补做一次。这个“补做”特性很重要很多监控遗漏就是因为停机期间任务没有补偿机制。如果用 cron通常写成* * * * *想精确到秒就要换 systemd timer。3.3 日志、退出码和状态判断脚本里最容易犯的错误是只看“命令是否执行成功”不检查命令的真实含义。比如curl -fsS的-f是为了让 HTTP 4xx/5xx 返回非零退出码但如果你只写curl URL服务返回 500 时脚本可能仍然认为健康。判断是否健康最好同时看退出码和响应体。我建议日志里至少包含三部分信息时间戳格式用 ISO 8601方便排序。服务名和状态比如 healthy、down、restarted、failed。触发动作比如 restart、cleanup、notify。这样后续排查时不用猜脚本当时做了什么。如果发现大量 restarted 记录说明服务本身不稳定应该去查服务日志而不是继续加脚本逻辑。如果退出码是 0 但服务实际异常先确认检查地址是否真的指向健康检查端点而不是首页。4. 从单服务到多服务参数化、批量化和任务队列4.1 服务清单和公共参数表当要维护的服务从 1 个变成 5 个、10 个之后在脚本里写满 if else 是错误做法。正确做法是参数化把服务名、检查地址、重启命令、超时时间、重试次数、通知开关都抽出来放到一个配置文件或数据结构里。我一般用 Python 写配置时是这样的结构SERVICES [ { name: webapp, check_url: http://127.0.0.1:8080/healthz, restart_cmd: systemctl restart webapp, timeout: 5, max_retry: 2, notify: True, }, { name: worker, check_url: http://127.0.0.1:8081/healthz, restart_cmd: systemctl restart worker, timeout: 10, max_retry: 0, notify: True, }, ]这样新增一个服务只需要新增一条配置不需要改逻辑。这也是批量化的前提。配置和代码分离还有一个好处运维同学不需要改脚本只要改配置就能接入新服务。4.2 并发、超时和失败重试怎么取舍批量任务看起来只要用 for 循环遍历服务就行真正需要注意三个参数timeout单个检查请求的超时时间建议根据服务响应速度设置。设置太短会误报太长会被一个慢服务拖死。max_retry连续失败后的重试次数。重试能缓解瞬时抖动但服务因为配置错误挂掉时重试只会加重负载。concurrency同时检查的服务数量。默认先串行确认没有资源竞争后再逐步增加并发。判断标准可以按这个经验来当只有 1 个服务异常时并发影响不大当多个服务同时异常时并发请求可能把负载推得更高。所以除非服务数量很大否则先串行更稳妥。我实际测试过同一台机器上 20 个服务的健康检查串行跑完也就几秒钟完全没必要为了并发而并发。4.3 输出命名、去重和人工确认批量场景还有一个容易被忽略的问题告警去重和人工确认。如果某个服务持续异常脚本每分钟发一条告警很快通知通道就会被刷爆人也容易产生“告警疲劳”。更合理的做法是引入状态变化机制首次异常时发一条告警。后续持续异常时不重复发或者每隔一段时间再提醒一次。服务恢复之后发一条恢复通知。日志文件命名也要统一建议按“服务名/日期/任务ID”组织目录方便按时间回溯。输出文件避免覆盖每次执行都生成独立记录否则排查时只能看到最后一次结果。有些团队喜欢把所有告警都堆在一个群表面看很热闹真出了问题反而没人响应。5. 别只看“能不能跑”要看稳定性和资源占用5.1 指标怎么定很多刚开始搭建看护系统的人只看“脚本能跑”这是不够的。至少要看四个指标指标含义判断方法检查成功率单次检查是否正常返回统计日志里 healthy 的占比单轮耗时一轮批量检查花费的时间不超过调度间隔的 1/3误报率服务正常但被判定异常的比例对比服务日志和看护日志恢复耗时从异常到恢复的时间与重启时间、服务启动时间有关如果一个脚本单轮耗时 50 秒却设置了每 30 秒调度一次任务必然堆积。这种情况要先看日志通常会发现某个服务响应过慢或某个命令排队等待。单轮耗时这个指标特别重要它决定了调度间隔怎么设计。5.2 资源占用和告警风暴低配置机器也可以跑 caretaker但需要注意资源占用。每分钟执行健康检查、读日志、写日志这些操作看似轻量批量大了以后会有 CPU 和磁盘 IO 波动。我的建议是不要在高峰期跑大范围的日志扫描。检查频率不要高于实际需求。服务 30 秒检查一次足够就没必要 5 秒一次。日志要设置轮转避免单日志文件无限增长。如果脚本导致机器负载上升先看是不是并发设置过高或者检查目标服务本身有排队现象。不要一上来就怪脚本先看数据。你也可以在任务执行前后记录date %s%N计算真实耗时再决定是不是需要优化检查逻辑。5.3 重复运行、幂等性和锁cron 或 timer 调度脚本存在一个隐患上一次任务还没结束下一次任务又开始了。对于健康检查这种短任务影响不大但如果是清理磁盘、删除旧日志、迁移文件这类任务重复运行就可能导致错删或双重处理。解决方式有两个一是用锁文件防止并发二是让任务本身具备幂等性。锁文件非常简单Linux 下用 flock 就能实现exec 9/tmp/caretaker.lock flock -n 9 || exit 0这里解释一下flock -n表示非阻塞尝试获取锁如果拿不到说明上一次任务还在跑直接退出。exit 0而不是exit 1是为了不让 cron 把并发跳过当成错误来告警。幂等性则要求脚本无论执行一次还是多次结果都应该一致。比如清理旧文件时只按“修改时间早于 N 天”来删而不是按文件名去重这样才能保证多跑几次不会误删新文件。6. 常见报错与排查顺序6.1 先看现象再分输入、环境、参数、工具四层排查后台任务报错时最容易犯的错误是直接改脚本。实际上很多问题根本不在脚本逻辑里。我自己的排查顺序是先看日志和退出码确认是脚本没执行、执行失败还是执行成功但结果异常。检查输入比如健康检查地址、服务名、文件路径是否存在。检查环境比如权限、时区、磁盘空间、依赖版本。检查参数比如 timeout、max_retry、并发数、轮询间隔。最后才怀疑工具本身。这个过程是一条链路不要跳步。比如脚本报 permission denied直接改脚本逻辑没用真正要改的是用户权限或目录属主。这类问题往往不是脚本 bug而是部署时的“环境假设”没有满足。6.2 几个高频问题根据我的经验几个高频问题往往集中在这些地方现象检查点常见原因任务没执行cron/systemd 状态、时区时区不同导致调度时间错位输出为空目录是否创建、权限日志目录不存在脚本没有权限创建一直重启restart_cmd 是否正确服务单元名写错或依赖未启动通知收不到webhook URL、网络通知服务地址变了或证书过期重复执行锁文件、幂等性调度器配置错误或锁文件路径不可写时区这个问题特别容易坑人。cron 默认使用系统时区如果你的机器是 UTC而你的预期是北京时间所有定时任务都会偏移 8 小时。检查时先用date确认系统时区再看调度器的时区配置。如果脚本里用了date生成日志文件名时区不对会导致每天的文件分割点完全错乱。6.3 预判边界看护脚本不是万能运维最后要说清楚边界。Caretakers 适合处理已知异常模式的自动恢复但不能替代人工排障。如果服务日志频繁报数据库连接错误、配置错误或代码缺陷正确做法是解决根本问题而不是让脚本每分钟重启一次服务。我通常会设置一个熔断条件比如同一服务在 10 分钟内被重启超过 3 次就停止自动重启只保留通知。这个逻辑可以用参数配置实现本质上是对自动操作设置信任边界。判断标准也很简单自动化的目的是让人少处理重复问题而不是掩盖真正的问题。7. 从“脚本”到“产品”的几条路线7.1 监控告警体系怎么选当脚本稳定运行一段时间后你可能会发现它仍然停留在“单机自愈”层面。如果需要覆盖多台机器或需要集中查看历史状态就可以考虑把健康检查结果上报到现有监控体系比如 Prometheus、Zabbix 或团队已有的通知平台。我建议的过渡方式不是推翻重写而是让脚本继续保留自愈逻辑同时增加一个“上报通道”。这样监控平台负责看趋势脚本负责现场处理两边职责清晰排查起来也方便。上报时要注意数据格式统一否则监控平台侧要写一堆解析逻辑。7.2 自动化修复的灰度思路自动化修复最怕一上来就全员放开。更稳的做法是分阶段灰度第一阶段只检查、只记录、只通知不做任何自动修改。第二阶段对低风险服务开启自动重启比如临时 worker 或非核心应用。第三阶段对核心服务开启自动恢复但要配置熔断和人工确认。这个思路适合所有自愈型任务。先积累足够的异常样本和处理记录再决定是否让机器自动操作比直接相信脚本可靠很多。灰度期间建议保留一份“人工可一键关闭自动修复”的开关避免突发状况下改配置都来不及。7.3 可以沉淀成接口和配置模板当配置项越来越多后可以考虑把检查任务做成一个可配置的 CLI 或 API而不是继续往 bash 里堆变量。接口化的价值在于其他地方可以通过 HTTP 请求触发一次检查也可以把服务列表放到配置中心统一管理。模板方面我至少会沉淀三样东西服务清单模板包含名称、检查地址、重启命令、超时、重试、通知开关。日志规范模板统一时间戳、服务名、状态、动作四个字段。告警文案模板异常告警和恢复通知分开方便群里机器人解析。这些沉淀不依赖具体语言和框架迁移成本很低。就算以后换了监控平台只要配置和数据格式保持清晰成本主要在外层不在逻辑里。如果你只想记住一句话那就记住The Caretakers 不要一开始追求大而全先跑通一条“检查、记录、重试、通知”的最小链路再逐步加参数、加服务、加接口和灰度开关。