1. 部署思路:为什么时序数据库要选 VictoriaMetrics
1.1 标题场景拆解:一套方案搞定监控全链路
先把这个标题拆开看,它其实覆盖了一套完整的监控数据链路:采集端(Prometheus)→ 存储端(VictoriaMetrics)→ 运维保障(备份恢复)。也就是说,不只是"起一个容器"那么简单的部署问题,而是要把"怎么落地、怎么接入、怎么保证数据安全"这一整条线走通。
这套方案的典型使用场景非常明确:你的服务已经跑了 Prometheus + Grafana,但 Prometheus 自带的本地 TSDB 存储总在容量、长期留存、查询性能上卡脖子;或者你刚接手一套监控系统,看到裸奔的 Prometheus 数据目录又没备份方案,着急改造。于是引入 VictoriaMetrics 做统一时序存储,Prometheus 只负责抓取和告警,数据长期落盘在 VM 里,Grafana 的查询走 VM 的接口,各司其职。
我在实战中为什么坚定推荐这套组合?因为 Prometheus 本身的 remote write / remote read 协议是开箱即用的,只要 VictoriaMetrics 暴露的 HTTP 接口和 Prometheus 的语言兼容,那头都不用改。而 VictoriaMetrics 官方就专门提供了对 Prometheus remote write/read 协议的完整支持,甚至很多场景下可以直接当 Prometheus 的远端存储用,迁移成本极低。相比自建 Thanos + Cortex 那套组件矩阵,VM 单机版一个二进制搞定,对中小团队来说省心太多。
1.2 方案选型:单机版还是集群版,怎么判断
标题里写的是"docker-compose 启动 VM 时序数据库",那第一件事就是分清单机版和集群版。
VictoriaMetrics 有两个形态:
| 形态 | 组件 | 适用规模 | 部署复杂度 |
|---|---|---|---|
| 单机版(vmsingle) | 单个 victoria-metrics 二进制 | 单机百万时间序列以内,个人/小团队 | 低,一个容器搞定 |
| 集群版(vmcluster) | vmstorage + vminsert + vmselect | 海量时间序列、多副本、水平扩展 | 高,至少3类组件配合 |
先说结论:绝大多数场景,先上单机版。别看集群版听起来更"正规",它引入的复杂度是肉眼可见的——三个组件各自的端口、存储路径、副本配置都要自己维护,一旦 docker-compose 编排没写好,排查问题的成本是单机版的翻倍。VM 官方自己的说法也很直接:单机版单节点可以处理每秒数百万数据点,几十万时间序列完全不在话下。对绝大多数业务监控够用了。
真正考虑集群版的时候,通常是你已经碰到了这几个信号:单节点 CPU 常年 80% 以上、磁盘 IO 扛不住、或者明确需要多副本容灾。标题里提到的是 docker-compose 启动,说明定位是轻量部署,那我的建议就是:单机版起步,数据目录挂出来,备份做好,后面真要扩容再迁集群。这篇文章的主体也按单机版来写,集群版的备份思路和它一脉相承,只是多几个组件的快照协调。
2. 用 docker-compose 快速拉起时序数据库
2.1 规划目录结构与配置文件
动手写 docker-compose.yml 之前,先把目录规划这一步做扎实。别小看目录结构,我见过很�便宜"rm -rf 一时爽"的事故,就是因为容器数据目录随便挂载、没做统一规划,最后迁数据的时候手忙脚乱。
建议按这个结构来安排:
/opt/victoria-metrics/ ├── docker-compose.yml ├── data/ # VM 数据目录,挂载到容器 ├── backup/ # 本地备份暂存(如有) └── prometheus/ └── prometheus.yml # Prometheus 配置(可放同一台机器)数据目录一定放到宿主机持久化路径上,不要放在容器内——docker-compose 的容器重建太频繁了,动不动 update 一下,如果数据在容器可写层里那就是灾难。VM 镜像里默认的数据目录是/victoria-metrics-data,我们把它映射出来,这是整套方案的生命线。
2.2 单机版 compose 文件详解
这是最核心的一个文件。先给你我的生产级配置,再逐行解释为什么这么写:
version: '3.8' services: victoria-metrics: image: victoriametrics/victoria-metrics:v1.93.12 container_name: vmsingle restart: unless-stopped ports: - "8428:8428" volumes: - ./data:/victoria-metrics-data command: - "-storageDataPath=/victoria-metrics-data" - "-retentionPeriod=30d" # 数据保留30天 - "-httpListenAddr=:8428" - "-memory.allowedPercent=30" # VM最多使用宿主机30%内存 - "-search.maxQueryDuration=30s" - "-search.maxMemoryPerQuery=0" ulimits: nofile: 65536 healthcheck: test: ["CMD", "wget", "-qO", "-", "http://localhost:8428/health"] interval: 30s timeout: 5s retries: 3 start_period: 10s几个关键参数逐一说:
镜像版本。我看到很多人在玩"latest"标签,图一时省事。这个习惯很危险——VM 的版本升级有时会带来存储格式变化或者参数废弃,一旦镜像悄悄更新,重启容器后起不来,或者数据目录不兼容,就是半夜被 call 的节奏。生产环境务必固定到具体版本号,比如v1.93.12,升级的时候有意识地去操作,而不是被 latest 绑架。
retentionPeriod。数据保留周期,这是最影响磁盘占用的参数。单位可以是d(天)、w(周)、y(年),也可以直接写30d。留意它和快照备份的关系——VM 只会清理当前时间点超过保留周期的旧数据,这个过程是后台的vmstorage-rem分片机制做的,日常不用管。
memory.allowedPercent。VM 的内存管理很激进,默认会尽量多用内存来缓存热数据,提升查询性能。单机版如果跑在 4G 内存的小机器上,不限制它会吃满一半以上。设成30意思是允许用到宿主机 30% 的内存。注意这是百分比,不是绝对数值,更精确的控制可以改用-memory.allowedBytes=2GB。
search.maxQueryDuration。直接限制单条查询的最大执行时间,默认 5s 对复杂聚合确实不够,但在大面板上我建议调到 30s 就够了。太长会让并发查询把 CPU 打满,拖垮整个实例。
然后是ulimits里把文件描述符开到 65536。这个在很多部署文档里被忽略了——当时间序列数量大、查询并发上来时,VM 要同时打开大量数据文件,默认 1024 的 ulimit 会导致报too many open files,到时候会看到查询突然报错。提前设好,省得后面排查。
健康检查为什么要单独配?因为 docker-compose 默认是不管容器内部服务死没死的。VM 进程起了但内部 HTTP 服务异常,或者端口没起来,容器还是显示 running,编排系统不会感知。加上 healthcheck 后,你可以配合后续的 prometheus 抓取或者容器编排做自动恢复,这在自动化运维里是基本素养。
2.3 启动、验证与内存调优
配置写好后,启动三板斧:
docker compose up -d docker compose logs -f victoria-metrics curl -s http://localhost:8428/healthdocker compose logs看启动日志,正常会出现类似VictoriaMetrics is ready的字样。然后 curl 一下/health接口,返回OK就是真的起来了。这一步是最快的健康验证,比盯着docker ps强得多。
再检查一下测试接口,确认能写入和查询:
curl -X POST 'http://localhost:8428/api/v1/write' -d 'test_metric{label="check"} 100' curl 'http://localhost:8428/api/v1/query' --data-urlencode 'query=test_metric'这两条命令能通,说明写入链路和查询链路都没问题。写测试数据的时候注意,VM 对没有时间戳的数据会默认带上当前时间,所以查回来能看到值,这条链路就算验证过了。
内存这块再补充一句:观察曲线的斜率比观察绝对峰值更重要。VM 的内存使用和活跃时间序列数量强相关,如果内存持续上涨、不回落,就要怀疑是不是有大量不规律的唯一标签组合(高基数问题)在写入,而非简单调大内存能解决。通过内置的/vmui页面(浏览器访问http://localhost:8428/vmui)可以直接看时间序列数量、查询耗时等指标,很直观,比命令行快。
3. Prometheus 数据接入配置
3.1 remote_write 配置:把 Prometheus 的数据双写到 VM
装好 VM 之后,第二步就是把 Prometheus 的数据接进来。这里有一个设计上的取舍:你是想全部数据都写入 VM,还是只把长期保留的数据写入 VM?
我的建议是:Prometheus 本地保留短周期(如最近6小时~24小时),同时通过 remote_write 把全量数据写入 VM,查询主要走 VM。这样做的理由是:Prometheus 的本地存储很适合做最近数据的快速查询和告警恢复判断,而 VM 主要负责长期存储和批量分析,两边各取所长。
在 Prometheus 的配置文件prometheus.yml里增加这一段:
remote_write: - url: "http://victoria-metrics:8428/api/v1/write" queue_config: max_samples_per_send: 5000 capacity: 100000 min_shards: 2 max_shards: 20注意这里的url是 VictoriaMetrics 的写入接口路径/api/v1/write,这是它兼容 Prometheus 协议的地方。victoria-metrics是 docker-compose 网络内的服务名,如果你在同一 compose 网络里,直接用服务名解析即可;如果分开部署,则用宿主机的 IP 或域名。
队列参数为什么要调?我推演一下这个逻辑:capacity是每个分片内存队列的容量,max_samples_per_send是每次批处理发送的最大样本数。默认配置说实话也能跑,但在高写入下有几点体验不好——队列容易积压、分片数量不够导致吞吐上不去。调成max_samples_per_send: 5000是因为 VM 官方单次批量解析 5000 行是性价比很好的平衡点,吞吐高、内存开销可控。max_shards: 20让 Prometheus 可以在写入压力大时自动扩容分片,类似汽车自动挡的换挡逻辑,而不是死板地用一个分片慢慢写。
3.2 remote_read 配置:Grafana 查询走 VM 接口
如果只配了 write 没配 read,那 Prometheus 本地查询还是走它自己的 TSDB,Grafana 数据源连的也是 Prometheus 的话,你看到的还是短周期数据。要做长期查询,就在 Prometheus 配置里加上 remote_read:
remote_read: - url: "http://victoria-metrics:8428/api/v1/read" read_recent: true这里有个小坑值得展开说一下:read_recent这个参数。默认情况下,Prometheus 的 remote_read 优先查本地存储,本地没有的数据才去远端查,这样能保证查询性能和一致性。但当你明确想用 VM 做主查询源时——比如 Grafana 面板时间范围远超 Prometheus 本地保留周期——read_recent: true让 Prometheus 直接读远端数据,不走本地缓存判断。我实际部署时,如果 Grafana 数据源配的是 Prometheus 而 Prometheus 又只留了 24 小时数据,不加read_recent: true就会出现选了大时间范围却拉不到数据的诡异现象。加上它之后,查询全走 VM,问题消失。
但注意,如果你的Grafana 数据源直接配的 VictoriaMetrics,那remote_read这段是不需要的。Grafana 原生支持 VictoriaMetrics 数据源插件,直接填 http 地址即可。两条路线我都跑过,结论是:能用 VM 数据源插件就直接用插件,性能和功能更完整;如果运营面板已经统一配置了 Prometheus 数据源,就用 remote_read 转发。
3.3 验证数据是否真正写入
配置完 remote_write/read 后,别急着看面板,先在 VM 侧确认有没有数据:
# 在 VM 上查最近5分钟是否有数据 curl 'http://localhost:8428/api/v1/query' --data-urlencode 'query=up' # 或者查特定指标 curl 'http://localhost:8428/api/v1/query_range' \ --data-urlencode 'query=up' \ --data-urlencode 'start=1671000000' \ --data-urlencode 'end=1671000300' \ --data-urlencode 'step=30'这里有两条验证链路:一条是 Prometheus 侧看 remote write 队列有没有积压,/api/v1/runtimeinfo或 metrics 里的prometheus_remote_storage_queue_highest_sent_timestamp_seconds和prometheus_remote_storage_pending_samples,如果 pending 一直涨说明写入跟不上;另一条是 VM 侧看写入的 api 是否正确返回 204。
更直接的方式是到 VM 的http://localhost:8428/vmui页面上,输入{__name__=~".*"},能查到指标列表说明数据已经入到 VM 存储了。
我踩过的一个坑是:Prometheus 的 remote_write 里写的是http://localhost:8428/api/v1/write,而 Prometheus 容器和 VM 容器不在同一个网络命名空间,导致 curl 在宿主机通、但 Prometheus 容器内部访问不到localhost。解决方式就是把 URL 改成 docker-compose 网络内互通的服务名或宿主机 IP,例如http://victoria-metrics:8428/api/v1/write。这个问题看起来基础,但出问题时很容易绕进去——因为你宿主机 curl 是通的,容器内不通,你甚至会怀疑是不是 Prometheus 配置没生效。
4. 备份恢复:VM 的快照与备份工具
4.1 备份原理:为什么 VM 不直接打包数据目录
很多人会想:备份数据把./data目录 tar 起来不就行了?说真的,这个想法第一眼看没毛病,但在线备份时会踩大坑。
VM 的存储引擎在运行时会持续写数据分片(partition)和内存中的相关索引,直接tar -czf一个正在被写入的数据目录,大概率得到的是一个"在某一瞬间被撕裂的镜像"——文件夹结构不完整、部分数据文件正在写入却被打包成半个文件、数据目录内部的临时文件和正在进行 flush 的分区处于中间态。这样的备份拿去恢复,轻则丢数据,重则恢复出来的 VM 起不来,报各种损坏错误。
所以 VM 官方给出的推荐路径是:先用快照接口生成一致的备份起点,再用 vmbackup 工具备份快照目录。整个备份过程是"在线"的,不需要停止 VM 服务,非常关键。
VM 的接口是:
# 创建快照 curl -X POST 'http://localhost:8428/snapshot/create' # 返回内容为 {"status":"ok","snapshot":"20240201_1234567890"}创建一个快照后,在数据目录下会生成一个snapshots/20240201_1234567890子目录。逐一说明这个过程:VM 在接到快照请求时会做两件事——强制把内存中的数据进行落盘,同时记录一个数据一致性位置,之后新写入的数据不会影响这个快照点的完整性。所以快照目录就是某一时刻数据库数据的一致副本,此时你再对它做文件级备份就是安全的。
4.2 vmbackup 实战:备份到对象存储或 NFS
打快照只是第一步,快照还在 VM 本机的数据目录里,如果这台机器磁盘坏了,快照一样灰飞烟灭。所以要用vmbackup把快照传到异地存储。
官方提供的镜像是victoriametrics/vmbackup,docker-compose 可以直接跑一次性任务:
docker run --rm \ -v /opt/victoria-metrics/data:/data \ -e "BACKUP_DIR=/backups" \ victoriametrics/vmbackup:v1.93.12 \ -storageDataPath=/data \ -snapshotName=20240201_1234567890 \ -dst=gs://my-bucket/vm-backupdst目标支持的对象存储有 GCS、S3、Azure Blob、MinIO 等,甚至本地目录fs://也行。对你来说最常见的是 S3 兼容存储——包括腾讯云 COS、阿里云 OSS、MinIO 都能用 S3 协议对接。参数里加一组 access key 和环境变量即可。
还有一个非常实用的参数:-origin。它的作用是做增量备份——origin指向上一次完整备份的路径,vmbackup 只把"快照相对于 origin 的变化部分"上传。监控数据的备份其实每天变化量不大,但全备的话数据量会越滚越大,增量备份能大幅节省存储成本和时间。建议初始做一次全备,之后每天跑的备份命令带上-origin=gs://my-bucket/vm-backup-YYYYMMDD,可以精确控制。
备份策略和频率方面,我操作时的建议是:每天一个增量备份,每周一个全量备份,全量备份保留4周。如果是比较重要的业务监控,可以再叠加一次每月全备保留更长周期。备份这件事永远是"宁可多备不可少备",但也不建议无脑堆——因为恢复时要能找得到目标时间点,堆太多反而干扰。
4.3 快照管理和生命周期
快照目录会一直留在 VM 的数据目录里,如果不清理,它占用的磁盘空间会不断累积。官方提供了删除接口:
# 删除单个快照 curl -X POST 'http://localhost:8428/snapshot/delete' \ --data-urlencode 'snapshot=20240201_1234567890' # 删除全部快照 curl -X POST 'http://localhost:8428/snapshot/delete_all'我建议在 vmbackup 备份成功后立即删掉对应的本地快照,没必要留着。但注意一个时序问题:先备份,再删除快照,这个顺序不能反。我见过有人图省事写了个 cron 里先 delete_all 再跑 backup,结果备份了个寂寞,恢复点全部丢失。这就是一个很明显的工作流顺序问题。
另外提醒一下,快照删除对磁盘空间的释放不是瞬时的。VM 内部的vmstorage后台任务会逐步整理分区并合并小文件,磁盘空间真正释放可能需要一些时间。如果你删完快照发现磁盘使用率还是很高,别慌,观察一段时间,它会逐渐把可回收的分区合并后释放。这个和大多数基于 LSM 树的存储引擎行为一致。
4.4 vmrestore 恢复:一次完整的演练
恢复就是备份的逆过程。假设你备份在 GCS 的gs://my-bucket/vm-backup目录下,现在 VM 数据目录全丢了,要恢复到新机器:
# 1. 先停掉 VM 容器 docker compose stop victoria-metrics # 2. 恢复到一个全新的数据目录 docker run --rm \ -v /opt/victoria-metrics/data_restore:/data \ victoriametrics/vmrestore:v1.93.12 \ -src=gs://my-bucket/vm-backup \ -storageDataPath=/data # 3. 把恢复出来的数据目录替换原路径 mv /opt/victoria-metrics/data_restore /opt/victoria-metrics/data # 4. 重新启动 VM docker compose up -d步骤 2 里,vmrestore会自动从远端存储拉取备份数据并解压归档。有个细节:如果你用增量备份,-src也要指向增量备份的那个路径,vmrestore 会自动关联 previous 的增量链式恢复,不需要手动合并——当然前提是你备份时的-origin链式关系是连续的。一旦断链,老实的做法是回到最近一次全备恢复,再补打后续增量,但操作复杂度会稍高一些。
恢复完成后的第一件事不是看面板,而是先验证数据完整性:
# 查一个已知指标的最近数据 curl 'http://localhost:8428/api/v1/query' --data-urlencode 'query=up' # 查最后一个时间点,确认数据没丢到最近 curl 'http://localhost:8428/api/v1/query' --data-urlencode 'query=last_over_time(up[24h])'我强烈建议在部署完这套方案后,在测试环境做一次完整的恢复演练。不用等出事故才操作,演练一次大概 30 分钟,但能让你对命令参数、恢复顺序、Log 输出这些细节烂熟于心。等真出事故的时候,你就是那个"还能冷静执行恢复预案"的救场人,而不是抽象地在文档里临时查命令。
5. 常见问题与排查技巧实录
5.1 docker-compose 启动失败:libz.so.1 错误
网上热词里反复出现docker-compose: error while loading shared libraries: libz.so.1: failed to map segment from shared object这个报错。这个场景最近集中爆发的原因,多半是 docker-compose v2 版本的二进制依赖 glibc 版本和宿主机的 glibc 不匹配,或者老机器的/lib路径问题。
先把结论说在前面:优先升级 Docker Engine 和 docker-compose 插件本身,不要在一个老旧的 compose 二进制上死磕。因为新版 Docker 已经把 compose 作为 docker 插件内置了,直接docker compose(带空格)调用,不需要单独维护docker-compose二进制。我用docker compose替代旧命令后,这种动态库错误几乎绝迹。
如果你暂时没法升级,还是必须用老版 docker-compose,可以临时用环境变量绕过,但我不推荐在生产环境这么裸奔:
# 临时切库路径(仅排查用) LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu docker-compose up -d这个 libz 问题的根源,是二进制预期链接的 zlib 库版本和系统实际存在的不一致。Linux 下解决方案其实就是换兼容版本、升级 Docker 或重装 zlib。排查时有几条路径:ldd $(which docker-compose)看它动态依赖哪些库,哪个标了not found就去装对应的库。治标先治本,最终还是要升级环境。
5.2 VM 容器一直重启:数据目录权限问题
有一次我把 VM 的数据目录从一个 NFS 挂载盘迁到本地,容器一直Restarting,日志里只看到权限报错:
cannot create directory "/victoria-metrics-data": permission denied再仔细看发现 NFS 挂载时用的 uid/gid 和宿主机不一致,VM 镜像里默认以非 root 用户运行,没有目标目录的写权限。解决方式很简单:
mkdir -p /opt/victoria-metrics/data chown -R 1000:1000 /opt/victoria-metrics/dataVM 镜像官方使用 uid 1000 作为默认用户,直接chown 1000:1000就能让容器正常写入。这个细节容易忽略,尤其是从 Windows 挂载盘迁过来、以及用 NFS 存储的时候,权限经常会出问题。排查顺序:数据目录权限 > 磁盘剩余空间 > 端口占用。
5.3 Prometheus 写入成功但 VM 查询无数据
我遇到过一种很隐蔽的情况:Prometheus 的 remote_write 显示成功了(没有 pending 积压),但 VM 上查询不到数据。当年排查了半个多小时,最后发现是时间范围不对——有些查询面板默认查最近5分钟或15分钟,而我测试写入的数据恰好是 2 个小时前的。这个问题看起来笨,但它暴露了一个关键点:VM 的查询默认不带时间过滤时是按当前时间往前推的,这很容易让人误判"没有数据"。
正确排查思路是,用 VMUI 的query页面直接输入一条不带时间的查询,比如sum(up),然后看返回结果。如果返回值非空,说明数据已经入库,面板查不到是时间范围的问题;如果返回空,则要看 Prometheus 的写入链路。
另一个排查点:Prometheus 里某些指标是带 labels 的,而查询时如果用up{job="foo"}这种精确匹配,VM 也遵循同样的 PromQL 语义。如果 label 大小写写错,也会查不到。用 VMUI 的自动补全功能,把 label 从列表里直接点选出来,能避免手写 label 拼写错误这种低级问题。
5.4 查询变慢:内存、磁盘、高基数
最后说说查询性能。VictoriaMetrics 的查询优化总体做得不错,但如果你的监控面板越来越卡,优先怀疑以下几点:
第一,查询范围过大。比如up这类高基数指标,拉 30 天的曲线,所耗内存会非常夸张。我见过有同事直接大范围 * 全量指标一起查,结果 OOM,把整台机器打挂了。解法是尽量缩小时间范围,或者用downsample接口让 VM 自动降采样,比如/api/v1/query_range支持step参数,Grafana 也会自动调整点距,但仍建议你在面板上限制时间跨度。
第二,磁盘 IO 瓶颈。VM 读数据依赖磁盘,如果查历史数据特别慢,看下iostat或vmstat,如果磁盘利用率长时间接近 100%,要么换 SSD、要么考虑把查询频繁的分区做缓存。VM 自带-storage.cacheSize之类的参数,可以适当加大查询缓存内存,但别贪——内存也被查询本身占用。
第三,高基数问题。时间序列数量过大对存储和查询都有影响。VMUI 页面里可以看 cardinality stats,如果发现有某个 label 有几万个不同值(典型的是pod、instance、user_id这类),就要认真权衡是否真的需要这个维度。高基数不是说绝对不能存,而是会让查询聚合变慢、存储膨胀。实际解决办法一般是减少不必要的 label,或者只在需要时用单独的指标跟踪,不要在同一个指标里打满各种唯一维度。
6. 踩过几次坑之后的运维心得
这套组合拳打下来,我在实际维护中最重要的体会是:时序数据库的运维核心不是"部署起来",而是"数据和查询的长期健康"。docker-compose 把 VictoriaMetrics 拉起来,Prometheus 数据接进来,备份策略配好,这些只是第一步。真正拉开差距的是接下来的持续关注——磁盘空间是否悄悄涨满、remote_write 队列是否积压、高基数指标是否在某次发布后暴涨、备份任务是否真的按时成功了。
我给自己定了一套日常巡检习惯,分享给你参考:每天花 30 秒看 VM 的磁盘使用曲线和远程存储队列积压;每周看一次备份任务日志,确认快照创建、备份上传、快照删除三步都完成了;每月用测试数据做一次恢复验证,确保 vmrestore 的链路和权限都没出问题。这30秒、一周、一个月的节奏,性价比极高。
另外一个小技巧:VMU I 页面里的/metrics是可以被 Prometheus 自身抓取的,把victoria-metrics的指标也纳入监控体系,这样 VM 自身健康状态也能被观测。这算是吃自己的狗粮,但如果连时序数据库的运维指标都没接入监控,出了问题往往是最措手不及的。
我已经踩过这些坑,你可以直接抄作业了。