docker: multi-arch image via buildx BUILDPLATFORM/TARGETARCH
Rework the Dockerfile to produce a proper multi-arch manifest from a single `docker buildx build --platform linux/amd64,linux/arm64`. The builder stage runs on the host's native arch ($BUILDPLATFORM) and Go cross-compiles to each requested $TARGETARCH via the Makefile's build-$TARGETARCH targets — no qemu-emulated builder, no per-arch Dockerfile duplication. VERSION/COMMIT/DATE flow from --build-arg through to the -ldflags -X injection so images stamp the same metadata as `make build` on bare metal. docker-compose.yml gains Docker Compose "profiles" (collector, aggregator, frontend) and an `env_file: .env`, mirroring vpp-maglev's pattern. All three services ship from one multi-arch image and select their binary via `command:`. Collector uses network_mode: host so UDP from host nginx on 127.0.0.1 actually reaches it; aggregator/frontend bridge-network with published ports. .env.example documents every COLLECTOR_*, AGGREGATOR_*, FRONTEND_* env var with its default plus COMPOSE_PROFILES and notes for Docker-specific cases (service DNS names, AGGREGATOR_COLLECTORS spelling). .gitignore excludes /.env so local tunables stay local. Verified: `docker buildx build --platform linux/amd64,linux/arm64` goes through cleanly from one invocation; local --load build's four binaries report version 0.9.1 with the injected commit/date. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
127
.env.example
Normal file
127
.env.example
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# .env.example — docker-compose.yml environment for nginx-logtail.
|
||||||
|
#
|
||||||
|
# Copy to .env and edit. `.env` is gitignored so local edits stay local:
|
||||||
|
#
|
||||||
|
# cp .env.example .env
|
||||||
|
# $EDITOR .env
|
||||||
|
# docker compose up -d
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Which containers start
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Docker Compose "profiles" decide which services actually start when you
|
||||||
|
# run `docker compose up`. Set COMPOSE_PROFILES below to a comma-separated
|
||||||
|
# list of the services you want. Valid values:
|
||||||
|
#
|
||||||
|
# collector — ingests nginx logs (file + UDP); runs on each nginx host
|
||||||
|
# aggregator — merges collectors into a central view
|
||||||
|
# frontend — HTTP dashboard (reads aggregator or a single collector)
|
||||||
|
#
|
||||||
|
# Leave empty (or delete the line) to start nothing.
|
||||||
|
|
||||||
|
# Default: central host stack (aggregator + frontend), mirrors the
|
||||||
|
# shipped docker-compose.yml layout.
|
||||||
|
COMPOSE_PROFILES=aggregator,frontend
|
||||||
|
|
||||||
|
# Examples:
|
||||||
|
#COMPOSE_PROFILES=collector,aggregator,frontend # single-host all-in-one
|
||||||
|
#COMPOSE_PROFILES=collector # nginx host running only the collector
|
||||||
|
#COMPOSE_PROFILES=frontend # frontend pointing at a remote aggregator
|
||||||
|
#COMPOSE_PROFILES= # nothing
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Collector (nginx-logtail-collector)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# The variable names below mirror /etc/default/nginx-logtail on a Debian
|
||||||
|
# install, so an operator moving between bare-metal and containers can
|
||||||
|
# reuse muscle memory. Each variable is read by the collector via an
|
||||||
|
# envOr(..., COLLECTOR_*, ...) fallback in cmd/collector/main.go.
|
||||||
|
|
||||||
|
# gRPC listen address inside the container. Host-networked, so this is
|
||||||
|
# also the address the aggregator dials.
|
||||||
|
COLLECTOR_LISTEN=:9090
|
||||||
|
|
||||||
|
# Prometheus /metrics listen address. Set to "" to disable the endpoint.
|
||||||
|
COLLECTOR_PROM_LISTEN=:9100
|
||||||
|
|
||||||
|
# Comma-separated log file paths or glob patterns. At least one of
|
||||||
|
# COLLECTOR_LOGS, COLLECTOR_LOGS_FILE, or COLLECTOR_LOGTAIL_PORT > 0 must
|
||||||
|
# be set; otherwise the collector refuses to start. Leave empty to run
|
||||||
|
# UDP-only. The shipped compose file bind-mounts /var/log/nginx:ro so
|
||||||
|
# paths under that tree are readable.
|
||||||
|
COLLECTOR_LOGS=
|
||||||
|
|
||||||
|
# File containing one path/glob per line.
|
||||||
|
COLLECTOR_LOGS_FILE=
|
||||||
|
|
||||||
|
# Name for this collector in query responses and ListTargets. Docker's
|
||||||
|
# default container hostname is the short container id; set something
|
||||||
|
# stable here if you want meaningful source names across restarts.
|
||||||
|
#COLLECTOR_SOURCE=nginx1
|
||||||
|
|
||||||
|
# IPv4 / IPv6 prefix lengths for client address bucketing.
|
||||||
|
COLLECTOR_V4PREFIX=24
|
||||||
|
COLLECTOR_V6PREFIX=48
|
||||||
|
|
||||||
|
# How often to rescan COLLECTOR_LOGS globs for new/removed files.
|
||||||
|
COLLECTOR_SCAN_INTERVAL=10s
|
||||||
|
|
||||||
|
# UDP port for ipng_stats_logtail datagrams from nginx-ipng-stats-plugin.
|
||||||
|
# Set to 0 to disable the UDP listener entirely.
|
||||||
|
COLLECTOR_LOGTAIL_PORT=9514
|
||||||
|
|
||||||
|
# UDP bind address. Host-networked default accepts localhost traffic
|
||||||
|
# from host nginx; change to 0.0.0.0 if packets come from off-host.
|
||||||
|
COLLECTOR_LOGTAIL_BIND=127.0.0.1
|
||||||
|
|
||||||
|
# Extra arguments appended to the collector argv. Useful for temporary
|
||||||
|
# overrides or flags without an env-var form.
|
||||||
|
COLLECTOR_ARGS=
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Aggregator (nginx-logtail-aggregator)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# gRPC listen address. The compose file publishes 9091 regardless.
|
||||||
|
AGGREGATOR_LISTEN=:9091
|
||||||
|
|
||||||
|
# Comma-separated collector addresses (MANDATORY). How you spell them
|
||||||
|
# depends on whether the collectors are in the same compose stack:
|
||||||
|
#
|
||||||
|
# same-host all-in-one (COMPOSE_PROFILES=collector,aggregator,frontend):
|
||||||
|
# the collector uses network_mode: host, so reach it via the host's
|
||||||
|
# address — "host.docker.internal:9090" on Docker Desktop, or the
|
||||||
|
# host's LAN address on Linux.
|
||||||
|
#
|
||||||
|
# central aggregator pointing at remote collectors:
|
||||||
|
# list each remote host, e.g. "nginx1:9090,nginx2:9090,nginx3:9090".
|
||||||
|
AGGREGATOR_COLLECTORS=nginx1:9090
|
||||||
|
|
||||||
|
# Display name for this aggregator. Uncomment to override the short
|
||||||
|
# container id that os.Hostname() returns inside Docker.
|
||||||
|
#AGGREGATOR_SOURCE=agg-prod
|
||||||
|
|
||||||
|
# Extra arguments appended to the aggregator argv.
|
||||||
|
AGGREGATOR_ARGS=
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Frontend (nginx-logtail-frontend)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# HTTP listen address. The compose file publishes 8080 regardless.
|
||||||
|
FRONTEND_LISTEN=:8080
|
||||||
|
|
||||||
|
# Default gRPC endpoint the dashboard queries. When the aggregator runs
|
||||||
|
# in the same compose stack, Docker's internal DNS resolves the service
|
||||||
|
# name to the bridge IP — "aggregator:9091" just works. Point at a
|
||||||
|
# remote aggregator instead for a frontend-only deployment.
|
||||||
|
FRONTEND_TARGET=aggregator:9091
|
||||||
|
|
||||||
|
# Default number of table rows. Override per-URL with ?n=N.
|
||||||
|
FRONTEND_N=25
|
||||||
|
|
||||||
|
# Meta-refresh interval (seconds). Set 0 to disable auto-refresh.
|
||||||
|
FRONTEND_REFRESH=30
|
||||||
|
|
||||||
|
# Extra arguments appended to the frontend argv.
|
||||||
|
FRONTEND_ARGS=
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -7,6 +7,9 @@
|
|||||||
# Build output — per-arch binaries and .deb packages, all under build/.
|
# Build output — per-arch binaries and .deb packages, all under build/.
|
||||||
/build/
|
/build/
|
||||||
|
|
||||||
|
# Local docker-compose overrides (copy .env.example -> .env).
|
||||||
|
/.env
|
||||||
|
|
||||||
# Editor
|
# Editor
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|||||||
59
Dockerfile
59
Dockerfile
@@ -1,21 +1,64 @@
|
|||||||
FROM golang:1.24-alpine AS builder
|
# syntax=docker/dockerfile:1.6
|
||||||
|
#
|
||||||
|
# Multi-stage, multi-arch build for nginx-logtail.
|
||||||
|
#
|
||||||
|
# A single `docker buildx build --platform linux/amd64,linux/arm64`
|
||||||
|
# produces a proper multi-arch manifest without a qemu-emulated builder:
|
||||||
|
# the builder runs on the host's $BUILDPLATFORM and Go cross-compiles
|
||||||
|
# to each requested $TARGETARCH via the Makefile's build-amd64 /
|
||||||
|
# build-arm64 targets. Both BUILDPLATFORM and TARGETARCH are populated
|
||||||
|
# automatically by buildx.
|
||||||
|
#
|
||||||
|
# One runtime image contains all four binaries; docker-compose.yml
|
||||||
|
# picks which one each service runs via `command:`.
|
||||||
|
#
|
||||||
|
# Driven from the Makefile:
|
||||||
|
# make docker # native arch, --load into local daemon
|
||||||
|
# make docker-push # multi-arch manifest, pushed to the registry
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Builder — compiles all four binaries on the host's native arch.
|
||||||
|
# =============================================================================
|
||||||
|
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
|
||||||
|
|
||||||
|
# TARGETARCH (amd64, arm64, ...) is supplied by buildx; VERSION/COMMIT/DATE
|
||||||
|
# come in as --build-arg from `make docker` / `make docker-push`.
|
||||||
|
ARG TARGETARCH
|
||||||
ARG VERSION=dev
|
ARG VERSION=dev
|
||||||
ARG COMMIT=unknown
|
ARG COMMIT=unknown
|
||||||
ARG DATE=unknown
|
ARG DATE=unknown
|
||||||
|
|
||||||
ENV CGO_ENABLED=0 \
|
# make drives the build. git lets the Makefile stamp a commit hash via
|
||||||
LDFLAGS="-s -w -X git.ipng.ch/ipng/nginx-logtail/internal/version.Version=${VERSION} -X git.ipng.ch/ipng/nginx-logtail/internal/version.Commit=${COMMIT} -X git.ipng.ch/ipng/nginx-logtail/internal/version.Date=${DATE}"
|
# `git rev-parse` when --build-arg COMMIT isn't supplied; when it is,
|
||||||
|
# the command-line override below wins and git is only needed to avoid
|
||||||
|
# the fallback branch warning.
|
||||||
|
RUN apk add --no-cache make git
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
|
# Cache Go modules so code-only rebuilds skip the download.
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN go build -trimpath -ldflags="${LDFLAGS}" -o /out/collector ./cmd/collector && \
|
|
||||||
go build -trimpath -ldflags="${LDFLAGS}" -o /out/aggregator ./cmd/aggregator && \
|
|
||||||
go build -trimpath -ldflags="${LDFLAGS}" -o /out/frontend ./cmd/frontend && \
|
|
||||||
go build -trimpath -ldflags="${LDFLAGS}" -o /out/cli ./cmd/cli
|
|
||||||
|
|
||||||
|
# Generated proto files are checked in, but `docker COPY` resets mtimes,
|
||||||
|
# which can make `make` think they are stale relative to the .proto
|
||||||
|
# source. Touch them so the regen rule is skipped.
|
||||||
|
RUN touch proto/logtailpb/logtail.pb.go proto/logtailpb/logtail_grpc.pb.go
|
||||||
|
|
||||||
|
# The Makefile's build-<arch> target sets GOOS=linux GOARCH=<arch>
|
||||||
|
# CGO_ENABLED=0 and emits build/<arch>/<binary> with the usual -ldflags
|
||||||
|
# version injection. Forwarding VERSION/COMMIT_HASH/DATE on the command
|
||||||
|
# line stamps the values supplied by --build-arg into the binaries.
|
||||||
|
RUN make VERSION=$VERSION COMMIT_HASH=$COMMIT DATE=$DATE build-$TARGETARCH
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Runtime — scratch, no OS, no shell. All four binaries.
|
||||||
|
# =============================================================================
|
||||||
FROM scratch
|
FROM scratch
|
||||||
COPY --from=builder /out/ /usr/local/bin/
|
ARG TARGETARCH
|
||||||
|
COPY --from=builder /src/build/$TARGETARCH/collector /usr/local/bin/collector
|
||||||
|
COPY --from=builder /src/build/$TARGETARCH/aggregator /usr/local/bin/aggregator
|
||||||
|
COPY --from=builder /src/build/$TARGETARCH/frontend /usr/local/bin/frontend
|
||||||
|
COPY --from=builder /src/build/$TARGETARCH/cli /usr/local/bin/cli
|
||||||
|
|||||||
@@ -1,26 +1,69 @@
|
|||||||
|
# docker-compose.yml — nginx-logtail container stack
|
||||||
|
#
|
||||||
|
# One multi-arch image ships all four binaries (built by the companion
|
||||||
|
# Dockerfile). Each service below picks which binary it runs via
|
||||||
|
# `command:`. Services are opt-in via Docker Compose "profiles":
|
||||||
|
#
|
||||||
|
# cp .env.example .env
|
||||||
|
# $EDITOR .env # set COMPOSE_PROFILES and tunables
|
||||||
|
# docker compose up -d
|
||||||
|
#
|
||||||
|
# Valid profile names: collector, aggregator, frontend.
|
||||||
|
#
|
||||||
|
# Typical deployments:
|
||||||
|
#
|
||||||
|
# COMPOSE_PROFILES=aggregator,frontend # central host (default)
|
||||||
|
# COMPOSE_PROFILES=collector # nginx host running only the collector in Docker
|
||||||
|
# COMPOSE_PROFILES=collector,aggregator,frontend # single-host all-in-one
|
||||||
|
#
|
||||||
|
# See .env.example for every tunable.
|
||||||
|
|
||||||
services:
|
services:
|
||||||
aggregator:
|
collector:
|
||||||
build: .
|
profiles: [collector]
|
||||||
image: git.ipng.ch/ipng/nginx-logtail
|
build:
|
||||||
command: ["/usr/local/bin/aggregator"]
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: git.ipng.ch/ipng/nginx-logtail:latest
|
||||||
|
container_name: nginx-logtail-collector
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
command: ["/usr/local/bin/collector"]
|
||||||
AGGREGATOR_LISTEN: ":9091"
|
env_file: .env
|
||||||
AGGREGATOR_COLLECTORS: "" # e.g. "collector1:9090,collector2:9090"
|
|
||||||
AGGREGATOR_SOURCE: "" # defaults to container hostname
|
# Host networking is the simplest way to accept UDP datagrams from a
|
||||||
|
# host nginx on 127.0.0.1:9514 — bridge networking with port mapping
|
||||||
|
# does not round-trip packets that never leave loopback. Mac/Windows
|
||||||
|
# Docker Desktop users must switch to a bridge + explicit
|
||||||
|
# COLLECTOR_LOGTAIL_BIND.
|
||||||
|
network_mode: host
|
||||||
|
|
||||||
|
# Mount nginx log directory so the file tailer can read it. Harmless
|
||||||
|
# when COLLECTOR_LOGS is empty (the collector simply runs UDP-only).
|
||||||
|
volumes:
|
||||||
|
- /var/log/nginx:/var/log/nginx:ro
|
||||||
|
|
||||||
|
aggregator:
|
||||||
|
profiles: [aggregator]
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: git.ipng.ch/ipng/nginx-logtail:latest
|
||||||
|
container_name: nginx-logtail-aggregator
|
||||||
|
restart: unless-stopped
|
||||||
|
command: ["/usr/local/bin/aggregator"]
|
||||||
|
env_file: .env
|
||||||
ports:
|
ports:
|
||||||
- "9091:9091"
|
- "9091:9091" # gRPC
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
image: git.ipng.ch/ipng/nginx-logtail
|
profiles: [frontend]
|
||||||
command: ["/usr/local/bin/frontend"]
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: git.ipng.ch/ipng/nginx-logtail:latest
|
||||||
|
container_name: nginx-logtail-frontend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
command: ["/usr/local/bin/frontend"]
|
||||||
FRONTEND_LISTEN: ":8080"
|
env_file: .env
|
||||||
FRONTEND_TARGET: "aggregator:9091"
|
|
||||||
FRONTEND_N: "25"
|
|
||||||
FRONTEND_REFRESH: "30"
|
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080" # HTTP dashboard
|
||||||
depends_on:
|
|
||||||
- aggregator
|
|
||||||
|
|||||||
Reference in New Issue
Block a user