Dynamic prometheus.yml in docker with consul-template
The idea was to run 2 Prometheus instances in a high-availability (HA) setup with docker. Furthermore, we would like to introduce Thanos into this setup, in order to have a single datasource in Grafana for all Prometheus instances, and also to use long-term cloud storage for Prometheus data, which Thanos also enables.
Usually, a set of HA Prometheus instances would have identical configuration files (prometheus.yml
) - same targets, same labels etc. In case something would happen to one of the instances, a switch on some level could easily be made to the second instance, which would have virtually the same data as the first instance.
Thanos, among other things, offers the ability to deduplicate metrics coming from HA Prometheus instances. To be able to do that, it needs external_labels
, which must differ from instance to instance in the same HA set, so we would need to have at least one unique external_labels
parameter in prometheus.yml
on each of the instances.
One could use separate, static files for each of the instances, but that seemed too cumbersome to maintain. An approach, which would enable something in the container to "fill in" a configuration file template with correct values upon startup, seemed more convenient. This template would be shared among all instances, and deployed with the container.
To accomplish that, we used:
prom/prometheus
container as base for our customized Prometheus containerconsul-template
to fill inprometheus.yml
template inside the containerdumb-init
to runconsul-template
inside the container, which would run Prometheus itself, after the template would be filled in
consul-template
consul-template
is a tool from HashiCorp, which:
queries a Consul or Vault cluster and updates any number of specified templates on the file system. As an added bonus, it can optionally run arbitrary commands when the update process completes.
At this point we will not integrate with Consul or Vault just yet, but it will be useful nonetheless.
For our case we need 2 files:
- Configuration file for
consul-template
- A
prometheus.yml.ctpl
template file to fill in.
As consul-template
configuration file (config.hcl
) we used:
exec {
command = "/bin/prometheus --config.file=/etc/prometheus/prometheus.yml"
reload_signal = "SIGHUP"
kill_signal = "SIGTERM"
kill_timeout = "15s"
}
template {
source = "/etc/prometheus/prometheus.yml.ctpl"
destination = "/etc/prometheus/prometheus.yml"
perms = 0640
}
The important parts are command
, source
and destination
.
source
defines the path of the template within the container, which should be used to generate the actual Prometheus configuration file in the destination
path.
command
defines, which command should be run, after the configuration file has been generated.
dumb-init
dumb-init
is a simple process supervisor and minimal init system. It would allow us to string together consul-template
and Prometheus inside the same container. We would use dumb-init
as ENTRYPOINT
for our Prometheus container.
Besides the project description on GitHub, I also found Yelp's Engineering blog post on dumb-init
useful to understand its role in this case.
prometheus.yml template
This is the initial template file (prometheus.yml.ctpl
) we prepared for consul-template
to fill in:
global:
scrape_interval: 30s
evaluation_interval: 30s
external_labels:
prometheus_replica: {{ or (env "PROMETHEUS_REPLICA") "none" }}
scrape_configs:
- job_name: prometheus
honor_labels: true
static_configs:
- targets:
- localhost:9090
relabel_configs:
- target_label: instance
replacement: {{ or (env "PROMETHEUS_INSTANCE") "local" }}
We replace 2 parameters with values stored in specific environment variables on the docker host with consul-template
. These parameters could come from other sources as well, e.g., Vault or Consul, as mentioned earlier. Some default values are set here too, in case the environment variables are not set for some reason, so we would not end up with an invalid Prometheus configuration file.
These environment variables would be set on the docker instances we would run Prometheus containers on, and passed on to the containers when they are run via --env-file
parameter for docker
or env_file
parameter in docker-compose
file.
The rest of the file is quite basic - Prometheus is configured to scrape itself for built-in metrics and do some relabeling.
This template, along with consul-template
configuration and binary, should be inside the container, so we need a new Dockefile and docker image.
Dockerfile
FROM hashicorp/consul-template:0.20.0-scratch as consul-template
FROM prom/prometheus:v2.7.1
# Get dumb-init
RUN wget --no-check-certificate -O /home/dumb-init \
https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_amd64 && \
chmod +x /home/dumb-init
# Copy consul-template from consul-template image
COPY --from=consul-template /consul-template /bin/consul-template
# Copy consul-template configuration
COPY ./config/consul-template/config.hcl /consul-template/config.hcl
# Copy Prometheus configuration file template
COPY ./config/prometheus/prometheus.yml.ctpl /etc/prometheus/prometheus.yml.ctpl
ENTRYPOINT ["/home/dumb-init", "--"]
CMD ["/bin/consul-template", "-config=/consul-template/config.hcl"]
The important part here is the ENTRYPOINT
/CMD
- we configure dumb-init
to run consul-template
as the container starts up. It would then, based on the configuration we provided in config.hcl
, populate prometheus.yml.ctpl
template, creating prometheus.yml
. Lastly, it would start Prometheus itself, as configured config.hcl
.
Summary
This setup enables dynamic setting of external_labels
in Prometheus configuration files on HA Prometheus instances running in docker containers. Thanos would then be able to use these labels to perform metric deduplication, which is crucial in similar HA setups.