Language:Chinese VersionEnglish Version

Datadog 起价为每月每主机 23 美元。New Relic 的全栈可观测性服务价格为每月每主机 46 美元。如果你的独立 SaaS 项目运行三台服务器,每月收入 2000 美元,那么在为任何一行应用基础设施付费之前,你就要花费 3-7% 的收入在监控上。对于拥有数十名工程师和数百台主机的企业规模团队来说,这些价格是合理的。但对于其他人——独立运营者、小型产品团队、自筹资金的创始人——这些费用对尚未盈利的项目来说是一种架构税。围绕服务器监控 Prometheus Grafana 小团队构建的开源监控堆栈能提供与商业产品真正相当的可观测性,成本仅为几小时的设置时间和每台服务器约 350MB 的额外 RAM。

定价现实的真实情况

Datadog 和 New Relic 的定价页面设计得让比较变得困难。标明的价格——分别是每月每主机 23 美元和 46 美元——代表的是基础套餐,不包含你实际需要的功能:自定义指标、扩展保留期、APM 追踪、日志索引。一个小型团队使用三台主机、自定义应用程序指标、日志摄取和 APM 功能,实际的 Datadog 账单每月接近 180-250 美元。New Relic 在 2022 年改为基于消费的定价模式,听起来更灵活,但一旦实际使用起来往往会更贵。

这并不意味着这些工具不好。特别是 Datadog 拥有真正出色的用户体验、强大的告警基础设施和需要数周才能复制的集成功能。问题是,对于一个可能收入微薄或尚未产生收入的项目,这些优势是否值得每月 200 多美元。对于大多数小型团队,诚实的答案是否定的——不是因为这些工具不好,而是因为开源替代品已经足够好,差距不值得为此付出成本。

这里描述的监控堆栈——Prometheus、Grafana、Alertmanager 和 Uptime Kuma——在软件许可方面没有任何成本,只需要一台你几乎已经拥有的服务器。

堆栈及其各组件功能

这四个工具有着明确、不重叠的职责。了解每个工具的实际功能可以避免将整个堆栈视为单体而带来的困惑。

Prometheus 是一个时序数据库和指标收集引擎。它通过抓取以文本格式公开指标的 HTTP 端点来工作,将这些指标存储在本地,并提供一种查询语言 (PromQL) 用于分析。Prometheus 是基于拉取的——它会按计划调用您的服务,而不是让服务将数据推送到它。这种架构意味着您总是确切知道 Prometheus 在收集什么以及收集频率。资源占用:典型小型部署约需 200MB RAM,磁盘使用量与保留期和指标基数成正比。

Grafana 是一个可视化和仪表板层。它连接到 Prometheus(以及许多其他数据源)并将数据渲染为面板、图表、表格和状态卡片。Grafana 是您花费大部分时间的地方——构建仪表板、调查异常、设置警报规则。资源占用:约 150MB RAM。Grafana 不存储指标;它查询 Prometheus 并实时渲染结果。

Alertmanager 处理在 Prometheus 中定义的警报的通知路由。当 Prometheus 检测到超出阈值的条件——CPU 持续高五分钟、磁盘使用率达到 90%、HTTP 错误率超过 1%——它会将警报发送到 Alertmanager,后者将其路由到适当的目的地:Slack、PagerDuty、电子邮件或 webhook。Alertmanager 还处理去重、分组和静默功能,这些功能是有用警报系统和噪音生成器之间的区别所在。

Uptime Kuma 是一个自托管的状态页面和运行时间监控工具。它按计划检查您的端点(默认每 60 秒一次),跟踪响应时间和状态代码,并生成一个可以与用户共享的公共状态页面。它处理”网站从外部是否可用?”这个问题,这是内部 Prometheus 指标无法回答的,并且它提供了在服务中断期间与用户可靠沟通的事件状态页面。

Node Exporter:主机指标的基础

在 Prometheus 可以收集主机级别的指标——CPU、内存、磁盘、网络——之前,这些指标需要以 Prometheus 理解的格式公开。Node Exporter 就是做这个的。它作为轻量级守护进程在您要监控的每台主机上运行,在端口 9100 上暴露一个 /metrics 端点,Prometheus 会根据您配置的间隔抓取该端点。

安装过程很简单。从 Prometheus GitHub 仓库下载最新版本,解压后将其作为 systemd 服务运行:

# 下载 Node Exporter
wget https://github.com/prometheus/node_exporter/releases/download/v1.8.0/node_exporter-1.8.0.linux-amd64.tar.gz
tar xvf node_exporter-1.8.0.linux-amd64.tar.gz
sudo cp node_exporter-1.8.0.linux-amd64/node_exporter /usr/local/bin/

# 创建 systemd 服务
sudo tee /etc/systemd/system/node_exporter.service <<EOF
[Unit]
Description=Node Exporter
After=network.target

[Service]
User=node_exporter
ExecStart=/usr/local/bin/node_exporter
Restart=always

[Install]
WantedBy=multi-user.target
EOF

sudo useradd -rs /bin/false node_exporter
sudo systemctl daemon-reload
sudo systemctl enable --now node_exporter

Node Exporter 运行后,通过向你的 prometheus.yml 添加作业来配置 Prometheus 抓取它:

scrape_configs:
  - job_name: 'node'
    static_configs:
      - targets: ['localhost:9100', '10.0.0.2:9100', '10.0.0.3:9100']
    scrape_interval: 30s

Node Exporter 默认暴露超过 800 个指标。其中大多数你永远不会查看。对于日常运营真正重要的指标要少得多,这就是为什么有明确观点的仪表板比详尽的指标收集更重要。

真正重要的指标

运行一个有用的监控系统需要选择关注什么。监控一切与什么都不监控会产生同样的问题:当所有内容都在仪表板上时,没有任何内容是重要的。对于典型的 Web 应用服务器,值得跟踪的指标分为六类。

CPU 利用率在低到中等水平时不如大多数人想象的那么重要,但当接近饱和时则变得极其重要。需要关注的指标不是即时的 CPU 百分比,而是持续的利用率——具体来说,连续五分钟以上 CPU 使用率超过 80% 值得发出警报。即时的峰值是正常且预期的;持续的高 CPU 表明存在结构性问题。

内存压力比已使用内存百分比所暗示的更加微妙。可用内存包括内核在压力下可以回收的缓存内存,因此显示内存使用率为 85% 的服务器通常是完全健康的。表明真正内存压力的指标是可用内存趋近于零、活跃的交换使用量增加,以及内核日志中的 OOM 终止。为了获得更准确的图像,应跟踪 node_memory_MemAvailable_bytes 而不是使用百分比。

磁盘使用率和 I/O 分为两个不同的关注点。磁盘空间耗尽是一种硬性故障——当根分区填满时,大多数服务会以有趣且难以调试的方式失败。在达到 100% 之前,设置在 85% 满时发出警报,留有足够的操作时间。磁盘 I/O 饱和表现为 node_disk_io_time_seconds_total 指标升高以及读写延迟增加;特别是在数据库服务器上,这通常是查询模式或数据量发生变化的第一信号。

网络流量 基线跟踪主要用于异常检测。了解您的服务器通常每月传输 500GB 意味着一周内激增至 2TB 会立即显现为异常情况。同时跟踪入站和出站流量;意外的出站流量通常是数据泄露或配置错误的服务将流量发送到不应发送位置的第一个可观察迹象。

HTTP 响应时间和错误率 需要 Node Exporter 提供的应用级指标以外的内容。如果您正在运行 Nginx,nginx-prometheus-exporter 会暴露请求速率、错误率和活跃连接数。对于应用服务器,Go、Python、Node.js 和大多数其他语言的 Prometheus 客户端库可以让您直接为应用程序添加检测,并在 /metrics 端点上暴露自定义指标。定义服务健康状况的两个指标是 p95 和 p99 响应延迟,以及 HTTP 5xx 错误率占总请求的百分比。

正常运行时间和外部可达性 是 Uptime Kuma 填补内部指标留下的空白的地方。一个进程可能正在运行,正常消耗 CPU,并向内部健康检查返回 200 状态码,但由于防火墙规则更改、DNS 传播问题或上游网络问题,无法从公共互联网访问。如果您的产品依赖正常运行时间,来自不同网络位置的外部监控不是可选的。

告警疲劳:设置不会误报的阈值

破坏监控程序的故障模式不是漏报警报——而是来自过多不需要立即采取行动的警报导致的告警疲劳。当工程师开始忽略告警,因为其中大多数是非关键噪音时,真正关键的警报就会在噪音中丢失。这种情况发生得很快,而且比人们预期的更难逆转。

小型团队告警的操作原则是,每次触发的警报都应该要求有人在合理的时间窗口内查看,并且应该有已知的修复路径。如果一个警报定期触发,而响应总是”看起来没问题,无需操作”,那么这是一个应该被删除或调整其阈值的坏警报。

指标 警告阈值 严重阈值 评估窗口
CPU 利用率 > 80% 持续 5 分钟 > 95% 持续 5 分钟 5 分钟
可用内存 < 500MB < 200MB 2 分钟
磁盘使用率 > 80% > 90% 10 分钟
HTTP 5xx 错误率 > 请求的 1% > 请求的 5% 3 分钟
HTTP p99 延迟 > 2s > 5s 5 分钟
外部服务正常运行时间 连续 2 次失败 2 分钟

评估窗口与阈值同样重要。基于单个数据点触发的警报会在短暂峰值(问题自行解决)时持续触发。在 Prometheus 警报规则中使用 for: 5m 意味着条件必须持续五分钟为真才会触发警报,这可以消除大多数误报,代价是真实持续问题的通知会有轻微延迟。

一个简洁但有效的磁盘空间 Prometheus 警报规则:

groups:
  - name: host_alerts
    rules:
      - alert: DiskSpaceWarning
        expr: (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 < 20
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "磁盘空间不足 - {{ $labels.instance }}"
          description: "根文件系统已使用 {{ $value | printf "%.1f" }}%"

      - alert: HighErrorRate
        expr: rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) * 100 > 1
        for: 3m
        labels:
          severity: critical
        annotations:
          summary: "高 5xx 错误率 - {{ $labels.instance }}"
          description: "错误率为 {{ $value | printf "%.2f" }}%"

Grafana 仪表盘:构建有用的,而非美观的

使用 Grafana 时,人们倾向于构建看起来全面的仪表盘。十七个面板覆盖所有可用指标,多行布局,自定义配色方案。这些仪表盘在事件发生时使用起来令人疲惫,而恰恰在你需要仪表盘快速且明确的时候。

持续使用的生产仪表板应该是小型且具有明确观点的。主机概览仪表板应该每个面板回答一个问题:CPU正常吗?内存正常吗?磁盘正常吗?错误率是否升高?响应时间是否正常?覆盖这五个问题的五个面板比覆盖 Node Exporter 暴露的所有指标的二十个面板更有用。

grafana.com/grafana/dashboards 上的 Grafana 仪表板仓库有预构建的 Node Exporter 仪表板(ID 1860 是使用最广泛的),它们是很好的起点。使用社区仪表板的价值在于 PromQL 查询已经过数千用户的测试和优化。缺点是它们往往比必要的更全面——计划删除与你实际关心的事物不对应的面板。

对于特定应用的仪表板,如果你已定义了 SLO(服务级别目标),则围绕它们构建,或者围绕你最关心的面向用户的行为构建。请求数量、错误率和 p95 响应时间——即”RED 方法”——为任何服务仪表板提供了起点。添加针对你特定应用关注点的面板(队列深度、缓存命中率、活动会话)完全取决于你的应用程序的功能。

Grafana Cloud 免费版:一个合理的中等选择

在被监控的同一台服务器上运行 Prometheus 会造成监控可用性问题:如果服务器宕机,你的监控也会随之停止。Grafana Cloud 的免费版为此提供了一个合理的解决方案,无需专用的监控服务器。

免费版提供 10,000 个活跃指标序列、50GB 的日志存储和 14 天的数据保留期——足以监控两三台具有合理指标基数的服务器。该架构使用本地 Prometheus 实例进行抓取和规则评估,并配置远程写入将指标发送到 Grafana Cloud 进行存储和可视化:

remote_write:
  - url: https://prometheus-prod-01-eu-west-0.grafana.net/api/prom/push
    basic_auth:
      username: <your-instance-id>
      password: <your-api-key>

这种混合方法让本地 Prometheus 实例处理抓取和告警,而仪表板和长期存储则位于 Grafana Cloud 中。当你的服务器宕机时,你仍然可以查看最近的指标并调查导致停机的事件时间线——而这正是可见性最重要的时刻。

10,000 个序列的限制听起来很慷慨,直到你理解了指标基数。每个指标名称和标签值的唯一组合都是一个独立的序列。如果你的服务在数百个端点上暴露按 URL 路径的指标,你会很快耗尽 10,000 个序列的限制。将高基数标签(用户 ID、请求 ID、特定 URL)排除在 Prometheus 指标之外,而是使用 Loki 处理日志级别的数据。

Loki 用于日志:缺失的一环

Prometheus 处理数值指标。日志是不同的数据类型——是非结构化文本,需要与指标异常进行搜索和关联。Loki 是 Grafana 的日志聚合系统,设计上采用基于标签的数据模型,以补充 Prometheus。

Loki 的关键架构决策是它不索引日志内容——只索引日志标签(来源、服务名称、主机、级别)。这使得 Loki 的运行成本远低于基于 Elasticsearch 的堆栈,代价是在高容量日志流上的全文搜索性能有所降低。对于小团队来说,这种权衡几乎总是值得的:在适中的服务器上运行的 Loki 可以处理典型小型 SaaS 的日志量,而不会消耗大量资源。

Promtail 是将日志从您的服务器发送到 Loki 的代理。它监视日志文件(或通过 Docker socket 的 Docker 容器日志)并转发带有标签的条目。典型配置会监视系统日志和应用程序日志文件:

scrape_configs:
  - job_name: system
    static_configs:
      - targets:
          - localhost
        labels:
          job: varlogs
          host: my-server
          __path__: /var/log/*.log

  - job_name: nginx
    static_configs:
      - targets:
          - localhost
        labels:
          job: nginx
          host: my-server
          __path__: /var/log/nginx/access.log

当 Prometheus 和 Loki 都将数据输入 Grafana 时,您可以将指标中可见的 CPU 峰值与同一时间戳日志中可见的特定请求关联起来——这正是将监控从通知系统转变为实际调试工具的关键。

何时升级到付费监控

这里描述的开源堆栈有实际局限性,了解这些局限性的位置可以防止您在应该选择更好的付费方案时,浪费时间与工具作斗争。

表明您应该认真考虑付费监控的信号是具体且可观察的:

  • 您的团队已经发展到需要多人可靠接收告警的程度,而您自托管的 Alertmanager 反而导致了值班协调问题,而不是解决这些问题。
  • 您正在运行超过 10-15 个具有复杂服务间依赖关系的主机,维护监控堆栈的操作开销已成为可测量的时间成本。
  • 您需要在微服务之间进行分布式追踪。Prometheus 和 Loki 不能很好地解决分布式追踪问题;Jaeger 或商业 APM 工具可以。
  • 合规性要求需要日志保留策略、审计跟踪,或能够向审计人员展示高可用性的监控基础设施。
  • 您的监控基础设施与应用程序基础设施同时宕机,缺乏可见性使事件解决变得明显困难。这是最具说服力的运营信号。

当监控工具维护每月消耗工程师超过两到三小时的时间——包括升级、存储管理和配置漂移——自托管监控的经济性开始转变。资深工程师每月两小时的时间成本已接近基础版 Datadog 订阅的价格。这个计算改变了答案。

资源开销的现实检查

在专用的监控服务器或轻度使用的主机上运行完整堆栈——Prometheus、Grafana、Alertmanager、Loki、Promtail 和 Uptime Kuma——在典型操作中大约需要 600-800MB 的 RAM。在每月 6 美元、1GB RAM 的 VPS 上,这占用了整个机器。而在每月 12 美元、2GB RAM 的服务器上,则留有足够的余量。

对于小型团队来说,更常见的模式是在承担其他任务的服务器上运行监控堆栈。一个 4GB RAM 的应用服务器可以在其主要工作负载之外舒适地托管监控堆栈,前提是应用服务配置了内存限制,并且将监控组件视为一等进程而非事后考虑。在 RAM 受限的服务器上,应明确设置 Prometheus 的存储配置保留期 —— --storage.tsdb.retention.time=15d —— 以防止磁盘无限增长。

Prometheus 在正常操作期间(抓取和存储)的 CPU 占用较低,而在复杂 PromQL 查询期间较高。在共享服务器上,此堆栈的实际限制是每个 Prometheus 实例监控大约 20-30 个主机,此时专用的监控基础设施成为更好的运营选择。

实用的前进路径

这里描述的监控堆栈——用于指标的 Prometheus、用于仪表板的 Grafana、用于通知的 Alertmanager、用于日志的 Loki、用于外部检查的 Uptime Kuma——代表了一个完整的可观测性设置,在商业平台上每月需要花费 500 美元以上。开源版本则需投入设置时间、少量服务器 RAM 以及每季度几小时的更新和配置变更等持续维护工作。

从每台主机上的 Node Exporter 和单个 Prometheus 实例开始。添加 Grafana 并连接到您的 Prometheus 实例。构建一个涵盖 CPU、内存、磁盘和网络的主机概览仪表板。配置两三个真正可操作的警报。添加 Uptime Kuma 并指向您的公共端点。这个核心设置可以在几小时内完成,并覆盖 80% 的监控价值。随着需求的明确,逐步添加 Loki、Alertmanager 配置和应用特定指标。

放弃自托管监控的团队通常是因为他们一次性构建了太多内容,无法维护这种复杂性,并得出结论认为工具本身有问题。而成功运行自托管监控的团队从小处着手,只添加他们需要的功能,并将监控堆栈视为生产基础设施,给予与所监控应用程序同等的重视——包括固定版本、重启策略以及对 Prometheus 数据目录的实际备份策略。

监控并不是一个投入越多资金就一定能获得更好结果的领域。监控的结果是可见性,而这种可见性取决于您配置的警报是否真正有意义,以及您构建的仪表板是否反映了您需要解答的问题。这些都是工程决策,而非许可决策。

By Michael Sun

Founder and Editor-in-Chief of NovVista. Software engineer with hands-on experience in cloud infrastructure, full-stack development, and DevOps. Writes about AI tools, developer workflows, server architecture, and the practical side of technology. Based in China.

Leave a Reply

Your email address will not be published. Required fields are marked *

You missed