跳至主要内容
五折优惠 全部方案,限时优惠。起价 $2.48/mo
14 min left
开发者工具与 DevOps

在 VPS 上搭建 Grafana + Prometheus 监控栈(Docker Compose 指南)

C 作者 Chike 14 分钟阅读
Prometheus and Grafana monitoring stack illustration: a central dashboard panel showing Prometheus and Grafana logos over metric graphs, with three server nodes feeding metrics into it

如果你还不清楚 Prometheus 和 Grafana 到底是什么,可以先读我们这篇文章: Prometheus 与 Grafana 对比。如果你已经清楚两者的区别,下面就是把它们一起跑起来的方法。

本文是配套的实战篇。它用 Docker Compose 在单台 VPS 上部署 Prometheus、Grafana 和 Node Exporter,用 Caddy 为 Grafana 加上 HTTPS,导入 Node Exporter Full 仪表板(ID 1860),并演示如何通过 WireGuard 抓取第二台 VPS 的指标。

最初的测试在 2026 年 4 月进行,环境是 Ubuntu 24.04 LTS,搭配 Docker Engine 27.x 和 Docker Compose v2.30。下面配置中锁定的版本此后已更新为当前受支持的版本。在那次五台主机的测试中,中心节点空闲时约占用 300 MB 内存,持续抓取时为 530 到 650 MB。

TL;DR(太长不看版)

  • 中心 VPS 用一个 Compose 栈运行 Prometheus、Grafana 和 Node Exporter,Caddy 则直接装在宿主机上作为反向代理。
  • Prometheus 和 Grafana 只监听 127.0.0.1,公网访问一律经过启用自动 HTTPS 的 Caddy。
  • 要监控更多服务器,只需在每台上安装 Node Exporter,并把对应条目追加到 prometheus.yml.
  • 中心与各节点之间推荐走 WireGuard。如果你的服务器本来就在同一个私有网络里,直接用私有网络也很合适。
  • 下面这次五台主机的测试用 2 GB 的 VPS 就够了,但请把它当作参考基线,而不是按主机数量套用的固定规则。

你将搭建什么

Diagram of monitoring traffic boundaries: ports 80 and 443 are public, Grafana on 3000, Prometheus on 9090 and Node Exporter on 9100 stay on loopback, and a remote Node Exporter on 10.10.0.2:9100 is reachable only over the private network

下面是完成之后整套架构的大致样子。

                 +-------------------+
                 |  Your laptop      |
                 +---------+---------+
                           | HTTPS
                           v
+--------------------------+----------------------------+
| MONITORING HUB VPS    (2 GB RAM starting point) |
|  Caddy        (reverse proxy, auto HTTPS)             |
|  Grafana      (port 3000, only via Caddy)             |
|  Prometheus   (port 9090, internal only)              |
|  Node Exporter (port 9100, scraped on localhost)      |
+----+---------------------------------+----------------+
     |                                 |
     | scrape over WireGuard           | scrape over WG
     v                                 v
+----+--------+                  +-----+-------+
|  App VPS 1  |                  |  App VPS 2  |
|  Node Expo. |                  |  Node Expo. |
+-------------+                  +-------------+

整套监控栈只放在一台中心 VPS 上。其他每台需要监控的 VPS 只跑 Node Exporter。中心上的 Prometheus 从各台拉取指标,Grafana 负责展示,Caddy 负责 HTTPS。

你需要的一切

要照搬这套配置,你需要一台运行 Ubuntu 的 VPS 作中心节点、一个域名,以及 sudo 权限。

  • 一台内存至少 2 GB、运行 Ubuntu 24.04 LTS 的 VPS。
  • 一个域名,并把 A 记录指向 VPS 的公网 IP(例如 grafana.example.com)。启用 HTTPS 必须要有。
  • 通过 SSH 获得 root 或 sudo 权限。

本文中所有出现的 grafana.example.com,都请替换成你实际指向监控 VPS 的那个子域名。Grafana 环境变量、DNS 检查和 Caddyfile 里要用同一个域名。

如果你只想监控一台服务器、暂时也不需要 HTTPS,那就可以跳过反向代理那一节,直接在现有 VPS 上跑这套 Compose 栈。其余内容依然适用。

部署 Ubuntu VPS

立即启动拥有 root 权限和 NVMe 存储的 Ubuntu VPS。

部署 Ubuntu VPS

第 1 步:服务器准备

用具备 sudo 权限的用户 SSH 登录中心 VPS,先做一次系统更新。

sudo apt update && sudo apt upgrade -y

从 Docker 官方仓库安装 Docker Engine 和 Compose 插件。

# Install prerequisites
sudo apt install -y ca-certificates curl gnupg lsb-release

# Add Docker's official GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Add the Docker repository
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

确认两者都已安装。

docker --version
docker compose version

两条命令都应能正常输出版本号。Docker Engine 和 Compose 的具体版本,取决于你安装时官方仓库提供的是哪一版。

sudo usermod -aG docker $USER

继续之前请先注销再重新登录,让新的用户组成员身份生效。docker 组实际上等同于 root 权限,因此只把可信的管理员加进去。

配置 UFW,在监控中心上放行 SSH、HTTP 和 HTTPS。80 端口负责 HTTP 到 HTTPS 的跳转和 ACME 验证,Grafana 本身依旧待在 Caddy 后面。

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status

不要把 3000、9090、9100 端口暴露到公网。这些监控服务只监听 127.0.0.1,是有原因的。

第 2 步:Compose 栈

为这套栈和 Prometheus 配置文件创建一个目录。

mkdir -p ~/monitoring/prometheus
cd ~/monitoring

编写 Compose 文件。

# ~/monitoring/docker-compose.yml
services:
  prometheus:
    image: prom/prometheus:v3.13.2
    container_name: prometheus
    restart: unless-stopped
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=15d'
      - '--web.enable-lifecycle'
      - '--web.listen-address=127.0.0.1:9090'
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus_data:/prometheus
    network_mode: host
  node-exporter:
    image: prom/node-exporter:v1.12.1
    container_name: node-exporter
    restart: unless-stopped
    pid: host
    network_mode: host
    command:
      - '--path.rootfs=/host'
      - '--web.listen-address=127.0.0.1:9100'
      - '--collector.filesystem.mount-points-exclude=^/(dev|proc|sys|var/lib/docker/.+|var/lib/kubelet/.+)($$|/)'
    volumes:
      - '/:/host:ro,rslave'
  grafana:
    image: grafana/grafana:13.1.3
    container_name: grafana
    restart: unless-stopped
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD}
      - GF_USERS_ALLOW_SIGN_UP=false
      - GF_SERVER_ROOT_URL=https://grafana.example.com
      - GF_SERVER_HTTP_ADDR=127.0.0.1
    volumes:
      - grafana_data:/var/lib/grafana
    network_mode: host
    depends_on:
      - prometheus
volumes:
  prometheus_data:
  grafana_data:

接着编写 Prometheus 的配置文件。

# ~/monitoring/prometheus/prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s
  external_labels:
    monitor: 'monitoring-hub'
scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['127.0.0.1:9090']
  - job_name: 'node'
    static_configs:
      - targets: ['127.0.0.1:9100']
        labels:
          host: 'monitoring-hub'

创建一个 .env 文件存放 Grafana 管理员密码。密码要够强,并且不要把这个文件提交到 git。

cat > ~/monitoring/.env <<'EOF'
GRAFANA_ADMIN_PASSWORD='replace-with-a-strong-password'
EOF
chmod 600 ~/monitoring/.env

上面有几个参数值得各说一句。

  • --web.listen-address=127.0.0.1:9090 让 Prometheus 只监听宿主机的回环接口。这样 9090 端口就不会暴露在公网上,同时 Grafana 和本地管理操作仍然访问得到。
  • --web.enable-lifecycle 会启用 POST /-/reload,这样修改 Prometheus 配置后无需重启容器即可生效。
  • pid: host, network_mode: host、宿主机根目录的 bind 挂载,以及 --path.rootfs=/host 让容器里的 Node Exporter 拿到它需要的宿主机上下文,而不是只监控自己所在的容器环境。
  • GF_USERS_ALLOW_SIGN_UP=false 可以阻止访客自行注册 Grafana 账号,但这并不等于把 Grafana 变成私有的,登录页面通过 Caddy 依然是公开可访问的。

小贴士: 如果你不想手动装,Cloudzy 提供一键部署的 GrafanaPrometheus 。其中 Prometheus 的部署还可以顺带装上 Node Exporter。

第 3 步:首次启动与验证

把这套栈跑起来。

cd ~/monitoring
docker compose up -d

等几秒钟,然后检查三个容器是否都在运行。

docker compose ps

三个服务都应显示为运行中。

从你的笔记本开一条 SSH 隧道,去查看 Prometheus 的 targets 页面。

ssh -L 9090:localhost:9090 your-user@your-vps-ip

然后在浏览器里打开 http://localhost:9090/targets。你应该能看到两个目标,状态都是 UP:

prometheus            UP    http://127.0.0.1:9090/metrics
node                  UP    http://127.0.0.1:9100/metrics

如果有任何一个是 DOWN,先跳到「常见问题」一节。两个都变成 UP 之前不要继续往下做。

检查完目标后就把隧道关掉。从 VPS 外部看,Prometheus 依然只能通过这条 SSH 隧道访问。下一步我们把 Grafana 通过 HTTPS 暴露出去。

第 4 步:用 Caddy 做带 HTTPS 的反向代理

Caddy 只是一个二进制文件,并且会自动处理 HTTPS。对于单站点的反向代理,它的配置比等效的 Nginx 配置块更短。

从官方仓库安装 Caddy。

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | \
  sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | \
  sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
sudo chmod o+r /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddy

进行下一步之前,先确认 grafana.example.com 的 DNS A 记录指向了 VPS 的公网 IP。否则 ACME 验证会失败。

dig +short grafana.example.com
# Should print the VPS public IP

编辑 Caddyfile。

# /etc/caddy/Caddyfile
grafana.example.com {
    reverse_proxy 127.0.0.1:3000
    encode gzip
}

重新加载 Caddy。

sudo systemctl reload caddy

只要域名解析到你的服务器、80 和 443 端口可达,Caddy 就会通过 ACME 自动申请并续期公开受信任的 TLS 证书。在浏览器里访问 https://grafana.example.com,你应该能在有效的 HTTPS 连接上看到 Grafana 登录页。用 admin 和 .env 文件里的密码登录即可。

第 5 步:把 Prometheus 添加为数据源并导入 1860 号仪表板

在 Grafana 界面中,依次进入 连接 > 数据源 > 添加数据源 ,然后选择 Prometheus.

把 URL 设置为:

http://127.0.0.1:9090

在这套配置里,Grafana 和 Prometheus 共用宿主机网络,而 Prometheus 只监听回环地址。点击 Save & test ,确认 Grafana 能够查询 Prometheus API。你应该会看到「Successfully queried the Prometheus API」。

现在导入仪表板。Node Exporter Full(rfmoz 制作,ID 1860)是社区里非常常用的 Node Exporter 指标仪表板,覆盖 CPU、内存、磁盘 I/O、网络、文件描述符,以及主机有暴露时的硬件温度。它还带有 job 和 instance 变量,因此不用改动就能直接用于中心辐射式架构。

前往 仪表板 > > 导入仪表板,填入仪表板 ID 1860,选择你的 Prometheus 数据源,然后导入。

只要 Node Exporter 目标处于 UP 状态,仪表板就应该开始有数据。1860 号仪表板的部分面板还会用到可选的 systemd 和 processes 采集器的指标,所以个别面板空白,并不一定说明目标出了问题。

第 6 步:加入第二台 VPS(中心辐射式)

启动 Node Exporter 之前,先确认你打算绑定的那个私有 IP 在第二台 VPS 上确实已经存在。如果用的是 WireGuard,请先把 WireGuard 配好。

在第二台 VPS 上,按第 1 步中安装 Docker 的部分把 Docker 装好,但不要仅仅为了监控就开放 80 或 443 端口。然后只运行 Node Exporter。

mkdir -p ~/node-exporter
cd ~/node-exporter

cat > docker-compose.yml <<'EOF'
services:
  node-exporter:
    image: prom/node-exporter:v1.12.1
    container_name: node-exporter
    restart: unless-stopped
    pid: host
    command:
      - '--path.rootfs=/host'
      - '--web.listen-address=10.10.0.2:9100'  # bind to the private monitoring IP, never 0.0.0.0
    volumes:
      - '/:/host:ro,rslave'
    network_mode: host
EOF

docker compose up -d

把 10.10.0.2 换成这台服务器用于监控的私有 IP。用 WireGuard 就填它的 WireGuard 地址;用 Cloudzy 私有网络就填该 VPS 私有网卡的地址。如果这台 VPS 上已经启用了 UFW,只放行监控中心访问 Node Exporter 即可。比如中心的私有 IP 是 10.10.0.1:

sudo ufw allow proto tcp from 10.10.0.1 to any port 9100
sudo ufw status

把 10.10.0.1 换成中心节点真实的私有 IP。即便 UFW 没启用,显式指定的 --web.listen-address 依然能让 Node Exporter 不出现在公网接口上,但其他能进入那个私有网络的机器同样可以访问 9100 端口。

中心与各节点之间的网络路径,有两个不错的选择:

  1. WireGuard 网络。 让每台 VPS 都加入同一个 WireGuard 网络,Prometheus 直接抓取私有 IP。初次配好 WireGuard 之后,这是最安全的方案。我们提供 WireGuard 一键部署,另外还有一篇 现成的配置教程
  2. Cloudzy 私有网络。 同一区域内的 Cloudzy VPS 实例都会分配一个用于东西向流量的私有网卡,因此你可以直接抓取该地址,而不必另外搭一条 WireGuard 隧道。

等 Node Exporter 在第二台 VPS 上跑起来、并且能通过私有 IP 访问之后,把 prometheus.yml 中心节点上的这个文件里原有的 node job,替换成下面这段。

# Update the existing 'node' job in ~/monitoring/prometheus/prometheus.yml
  - job_name: 'node'
    static_configs:
      - targets: ['127.0.0.1:9100']
        labels:
          host: 'monitoring-hub'
      - targets: ['10.10.0.2:9100']
        labels:
          host: 'app-vps-1'
          tier: 'production'

在不重启容器的情况下重新加载 Prometheus。

curl -X POST http://localhost:9090/-/reload

重新打开第 3 步里的 SSH 隧道,然后访问 http://localhost:9090/targets。新目标应该显示为 UP。打开 Node Exporter Full 仪表板,把顶部的 instance 变量切换到新服务器,就能看到它的图表了。

资源占用与何时升级

Observed five-host baseline from the April 2026 test: Prometheus 250 to 350 MB, Grafana 200 MB, Caddy 40 MB, hub Node Exporter 15 to 20 MB, total hub 530 to 650 MB RAM on a 2 GB VPS with a 1.5 GB fifteen-day working set

这些数字来自 2026 年 4 月的最初测试,环境是一台 2 GB 的 Ubuntu VPS,监控五台主机。请把它们当作这类负载的参考基线,而不是对更新版本的固定配置保证。

组件空闲内存工作时内存(1 台主机)5 台主机时内存磁盘(保留 15 天,5 台主机)
Prometheus~100 MB~150 MB~250-350 MB~500 MB 到 1.5 GB
Grafana~150 MB~180 MB~200 MB~50 MB
Node Exporter~15 MB~20 MB不适用可忽略不计
Caddy约 30 MB~40 MB~40 MB不适用
中心节点合计~300 MB~390 MB~530-650 MB工作集约 1.5 GB

当中心节点的内存或磁盘余量开始吃紧时就该升级了。单看主机数量并不是好的判断依据,因为序列基数、启用的采集器、抓取间隔和保留时长都会改变实际占用。若需要更长期的集中存储,Prometheus 支持远程存储集成,VictoriaMetrics 就是一个兼容 Prometheus 的选择。

常见问题

最常出现的七个问题及其解决办法。

  1. Prometheus 可以从公网访问。 直接写成 9090:9090 的端口映射,会把 Prometheus 暴露给全世界。解决办法:在 Prometheus 的 command 中保留上面那样的 --web.listen-address=127.0.0.1:9090 参数。这样 Prometheus 就只监听宿主机的回环接口。
  2. Grafana 数据源连不上 Prometheus。 这套栈用的是宿主机网络,所以 Grafana 可以通过 http://127.0.0.1:9090 访问 Prometheus。请检查 Prometheus 是否还在运行、是否仍在回环地址上监听。
  3. Node Exporter 仪表板显示「No data」。 通常有三种原因。(a) Prometheus 根本没在抓取该目标,去看看 /targets。(b) 仪表板变量里的 job 标签,和抓取配置中的 job_name 对不上。(c) 防火墙挡住了中心与目标之间的 9100 端口。
  4. 生产环境里 Grafana 管理员密码还是 admin/admin 设置 GF_SECURITY_ADMIN_PASSWORD 。解决办法:在首次启动前,通过 .env 文件设置 GF_SECURITY_ADMIN_PASSWORD。如果已经漏掉了,就在第一次登录时改掉密码,并关闭自助注册。
  5. Caddy 报「ACME challenge failed」。 要么 DNS 的 A 记录还没生效,要么 80 端口被挡住了。执行 ufw allow 80,443/tcp,等待 DNS 生效,再执行 sudo systemctl reload caddy。可以用 dig +short 来确认解析是否已经生效。
  6. Prometheus 的磁盘被写满。 高基数的标签,或者主机很多时抓取间隔又太短,都会很快把卷写满。注意观察 prometheus_tsdb_head_series 以及卷的大小。缓解办法:关掉用不到的 Node Exporter 采集器,把 scrape_interval 调到 30s,或者缩短保留时长。
  7. bind 挂载出现权限错误。 如果你挂载的是宿主机目录而不是具名卷,那么容器的 UID(Prometheus 是 65534,Grafana 是 472)必须有写权限。像上面 Compose 文件里那样使用具名卷,就不会遇到这个问题。

什么情况下这套栈是杀鸡用牛刀

如果你只是想在某个 URL 无响应时收到告警,这套栈就有点杀鸡用牛刀了。 Uptime Kuma 要轻量得多,如果你只需要基本的可用性检查,用它就够了。

常见问题

Prometheus 和 Grafana 有什么区别?

Prometheus 是一个时序数据库:它按你设定的间隔从配置好的目标抓取指标,写入磁盘,并响应 PromQL 查询。Grafana 则是可视化层,连接 Prometheus(以及其他许多数据源)并渲染仪表板。绝大多数情况下两者都需要:Prometheus 负责采集和存储,Grafana 负责展示。

Prometheus + Grafana 这套需要多少内存?

在单台 VPS 上同时运行 Prometheus、Grafana、Node Exporter 和一个反向代理的监控中心,空闲时约占用 300 MB 内存;以 15 秒间隔持续抓取五台主机时为 530 到 650 MB。

能用一个 Grafana 实例监控多台服务器吗?

可以。标准做法就是中心辐射式:中心 VPS 上的一个 Prometheus 实例,去抓取你想监控的每台其他服务器上运行的 Node Exporter;中心上的 Grafana 只查询这一个 Prometheus。Node Exporter Full 仪表板(ID 1860)支持 instance 变量,所以在同一个仪表板里就能切换查看不同服务器。

Node Exporter 用哪个 Grafana 仪表板最好?

对这套配置来说,Node Exporter Full(rfmoz 制作,仪表板 ID 1860)是一个很稳妥的默认选择。它覆盖了 Node Exporter 的主要主机指标,并支持 job 和 instance 变量以便监控多台服务器。部分面板依赖 Node Exporter 的可选采集器,因此个别面板空白,并不一定意味着抓取目标出了问题。

如何通过 HTTPS 暴露 Grafana?

让 Grafana 绑定在 127.0.0.1:3000 上运行,再在它前面放一个负责 HTTPS 的反向代理。最简单的选择是 Caddy:一个四行的 Caddyfile,里面写上 reverse_proxy 127.0.0.1:3000 和一个域名区块,就能把包括自动管理 TLS 证书在内的所有事情都办好。

Prometheus + Grafana 商用是免费的吗?

Prometheus 采用 Apache 2.0 许可证,Grafana OSS 采用 AGPLv3。内部使用和商业使用未经修改的 Grafana OSS 是允许的,但如果你修改、分发它,或者通过网络提供修改后的版本,就可能触发 AGPL 下的源码共享义务。Grafana Enterprise 和 Grafana Cloud 则适用另外的商业条款。

什么时候该从 Prometheus 换成 VictoriaMetrics?

当更长的保留时间、很高的序列基数,或者要跑多个 Prometheus 实例,使得在服务器的内存和磁盘预算内维护本地 Prometheus TSDB 变得吃力时,就可以考虑 VictoriaMetrics 了。它能接收 Prometheus 的数据,并提供兼容 Prometheus 的查询 API,因此既可以充当长期存储,也可以作为 Grafana 的指标后端。请拿你自己的实际负载去实测,而不是卡在某个固定的内存阈值上换。

分享

博客更多内容

继续阅读。

准备好部署了吗? 起价 $2.48/月。

独立云厂商,自 2008 年起。AMD EPYC、NVMe、40 Gbps。14 天退款保证。