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
This commit is contained in:
Frank
2026-08-22 03:59:14 -07:00
parent 816986139f
commit 1f4766f233
6 changed files with 76 additions and 11 deletions
+15
View File
@@ -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
+6 -4
View File
@@ -149,7 +149,8 @@ services:
retries: 3 retries: 3
loki: loki:
image: docker.io/grafana/loki:latest # Local build: grafana/loki + static busybox (healthcheck).
image: franksdank/loki:latest
container_name: loki container_name: loki
restart: unless-stopped restart: unless-stopped
# Rootless: container root maps to the invoking user (dank), which # Rootless: container root maps to the invoking user (dank), which
@@ -163,13 +164,14 @@ services:
- /opt/loki/config:/etc/loki - /opt/loki/config:/etc/loki
- /opt/loki/data:/loki - /opt/loki/data:/loki
healthcheck: 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 interval: 30s
timeout: 10s timeout: 10s
retries: 3 retries: 3
alloy: alloy:
image: docker.io/grafana/alloy:latest # Local build: grafana/alloy + static busybox (healthcheck).
image: franksdank/alloy:latest
container_name: alloy container_name: alloy
restart: unless-stopped restart: unless-stopped
volumes: volumes:
@@ -180,7 +182,7 @@ services:
depends_on: depends_on:
- loki - loki
healthcheck: 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 interval: 30s
timeout: 10s timeout: 10s
retries: 3 retries: 3
+15
View File
@@ -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
+10 -4
View File
@@ -139,10 +139,16 @@ fi
if [ '${COMPOSE_UP[$TARGET]}' = '1' ]; then if [ '${COMPOSE_UP[$TARGET]}' = '1' ]; then
echo '[4/4] Restarting stack...' echo '[4/4] Restarting stack...'
if ! podman image exists franksdank/caddy-porkbun:latest; then # Locally built images: caddy + porkbun module, and loki/alloy + a static
echo ' franksdank/caddy-porkbun image missing; building it (a few minutes)...' # busybox (their upstream images lack the wget the healthchecks need).
podman build -t franksdank/caddy-porkbun:latest -f dankclaw/caddy/Containerfile . for dir in caddy loki alloy; do
fi 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 podman-compose -f '${COMPOSE[$TARGET]}' up -d
else else
echo '[4/4] Skipping compose restart (stack runs as bare containers on this host)' echo '[4/4] Skipping compose restart (stack runs as bare containers on this host)'
+4 -3
View File
@@ -6,9 +6,10 @@ logging {
} }
// Local listener so the container healthcheck can probe us. // Local listener so the container healthcheck can probe us.
server { // Alloy >= 1.0 renamed the server component to ui.
http_listen_addr = "0.0.0.0:12345" ui {
grpc_listen_addr = "0.0.0.0:12346" host = "0.0.0.0"
port = 12345
} }
// Ship logs to Loki (container name on the podman network). // Ship logs to Loki (container name on the podman network).
+26
View File
@@ -136,3 +136,29 @@ CADDYFILE="$REPO_ROOT/dankclaw/Caddyfile"
} }
done 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"
}