diff --git a/alloy/docker-compose.yml b/alloy/docker-compose.yml new file mode 100644 index 0000000..68c62c2 --- /dev/null +++ b/alloy/docker-compose.yml @@ -0,0 +1,128 @@ +# Required shell env vars (export before `docker compose up`): +# ALLOY_HOSTNAME PROM_URL PROM_USER LOKI_URL LOKI_USER GRAFANA_TOKEN + +services: + alloy: + image: grafana/alloy:v1.10.0 + container_name: alloy + restart: unless-stopped + hostname: ${ALLOY_HOSTNAME:?required} + user: "0:0" + environment: + ALLOY_HOSTNAME: ${ALLOY_HOSTNAME} + PROM_URL: ${PROM_URL:?required} + PROM_USER: ${PROM_USER:?required} + LOKI_URL: ${LOKI_URL:?required} + LOKI_USER: ${LOKI_USER:?required} + GRAFANA_TOKEN: ${GRAFANA_TOKEN:?required} + # UI reachable only from inside the Docker network: http://alloy:12345 + # (attach another container to the same network to access it) + expose: [ "12345" ] + security_opt: [ "no-new-privileges:true" ] + cap_drop: [ ALL ] + volumes: + - alloy-data:/var/lib/alloy/data + - /var/run/docker.sock:/var/run/docker.sock:ro + - /var/lib/docker:/var/lib/docker:ro + - /proc:/host/proc:ro + - /sys:/host/sys:ro + - /:/rootfs:ro,rslave + - /var/log/journal:/var/log/journal:ro + configs: + - { source: alloy_config, target: /etc/alloy/config.alloy } + command: [ run, --server.http.listen-addr=0.0.0.0:12345, --storage.path=/var/lib/alloy/data, /etc/alloy/config.alloy ] + +volumes: + alloy-data: + +configs: + alloy_config: + content: | + prometheus.remote_write "gc" { + endpoint { + url = sys.env("PROM_URL") + basic_auth { + username = sys.env("PROM_USER") + password = sys.env("GRAFANA_TOKEN") + } + } + } + + loki.write "gc" { + endpoint { + url = sys.env("LOKI_URL") + basic_auth { + username = sys.env("LOKI_USER") + password = sys.env("GRAFANA_TOKEN") + } + } + } + + prometheus.exporter.unix "host" { + rootfs_path = "/rootfs" + procfs_path = "/host/proc" + sysfs_path = "/host/sys" + filesystem { mount_points_exclude = "^/(dev|proc|sys|run|var/lib/docker)($|/)" } + netdev { device_exclude = "^(veth|docker|br-|lo).*$" } + } + + prometheus.exporter.cadvisor "containers" { + docker_only = true + } + + discovery.relabel "metrics" { + targets = array.concat( + prometheus.exporter.unix.host.targets, + prometheus.exporter.cadvisor.containers.targets, + ) + rule { target_label = "instance" replacement = constants.hostname } + } + + prometheus.scrape "metrics" { + targets = discovery.relabel.metrics.output + forward_to = [prometheus.relabel.filter.receiver] + scrape_interval = "60s" + } + + // Keep only metrics powering the standard Grafana Cloud dashboards. + // Essential for the 10k active-series cap on Cloud Free. + prometheus.relabel "filter" { + forward_to = [prometheus.remote_write.gc.receiver] + rule { + source_labels = ["__name__"] + action = "keep" + regex = "up|node_(cpu_seconds_total|load(1|5|15)|boot_time_seconds|uname_info|memory_(MemTotal|MemAvailable|Cached|Buffers|SwapTotal|SwapFree)_bytes|filesystem_(size|avail)_bytes|filesystem_readonly|disk_(read|written)_bytes_total|disk_io_time_seconds_total|network_(receive|transmit)_(bytes|errs)_total)|container_(cpu_usage_seconds_total|memory_usage_bytes|fs_(usage|limit)_bytes|network_(receive|transmit)_bytes_total)|machine_memory_bytes" + } + } + + discovery.docker "containers" { + host = "unix:///var/run/docker.sock" + } + + discovery.relabel "container_logs" { + targets = discovery.docker.containers.targets + rule { target_label = "instance" replacement = constants.hostname } + rule { + source_labels = ["__meta_docker_container_name"] + regex = "/(.*)" + target_label = "container" + } + rule { + source_labels = ["__meta_docker_container_name"] + regex = "/alloy" + action = "drop" + } + } + + loki.source.docker "containers" { + host = "unix:///var/run/docker.sock" + targets = discovery.relabel.container_logs.output + forward_to = [loki.write.gc.receiver] + } + + loki.source.journal "system" { + max_age = "12h" + path = "/var/log/journal" + forward_to = [loki.write.gc.receiver] + labels = { job = "systemd-journal", instance = constants.hostname } + }