Docker Swarm에서 Traefik으로 서비스 노출하기 - 고급편
출처: Docker Swarm에서 Traefik으로 서비스 노출하기 - 고급편 (Exposing Services with Traefik on Docker Swarm - Advanced)
본문
Docker Swarm에서 Traefik으로 서비스 노출하기 - 고급편
이 가이드는 기본 가이드의 개념과 셋업을 바탕으로 이어져요. 진행하기 전에 기본 가이드를 끝냈고 Docker Swarm에서 Traefik 셋업이 동작하는 상태인지 확인하세요.
이 고급 가이드에서는 Traefik 배포를 다음으로 강화하는 법을 배우게 됩니다:
-
보안 헤더와 접근 제어를 위한 미들웨어
-
자동 인증서 관리를 위한 Let's Encrypt
-
상태 유지 애플리케이션을 위한 스티키 세션
-
복잡한 인증 시나리오를 위한 다중 레이어 라우팅
-
서비스 레벨에서 미들웨어를 적용하기 위한 서비스 미들웨어
사전 준비물
-
기본 가이드를 완료했을 것
-
초기화된 Docker Swarm 클러스터
-
기본 가이드의 동작하는 Traefik 셋업
미들웨어 추가하기
미들웨어를 쓰면 요청이나 응답이 Traefik을 지나는 동안 수정할 수 있어요. 두 가지 유용한 미들웨어를 추가해 볼게요: 보안을 위한 Headers와 접근 제어를 위한 IP allowlist입니다.
docker-compose.yml의 whoami 서비스 deploy 섹션에 다음 레이블을 추가하세요:
deploy:
# ... existing configuration ...
labels:
# ... existing labels ...
# Secure Headers Middleware
- "traefik.http.middlewares.secure-headers.headers.frameDeny=true"
- "traefik.http.middlewares.secure-headers.headers.sslRedirect=true"
- "traefik.http.middlewares.secure-headers.headers.browserXssFilter=true"
- "traefik.http.middlewares.secure-headers.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.secure-headers.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.secure-headers.headers.stsPreload=true"
- "traefik.http.middlewares.secure-headers.headers.stsSeconds=31536000"
# IP Allowlist Middleware
- "traefik.http.middlewares.ip-allowlist.ipallowlist.sourceRange=127.0.0.1/32,192.168.0.0/16,10.0.0.0/8"
# Apply the middlewares
- "traefik.http.routers.whoami.middlewares=secure-headers,ip-allowlist"
같은 미들웨어를 whoami-api 서비스에도 추가하세요:
deploy:
# ... existing configuration ...
labels:
# ... existing labels ...
- "traefik.http.routers.whoami-api.middlewares=secure-headers,ip-allowlist"
변경 사항을 적용하세요:
docker stack deploy -c docker-compose.yml traefik
미들웨어 테스트하기
이제 미들웨어가 제대로 동작하는지 확인해 볼게요:
Secure Headers 미들웨어 테스트:
curl -k -I -H "Host: whoami.swarm.localhost" https://localhost/
응답 헤더에서 미들웨어가 설정한 보안 헤더를 볼 수 있어요:
-
X-Frame-Options: DENY
-
X-Content-Type-Options: nosniff
-
X-XSS-Protection: 1; mode=block
-
적절한 설정의 Strict-Transport-Security
IP Allowlist 미들웨어 테스트:
요청이 allow list에 있는 IP(예: 127.0.0.1)에서 오면 성공해야 해요:
curl -k -I -H "Host: whoami.swarm.localhost" https://localhost/
allow list에 없는 IP에서 접근하려 하면 요청이 403 Forbidden 응답으로 거부돼요. 로컬 환경에서 이를 시뮬레이션하려면 미들웨어 구성을 임시로 수정해 여러분의 IP를 제외한 뒤 다시 테스트하면 됩니다.
Let's Encrypt로 인증서 생성하기
Let's Encrypt는 무료 자동 TLS 인증서를 제공해요. Traefik이 우리 서비스의 인증서를 자동으로 발급받고 갱신하도록 구성해 볼게요.
자체 서명 인증서 대신, 기존 docker-compose.yml 파일을 다음 변경 사항들로 갱신하세요:
Traefik 서비스 command 섹션에 Let's Encrypt 인증서 리졸버를 추가하세요:
command:
# ... existing commands ...
# Let's Encrypt configuration
- "[email protected]" # replace with your actual email
- "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
Let's Encrypt 인증서용 볼륨을 추가하세요:
volumes:
# ...Existing volumes...
- letsencrypt:/letsencrypt
인증서 리졸버를 쓰도록 서비스 레이블을 갱신하세요:
labels:
# ... existing labels ...
- "traefik.http.routers.whoami.tls.certresolver=le"
보호하려는 다른 서비스에도 똑같이 하세요:
labels:
# ... existing labels ...
- "traefik.http.routers.whoami-api.tls.certresolver=le"
Let's Encrypt 인증서를 저장할 네임드 볼륨을 volumes 섹션에 추가해 만드세요:
volumes:
# ... existing volumes ...
letsencrypt:
driver: local
변경 사항을 적용하세요:
docker stack deploy -c docker-compose.yml traefik
공개 DNS 필요
Let's Encrypt는 도메인 소유권을 검증하기 위해 공개적으로 접근 가능한 도메인이 필요할 수 있어요. whoami.swarm.localhost 같은 로컬 도메인으로 테스트할 때는 인증서가 자체 서명으로 유지됩니다. 프로덕션에서는 Traefik 인스턴스를 가리키는 공개 DNS 레코드가 있는 실제 도메인으로 바꾸세요.
인증서가 발급되면 확인할 수 있어요:
# Verify the certificate chain
curl -v https://whoami.swarm.localhost/ 2>&1 | grep -i "server certificate"
인증서가 Let's Encrypt가 발급한 것임을 볼 수 있을 거예요.
스티키 세션 구성하기
스티키 세션은 사용자의 요청이 항상 같은 백엔드 서버로 가도록 보장해요. 세션 상태를 유지하는 애플리케이션에 필수적이죠. whoami 서비스를 위해 스티키 세션을 구현해 볼게요.
Docker Swarm에는 이미 여러 레플리카가 실행 중이므로, 이제 스티키 세션 구성을 추가할 거예요. docker-compose.yml의 whoami 서비스를 갱신하세요:
스티키 세션 구성 추가하기
docker-compose.yml 파일의 whoami 서비스에 다음 레이블을 추가하세요:
deploy:
# ... existing configuration ...
labels:
# ... existing labels ...
# Sticky Sessions Configuration
- "traefik.http.services.whoami.loadbalancer.sticky.cookie=true"
- "traefik.http.services.whoami.loadbalancer.sticky.cookie.name=sticky_cookie"
- "traefik.http.services.whoami.loadbalancer.sticky.cookie.secure=true"
- "traefik.http.services.whoami.loadbalancer.sticky.cookie.httpOnly=true"
변경 사항을 적용하세요:
docker stack deploy -c docker-compose.yml traefik
스티키 세션 테스트하기
여러 요청을 보내 모두 같은 백엔드 컨테이너로 가는지 관찰하면 스티키 세션을 테스트할 수 있어요:
# First request - save cookies to a file
curl -k -c cookies.txt -H "Host: whoami.swarm.localhost" https://localhost/
# Subsequent requests - use the cookies
curl -k -b cookies.txt -H "Host: whoami.swarm.localhost" https://localhost/
curl -k -b cookies.txt -H "Host: whoami.swarm.localhost" https://localhost/
각 응답의 Hostname 필드에 주목하세요. 쿠키 파일을 쓰면 모든 요청에서 똑같이 유지되어야 해요. 이게 스티키 세션이 동작한다는 증거입니다.
비교를 위해 쿠키 없이 요청을 보내 보세요:
# Requests without cookies should be load-balanced across different containers
curl -k -H "Host: whoami.swarm.localhost" https://localhost/
curl -k -H "Host: whoami.swarm.localhost" https://localhost/
이 응답들에서는 서로 다른 Hostname 값이 보일 거예요. 각 요청이 다른 컨테이너로 로드 밸런싱되기 때문이죠.
브라우저 테스트
브라우저에서 테스트할 때는 쿠키를 유지하기 위해 같은 브라우저 세션을 써야 해요. 쿠키는 보안을 위해 httpOnly와 secure 플래그로 설정되므로 HTTPS 연결에서만 보내지고 JavaScript로 접근할 수 없어요.
더 고급 구성 옵션은 참조 문서를 확인하세요.
다중 레이어 라우팅
다중 레이어 라우팅은 라우터 간 계층 관계를 가능하게 해요. 부모 라우터가 자식 라우터가 최종 라우팅 결정을 내리기 전에 미들웨어를 통해 요청을 처리할 수 있죠. 인증 기반 라우팅이나 단계별 미들웨어 적용에 특히 유용합니다.
프로바이더 요구 사항
다중 레이어 라우팅은 File 프로바이더가 필요해요. Docker Swarm 레이블은 parentRefs 필드를 지원하지 않기 때문이죠. 하지만 Docker Swarm과 File 프로바이더를 함께 쓸 수 있어요. 서비스 발견에는 Swarm 레이블을, 다중 레이어 라우팅에는 File 구성을 쓰면 됩니다.
Docker Swarm으로 다중 레이어 라우팅 설정하기
Docker Swarm과 다중 레이어 라우팅을 쓰려면 Docker 프로바이더와 함께 File 프로바이더를 활성화해야 해요.
docker-compose.yml의 Traefik 서비스를 갱신하세요:
services:
traefik:
image: traefik:v3.4
command:
- "--api.dashboard=true"
- "--providers.docker.swarmMode=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=traefik_proxy"
- "--providers.file.directory=/etc/traefik/dynamic" # Enable File provider
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.websecure.http.tls=true"
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
configs:
- source: mlr-config
target: /etc/traefik/dynamic/mlr.yml
networks:
- traefik_proxy
deploy:
placement:
constraints:
- node.role == manager
configs:
mlr-config:
file: ./dynamic/mlr.yml
networks:
traefik_proxy:
external: true
인증 기반 라우팅 예시
부모 라우터가 요청을 인증하고, 자식 라우터가 사용자 역할에 따라 트래픽을 지시하는 다중 레이어 라우팅 셋업을 만들어 볼게요.
먼저, Docker Swarm 서비스를 평소처럼 레이블로 정의해 두세요:
# In docker-compose.yml
services:
# ... traefik service from above ...
# Admin backend service
admin-backend:
image: traefik/whoami
networks:
- traefik_proxy
environment:
- WHOAMI_NAME=Admin Backend
deploy:
replicas: 2
labels:
- "traefik.enable=true"
- "traefik.http.services.admin-backend.loadbalancer.server.port=80"
# User backend service
user-backend:
image: traefik/whoami
networks:
- traefik_proxy
environment:
- WHOAMI_NAME=User Backend
deploy:
replicas: 2
labels:
- "traefik.enable=true"
- "traefik.http.services.user-backend.loadbalancer.server.port=80"
이제 파일에 다중 레이어 라우팅 구성을 만들겠습니다. dynamic/mlr.yml을 만드세요:
http:
routers:
# Parent router with authentication middleware
api-parent:
rule: "Host(`api.swarm.localhost`) && PathPrefix(`/api`)"
middlewares:
- auth-middleware
entryPoints:
- websecure
# Note: No service and no TLS config - this is a parent router
# Child router for admin users
api-admin:
rule: "HeadersRegexp(`X-Auth-User`, `admin`)"
service: admin-backend@swarm # Reference Swarm service
parentRefs:
- api-parent@file # Explicit reference to parent in file provider
# Child router for regular users
api-user:
rule: "HeadersRegexp(`X-Auth-User`, `user`)"
service: user-backend@swarm # Reference Swarm service
parentRefs:
- api-parent@file # Explicit reference to parent in file provider
middlewares:
auth-middleware:
basicAuth:
users:
- "admin:$apr1$DmXR3Add$wfdbGw6RWIhFb0ffXMM4d0"
- "user:$apr1$GJtcIY1o$mSLdsWYeXpPHVsxGDqadI."
headerField: X-Auth-User
비밀번호 해시 생성
위의 비밀번호 해시는 htpasswd로 생성된 거예요. 자신만의 사용자 자격 증명을 만들려면:
# Using htpasswd (Apache utils)
htpasswd -nb admin yourpassword
크로스 프로바이더 참조
서비스 이름의 @swarm 접미사와 parentRefs의 @file 접미사에 주목하세요. File 프로바이더로 Swarm 서비스와 다중 레이어 라우팅을 조율할 때:
-
Swarm 서비스를 참조하려면 service-name@swarm을 쓰세요
-
parentRefs에서 File 프로바이더의 부모 라우터를 참조하려면 parent-name@file을 쓰세요
@provider 접미사는 Traefik에게 리소스를 어느 프로바이더 네임스페이스에서 찾아야 하는지 알려줘요.
스택을 배포하세요:
docker stack deploy -c docker-compose.yml traefik
다중 레이어 라우팅 테스트하기
라우팅 동작을 테스트해 볼게요:
# Request goes through parent router → auth middleware → admin child router
curl -k -u admin:test -H "Host: api.swarm.localhost" https://localhost/api
admin으로 인증하면 admin-backend 서비스의 응답이 보일 거예요. user:test 자격 증명으로 시도하면 user-backend 서비스에 도달하게 됩니다.
동작 방식
-
요청이 api.swarm.localhost/api에 도달해요
-
부모 라우터(api-parent)가 호스트와 경로를 기준으로 매칭해요
-
BasicAuth 미들웨어가 사용자를 인증하고 사용자 이름으로 X-Auth-User 헤더를 설정해요
-
자식 라우터(api-admin 또는 api-user)가 헤더 값을 기준으로 매칭해요
-
요청이 적절한 Swarm 서비스로 전달돼요
다중 레이어 라우팅에 대한 자세한 내용은 다중 레이어 라우팅 문서를 확인하세요.
서비스 미들웨어
서비스 미들웨어를 쓰면 개별 라우터가 아니라 서비스에 미들웨어를 적용할 수 있어요. 즉 어떤 라우터가 요청을 전달했든, 서비스가 처리하는 모든 요청에 미들웨어가 적용된다는 뜻이에요.
같은 미들웨어(예: 헤더, 속도 제한, 인증)를 서비스에 도달하는 모든 트래픽에 적용하고 싶은데 매 라우터에 구성하고 싶지 않을 때 유용합니다.
서비스 미들웨어를 언제 쓰나요?
다음과 같은 경우에 서비스 미들웨어를 쓰세요:
-
여러 라우터가 같은 서비스로 트래픽을 전달하는데 모두 같은 미들웨어를 적용해야 할 때
-
트래픽이 어떤 경로로 오든 서비스에 항상 미들웨어가 적용되도록 보장하고 싶을 때
-
관리하기 쉽도록 미들웨어 구성을 서비스 레벨에서 중앙 집중화할 때
서비스 미들웨어 레이블 추가하기
docker-compose.yml의 whoami 서비스 deploy 섹션에 다음 레이블을 추가하세요:
services:
whoami:
image: traefik/whoami
networks:
- traefik_proxy
deploy:
replicas: 2
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.swarm.localhost`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls=true"
# Define the middleware
- "traefik.http.middlewares.service-headers.headers.customRequestHeaders.X-Service-Middleware=applied"
# Attach middleware at the SERVICE level (not the router level)
- "traefik.http.services.whoami.middlewares=service-headers"
- "traefik.http.services.whoami.loadbalancer.server.port=80"
서비스 레벨 vs 라우터 레벨 미들웨어
-
라우터 레벨 미들웨어 (traefik.http.routers..middlewares): 트래픽이 그 특정 라우터의 규칙과 매칭될 때만 적용돼요
-
서비스 레벨 미들웨어 (traefik.http.services..middlewares): 어떤 라우터가 전달했든 서비스에 도달하는 모든 트래픽에 적용돼요
둘 다 구성되면 라우터 미들웨어가 먼저 실행되고, 이어서 서비스 미들웨어가 실행돼요.
스택을 배포하세요:
docker stack deploy -c docker-compose.yml traefik
서비스 미들웨어 테스트하기
서비스 미들웨어가 동작하는지 확인해 볼게요:
curl -k -H "Host: whoami.swarm.localhost" https://localhost/
whoami 응답에서 서비스 미들웨어가 추가한 커스텀 헤더를 볼 수 있어요:
X-Service-Middleware: applied
서비스 미들웨어에 대한 자세한 내용은 참조 문서를 확인하세요.
결론
이 고급 가이드에서 다음을 배웠어요:
-
보안 헤더와 IP allow listing 같은 미들웨어로 보안 추가하기
-
Let's Encrypt로 인증서 관리 자동화하기
-
상태 유지 애플리케이션을 위한 스티키 세션 구현하기
-
인증 기반 라우팅을 위한 다중 레이어 라우팅 셋업하기
-
중앙 집중식 미들웨어 관리를 위해 서비스 레벨에서 미들웨어 적용하기
이런 고급 기능들 덕분에 Docker Swarm에서 프로덕션 준비가 된 Traefik 배포를 만들 수 있어요. 각각은 여러분의 특정 요구에 맞게 더 커스터마이즈할 수 있습니다.
다음 단계
이제 Docker Swarm에서 Traefik의 기본과 고급 기능을 모두 익혔으니, 다음을 탐구해 보고 싶을 거예요:
-
쿼리 매개변수 매칭, 헤더 기반 라우팅 등 고급 라우팅 옵션
-
인증, 속도 제한, 요청 수정을 위한 추가 미들웨어
-
Traefik 배포를 모니터링하고 디버깅하기 위한 관측성 기능
-
TCP 서비스 노출을 위한 TCP 서비스
-
UDP 서비스 노출을 위한 UDP 서비스
-
Docker Swarm 통합에 대한 자세한 내용을 담은 Docker Swarm 프로바이더 문서