Running a resilient, secure service infrastructure across two nodes can be achieved by using a dual Traefik instance configuration. This guide walks through the setup of a primary Traefik proxy on Server 1 with automatic fallback routing to Server 2, where a secondary Traefik instance handles services not served by the primary. Both servers also run Authelia middleware for centralized forward authentication.
Prehľad Link to heading
- Server 1 predvolene obsluhuje všetku verejnú prevádzku a beží na ňom kontajner Authelia.
- Ak sa služba na Serveri 1 nenájde, Traefik presmeruje požiadavku na Server 2.
- TLS terminácia prebieha na Serveri 1, čo znamená, že HTTPS požiadavky sú dešifrované tam.
- Komunikácia medzi inštanciami Traefik je zabezpečená (aj keď pomocou self-signed certifikátov, čo je postačujúce pre LAN).
- Middleware Authelia je nakonfigurovaný na oboch uzloch, takže autentifikácia funguje nezávisle od toho, ktorý server požiadavku spracuje.
- Automatická detekcia služieb a štítky Traefik fungujú na oboch serveroch rovnako.
SERVER 1 - docker-compose.yml príklad Link to heading
services:
traefik:
image: traefik
container_name: traefik
networks:
- auth
- default
ports:
- target: 443
published: 443
protocol: tcp
mode: host
security_opt:
- no-new-privileges:true
environment:
- TZ=Europe/Amsterdam
- CLOUDFLARE_DNS_API_TOKEN=${TRAEFIK_CLOUDFLARE_DNS_API_TOKEN}
- LEGO_DISABLE_CNAME_SUPPORT=true
- TRAEFIK_DOMAIN=${TRAEFIK_DOMAIN}
- SERVER_LAN_IP=${SERVER_LAN_IP}
- SERVER2_LAN_IP=${SERVER2_LAN_IP}
command:
- --log.level=INFO
- --providers.docker=true
- --providers.docker.exposedByDefault=false
- --providers.file.filename=/etc/traefik/config.yml
- --providers.file.watch=true
- --entrypoints.websecure.address=:443
- --entrypoints.websecure.http.tls.domains[0].main=*.${TRAEFIK_DOMAIN}
- --entrypoints.websecure.http.tls.certresolver=cloudflare
- --entrypoints.websecure.forwardedHeaders.trustedIPs=127.0.0.1/32,${SERVER2_LAN_IP}
- --certificatesresolvers.cloudflare.acme.dnschallenge=true
- --certificatesresolvers.cloudflare.acme.dnschallenge.provider=cloudflare
- --certificatesResolvers.cloudflare.acme.dnsChallenge.resolvers=1.1.1.1:53,1.0.0.1:53
- --certificatesResolvers.cloudflare.acme.dnsChallenge.delayBeforeCheck=5
- --certificatesresolvers.cloudflare.acme.email=peter@${TRAEFIK_DOMAIN}
- --certificatesresolvers.cloudflare.acme.storage=/etc/traefik/acme.json
- --serverstransport.insecureskipverify=true
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /volume1/docker/traefik:/etc/traefik
restart: always
auth:
image: authelia/authelia
container_name: auth
user: 1337:1337
networks:
- auth
security_opt:
- no-new-privileges:true
environment:
- TZ=Europe/Amsterdam
- X_AUTHELIA_CONFIG_FILTERS=template
- TRAEFIK_DOMAIN=${TRAEFIK_DOMAIN}
- REDIS_PASS=${REDIS_PASS}
labels:
- traefik.enable=true
- traefik.docker.network=auth
- traefik.http.routers.auth.rule=Host(`auth.${TRAEFIK_DOMAIN}`)
- traefik.http.routers.auth.entrypoints=websecure
- traefik.http.middlewares.auth.forwardAuth.address=http://auth:9091/api/authz/forward-auth
- traefik.http.middlewares.auth.forwardAuth.trustForwardHeader=true
- traefik.http.middlewares.auth.forwardAuth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email
volumes:
- /volume1/docker/auth/secrets:/secrets:ro
- /volume1/docker/auth/config:/config
restart: always
auth-redis:
image: redis:alpine
container_name: auth-redis
user: 1337:1337
networks:
- auth
security_opt:
- no-new-privileges:true
command: redis-server --requirepass "${REDIS_PASS}"
volumes:
- /volume1/docker/auth-redis:/data
restart: always
networks:
default:
auth:
SERVER 1 - traefik config.yml príklad Link to heading
http:
routers:
fallback:
entryPoints:
- websecure
rule: "PathPrefix(`/`)"
priority: 1
tls:
certResolver: cloudflare
service: forward-to-secondary
services:
forward-to-secondary:
loadBalancer:
servers:
- url: "https://{{ env "SERVER2_LAN_IP"}}:443"
passHostHeader: true
SERVER 1 - authelia configuration.yml príklad Link to heading
server:
address: 'tcp4://:9091'
log:
level: info
identity_validation:
elevated_session:
require_second_factor: true
reset_password:
jwt_lifespan: '5 minutes'
jwt_secret: {{ secret "/secrets/jwt_secret.txt" | mindent 0 "|" | msquote }}
totp:
disable: false
issuer: 'auth.{{ env "TRAEFIK_DOMAIN" }}'
period: 30
skew: 1
password_policy:
zxcvbn:
enabled: true
min_score: 4
webauthn:
disable: false
enable_passkey_login: true
experimental_enable_passkey_uv_two_factors: true
display_name: 'pd'
timeout: '60 seconds'
attestation_conveyance_preference: direct
selection_criteria:
attachment: ''
discoverability: 'preferred'
user_verification: 'preferred'
authentication_backend:
file:
path: '/config/users.yml'
password:
algorithm: 'argon2'
argon2:
variant: 'argon2id'
iterations: 3
memory: 65535
parallelism: 4
key_length: 32
salt_length: 16
access_control:
default_policy: 'deny'
rules:
- domain: '*.{{ env "TRAEFIK_DOMAIN" }}'
policy: 'bypass'
resources:
- '^/api.*'
networks:
- '10.10.10.3/32'
- domain: '*.{{ env "TRAEFIK_DOMAIN" }}'
policy: 'two_factor'
session:
name: 'authelia_session'
secret: {{ secret "/secrets/session_secret.txt" | mindent 0 "|" | msquote }}
cookies:
- domain: '{{ env "TRAEFIK_DOMAIN" }}'
authelia_url: 'https://auth.{{ env "TRAEFIK_DOMAIN" }}'
redis:
host: auth-redis
port: 6379
password: '{{ env "REDIS_PASS" }}'
regulation:
max_retries: 4
find_time: 120
ban_time: 300
storage:
encryption_key: {{ secret "/secrets/storage_encryption_key.txt" | mindent 0 "|" | msquote }}
local:
path: '/config/db.sqlite3'
notifier:
disable_startup_check: false
filesystem:
filename: '/config/notification.txt'
theme: auto
# OIDC for Synology
#identity_providers:
# oidc:
# hmac_secret: {{ secret "/secrets/hmac_secret.txt" | mindent 0 "|" | msquote }}
# jwks:
# - key: {{ secret "/secrets/rsa.2048.key" | mindent 10 "|" | msquote }}
# clients:
# - client_id: '<client_id>'
# client_name: 'Synology DSM'
# client_secret: '<encoded client_secret>'
# public: false
# authorization_policy: 'two_factor'
# redirect_uris:
# - 'https://server.{{ env "TRAEFIK_DOMAIN" }}'
# scopes:
# - 'openid'
# - 'profile'
# - 'groups'
# - 'email'
# userinfo_signed_response_alg: 'none'
# token_endpoint_auth_method: 'client_secret_post'
# consent_mode: implicit
SERVER 2 - docker-compose.yml príklad Link to heading
services:
traefik:
image: traefik
container_name: traefik
network_mode: host
security_opt:
- no-new-privileges:true
environment:
- TZ=Europe/Amsterdam
- TRAEFIK_DOMAIN=${TRAEFIK_DOMAIN}
command:
- --log.level=INFO
- --providers.docker=true
- --providers.docker.exposedByDefault=false
- --providers.file.filename=/etc/traefik/config2.yml
- --providers.file.watch=true
- --entrypoints.websecure.address=:443
- --entrypoints.websecure.http.tls=true
- --entrypoints.websecure.forwardedHeaders.trustedIPs=127.0.0.1/32,${SERVER_LAN_IP}
- --serverstransport.insecureskipverify=true
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /volume1/docker/traefik:/etc/traefik
restart: always
SERVER 2 - traefik config2.yml príklad Link to heading
http:
middlewares:
auth:
forwardAuth:
address: https://auth.{{env "TRAEFIK_DOMAIN"}}/api/authz/forward-auth
trustForwardHeader: true
authResponseHeaders:
- Remote-User
- Remote-Groups
- Remote-Name
- Remote-Email
A potom môžeme nasadiť kontajnery so štítkami:
labels:
- traefik.enable=true
- traefik.http.routers.test.rule=Host(`test.${TRAEFIK_DOMAIN}`)
- traefik.http.routers.test.entrypoints=websecure
- traefik.http.routers.test.middlewares=auth@file # or just auth in case of server1 since middleware is set with labels instead of file
- traefik.http.services.test.loadbalancer.server.port=1234
#- traefik.http.services.test.loadbalancer.server.scheme=https # optional for containers that serve https only on backend
Prajem príjemný deň!