Add multi-arch Docker build and docker-compose stack
Introduce a multi-stage Alpine Dockerfile that cross-compiles via buildx ($BUILDPLATFORM -> $TARGETARCH) so a single invocation produces both linux/amd64 and linux/arm64 images without a qemu-emulated builder. `make docker` loads the native-arch image locally for smoke tests; `make docker-push` publishes a multi-arch manifest. Ship a docker-compose.yaml with opt-in profiles for maglevd/frontend and a .env.example template so operators can mirror /etc/default/vpp-maglev muscle memory into containers.
This commit is contained in:
90
.env.example
Normal file
90
.env.example
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# .env.example — docker-compose.yaml environment for vpp-maglev{d,-frontend}
|
||||||
|
#
|
||||||
|
# Copy to .env and edit. `.env` is gitignored so credentials 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:
|
||||||
|
#
|
||||||
|
# maglevd — the health-checker daemon (needs VPP on the host)
|
||||||
|
# frontend — the read-only web dashboard
|
||||||
|
#
|
||||||
|
# Leave empty (or delete the line) to start nothing. The two services
|
||||||
|
# are fully independent — you can run just the frontend (IPng's default),
|
||||||
|
# just the daemon, or both.
|
||||||
|
#
|
||||||
|
# IPng default: frontend only, connecting to remote maglevds.
|
||||||
|
COMPOSE_PROFILES=frontend
|
||||||
|
|
||||||
|
# Examples:
|
||||||
|
#COMPOSE_PROFILES=maglevd,frontend # both (typical single-host deploy)
|
||||||
|
#COMPOSE_PROFILES=maglevd # daemon only (headless fleet member)
|
||||||
|
#COMPOSE_PROFILES= # nothing
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# maglevd — the health-checker daemon
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# The variable names below mirror /etc/default/vpp-maglev on a Debian
|
||||||
|
# install, so an operator moving between bare-metal and containers can
|
||||||
|
# reuse muscle memory. Each variable is read by the daemon via a
|
||||||
|
# stringFlag(..., MAGLEV_*, ...) fallback in cmd/server/main.go.
|
||||||
|
|
||||||
|
# Path to the YAML config INSIDE the container. Mount your maglev.yaml
|
||||||
|
# at this path — the shipped docker-compose.yaml mounts ./maglev.yaml
|
||||||
|
# from the repo root, so drop your file there or edit the volume line.
|
||||||
|
MAGLEV_CONFIG=/etc/vpp-maglev/maglev.yaml
|
||||||
|
|
||||||
|
# gRPC listen address inside the container. The compose file publishes
|
||||||
|
# port 9090 regardless; keep this as :9090 unless you know why.
|
||||||
|
MAGLEV_GRPC_ADDR=:9090
|
||||||
|
|
||||||
|
# Prometheus /metrics listen address. Empty string disables it.
|
||||||
|
MAGLEV_METRICS_ADDR=:9091
|
||||||
|
|
||||||
|
# VPP binary-API and stats sockets. The compose file bind-mounts
|
||||||
|
# /run/vpp from the host so these resolve when the daemon runs on a
|
||||||
|
# host that also runs VPP. On a host without VPP the maglevd profile
|
||||||
|
# should not be activated — but if it is, maglevd will log repeated
|
||||||
|
# reconnect failures and the LB reconciler will stay idle.
|
||||||
|
MAGLEV_VPP_API_ADDR=/run/vpp/api.sock
|
||||||
|
MAGLEV_VPP_STATS_ADDR=/run/vpp/stats.sock
|
||||||
|
|
||||||
|
# Log verbosity: debug, info, warn, error.
|
||||||
|
MAGLEV_LOG_LEVEL=info
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# maglevd-frontend — the read-only web dashboard
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Comma-separated list of maglevd gRPC addresses. Two common cases:
|
||||||
|
#
|
||||||
|
# - Same-host stack (COMPOSE_PROFILES=maglevd,frontend): the frontend
|
||||||
|
# reaches the daemon via Docker's internal DNS, so "maglevd:9090"
|
||||||
|
# works out of the box.
|
||||||
|
#
|
||||||
|
# - Frontend-only host (IPng case): list one or more remote maglevd
|
||||||
|
# addresses, e.g. "chbtl2.ipng.ch:9090,chlzn1.ipng.ch:9090".
|
||||||
|
MAGLEV_FRONTEND_SERVERS=maglevd:9090
|
||||||
|
|
||||||
|
# HTTP bind address inside the container. The compose file publishes
|
||||||
|
# port 8080 regardless.
|
||||||
|
MAGLEV_FRONTEND_LISTEN=:8080
|
||||||
|
|
||||||
|
# Log verbosity: debug, info, warn, error.
|
||||||
|
MAGLEV_FRONTEND_LOG_LEVEL=info
|
||||||
|
|
||||||
|
# Optional basic-auth credentials for the /admin/ surface. When BOTH
|
||||||
|
# are set and non-empty, /admin/ is reachable and the SPA exposes
|
||||||
|
# backend lifecycle mutations (pause, resume, enable, disable,
|
||||||
|
# set-weight). When either is missing, /admin/ returns 404 and the
|
||||||
|
# SPA hides the admin toggle entirely. Leave commented for a strictly
|
||||||
|
# read-only deployment.
|
||||||
|
#MAGLEV_FRONTEND_USER=admin
|
||||||
|
#MAGLEV_FRONTEND_PASSWORD=changeme
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,5 +1,11 @@
|
|||||||
build/
|
build/
|
||||||
/*.yaml
|
/*.yaml
|
||||||
|
# docker-compose.yaml is an exception to the /*.yaml rule above; it's
|
||||||
|
# tracked as part of the container deployment.
|
||||||
|
!/docker-compose.yaml
|
||||||
|
# .env holds (potentially secret) docker-compose runtime settings and
|
||||||
|
# must never be committed. .env.example is the tracked template.
|
||||||
|
.env
|
||||||
docs/implementation/
|
docs/implementation/
|
||||||
tests/out/
|
tests/out/
|
||||||
tests/.venv/
|
tests/.venv/
|
||||||
|
|||||||
86
Dockerfile
86
Dockerfile
@@ -1,26 +1,82 @@
|
|||||||
FROM golang:1.25 AS builder
|
# syntax=docker/dockerfile:1.6
|
||||||
|
#
|
||||||
|
# Multi-stage, multi-arch build for vpp-maglev container images.
|
||||||
|
#
|
||||||
|
# Produces two runtime images from a single Alpine-based builder:
|
||||||
|
# --target maglevd -> git.ipng.ch/ipng/vpp-maglevd:latest
|
||||||
|
# --target frontend -> git.ipng.ch/ipng/vpp-maglevd-frontend:latest
|
||||||
|
#
|
||||||
|
# The builder stage runs on the host's native arch ($BUILDPLATFORM) and
|
||||||
|
# Go cross-compiles to the image's target arch ($TARGETARCH), so a
|
||||||
|
# single `docker buildx build --platform linux/amd64,linux/arm64`
|
||||||
|
# produces a proper multi-arch manifest without a qemu-emulated
|
||||||
|
# builder. Both BUILDPLATFORM and TARGETARCH are populated
|
||||||
|
# automatically by buildx.
|
||||||
|
#
|
||||||
|
# Both images are driven by docker-compose.yaml at the repo root; see
|
||||||
|
# README.md and .env.example.
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Builder — compiles all four binaries against golang:alpine.
|
||||||
|
# =============================================================================
|
||||||
|
FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder
|
||||||
|
|
||||||
|
# TARGETARCH is supplied by buildx (amd64, arm64, ...). Declared here
|
||||||
|
# so the make invocation below can reference it.
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
# make drives the Go build; nodejs/npm are needed for the SolidJS SPA
|
||||||
|
# that maglevd-frontend embeds at link time via //go:embed; git is used
|
||||||
|
# by the Makefile's `git rev-parse --short HEAD` to stamp the commit
|
||||||
|
# hash into the binary via -ldflags, and golang:alpine doesn't ship it.
|
||||||
|
RUN apk add --no-cache make nodejs npm git
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
|
# Cache Go modules first 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 make build
|
|
||||||
|
|
||||||
# ---- runtime image ----------------------------------------------------------
|
# The protoc-generated files are checked in, but docker COPY resets
|
||||||
FROM debian:bookworm-slim
|
# their mtimes so make may think they need regenerating. Touch them
|
||||||
|
# to ensure they are newer than the proto file and skip the rule.
|
||||||
|
RUN touch internal/grpcapi/maglev.pb.go internal/grpcapi/maglev_grpc.pb.go
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
# build-amd64 / build-arm64 both set GOOS=linux GOARCH=<arch> and emit
|
||||||
iproute2 \
|
# into build/<arch>/, so the runtime stages below can COPY straight
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
# out of build/$TARGETARCH/. CGO_ENABLED=0 is exported by the Makefile.
|
||||||
|
RUN make build-$TARGETARCH
|
||||||
|
|
||||||
COPY --from=builder /src/bin/maglevd /usr/local/bin/maglevd
|
# =============================================================================
|
||||||
|
# Runtime: maglevd (health-checker daemon)
|
||||||
|
# =============================================================================
|
||||||
|
FROM alpine:3 AS maglevd
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
# Required capabilities:
|
# ca-certificates — HTTPS health checks need a trust store.
|
||||||
# CAP_NET_RAW — open raw ICMP sockets for health probing
|
# iproute2 — `ip netns` helpers, useful for debugging netns-scoped probes.
|
||||||
#
|
RUN apk add --no-cache ca-certificates iproute2
|
||||||
# Grant these in your container runtime, e.g.:
|
|
||||||
# docker run --cap-add NET_RAW ...
|
|
||||||
# or in Kubernetes via securityContext.capabilities.add
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/maglevd"]
|
COPY --from=builder /src/build/$TARGETARCH/maglevd /usr/sbin/maglevd
|
||||||
|
|
||||||
|
# gRPC control plane (9090) and Prometheus /metrics (9091). The actual
|
||||||
|
# listen addresses are set by MAGLEV_GRPC_ADDR / MAGLEV_METRICS_ADDR.
|
||||||
|
EXPOSE 9090 9091
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/sbin/maglevd"]
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Runtime: maglevd-frontend (read-only dashboard + optional /admin/)
|
||||||
|
# =============================================================================
|
||||||
|
FROM alpine:3 AS frontend
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
RUN apk add --no-cache ca-certificates
|
||||||
|
|
||||||
|
COPY --from=builder /src/build/$TARGETARCH/maglevd-frontend /usr/sbin/maglevd-frontend
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/sbin/maglevd-frontend"]
|
||||||
|
|||||||
23
Makefile
23
Makefile
@@ -54,7 +54,7 @@ GO_VERSION ?= 1.25.0
|
|||||||
# make install-deps GOLANGCI_LINT_VERSION=2.0.0
|
# make install-deps GOLANGCI_LINT_VERSION=2.0.0
|
||||||
GOLANGCI_LINT_VERSION ?= 1.64.0
|
GOLANGCI_LINT_VERSION ?= 1.64.0
|
||||||
|
|
||||||
.PHONY: all build build-amd64 build-arm64 test proto vpp-binapi lint fixstyle fixstyle-web pkg-deb robot-test clean maglevd-frontend-web install-deps install-deps-apt install-deps-go install-deps-go-tools
|
.PHONY: all build build-amd64 build-arm64 test proto vpp-binapi lint fixstyle fixstyle-web pkg-deb docker docker-push robot-test clean maglevd-frontend-web install-deps install-deps-apt install-deps-go install-deps-go-tools
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
@@ -93,6 +93,27 @@ pkg-deb: build-amd64 build-arm64
|
|||||||
debian/build-deb.sh vpp-maglev amd64 $(VERSION)
|
debian/build-deb.sh vpp-maglev amd64 $(VERSION)
|
||||||
debian/build-deb.sh vpp-maglev arm64 $(VERSION)
|
debian/build-deb.sh vpp-maglev arm64 $(VERSION)
|
||||||
|
|
||||||
|
# docker — build both container images for the current host arch and
|
||||||
|
# load them into the local docker daemon. Uses buildx so the Dockerfile
|
||||||
|
# can cross-compile via $TARGETARCH; --load only supports a single
|
||||||
|
# platform, so this target is for local smoke tests. Use docker-push
|
||||||
|
# for a true multi-arch manifest. Each image is tagged both :v$(VERSION)
|
||||||
|
# and :latest in one build, so bumping VERSION is the only change
|
||||||
|
# needed to cut a new release — no hand-edited tag lists to forget.
|
||||||
|
docker:
|
||||||
|
docker buildx build --load --target maglevd -t git.ipng.ch/ipng/vpp-maglevd:v$(VERSION) -t git.ipng.ch/ipng/vpp-maglevd:latest .
|
||||||
|
docker buildx build --load --target frontend -t git.ipng.ch/ipng/vpp-maglevd-frontend:v$(VERSION) -t git.ipng.ch/ipng/vpp-maglevd-frontend:latest .
|
||||||
|
|
||||||
|
# docker-push — build a multi-arch (linux/amd64,linux/arm64) manifest
|
||||||
|
# for both images and push it straight to the registry, tagged both
|
||||||
|
# :v$(VERSION) and :latest. Buildx won't --load a multi-platform
|
||||||
|
# result into the local daemon, so push is the only way to
|
||||||
|
# materialise the combined manifest. Assumes the caller is already
|
||||||
|
# logged in to git.ipng.ch.
|
||||||
|
docker-push:
|
||||||
|
docker buildx build --platform linux/amd64,linux/arm64 --push --target maglevd -t git.ipng.ch/ipng/vpp-maglevd:v$(VERSION) -t git.ipng.ch/ipng/vpp-maglevd:latest .
|
||||||
|
docker buildx build --platform linux/amd64,linux/arm64 --push --target frontend -t git.ipng.ch/ipng/vpp-maglevd-frontend:v$(VERSION) -t git.ipng.ch/ipng/vpp-maglevd-frontend:latest .
|
||||||
|
|
||||||
test: $(GEN_FILES)
|
test: $(GEN_FILES)
|
||||||
go test ./...
|
go test ./...
|
||||||
|
|
||||||
|
|||||||
46
README.md
46
README.md
@@ -114,13 +114,43 @@ deployments.
|
|||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
```sh
|
A single multi-stage Alpine `Dockerfile` produces two images, driven
|
||||||
docker build -t maglevd .
|
from `docker-compose.yaml` at the repo root:
|
||||||
docker run --cap-add NET_RAW \
|
|
||||||
-v /etc/vpp-maglev:/etc/vpp-maglev maglevd
|
|
||||||
|
|
||||||
# With netns-scoped health checks (maglev.yaml sets healthchecker.netns):
|
- `git.ipng.ch/ipng/vpp-maglevd:latest` — the health-checker daemon.
|
||||||
docker run --cap-add NET_RAW --cap-add SYS_ADMIN \
|
- `git.ipng.ch/ipng/vpp-maglevd-frontend:latest` — the read-only web
|
||||||
-v /etc/vpp-maglev:/etc/vpp-maglev \
|
dashboard.
|
||||||
-v /var/run/netns:/var/run/netns maglevd
|
|
||||||
|
Both services are **opt-in** via Docker Compose profiles, so the same
|
||||||
|
stack file works for operators who want the daemon only, the frontend
|
||||||
|
only (IPng's own deployment), or both on one host. Copy the example
|
||||||
|
env file, choose which services to run, and start the stack:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cp .env.example .env
|
||||||
|
$EDITOR .env # set COMPOSE_PROFILES and any overrides
|
||||||
|
docker compose up -d # starts whichever profiles are active
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Valid `COMPOSE_PROFILES` values are `maglevd`, `frontend`, or both
|
||||||
|
comma-separated. Leaving it empty starts nothing. The daemon
|
||||||
|
container runs with all capabilities granted (`cap_add: ALL`) so ICMP
|
||||||
|
probes and `netns`-scoped probes both work without re-plumbing the
|
||||||
|
container; the frontend runs with no extra privileges. The `MAGLEV_*`
|
||||||
|
variables in `.env.example` mirror `/etc/default/vpp-maglev` on a
|
||||||
|
Debian install, so muscle memory carries over between the two
|
||||||
|
deployment modes.
|
||||||
|
|
||||||
|
Build or push the images:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make docker # buildx --load, native arch only (local smoke test)
|
||||||
|
make docker-push # buildx --push linux/amd64,linux/arm64 multi-arch manifest
|
||||||
|
```
|
||||||
|
|
||||||
|
`make docker` loads a single-arch image into the local daemon so you
|
||||||
|
can run it immediately; `make docker-push` produces a true multi-arch
|
||||||
|
manifest and pushes it to `git.ipng.ch/ipng/...`. Both use `docker
|
||||||
|
buildx`, and the Dockerfile cross-compiles from the host's
|
||||||
|
`$BUILDPLATFORM` to each `$TARGETARCH` via `make build-<arch>`, so no
|
||||||
|
qemu-emulated builder is involved.
|
||||||
|
|||||||
64
docker-compose.yaml
Normal file
64
docker-compose.yaml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# docker-compose.yaml — vpp-maglev{d,-frontend} container stack
|
||||||
|
#
|
||||||
|
# Two services built from a single multi-stage Alpine Dockerfile:
|
||||||
|
#
|
||||||
|
# maglevd -> git.ipng.ch/ipng/vpp-maglevd:latest (health-checker daemon)
|
||||||
|
# frontend -> git.ipng.ch/ipng/vpp-maglevd-frontend:latest (read-only web dashboard)
|
||||||
|
#
|
||||||
|
# Both services are opt-in via Docker Compose "profiles". Copy
|
||||||
|
# .env.example to .env, set COMPOSE_PROFILES to include the
|
||||||
|
# services you want, and run:
|
||||||
|
#
|
||||||
|
# docker compose up -d
|
||||||
|
#
|
||||||
|
# See .env.example for every tunable. See README.md for the
|
||||||
|
# operational overview.
|
||||||
|
|
||||||
|
services:
|
||||||
|
maglevd:
|
||||||
|
profiles: [maglevd]
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
target: maglevd
|
||||||
|
image: git.ipng.ch/ipng/vpp-maglevd:latest
|
||||||
|
container_name: vpp-maglevd
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# maglevd needs CAP_NET_RAW for ICMP probes and CAP_SYS_ADMIN for
|
||||||
|
# netns-scoped probes (see docs/design.md NFR-4.1). Granting ALL
|
||||||
|
# keeps the container operationally identical to a bare-metal
|
||||||
|
# maglevd running under the Debian systemd unit, and lets
|
||||||
|
# operators flip healthchecker.netns without re-plumbing the
|
||||||
|
# container's capability set.
|
||||||
|
cap_add:
|
||||||
|
- ALL
|
||||||
|
|
||||||
|
# The daemon reads these via the same env-var fallback that the
|
||||||
|
# systemd unit uses — see debian/default.vpp-maglev.
|
||||||
|
env_file: .env
|
||||||
|
|
||||||
|
# Mount the config and VPP's runtime sockets. The /run/vpp mount
|
||||||
|
# is only meaningful when the container runs on a host that also
|
||||||
|
# runs VPP; on a frontend-only host the maglevd profile is not
|
||||||
|
# activated and this block is irrelevant.
|
||||||
|
volumes:
|
||||||
|
- ./maglev.yaml:/etc/vpp-maglev/maglev.yaml:ro
|
||||||
|
- /run/vpp:/run/vpp
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- "9090:9090" # gRPC control plane
|
||||||
|
- "9091:9091" # Prometheus /metrics
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
profiles: [frontend]
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
target: frontend
|
||||||
|
image: git.ipng.ch/ipng/vpp-maglevd-frontend:latest
|
||||||
|
container_name: vpp-maglevd-frontend
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file: .env
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
Reference in New Issue
Block a user