The host resolves the tailnet through MagicDNS on tailscale0 and everything else through Cloudflare (systemd-resolved split DNS). MagicDNS has no upstream for names outside the tailnet and answers them SERVFAIL, so handing 100.100.100.100 to job containers as their first resolver made every public lookup wait on a failure before falling back: image pulls stalled and the e2e lane's *.localtest.me fetches blew a 5s poll. A CoreDNS forwarder at a fixed address on the runner network now answers both: ts.net is forwarded to MagicDNS, the rest to 1.1.1.1/1.0.0.1. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018j9YG8YFjcsVLnqzNz7LmA
119 lines
5.2 KiB
YAML
119 lines
5.2 KiB
YAML
services:
|
|
# Split DNS for the dind daemon and its job containers: ts.net -> MagicDNS,
|
|
# everything else -> Cloudflare. See Corefile for why MagicDNS alone fails.
|
|
dns:
|
|
image: coredns/coredns@sha256:af8c8d35a5d184b386c4a6d1a012c8b218d40d1376474c7d071bb6c07201f47d
|
|
container_name: gitea-runner-dns
|
|
restart: unless-stopped
|
|
command: ["-conf", "/Corefile"]
|
|
volumes:
|
|
- ./Corefile:/Corefile:ro
|
|
networks:
|
|
runner:
|
|
ipv4_address: 172.28.0.53
|
|
|
|
# Isolated Docker daemon for the runner's job containers.
|
|
# Keeps the host Docker clean and gives reliable cache networking.
|
|
dind:
|
|
image: docker:29-dind
|
|
container_name: gitea-runner-dind
|
|
privileged: true
|
|
restart: unless-stopped
|
|
# The third --host exposes the daemon over a unix socket on a volume shared
|
|
# with the runner (dind-sock), *in addition to* its own /var/run/docker.sock.
|
|
# act_runner's job-container DooD wiring only understands unix:// (or bare)
|
|
# socket paths -- it bind-mounts that path from ITS OWN filesystem into every
|
|
# job container as /var/run/docker.sock, it does not pass DOCKER_HOST through
|
|
# as an env var. A bare "tcp://..." isn't recognized and silently falls back
|
|
# to /var/run/docker.sock, which doesn't exist in the runner container -- so
|
|
# this shared socket is what makes `docker build` etc. work inside jobs.
|
|
# Two DNS settings, for two different resolvers, and both are load-bearing.
|
|
#
|
|
# dns: (below) the dind CONTAINER's resolv.conf, which is what dockerd
|
|
# itself uses to pull images. Without an explicit list it
|
|
# inherits Docker's embedded resolver, which forwards to
|
|
# the host's systemd-resolved; with Tailscale DNS enabled
|
|
# there, public names come back SERVFAIL and every job
|
|
# dies at "docker pull" of the runner image itself. This
|
|
# is what took the runner down on 2026-09-05.
|
|
#
|
|
# --dns (here) what dockerd hands to the JOB containers it creates.
|
|
# The tailnet resolver first, so NOMAD_ADDR and the
|
|
# staging Postgres resolve inside a job (they are
|
|
# MagicDNS names); a public resolver second, because
|
|
# 100.100.100.100 answers only for the tailnet and
|
|
# SERVFAILs everything else -- getaddrinfo falls through.
|
|
#
|
|
# --default-address-pool replaces the daemon default of fifteen /16s in
|
|
# 172.17-172.31, which a CI run exhausts ("all predefined address pools
|
|
# have been fully subnetted"): the host holds three, each lane creates its
|
|
# own network, and a killed job leaves one behind. 10.210/16 in /24s is 256
|
|
# networks and collides with nothing here (no 10.x route on the host; the
|
|
# tailnet is 100.64/10).
|
|
command: ["dockerd", "--host=tcp://0.0.0.0:2375", "--host=unix:///var/run/docker.sock", "--host=unix:///dind-sock/docker.sock", "--tls=false", "--dns=172.28.0.53", "--dns=1.1.1.1", "--default-address-pool", "base=10.210.0.0/16,size=24"]
|
|
environment:
|
|
DOCKER_TLS_CERTDIR: ""
|
|
dns:
|
|
- 172.28.0.53
|
|
- 1.1.1.1
|
|
depends_on:
|
|
- dns
|
|
networks:
|
|
- runner
|
|
volumes:
|
|
# Persist pulled job images + docker layer cache => fast Rust rebuilds.
|
|
- dind-storage:/var/lib/docker
|
|
# Shared with the runner container; see the --host comment above.
|
|
- dind-sock:/dind-sock
|
|
healthcheck:
|
|
test: ["CMD", "docker", "info"]
|
|
interval: 5s
|
|
timeout: 3s
|
|
retries: 10
|
|
start_period: 10s
|
|
|
|
runner:
|
|
# Ghim theo digest, KHÔNG dùng :nightly. Tag tự cập nhật + cache nằm trong
|
|
# writable layer = một lệnh 'compose pull' là mất sạch cache.
|
|
# Nâng cấp có chủ đích: đổi digest ở đây rồi commit.
|
|
image: docker.io/gitea/act_runner@sha256:7940221bcfc9df16f3401ddafb2231bf98bba5517bc381224c7a2c504b558974
|
|
container_name: gitea-runner-wsl
|
|
restart: unless-stopped
|
|
depends_on:
|
|
dind:
|
|
condition: service_healthy
|
|
networks:
|
|
- runner
|
|
environment:
|
|
GITEA_INSTANCE_URL: ${GITEA_INSTANCE_URL}
|
|
GITEA_RUNNER_REGISTRATION_TOKEN: ${GITEA_RUNNER_REGISTRATION_TOKEN}
|
|
GITEA_RUNNER_NAME: ${GITEA_RUNNER_NAME}
|
|
GITEA_RUNNER_LABELS: ""
|
|
# Point the runner at the DinD daemon. Overridden at startup by act_runner
|
|
# itself to config.yaml's container.docker_host (the unix socket below),
|
|
# since our labels require docker; kept here as a documented fallback.
|
|
DOCKER_HOST: tcp://dind:2375
|
|
CONFIG_FILE: /config.yaml
|
|
volumes:
|
|
- ./config.yaml:/config.yaml:ro
|
|
# Persist runner registration so it survives restarts.
|
|
- runner-data:/data
|
|
# Shared with dind; see the --host comment on the dind service.
|
|
- dind-sock:/dind-sock
|
|
# Cache của actions/cache. Mặc định act_runner ghi vào
|
|
# $HOME/.cache/actcache = WRITABLE LAYER của container -> mất sạch mỗi
|
|
# lần container bị tạo lại. Phải khớp với cache.dir trong config.yaml.
|
|
- runner-cache:/cache
|
|
|
|
networks:
|
|
runner:
|
|
ipam:
|
|
config:
|
|
- subnet: 172.28.0.0/24
|
|
|
|
volumes:
|
|
dind-storage:
|
|
runner-data:
|
|
dind-sock:
|
|
runner-cache:
|