컨텐츠로 건너뛰기

너무나 쉬운 Traefik v2 리버스 프록시

들어 가는 글

Traefik 공식 문서를 보면 foo bar 이렇게 해놔서 헷갈리는 부분이 많죠.
띄어쓰기에 엄격한 Yaml, 이스케이핑이 필요한 문법, 등등 같이 결합해서 아주 그냥 환상의 하모니를 자랑합니다.

1. Traefik의 구성

간단하게 정리해 보겠습니다. Traefik은 크게 세 가지로 구성됩니다.

  1. Service(서비스)

  2. Middleware(미들웨어)

  3. Router(라우터)

2. 글을 읽을 때 기억할 것 2가지

  1. 큰 틀에서 Traefik의 문법을 따라간다.

  2. 이름을 붙이고 그걸 연결한다

3. Traefik 설정의 핵심

Traefik 설정에서 핵심은 1. 서비스3. 라우터 입니다.

서비스와 라우터만 제대로 설정하면 크게 문제없고 미들웨어는 라우터를 보조한다고 생각하시면 편합니다.
사용자가 이름을 부여 한대로 된다는 것에서 쿠버네티스와 비슷한 성격을 갖고 있습니다.
Traefik의 설정은 큰 틀은 Traefik의 문법을 따라가지만, 그 안에서 이름붙이는 것은 사용자의 맘입니다.

your-container: #
image: your-docker-image
labels:
# Attach add-foo-prefix@file middleware (declared in file)
- 'traefik.http.routers.my-container.middlewares=add-foo-prefix@file'

이렇게 foo, bar, my-container 이렇게만 달랑 해놓고 이름붙이는 것에 대한 설명을 안해주니 문서를 보고 있으면 정신이 아득해 집니다.

4. docker-compose.yml 예제

traefik:
image: traefik:v2.10
restart: unless-stopped
logging:
options:
max-size: "10m"
command:
- "--log.level=INFO"
- "--accesslog=true"
- "--api.dashboard=true"
- "--ping=true"
- "--ping.entrypoint=ping"
- "--entryPoints.ping.address=:8082"
- "--entryPoints.web.address=:80"
- "--entryPoints.websecure.address=:443"
- "--entryPoints.web.http.redirections.entryPoint.to=websecure"
- "--entrypoints.web.http.redirections.entryPoint.scheme=https"
- "--entrypoints.web.http.redirections.entrypoint.permanent=true"
- "--providers.docker=true"
- "--providers.docker.watch=true"
- "--providers.docker.network=traefik-network"
- "--providers.docker.endpoint=unix:///var/run/docker.sock"
- "--providers.docker.exposedByDefault=false"
# Let's Encrypt ACME 설정
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--certificatesresolvers.letsencrypt.acme.keyType=EC256"
- "--certificatesresolvers.letsencrypt.acme.email=webmaster@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json"
# Let's Encrypt ACME DNS Challenge (CLoudflare)
#- "--certificatesResolvers.letsencrypt.acme.dnsChallenge=true"
#- "--certificatesResolvers.letsencrypt.acme.dnsChallenge.provider=cloudflare"
#- "--certificatesResolvers.letsencrypt.acme.dnsChallenge.resolvers=1.1.1.1:53,8.8.8.8:53"
#- "--certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=0"
# Prometheus 메트릭 설정
- "--metrics.prometheus=true"
- "--metrics.prometheus.buckets=0.1,0.3,1.2,5.0"
- "--metrics.prometheus.addServicesLabels=true"
- "--metrics.prometheus.addrouterslabels=true"
- "--metrics.prometheus.addEntryPointsLabels=true"
# 버전 확인 및 익명 사용 통계 설정
- "--global.checkNewVersion=true"
- "--global.sendAnonymousUsage=false"
# HTTP3 설정
- "--experimental.http3=true"
- "--entrypoints.websecure.http3"
- "--entrypoints.websecure.http3.advertisedport=443"
# environment:
# CF_DNS_API_TOKEN:
healthcheck:
test: ["CMD", "wget", "http://localhost:8082/ping", "--spider"]
interval: 10s
timeout: 2s
retries: 3
start_period: 5s
labels:
- "traefik.enable=true"
- "traefik.http.services.dashboard.loadbalancer.server.port=8080"
- "traefik.http.services.dashboard.loadbalancer.passhostheader=true"
- "traefik.http.routers.dashboard.rule=Host(`web.example.com`)"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.tls=true"
- "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./traefik-certificates:/etc/traefik/acme
networks:
- traefik-network
ports:
- target: 80
published: 80
mode: host
- target: 443
published: 443
mode: host
protocol: tcp
- target: 443
published: 443
mode: host
protocol: udp
rhymix:
image: navystack/rhymix:latest
restart: unless-stopped
depends_on:
- rhymix-db
logging:
options:
max-size: "10m"
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik-network"
- "traefik.http.services.rhymix-srv.loadbalancer.server.port=80"
- "traefik.http.services.rhymix-srv.loadbalancer.passhostheader=true"
- "traefik.http.middlewares.www-redir.redirectregex.regex=^https://www.(.*)"
- "traefik.http.middlewares.www-redir.redirectregex.replacement=https://$${1}"
- "traefik.http.middlewares.www-redir.redirectregex.permanent=true"
- "traefik.http.middlewares.compresstraefik.compress=true"
- "traefik.http.routers.rhymix-rt.rule=Host(`example.com`) || Host(`www.example.com`)"
- "traefik.http.routers.rhymix-rt.entrypoints=websecure"
- "traefik.http.routers.rhymix-rt.service=rhymix-srv"
- "traefik.http.routers.rhymix-rt.middlewares=www-redir, compresstraefik"
- "traefik.http.routers.rhymix-rt.tls=true"
- "traefik.http.routers.rhymix-rt.tls.certresolver=letsencrypt"
- "traefik.http.routers.rhymix-rt.tls.domains[0].main=example.com"
- "traefik.http.routers.rhymix-rt.tls.domains[0].sans=*.example.com"
volumes:
- rhymix-data:/var/www/html
networks:
- traefik-network
volumes:
rhymix-data:
rhymix-db:
networks:
traefik-network:
external: true

위의 코드 스니펫은 docker-compose의 일부입니다.
처음 볼 때는 한숨만 나오죠 길기도 길 뿐더러 낯선 개념들 뿐입니다.

5. traefik command Tear Down

천천히 뜯어보겠습니다. 아래의 모든 줄 번호는 docker-compose.yml 예제에 있는 번호입니다.

처음 말씀 드린것 처럼 기억할 것은 딱 두가지 입니다.

  1. 핵심은 서비스와 라우터다

  2. 큰 틀은 Traefik의 문법을 따르지만 이름 붙이는 것은 내 마음이다.

1. 로그, 대시보드와 PING

Traefik 분해해서 알아보기 1
- '--log.level=INFO'
- '--accesslog=true'
- '--api.dashboard=true'
- '--ping=true'
- '--ping.entrypoint=ping'

전반적으로 Traefik 문법입니다. 규칙 2번(큰 틀은 Traefik의 문법을 따르지만 이름 붙이는 것은 내 마음이다.)입니다. ⓐ 역시 규칙 2번입니다. --ping.entrypoint= 여기까지가 Traefik 문법이고 ping 여기는 내가 지은 이름입니다.

2. 엔트리포인트의 정의

Traefik 분해해서 알아보기 2
- '--entryPoints.ping.address=:8082'
- '--entryPoints.web.address=:80'
- '--entryPoints.websecure.address=:443'
  • 각각의 앞부분 --entryPoints. 까지는 Traefik 문법이고 .을 기준으로 각각 ping, web, websecure은 내가 지은 이름입니다.

  • 그리고 그다음부터 각각의 줄에서 이어지는 address부터는 역시 Traefik 문법이고 :을 기준으로 나오는 숫자들은 역시 내가 부여한 번호들입니다. (물론 HTTP, HTTPS를 위한 포트이기는 하지만요)

앞에서

- '--ping.entrypoint=ping'

라고 ping이라고 이름 붙였죠? (“=” 다음에 오는 ping) 앞에서 선언한 엔트리포인트 ping에 대해서 나는

- '--entryPoints.ping.address=:8082'

8082번을 “사용하겠다.” 입니다.

3. 리다이렉션의 정의

Traefik 분해해서 알아보기 3
- '--entryPoints.web.http.redirections.entryPoint.to=websecure'
- '--entrypoints.web.http.redirections.entryPoint.scheme=https'
- '--entrypoints.web.http.redirections.entrypoint.permanent=true'
  • 각각의 --entryPoints.는 트래픽 문법입니다. 그다음에 오는 web은 내가 위에서지은 이름입니다. 나머지는 Traefik 문법 이고, 그 중에서 redirections에 대해서 정의하고 있습니다.

  • 위에 세 줄을 뜯으면 내가 정의한 web에대해서 내가 정의한 websecure로 리다이렉션 하겠다. 이 때의 스키마는 https이고, permanent 즉 301 리다이렉션 하겠다 입니다.

  • 간단히 이야기해서 http 80으로 들어오면 나는 https 443으로 돌리겠다 입니다.

4. Providers의 정의

Traefik 분해해서 알아보기 4
- '--providers.docker=true'
- '--providers.docker.watch=true'
- '--providers.docker.network=traefik-network'
- '--providers.docker.endpoint=unix:///var/run/docker.sock'
- '--providers.docker.exposedByDefault=false'

여기서는 traefik 문법입니다.

  • ⓐ의 첫 번째 줄은 docker를 사용하겠다. 두 번째 줄은 label이 변경되면 즉시 적용하겠다.

  • ⓑ는 traefik이 감시할 네트워크는 내가 이름붙인 traefik-network 이다.

  • ⓒ의 첫번째 줄: 사용할 도커의 엔드포인트는 unix:///var/run/docker.sock이다. 두 번째 줄: 내가 true한 것만 노출해라

5. 인증서의 정의 (TLS)

Traefik 분해해서 알아보기 5
# Let's Encrypt ACME 설정
- '--certificatesresolvers.letsencrypt.acme.tlschallenge=true'
- '--certificatesresolvers.letsencrypt.acme.keyType=EC256'
- '--certificatesresolvers.letsencrypt.acme.email=webmaster@example.com'
- '--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json'

각각의 줄에서 --certificatesresolvers.은 Traefik 문법입니다. 그리고 그다음에 오는 letsencrypt는 letsencrypt를 사용할 것이니 그냥 내가 지은 이름입니다.

풀어서 설명하면.

  1. certificatesresolvers로 나는 letsencrypt라고 이름붙인 것을 사용할거고 acme 프로토콜이고 tlschallenge를 사용할 것이다.

  2. 인증서는 EC256로 발급해라

  3. 사용할 이메일은 webmaster@example.com 이다

  4. 인증서를 저장할 장소는 Traefik에 바인드된 /etc/traefik/acme/acme.json이다.

6. 인증서의 정의 (DNS)

마찬가지로 현재는 주석처리 되어있지만 아래는 DNS챌린지를 사용하는 경우입니다.

Traefik 분해해서 알아보기 6
# Let's Encrypt ACME DNS Challenge (CLoudflare)
#- "--certificatesResolvers.letsencrypt.acme.dnsChallenge=true"
#- "--certificatesResolvers.letsencrypt.acme.dnsChallenge.provider=cloudflare"
#- "--certificatesResolvers.letsencrypt.acme.dnsChallenge.resolvers=1.1.1.1:53,8.8.8.8:53"
#- "--certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=0"

이거는 한번 해석해 보세요 :)

7. prometheus metrics의 정의

Traefik 분해해서 알아보기 7
# Prometheus 메트릭 설정
- '--metrics.prometheus=true'
- '--metrics.prometheus.buckets=0.1,0.3,1.2,5.0'
- '--metrics.prometheus.addServicesLabels=true'
- '--metrics.prometheus.addrouterslabels=true'
- '--metrics.prometheus.addEntryPointsLabels=true'
# 버전 확인 및 익명 사용 통계 설정
- '--global.checkNewVersion=true'
- '--global.sendAnonymousUsage=false'

여기는 그냥 Traefik 문법입니다. 자세한 사항은 공식문서 보시면 됩니다.

8. HTTP3 QUIC의 정의

Traefik 분해해서 알아보기 8
# HTTP3 설정
- '--experimental.http3=true'
- '--entrypoints.websecure.http3'
- '--entrypoints.websecure.http3.advertisedport=443'
  • 여기는 현재 HTTP3를 사용하려면 활성화 해야하는 설정입니다.

  • 현재는 experimental 이고 --entrypoints.websecure.http3 여기에서 websecure는 https라서 websecure가 아니고, 우리가 websecure라고 이름지었기 때문에 websecure인겁니다.

자 쭉 넘어가서

labels:
- 'traefik.enable=true'
- 'traefik.http.services.dashboard.loadbalancer.server.port=8080'
- 'traefik.http.services.dashboard.loadbalancer.passhostheader=true'
- 'traefik.http.routers.dashboard.rule=Host(`web.example.com`)'
- 'traefik.http.routers.dashboard.entrypoints=websecure'
- 'traefik.http.routers.dashboard.service=api@internal'
- 'traefik.http.routers.dashboard.tls=true'
- 'traefik.http.routers.dashboard.tls.certresolver=letsencrypt'

자 여기 보시면 이제

  1. 핵심은 서비스와 라우터다

  2. 큰 틀은 Traefik의 문법을 따르지만 이름 붙이는 것은 내 마음이다. 요기에서 1번이 나옵니다.

9. services의 정의

Traefik 분해해서 알아보기 9
- 'traefik.enable=true'
- 'traefik.http.services.dashboard.loadbalancer.server.port=8080'
- 'traefik.http.services.dashboard.loadbalancer.passhostheader=true'
  1. 아까 26번 줄에서 선언했기 때문에 활성화하는 옵션입니다.
- '--providers.docker.exposedByDefault=false'

라고 선언했기 때문에 활성화하는 옵션입니다.

  1. 여기에서 - "traefik.http.services.는 서비스를 정의하겠다.
    이름은 dashboard이고 로드밸런스(리버스 프록싱)할 포트는 8080이다 라고 선언하는 겁니다.

  2. passhostheader헤더를 넘기겠다.

여기까지가 서비스 정의를 한것입니다. 이제 핵심 중 서비스를 정의했으며 라우터를 정의하겠습니다.

10. routers의 정의

Traefik 분해해서 알아보기 10
- 'traefik.http.routers.dashboard.rule=Host(`web.example.com`)'
- 'traefik.http.routers.dashboard.entrypoints=websecure'
- 'traefik.http.routers.dashboard.service=api@internal'
- 'traefik.http.routers.dashboard.tls=true'
- 'traefik.http.routers.dashboard.tls.certresolver=letsencrypt'
  1. traefik.http.routers. 라우터를 정의하겠다 이름은 dashboard라고 지을거고 도메인(Host)는 web.example.com 이다.

  2. 내가 이름 붙인 dashboard가 들어올 곳은 내가 이름붙인 websecure이다.

  3. 중간에 뺀건 traefik문법입니다. :)

  4. tls(ssl)사용할거고

  5. 인증서는 내가 이름 붙인 letsencrypt로 제공해라 입니다.

6. rhymix labels Tear Down

자 거의 다왔습니다.

11. rhymix 서비스 정의

Traefik 분해해서 알아보기 11
labels:
- 'traefik.enable=true'
- 'traefik.docker.network=traefik-network'
  • traefik아 감시해라, 감시할 네트워크는 내가 이름 붙인 traefik-network이다.

12. 서비스 포트의 정의

Traefik 분해해서 알아보기 12
- 'traefik.http.services.rhymix-srv.loadbalancer.server.port=80'
- 'traefik.http.services.rhymix-srv.loadbalancer.passhostheader=true'
  1. 서비스의 이름은 rhymix-srv이고 리버스프록싱(로드밸런싱)할 포트는 80 포트이다.

  2. 헤더 넘긴다.

13. www를 root로 미들웨어

Traefik 분해해서 알아보기 13
- 'traefik.http.middlewares.www-redir.redirectregex.regex=^https://www.(.*)'
- 'traefik.http.middlewares.www-redir.redirectregex.replacement=https://$${1}'
- 'traefik.http.middlewares.www-redir.redirectregex.permanent=true'
  1. www-redir라는 이름의 미들웨어를 정의하는데 그게 무엇이냐하면 redirectregex 정규식으로 리다이렉션 하겠다. 정규식은 ^https://www.(.*)로 시작하면이다.
  2. redirectregex 할 것인데 어떻게 할 것인가 하면 replacement(교체) 하겠다. (.*)인 부분을 잘라서 ${1} 여기에 넣겠다
  • https://$${1}에 넣을 건데 왜 $가 2개냐 하면 제가 달러를 좋아해서가 아니고 이스케이핑 하는겁니다.
  • $${1} 이렇게 적어줘야 안으로 넘어갈 때는 ${1}로 넘어갑니다.
  1. 301(permanent)로 하겠다

14. 압축하는 미들웨어

Traefik 분해해서 알아보기 14
- 'traefik.http.middlewares.compresstraefik.compress=true'
  • 117번 줄 미들웨어를 하나 더 정의할 것인데 이름은 compresstraefik이다

15. rhymix 라우터

Traefik 분해해서 알아보기 15
- 'traefik.http.routers.rhymix-rt.rule=Host(`example.com`) || Host(`www.example.com`)'
- 'traefik.http.routers.rhymix-rt.entrypoints=websecure'
- 'traefik.http.routers.rhymix-rt.service=rhymix-srv'
- 'traefik.http.routers.rhymix-rt.middlewares=www-redir, compresstraefik'
  1. 라우터의 이름은 rhymix-rt이고 도메인은 example.com 혹은 www.example.com 이다.

  2. 이름이 rhymix-rt인 라우터가 들어올 곳은 websecure 이고

  3. 라우팅할 서비스의 이름은 rhymix-srv이다

  4. 이름이 rhymix-rt에 적용할 미들웨어는 내가 앞에서 정의한 www-redir, compresstraefik이다.

16. 라우터의 인증서 정의

Traefik 분해해서 알아보기 16
- 'traefik.http.routers.rhymix-rt.tls=true'
- 'traefik.http.routers.rhymix-rt.tls.certresolver=letsencrypt'
- 'traefik.http.routers.rhymix-rt.tls.domains[0].main=example.com'
- 'traefik.http.routers.rhymix-rt.tls.domains[0].sans=*.example.com'
  1. tls(ssl)적용할거고

  2. 내가 정의한 letsencrypt로 제공해라

  3. 혹시 인증서 없으면 메인은 example.com로 발급하고 SANS로 *.example.com로 발급해라.

  4. 와일드카드 발급은 DNS 챌린지로 하셔야 발급됩니다.

이게 전부입니다.

가끔 보면 이름짓는다는 것에 착안해서

Let me out.
labels:
- 'traefik.enable=true'
- 'traefik.docker.network=traefik-network'
- 'traefik.http.services.rhymix.loadbalancer.server.port=80'
- 'traefik.http.services.rhymix.loadbalancer.passhostheader=true'
- 'traefik.http.middlewares.www-redir.redirectregex.regex=^https://www.(.*)'
- 'traefik.http.middlewares.www-redir.redirectregex.replacement=https://$${1}'
- 'traefik.http.middlewares.www-redir.redirectregex.permanent=true'
- 'traefik.http.middlewares.compresstraefik.compress=true'
- 'traefik.http.routers.rhymix.rule=Host(`example.com`) || Host(`www.example.com`)'
- 'traefik.http.routers.rhymix.entrypoints=websecure'
- 'traefik.http.routers.rhymix.service=rhymix'
- 'traefik.http.routers.rhymix.middlewares=www-redir, compresstraefik'
- 'traefik.http.routers.rhymix.tls=true'
- 'traefik.http.routers.rhymix.tls.certresolver=letsencrypt'
- 'traefik.http.routers.rhymix.tls.domains[0].main=example.com'
- 'traefik.http.routers.rhymix.tls.domains[0].sans=*.example.com'

서비스의 이름이 rhymix이고 라우터의 이름도 rhymix인 것은 아무런 인과 관계가 없습니다.
이렇게 기술하면 이해하기 어려울 수 있지만, 전역으로 정의하는 방법도 있습니다. 또한 다이나믹 라우팅이나 명령어를 줄여서 기록할 수도 있습니다. 나중에 기회가 되면 이에 대해 더 자세히 설명하겠습니다.

docker-compose.yml로 직접 해보면서 해보실 분은 github에 코드를 올려놓았습니다.
확인해 보시면서 하면 금방 이해하실 것 같습니다.

수고 많으셨습니다. 감사합니다.


Askfront.com (에스크프론트)

기존의 댓글 대신, 초보자도 자유롭게 질문할 수 있는 포럼을 만들었습니다.
에스크프론트에서는 가이드뿐만 아니라 모든 종류의 질문을 하실 수 있습니다.
검색해도 오래된 정보나 도움이 되지 않는 정보만 나오는 것 같고, 주화입마에 빠진 것 같은 기분이 들 때가 있습니다.
그럴 때, 부담 없이 질문해 주세요 :) 같이 의논하며 생각해봅시다.
가능하다면, 제가 답변 드리겠습니다. 고맙습니다.