Files
traefik-llmgateway/integration/docker-compose.yml
T
lukaszraczylo 24f4a2d8c5 test(integration): config hot-reload proof via directory provider
Single-file file-provider watch (providers.file.filename) never fires
under Docker Desktop's VirtioFS bind mount — empirically confirmed
against this stack. Traefik's directory-based file provider
(providers.file.directory) is the fix, and is also the correct pattern
for a Kubernetes ConfigMap mount (a symlink swap, which a single-file
watch never observes either).

integration/traefik/traefik.yml: providers.file.filename ->
providers.file.directory (watch: true unchanged). docker-compose.yml:
both traefik1 and traefik2 now bind-mount integration/traefik/dynamic/
(a directory) instead of the single rendered dynamic.yml. Makefile's
integration-up now mkdir -p's and renders into
integration/traefik/dynamic/dynamic.yml; .gitignore updated to match.

New TestConfigHotReload (build tag integration): baseline chat succeeds,
then a sustained ~20rps request loop runs against an existing model
while the rendered config is rewritten (temp file + os.Rename, atomic,
mirroring a k8s symlink swap) to add a new model alias. /v1/models is
polled up to 30s for the alias to appear. The request loop's zero-
failures property is asserted unconditionally, whether or not the watch
fired within the bound; only when it fired does the test also chat
through the new alias and expect 200. Empirically, on this host the
directory watch fires reliably but with variable latency (observed
under 1s up to several seconds; one run exceeded the 30s bound and hit
the Skip path with zero request failures throughout).

Found and fixed during verification: the test's cleanup step now writes
the restore atomically and WAITS for it to propagate before returning.
Without that wait, a delayed rebuild triggered by the restore could land
during a later test's own non-atomic config write (TestUsersFileHotReload's
users.json), reading a torn file mid-write and failing plugin
construction outright — a global 404 outage until the next unrelated
config change happened to trigger a successful rebuild. Reproduced and
confirmed fixed: two full `make integration` runs (cold teardown-to-up,
then immediately again) both pass end to end.
2026-08-20 23:37:53 +01:00

123 lines
3.3 KiB
YAML

networks:
llmgw-integration:
driver: bridge
services:
redis:
image: redis:7-alpine
networks: ["llmgw-integration"]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 2s
timeout: 2s
retries: 15
mockopenai:
build:
context: .
dockerfile: mock/Dockerfile
command: ["-mode", "openai"]
networks: ["llmgw-integration"]
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8080/healthz"]
interval: 2s
timeout: 2s
retries: 15
mockanthropic:
build:
context: .
dockerfile: mock/Dockerfile
command: ["-mode", "anthropic"]
networks: ["llmgw-integration"]
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8080/healthz"]
interval: 2s
timeout: 2s
retries: 15
mockgemini:
build:
context: .
dockerfile: mock/Dockerfile
command: ["-mode", "gemini"]
networks: ["llmgw-integration"]
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8080/healthz"]
interval: 2s
timeout: 2s
retries: 15
mocktool:
build:
context: .
dockerfile: mock/Dockerfile
command: ["-mode", "tool"]
networks: ["llmgw-integration"]
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8080/healthz"]
interval: 2s
timeout: 2s
retries: 15
traefik1:
image: traefik:v3.5
command: ["--configfile=/etc/traefik/traefik.yml"]
networks: ["llmgw-integration"]
ports:
- "19081:80"
volumes:
- ../:/plugins-local/src/github.com/lukaszraczylo/traefik-llmgateway:ro
- ./traefik/traefik.yml:/etc/traefik/traefik.yml:ro
- ./traefik/dynamic:/etc/traefik/dynamic:ro
- ./users:/users:ro
depends_on:
redis:
condition: service_healthy
mockopenai:
condition: service_healthy
mockanthropic:
condition: service_healthy
mockgemini:
condition: service_healthy
mocktool:
condition: service_healthy
traefik2:
image: traefik:v3.5
command: ["--configfile=/etc/traefik/traefik.yml"]
networks: ["llmgw-integration"]
ports:
- "19082:80"
volumes:
- ../:/plugins-local/src/github.com/lukaszraczylo/traefik-llmgateway:ro
- ./traefik/traefik.yml:/etc/traefik/traefik.yml:ro
- ./traefik/dynamic:/etc/traefik/dynamic:ro
- ./users:/users:ro
depends_on:
redis:
condition: service_healthy
mockopenai:
condition: service_healthy
mockanthropic:
condition: service_healthy
mockgemini:
condition: service_healthy
mocktool:
condition: service_healthy
# In-network streaming timing prober. Never started by `up`; the test
# invokes it on demand via `docker compose run --rm probe`, from inside
# this same compose network so its raw-TCP read against traefik1
# reflects true inter-chunk arrival timing instead of the buffered
# behaviour Docker Desktop's host port-proxy would introduce.
probe:
build:
context: .
dockerfile: mock/Dockerfile
command: ["-probe", "-target", "traefik1:80", "-apikey", "sk-int-alice", "-model", "openai/gpt-mock"]
networks: ["llmgw-integration"]
profiles: ["probe"]
depends_on:
- traefik1