From 1f4766f233e59a554fcc14c59d3137c9716c0a95 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 22 Aug 2026 03:59:14 -0700 Subject: [PATCH] observability: fix alloy ui component, busybox healthchecks for loki/alloy - alloy 1.x renamed the server component to ui; the old block failed the initial load and the container crash-looped - grafana/loki is distroless and grafana/alloy lacks wget, so their healthchecks could never run; local images add a static busybox from alpine for that one command - deploy.sh builds all three local images if missing - tests: 3 new seams --- dankclaw/alloy/Containerfile | 15 +++++++++++++++ dankclaw/compose.yaml | 10 ++++++---- dankclaw/loki/Containerfile | 15 +++++++++++++++ scripts/deploy.sh | 14 ++++++++++---- shared/alloy/config.alloy | 7 ++++--- tests/caddyfile.bats | 26 ++++++++++++++++++++++++++ 6 files changed, 76 insertions(+), 11 deletions(-) create mode 100644 dankclaw/alloy/Containerfile create mode 100644 dankclaw/loki/Containerfile diff --git a/dankclaw/alloy/Containerfile b/dankclaw/alloy/Containerfile new file mode 100644 index 0000000..97008cf --- /dev/null +++ b/dankclaw/alloy/Containerfile @@ -0,0 +1,15 @@ +# Alloy + a static busybox. +# +# grafana/alloy has a shell but no wget/curl, so the compose healthcheck +# cannot run anything inside the container. This image drops a static +# busybox into it for the healthcheck command only. All Alloy behavior is +# unchanged. +# +# Build on dankclaw: +# podman build -t franksdank/alloy:latest -f dankclaw/alloy/Containerfile . + +FROM docker.io/library/alpine:latest AS tools +# busybox provides wget for the healthcheck. + +FROM docker.io/grafana/alloy:latest +COPY --from=tools /bin/busybox /usr/local/bin/busybox \ No newline at end of file diff --git a/dankclaw/compose.yaml b/dankclaw/compose.yaml index 27d7f54..07a4b00 100644 --- a/dankclaw/compose.yaml +++ b/dankclaw/compose.yaml @@ -149,7 +149,8 @@ services: retries: 3 loki: - image: docker.io/grafana/loki:latest + # Local build: grafana/loki + static busybox (healthcheck). + image: franksdank/loki:latest container_name: loki restart: unless-stopped # Rootless: container root maps to the invoking user (dank), which @@ -163,13 +164,14 @@ services: - /opt/loki/config:/etc/loki - /opt/loki/data:/loki healthcheck: - test: ["CMD", "wget", "--spider", "-q", "http://localhost:3100/ready"] + test: ["CMD", "/usr/local/bin/busybox", "wget", "--spider", "-q", "http://localhost:3100/ready"] interval: 30s timeout: 10s retries: 3 alloy: - image: docker.io/grafana/alloy:latest + # Local build: grafana/alloy + static busybox (healthcheck). + image: franksdank/alloy:latest container_name: alloy restart: unless-stopped volumes: @@ -180,7 +182,7 @@ services: depends_on: - loki healthcheck: - test: ["CMD", "wget", "--spider", "-q", "http://localhost:12345/-/healthy"] + test: ["CMD", "/usr/local/bin/busybox", "wget", "--spider", "-q", "http://localhost:12345/-/healthy"] interval: 30s timeout: 10s retries: 3 diff --git a/dankclaw/loki/Containerfile b/dankclaw/loki/Containerfile new file mode 100644 index 0000000..cef73c4 --- /dev/null +++ b/dankclaw/loki/Containerfile @@ -0,0 +1,15 @@ +# Loki + a static busybox. +# +# grafana/loki is distroless: no shell, no wget, so the compose healthcheck +# cannot run anything inside the container. This image drops a static +# busybox into it for the healthcheck command only. All Loki behavior is +# unchanged. +# +# Build on dankclaw: +# podman build -t franksdank/loki:latest -f dankclaw/loki/Containerfile . + +FROM docker.io/library/alpine:latest AS tools +# busybox provides wget for the healthcheck. + +FROM docker.io/grafana/loki:latest +COPY --from=tools /bin/busybox /usr/local/bin/busybox \ No newline at end of file diff --git a/scripts/deploy.sh b/scripts/deploy.sh index c5c3682..20b0423 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -139,10 +139,16 @@ fi if [ '${COMPOSE_UP[$TARGET]}' = '1' ]; then echo '[4/4] Restarting stack...' - if ! podman image exists franksdank/caddy-porkbun:latest; then - echo ' franksdank/caddy-porkbun image missing; building it (a few minutes)...' - podman build -t franksdank/caddy-porkbun:latest -f dankclaw/caddy/Containerfile . - fi + # Locally built images: caddy + porkbun module, and loki/alloy + a static + # busybox (their upstream images lack the wget the healthchecks need). + for dir in caddy loki alloy; do + img=franksdank/$dir + [ "$dir" = "caddy" ] && img=franksdank/caddy-porkbun + if ! podman image exists $img:latest; then + echo " $img image missing; building it..." + podman build -t $img:latest -f dankclaw/$dir/Containerfile . + fi + done podman-compose -f '${COMPOSE[$TARGET]}' up -d else echo '[4/4] Skipping compose restart (stack runs as bare containers on this host)' diff --git a/shared/alloy/config.alloy b/shared/alloy/config.alloy index b040e87..97020ad 100644 --- a/shared/alloy/config.alloy +++ b/shared/alloy/config.alloy @@ -6,9 +6,10 @@ logging { } // Local listener so the container healthcheck can probe us. -server { - http_listen_addr = "0.0.0.0:12345" - grpc_listen_addr = "0.0.0.0:12346" +// Alloy >= 1.0 renamed the server component to ui. +ui { + host = "0.0.0.0" + port = 12345 } // Ship logs to Loki (container name on the podman network). diff --git a/tests/caddyfile.bats b/tests/caddyfile.bats index 8998f88..1a36423 100644 --- a/tests/caddyfile.bats +++ b/tests/caddyfile.bats @@ -136,3 +136,29 @@ CADDYFILE="$REPO_ROOT/dankclaw/Caddyfile" } done } + +# --- distroless healthchecks (loki/alloy carry a static busybox) --- + +@test "loki and alloy run from local images carrying a static busybox" { + local compose="$REPO_ROOT/dankclaw/compose.yaml" + grep -q 'image: franksdank/loki:latest' "$compose" || { echo "loki image not local"; return 1; } + grep -q 'image: franksdank/alloy:latest' "$compose" || { echo "alloy image not local"; return 1; } + local img + for img in loki alloy; do + local cf="$REPO_ROOT/dankclaw/$img/Containerfile" + [ -f "$cf" ] || { echo "missing $cf"; return 1; } + grep -q 'busybox' "$cf" || { echo "$cf does not install busybox"; return 1; } + done +} + +@test "loki and alloy healthchecks use the busybox wget" { + local compose="$REPO_ROOT/dankclaw/compose.yaml" + local n + n=$(grep -c '/usr/local/bin/busybox' "$compose") + [ "$n" -ge 2 ] || { echo "expected >=2 busybox healthchecks, found $n"; return 1; } +} + +@test "alloy config uses the ui component (the server component was renamed)" { + grep -qE '^ui \{' "$REPO_ROOT/shared/alloy/config.alloy" + ! grep -qE '^server \{' "$REPO_ROOT/shared/alloy/config.alloy" +}