Mar 07, 2024
Creating a Multi Retention Period VictoriaMetrics Cluster
After a few days of attempts, I finally managed to get my own VictoriaMetrics cluster working and have decided to write up the various components and setups for my own reference and in case its useful to anyone else. The majority of this was pieced together from their example docker deployments in their repo, and some random articles around the web, and their primary documentation.
This setup is far from perfect and there will be updated and refinements to it as I go and interact with it. This is especially true of the final vmauth
piece, as the set of url mappings is currently very limited and unlikely to support all uses.
The code blocks in this post will be sections from the docker-compose file with some modifications. It will usually just be the services portion so networks and volumes will be omitted. You can find the full compose file as part of the associated git repository.
Storage
Due to limitations in VictoriaMetrics storage nodes, retention filtering on a label basis is only available with VictoriaMetrics Enterprise (https://docs.victoriametrics.com/#retention-filters). The recommended solution for multiple retention periods using the community edition is therefore to host a cluster using different storage nodes. Each vmstorage
component manages the storage of metrics, and can be configured with a single retention period. Therefore, for my setup I wanted 3 retention periods (30 days, 6 months, indefinite) which means 3 vmstorage
cluster nodes.
1 | # Storage Nodes |
One thing to note is that the storage nodes also don’t support infinite retention, therefore for the indefinite storage we use an arbitrarily long retention (in this case 100 years).
Insertion
Each storage node also needs a linked insertion node which handles receiving metrics and pushing them into storage. vminsert
nodes can only point to a single storage node, so again you need three insert nodes, each one pointing to its relevant storage node.
1 | # Insertion Nodes |
Routing
Now that there are three insertion nodes, we need a way to abstract over the top of them so that clients don’t have to manually figure out which insertion node to target for specific metrics. This is done via vmagent
which handles routing writes to an insert node, and also the standard Prometheus scraping functionality.
A vmagent
can be configured to have any number of remote write endpoints. By default, it will attempt to share evenly over these urls, switching if any of them become unavailable and generally trying to even the load. However, you can specify relabelling configs on a per url basis. As part of the relabelling configurations, you can choose which metrics to keep and to drop which is how we make this retention system work.
1 | # Agent |
This setup has a few more command sections, the showURL
is used to make the logs show the actual urls its targeting. This isn’t required and was a holdover from debugging when I couldn’t figure out why it was failing to use the VictoriaMetrics API for sending metrics. This makes use of 3 configuration files, one rewrite config per insertion node
1 | # 30d-rewrite.conf |
1 | # 6mo-rewrite.conf |
1 | # infd-rewrite.conf |
In summary, for the 6 month and indefinite storage, drop all metrics unless they are specifically labelled with a matching retention period. For the 30 day node, we should drop any metrics with an explicit retention period that would be matched by another node, but accept everything else. This ensures that metrics with configured retention periods end up where they are meant to, and those that don’t end up in 30 day storage. This is designed to keep data usage down in the case of a misconfigured metric until it can be fixed and routed to the right place.
As mentioned before, vmagent
also handles the default prometheus scraping tasks so this is how we ingest metrics from the rest of the project to provide the backing data for the VictoriaMetrics dashboards. You should note that this is quite a lot of metrics so configure it as you please.
1 | # cluster-metric-ingestion.yml |
As you can see, this also uses the label configs to force each metric from these scrapes to be routed to the right storage node. In this case I’ve chosen 6 months because the overview metrics here can be quite useful, but there are a lot of metrics here that I don’t really care about. I want to be able to see how the system is changing over time, but not necessarily all the way back to its creation. If you want to reduce the amount of datapoints being ingested this way, my recommendation is to increase the scrape_interval
.
Querying
Now that we have data being ingested into the cluster and routed to its correct storage nodes, we want a way to query the data and actually make it usable. This is the role of vmselect
(unsurprisingly), and thankfully, it supports any number of storage nodes which means we can connect it up to all the storage nodes in one go
1 | vmselect: |
Alerting
VictoriaMetrics also ships with a full alerting tool that integrates with Prometheus’ AlertManager tool for paging / notifying. Alerting is somewhat unique in this setup in that it requires being able to read from and write to the metrics cluster (as it tracks running alerts via ALERTS
and ALERTS_FOR_STATE
).
1 | # Alerting |
So vmalert
handles running the actual alerts (hence the volume mount in /etc/alerts
). It will run the alerts against the provided datasource.url
/remoteRead.url
, and then write the metrics back to remoteWrite.url
. When an alert is actually firing, it will send the alerts to notifier.url
which is handled by the alertmanager
instance. You should configure the external.url
value to an accurate value, currently mine is just set to localhost (incorrectly, whoops).
AlertManager handles the actual notification system, the reference to rehook
here is a custom tool written which receives webhooks from AlertManager and sends them into my own custom notification system which ultimately ends up with them being delivered to my phone. Due to some auth issues this had to be implemented as its own thing which is why we use a custom build image. The image runs alertmanager and the rehook webhook server simultaneously using supervisord
.
Logging
VictoriaMetrics also has support for log management via their new victorialogs
and query language LogML
. For scraping the logs from docker, we use flutentbit
which will then route the logs into victorialogs
for storage and querying ability.
1 | # Logging |
And fluentbit is configured to parse the json and push it to victorialogs
1 | # fluent-bit.conf |
Visualisation
Finally, we use trusty Grafana for visualisation of the metrics and the logs via VictoriaMetrics custom data source. To make this work, we need to use a custom build that adds the script the provision the datasource on launch.
1 | # Grafana |
We also provide a set of pre-made dashboards for monitoring the VictoriaMetrics cluster, provided by the team. The launch command is overwritten to run the download and provisioning script for the victorialogs
data source on launch. This is also a nop if its already installed.
Due to some issues with how grafana handles permissions, bind mounts tend to be difficult to manage which is why this uses a docker volume for its storage.
Routing
Finally, as a last minute addition, I wanted to add vmauth
as a http proxy in front of all the components to provide a single endpoint with which to interact with the system. This is still a work in progress as I find endpoints that I need and have missed.
1 | # Routing |
And this uses a very, very basic config that needs expanding
1 | # auth.yml |
Summary
All in all, this produces 14 containers which all actually work together to produce a nice installation. This is being run locally right now but generally seems to be working well. The routing, as far as I can tell, seems to be working as expected by monitoring the bytes used and correlating it with when data sources started writing to it. This is still very much in progress, and my data sources are incredibly limited (right now its just the cluster and monitoring the temperatures on my 3d printer as a proof of concept). I might come back and update this document as I work on it and fix different bits but will generally try and keep the git repository up to date.
If you spot any errors in this configuration, or ways this could be improved, please let me know!