Traefik 동적 라우팅 심화

Traefik이란?

Traefik은 클라우드 네이티브 환경에 최적화된 리버스 프록시 겸 로드밸런서다. Nginx와 달리 설정 파일을 수동으로 수정하고 reload할 필요 없이, Docker·Kubernetes·Consul 등에서 서비스가 뜨면 자동으로 라우팅 규칙을 감지하고 적용한다. Let’s Encrypt 인증서도 자동 발급·갱신한다.

이 글에서는 Traefik의 핵심 아키텍처, Provider 별 동적 설정, Middleware 체이닝, TLS 자동화, 그리고 Docker Compose·Kubernetes 환경의 실전 패턴을 심화하여 다룬다.

핵심 아키텍처: EntryPoint → Router → Middleware → Service

┌──────────────┐
│  EntryPoint  │  ← 포트 리스닝 (80, 443)
└──────┬───────┘
       │
┌──────▼───────┐
│   Router     │  ← 요청 매칭 (Host, Path, Header)
└──────┬───────┘
       │
┌──────▼───────┐
│  Middleware  │  ← 요청 변환 (인증, Rate Limit, Redirect)
│  (체이닝)    │
└──────┬───────┘
       │
┌──────▼───────┐
│   Service    │  ← 백엔드 서버 (로드밸런싱)
└──────────────┘
  • EntryPoint: Traefik이 수신하는 네트워크 진입점 (포트)
  • Router: 요청을 규칙에 따라 분기 (Host, PathPrefix, Headers 등)
  • Middleware: 요청/응답을 가공 (인증, 압축, 헤더 추가 등)
  • Service: 실제 트래픽을 받는 백엔드 서버 그룹

정적 설정: traefik.yml

EntryPoint와 Provider는 정적 설정에서 정의한다. 이 설정은 Traefik 시작 시 한 번만 로드된다.

# traefik.yml
entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"
    http:
      tls:
        certResolver: letsencrypt

# Let's Encrypt 자동 인증서
certificatesResolvers:
  letsencrypt:
    acme:
      email: admin@example.com
      storage: /etc/traefik/acme.json
      httpChallenge:
        entryPoint: web

# Provider 설정
providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false  # 명시적 label만 노출
    watch: true
  file:
    directory: /etc/traefik/dynamic
    watch: true

# Dashboard
api:
  dashboard: true
  insecure: false

# 로깅
log:
  level: INFO
accessLog:
  filePath: /var/log/traefik/access.log
  format: json
  fields:
    headers:
      defaultMode: drop
      names:
        User-Agent: keep
        X-Forwarded-For: keep

Docker Provider: Label 기반 동적 라우팅

Docker 환경에서 Traefik은 컨테이너의 label을 읽어 라우팅을 자동 설정한다. 컨테이너가 시작/중지되면 실시간으로 반영된다.

# docker-compose.yml
version: "3.8"

services:
  traefik:
    image: traefik:v3.1
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - ./acme.json:/etc/traefik/acme.json
    labels:
      # Dashboard 라우팅
      - "traefik.enable=true"
      - "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.middlewares=auth"
      - "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$xyz"

  api:
    image: myapp-api:latest
    labels:
      - "traefik.enable=true"
      # Router 규칙
      - "traefik.http.routers.api.rule=Host(`api.example.com`)"
      - "traefik.http.routers.api.entrypoints=websecure"
      - "traefik.http.routers.api.tls.certresolver=letsencrypt"
      # Service (포트 매핑)
      - "traefik.http.services.api.loadbalancer.server.port=3000"
      # Middleware 체이닝
      - "traefik.http.routers.api.middlewares=rate-limit,compress,security-headers"

  frontend:
    image: myapp-web:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.web.rule=Host(`example.com`)"
      - "traefik.http.routers.web.entrypoints=websecure"
      - "traefik.http.routers.web.tls.certresolver=letsencrypt"
      - "traefik.http.services.web.loadbalancer.server.port=80"

고급 Router 규칙

Router는 다양한 매칭 규칙을 논리 연산자로 조합할 수 있다.

# Host + Path 조합
- "traefik.http.routers.api-v2.rule=Host(`api.example.com`) && PathPrefix(`/v2`)"

# Header 기반 라우팅 (A/B 테스트, Canary)
- "traefik.http.routers.canary.rule=Host(`api.example.com`) && Headers(`X-Canary`, `true`)"

# Query Parameter 매칭
- "traefik.http.routers.debug.rule=Host(`api.example.com`) && Query(`debug=true`)"

# 정규식 PathPrefix
- "traefik.http.routers.user.rule=Host(`api.example.com`) && PathPrefix(`/users/{id:[0-9]+}`)"

# 우선순위 제어 (높을수록 먼저 매칭)
- "traefik.http.routers.specific.priority=100"
- "traefik.http.routers.catchall.priority=1"

Middleware 실전 패턴

1. Rate Limiting

# 동적 설정 파일: /etc/traefik/dynamic/middlewares.yml
http:
  middlewares:
    rate-limit:
      rateLimit:
        average: 100       # 초당 평균 요청 수
        burst: 200          # 버스트 허용량
        period: 1s
        sourceCriterion:
          ipStrategy:
            depth: 1        # X-Forwarded-For 첫 번째 IP 기준

2. IP Whitelist + BasicAuth 조합

http:
  middlewares:
    admin-access:
      chain:
        middlewares:
          - admin-ip
          - admin-auth

    admin-ip:
      ipAllowList:
        sourceRange:
          - "10.0.0.0/8"
          - "172.16.0.0/12"

    admin-auth:
      basicAuth:
        users:
          - "admin:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"
        removeHeader: true  # 백엔드에 Auth 헤더 전달 안 함

3. 보안 헤더 일괄 적용

http:
  middlewares:
    security-headers:
      headers:
        frameDeny: true
        contentTypeNosniff: true
        browserXssFilter: true
        stsSeconds: 31536000
        stsIncludeSubdomains: true
        stsPreload: true
        customResponseHeaders:
          X-Robots-Tag: "noindex,nofollow"
          Referrer-Policy: "strict-origin-when-cross-origin"
        contentSecurityPolicy: "default-src 'self'"

4. Circuit Breaker

http:
  middlewares:
    circuit-breaker:
      circuitBreaker:
        expression: "LatencyAtQuantileMS(50.0) > 1000 || NetworkErrorRatio() > 0.10"
        checkPeriod: 10s
        fallbackDuration: 15s
        recoveryDuration: 30s

5. 요청 변환 (StripPrefix, AddPrefix)

# /api/users → 백엔드에서는 /users 로 수신
http:
  middlewares:
    strip-api:
      stripPrefix:
        prefixes:
          - "/api"

    # 반대로 prefix 추가
    add-v1:
      addPrefix:
        prefix: "/v1"

Weighted Round Robin: 카나리 배포

# docker-compose.yml
services:
  api-stable:
    image: myapp-api:1.0
    labels:
      - "traefik.enable=true"
      - "traefik.http.services.api-stable.loadbalancer.server.port=3000"

  api-canary:
    image: myapp-api:1.1-rc
    labels:
      - "traefik.enable=true"
      - "traefik.http.services.api-canary.loadbalancer.server.port=3000"

# 동적 설정으로 가중치 분배
# dynamic/canary.yml
http:
  services:
    api-weighted:
      weighted:
        services:
          - name: api-stable
            weight: 90
          - name: api-canary
            weight: 10

  routers:
    api:
      rule: "Host(`api.example.com`)"
      service: api-weighted
      entrypoints:
        - websecure

Kubernetes IngressRoute (CRD)

Kubernetes에서 Traefik은 표준 Ingress 대신 IngressRoute CRD를 사용하면 더 세밀한 제어가 가능하다.

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: api-route
  namespace: production
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`api.example.com`) && PathPrefix(`/v2`)
      kind: Rule
      priority: 10
      middlewares:
        - name: rate-limit
          namespace: traefik
        - name: security-headers
          namespace: traefik
      services:
        - name: api-service
          port: 3000
          weight: 90
          strategy: RoundRobin
        - name: api-canary
          port: 3000
          weight: 10
  tls:
    certResolver: letsencrypt
    domains:
      - main: api.example.com

---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: rate-limit
  namespace: traefik
spec:
  rateLimit:
    average: 100
    burst: 200

---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: compress
  namespace: traefik
spec:
  compress:
    excludedContentTypes:
      - text/event-stream

TCP/UDP 라우팅

Traefik은 HTTP 외에 TCP/UDP 프로토콜도 라우팅할 수 있다. DB 프록시나 gRPC에 유용하다.

# traefik.yml - EntryPoint 추가
entryPoints:
  postgres:
    address: ":5432"
  mysql:
    address: ":3306"

# 동적 설정: TCP 라우팅
tcp:
  routers:
    postgres:
      entryPoints:
        - postgres
      rule: "HostSNI(`db.example.com`)"
      tls:
        passthrough: true
      service: postgres-backend

  services:
    postgres-backend:
      loadBalancer:
        servers:
          - address: "db-primary:5432"
          - address: "db-replica:5432"

Health Check와 Failover

http:
  services:
    api:
      loadBalancer:
        servers:
          - url: "http://api-1:3000"
          - url: "http://api-2:3000"
          - url: "http://api-3:3000"
        healthCheck:
          path: /health
          interval: 10s
          timeout: 3s
          scheme: http
          headers:
            X-Health-Check: "traefik"
        sticky:
          cookie:
            name: server_id
            secure: true
            httpOnly: true

Metrics·Tracing 연동

# traefik.yml
metrics:
  prometheus:
    entryPoint: metrics
    addEntryPointsLabels: true
    addRoutersLabels: true
    addServicesLabels: true
    buckets:
      - 0.01
      - 0.05
      - 0.1
      - 0.5
      - 1.0
      - 5.0

tracing:
  otlp:
    grpc:
      endpoint: "otel-collector:4317"
      insecure: true

entryPoints:
  metrics:
    address: ":8082"

운영 Best Practices

항목 권장 설정 이유
exposedByDefault false 의도하지 않은 서비스 노출 방지
Docker Socket 읽기 전용 (:ro) 컨테이너 조작 권한 제한
acme.json 권한 chmod 600 인증서 키 보호
Access Log JSON 포맷 구조화 로그로 분석 용이
Dashboard BasicAuth + IP 제한 관리 인터페이스 보안

마무리

Traefik은 동적 서비스 디스커버리가 핵심이다. Docker label이나 Kubernetes CRD만으로 라우팅을 선언하면 Traefik이 실시간으로 반영한다. Nginx처럼 설정 파일을 수정하고 reload하는 과정이 없어 마이크로서비스 환경에서 운영 부담이 크게 줄어든다.

실무 적용 시 핵심 체크리스트:

  • Middleware 체이닝: Rate Limit → Auth → Security Headers 순서로 조합
  • Let’s Encrypt 자동화: HTTP Challenge로 인증서 발급·갱신 자동화
  • Weighted Service: 카나리 배포 시 가중치로 트래픽 점진적 이동
  • Health Check: 백엔드 장애 시 자동 failover

K8s Gateway API 라우팅 심화 글에서 Kubernetes 표준 라우팅과 비교하고, HAProxy 로드밸런싱 심화와 L4/L7 프록시 선택 기준을 함께 참고하면 좋다.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux