안녕하세요, 자바파커입니다.
"7편대로 ingress-nginx로 HTTPS까지 다 자동화했는데, 팀에서 Traefik으로 바꾸자는 이야기가 나왔습니다. 다시 다 엎어야 하나요?"
솔직히 저도 그랬습니다. 운영 중인 Controller를 바꾸는 건 진입점을 바꾸는 일 — 잘못하면 서비스 전체 접근 불가 상황이 벌어집니다. "어차피 annotation이랑 YAML 다시 쓰는 게 덜 나을까" 생각도 들고, 한 번 결정하면 돌이키기 어려워 보입니다.
결론부터 말씀드리면 — Controller 마이그레이션은 "병렬 운영 기간"만 제대로 두면 무중단으로 됩니다. nginx와 Traefik을 동시에 클러스터에 두고, Ingress별로 ingressClassName만 바꿔가며 점진적으로 전환하면 됩니다. cert-manager는 그대로 재사용 가능합니다. 오늘은 이 순서대로, nginx vs Traefik 차이부터 Traefik 설치, 실제 Ingress 전환, 미들웨어로 annotation 대체, 롤백 전략까지 정리하겠습니다.
이전 편을 안 보셨다면 K8S에 대한 이해 · Helm 완벽 가이드 · ArgoCD GitOps 가이드 · Ingress + cert-manager 가이드부터 읽으시면 흐름이 이어집니다.
왜 Traefik으로? — 현실적 동기 5가지
먼저 "굳이 바꿀 필요가 있나?"부터 정리합니다. 다음 중 2개 이상 공감되면 전환 고려 가치가 있습니다.
| 동기 | 구체적 상황 |
|---|---|
| 자동 서비스 디스커버리 | Docker·Consul·Kubernetes 라벨만으로 라우팅이 자동 구성됨 |
| CRD 기반 선언 | annotation으로 설정이 뒤얽히는 nginx와 달리 IngressRoute·Middleware로 깔끔히 분리 |
| 네이티브 Gateway API | 차세대 Ingress 표준을 Traefik이 가장 먼저 native 지원 |
| 내장 Dashboard·Metrics | 별도 도구 없이 트래픽·라우터 시각화 |
| Hot reload | 설정 변경 시 downtime 없이 재적용 (nginx-ingress도 되지만 동작 검증 편함) |
반대로 nginx-ingress를 유지할 이유:
- 이미 안정적 운영 중, 생태계·문서 가장 풍부
- 단순 기능만 쓰고 있음 (Traefik 장점이 실질적 가치를 주지 않음)
- 팀이 nginx 설정·annotation에 깊이 숙련됨
"이유 없는 전환은 비용"이라는 걸 먼저 짚어둡니다.
nginx vs Traefik — 설계 철학의 차이
차이를 한 줄로: annotation 중심 vs CRD 중심.
| 항목 | ingress-nginx | Traefik |
|---|---|---|
| 주 설정 방식 | Ingress + annotations |
IngressRoute CRD (또는 Ingress) |
| 미들웨어(rate·CORS) | annotation 값 | Middleware CRD로 재사용 가능 |
| 설정 가독성 | annotation 많아지면 한 리소스가 뚱뚱해짐 | 라우터·미들웨어 분리, 관심사 분리 |
| 커뮤니티 | 가장 크다 | 충분히 큼, 공식 docs 친절 |
| 서비스 디스커버리 | Kubernetes Ingress만 | 다중 Provider (K8S·Docker·Consul 등) |
| Dashboard | 별도 Prometheus+Grafana 조합 | 내장 |
| Gateway API | 지원 (후발) | 선도 |
| 표준 Ingress 호환 | ✓ | ✓ (하위 호환 모드) |
즉 Traefik은 "설정을 객체로 분해"하는 방향이고, nginx는 "한 Ingress 리소스에 옵션 총집합" 방향입니다. 취향 + 팀 규모의 문제.
마이그레이션 전략 — 4단계 병렬 운영
핵심은 "한 번에 교체하지 않는다"입니다. 단계를 나눕니다.
[1단계] Traefik 설치 (nginx 그대로 둠)
↓
[2단계] 비운영 Ingress 1~2개 전환 · 검증
↓
[3단계] 운영 Ingress 점진 전환 (도메인별·서비스별)
↓
[4단계] nginx 제거
핵심 장치: ingressClassName
쿠버네티스는 한 클러스터에 여러 Controller를 둘 수 있습니다. 각 Ingress가 ingressClassName: nginx 또는 ingressClassName: traefik으로 자기 Controller를 지정합니다. 이 값만 바꾸면 그 Ingress 하나만 Traefik으로 넘어갑니다. 나머지는 영향 없음.
# 기존 nginx로 처리되던 Ingress
spec:
ingressClassName: nginx # ← 이걸
# Traefik으로 전환
spec:
ingressClassName: traefik # ← 이걸로DNS 레코드나 LB IP는 바꾸지 않습니다 (후술).
1단계 — Traefik 설치
Helm으로 설치, nginx와 다른 네임스페이스에 둡니다.
helm repo add traefik https://traefik.github.io/charts
helm repo update
helm install traefik traefik/traefik \
--namespace traefik --create-namespace \
--set ingressClass.enabled=true \
--set ingressClass.isDefaultClass=false \
--set service.type=LoadBalancer핵심 포인트:
| 설정 | 의미 |
|---|---|
ingressClass.enabled=true |
traefik라는 IngressClass 리소스 생성 |
ingressClass.isDefaultClass=false |
기본값으로 두지 않음 — ingressClassName 미지정 Ingress가 자동으로 Traefik으로 가지 않도록 |
service.type=LoadBalancer |
새 LoadBalancer(별도 공인 IP) 생성 |
설치 확인:
kubectl get svc -n traefik
# traefik LoadBalancer <ClusterIP> <NEW_EXTERNAL_IP> 80:...,443:...
kubectl get ingressclass
# NAME CONTROLLER AGE
# nginx k8s.io/ingress-nginx 90d
# traefik traefik.io/ingress-controller 1m이제 IP 두 개가 공존합니다. 하나는 nginx용(기존 DNS 연결), 하나는 Traefik용(아직 연결 안 됨).
2단계 — 비운영 Ingress로 검증
dev·staging 도메인 하나를 골라서 먼저 Traefik으로 전환합니다.
2-a) IngressClass만 변경 (가장 간단한 전환)
기존 dev.javapark.kr Ingress가 이랬다면:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: dev-app
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/force-ssl-redirect: "true" # nginx 전용
spec:
ingressClassName: nginx
tls:
- hosts: [dev.javapark.kr]
secretName: dev-tls
rules:
- host: dev.javapark.kr
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dev-svc
port:
number: 80DNS를 새 IP(Traefik LB)로 바꾸고, ingressClassName을 traefik으로:
spec:
ingressClassName: traefik # ← 변경
# 나머지는 동일 — Traefik이 기본 Ingress도 그대로 처리HTTPS는 cert-manager가 그대로 해줍니다. ClusterIssuer의 solvers가 class: nginx로 되어 있다면 class: traefik으로 수정한 ClusterIssuer를 하나 더 만들거나, 기존 ClusterIssuer를 공용으로 쓸 수 있게 ingressClassName 명시 없이 두는 방법이 있습니다.
2-b) ClusterIssuer 준비
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod-traefik
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: you@example.com
privateKeySecretRef:
name: letsencrypt-prod-traefik-key
solvers:
- http01:
ingress:
class: traefik # ← Traefik용검증 방법:
# DNS 전파 확인
dig dev.javapark.kr +short
# → Traefik LB IP 나오는지
# HTTPS 접근
curl -vI https://dev.javapark.kr
# → 인증서 발급 + 정상 응답 확인
# Traefik 내부 라우터 확인 (Dashboard 활용, 후술)3단계 — 운영 Ingress 점진 전환
검증이 끝나면 본격 전환. 한 번에 전부 바꾸지 마세요. 권장 순서:
- 트래픽 낮은 시간대 (새벽) 작업
- 도메인별 또는 팀별로 나눠서 2~3일 간격
- 각 전환 후 모니터링 1시간 (에러율·응답시간·인증서 상태)
- 문제 발생 시 즉시 롤백 (DNS 또는 ingressClassName 되돌림)
annotation을 Traefik Middleware로 교체
이 단계가 가장 신경 쓰이는 부분입니다. nginx-ingress annotation을 Traefik 문법으로 맵핑:
| nginx annotation | Traefik 대응 |
|---|---|
nginx.ingress.kubernetes.io/force-ssl-redirect: "true" |
Middleware redirectScheme (HTTPS 강제) 또는 entrypoint 설정 |
nginx.ingress.kubernetes.io/proxy-body-size: 50m |
Middleware buffering.maxRequestBodyBytes |
nginx.ingress.kubernetes.io/rewrite-target: /$2 |
Middleware replacePathRegex |
nginx.ingress.kubernetes.io/enable-cors: "true" |
Middleware headers (Access-Control-*) |
nginx.ingress.kubernetes.io/rate-limit |
Middleware rateLimit |
Middleware 예시:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: force-https
namespace: default
spec:
redirectScheme:
scheme: https
permanent: true
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: cors-headers
spec:
headers:
accessControlAllowOriginList:
- "https://app.example.com"
accessControlAllowMethods:
- GET
- POST
- OPTIONS
accessControlMaxAge: 86400Ingress에 연결 (annotation으로 붙이는 방식):
metadata:
annotations:
traefik.ingress.kubernetes.io/router.middlewares: default-force-https@kubernetescrd,default-cors-headers@kubernetescrd실전 팁: Middleware는 네임스페이스 리소스라
<namespace>-<name>@kubernetescrd형식의 정식 이름으로 참조해야 합니다. 오타 1위 원인.
IngressRoute CRD로 점진적 전환 (선택)
더 풍부한 기능을 쓰려면 Ingress → IngressRoute로 바꿉니다. 같은 리소스를 Traefik 네이티브 방식으로:
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: my-app
spec:
entryPoints:
- websecure
routes:
- match: Host(`api.javapark.kr`) && PathPrefix(`/v1`)
kind: Rule
services:
- name: api-svc
port: 80
middlewares:
- name: cors-headers
- name: rate-limit-api
tls:
secretName: api-tlsIngress와 IngressRoute를 섞어 써도 Traefik은 둘 다 처리합니다. 서두르지 말고 기존 Ingress는 그대로 두고, 새 서비스부터 IngressRoute로 쓰는 게 좋은 전략입니다.
Traefik Dashboard — 라우팅 디버깅의 필수 도구
기본 설치 시 비활성화되어 있습니다. Helm values로 켜세요:
# values-traefik.yaml
ingressRoute:
dashboard:
enabled: true
matchRule: Host(`traefik.javapark.kr`)
entryPoints: [websecure]
tls:
secretName: traefik-dashboard-tls업그레이드:
helm upgrade traefik traefik/traefik -n traefik -f values-traefik.yamlDashboard에서 보이는 것:
- 현재 등록된 Router 목록 (도메인·경로 기반)
- Service (백엔드) 상태
- Middleware 체인
- TLS 인증서 요약
- 실시간 메트릭스 (요청 수·응답 시간·에러율)
보안 경고: Dashboard는 반드시 인증 걸기.
BasicAuthmiddleware + cert-manager HTTPS로 감싸고, IP 화이트리스트까지 걸면 안전합니다. 기본 상태로 공개하면 라우팅 정보가 노출됩니다.
4단계 — nginx-ingress 제거
모든 운영 Ingress가 Traefik으로 이전됐고 1주일 이상 안정 운영이 확인되면:
# 남은 nginx Ingress 확인 (0개여야 함)
kubectl get ingress -A -o json | jq '.items[] | select(.spec.ingressClassName=="nginx") | .metadata.name'
# Helm uninstall
helm uninstall ingress-nginx -n ingress-nginx
kubectl delete namespace ingress-nginx
# IngressClass도 정리
kubectl delete ingressclass nginxDNS도 정리 — nginx LB의 공인 IP로 연결된 레코드가 더 이상 없는지 확인. 클라우드 LB는 리소스 과금 중이니 빠르게 제거.
롤백 전략 — 문제 발생 시
3단계의 각 전환마다 롤백 경로를 명확히 해두세요. 2가지 방법:
방법 A) ingressClassName 되돌리기 (빠름)
kubectl patch ingress my-app -n default --type=merge -p \
'{"spec":{"ingressClassName":"nginx"}}'단, DNS가 Traefik IP로 바뀐 상태면 의미 없음. 아래 방법 B와 병행 필요.
방법 B) DNS 레코드 되돌리기 (근본적)
blog.javapark.kr A <nginx-LB-IP> # 원래 값으로TTL이 낮아야 빠른 롤백 가능 — 전환 2~3일 전에 TTL을 300초(5분) 수준으로 낮춰두세요.
데이터 플레인이 아닌 컨트롤 플레인 문제
Traefik 자체는 멀쩡한데 cert-manager 연동이 깨진 경우, 인증서만 발급 안 되는 상태가 됩니다. 이 경우:
# Challenge 확인
kubectl get challenge -A
# 이전 nginx용 Secret 재사용 (같은 secretName이면 자동 복구)
kubectl get secret <tls-secret-name> -o yaml인증서는 Secret에 저장되므로, Controller만 바뀌고 Secret이 그대로면 TLS는 즉시 복구됩니다.
실전 체크리스트
전환 작업 직전 돌려볼 최종 체크리스트:
- nginx와 Traefik 둘 다 Running이고 LoadBalancer에 IP 할당됨
-
ingressClass.isDefaultClass=false— Traefik이 기본 Controller 아님 - ClusterIssuer
letsencrypt-prod-traefik준비됨 (solvers.class: traefik) - DNS TTL이 300초 이하로 낮춰져 있음 (전환 2일 전)
- 대상 Ingress의 annotation 중 Traefik 미지원 항목 확인 (예: 매우 특수한 nginx.conf snippet)
- Dashboard 접근 가능 (인증 걸림)
- 롤백 DNS 값 메모 (이전 nginx LB IP)
- 모니터링 대시보드(Grafana 등) 열어둠
- 전환 창: 트래픽 낮은 시간 + 30분 이상 여유
자주 겪는 이슈 5가지
1) "Traefik에서 Host 매칭이 안 된다"
IngressRoute의 match 문법 주의. 백틱(`)으로 감싸야 합니다:
match: Host(`api.javapark.kr`) # ✓
match: Host("api.javapark.kr") # ✕ 동작 안 함2) "Middleware가 안 먹힌다"
<namespace>-<name>@kubernetescrd 형식 지켰는지 확인. 다른 네임스페이스 Middleware를 참조하려면 전체 경로 필수.
3) "cert-manager Challenge가 실패"
ClusterIssuer의 solvers.class가 기존 nginx로 남아있으면 Traefik Ingress에 대한 challenge가 실패. traefik용 ClusterIssuer 따로 쓰거나, class 필드를 아예 빼고 공용으로.
4) "전환 후 404"
ingressClassName을 바꿨는데 라우팅이 안 됨 → Traefik에 해당 Ingress가 아직 등록 안 된 상태. Traefik Pod 재시작 or Traefik CRD 명시 확인.
5) "Dashboard가 안 열린다"
IngressRoute+entryPoints: [websecure]설정 확인- TLS secret 존재 확인
BasicAuthmiddleware가 잘못 설정되면 401만 뜸 — secret 형식(bcrypt) 재생성
ArgoCD와 함께 — GitOps 기반 마이그레이션
이 모든 전환을 Git 커밋 단위로 하면 추적·롤백이 훨씬 편합니다.
k8s-manifests/
├── apps/
│ └── my-app/
│ └── ingress.yaml # ingressClassName 하나 바꾸는 커밋이 전환
├── platform/
│ ├── ingress-nginx/ # 초기 상태 · 나중에 삭제 커밋
│ ├── traefik/ # 신규 추가 커밋
│ └── cert-manager/ # 그대로 유지ArgoCD App of Apps 패턴에서 traefik Application을 먼저 추가 → 검증 → 각 서비스 앱의 ingressClassName을 PR로 전환 → nginx Application 제거 PR. 모든 전환이 Git 히스토리에 남아 롤백도 git revert 한 번으로 해결.
ArgoCD 편 패턴 그대로 재사용 가능.
FAQ
Q. Ingress로 계속 가도 되나요, 꼭 IngressRoute로 전환해야 하나요?
안 해도 됩니다. Traefik은 기본 Ingress를 완벽히 지원합니다. 이미 수십 개 Ingress가 있다면 그대로 두고, 신규만 IngressRoute로 쓰는 하이브리드가 현실적. IngressRoute는 Traefik 특화 기능(복잡한 매처·미들웨어 체인)을 쓸 때만 유리합니다.
Q. annotations가 너무 많아서 변환이 막막합니다.
nginx.ingress.kubernetes.io/ annotation을 grep으로 전부 뽑은 뒤 Traefik 공식 문서의 annotation reference와 맵핑 표를 만드세요. 실무에서 자주 쓰는 건 force-ssl-redirect·proxy-body-size·cors·rewrite 네 개입니다.
Q. 성능 차이가 있나요?
벤치마크상 의미 있는 차이는 없습니다 (초당 수만 req 이상에서만 유의미한 차이). Traefik이 Go로 쓰여 있어 메모리·CPU 효율은 비슷하거나 소폭 유리. 실제 병목은 Controller가 아니라 백엔드 앱이 대부분.
Q. Gateway API로 바로 가는 게 낫지 않나요?
관심 있으면 추천합니다. Traefik은 Gateway API를 가장 먼저 native 지원한 Controller라, Gateway·HTTPRoute 리소스로 쓰면 향후 어떤 Controller로 바꿔도 리소스 재사용 가능. 단, 아직 일부 기능은 Ingress보다 성숙도가 낮으니 신규 프로젝트 또는 POC에서 먼저 시도해보세요.
Q. 마이그레이션이 얼마나 걸리나요?
서비스 1020개 규모 기준 12주가 현실적입니다. 1단계(설치)는 1시간, 2단계(검증)는 반나절, 3단계(점진 전환)가 대부분의 시간을 먹고, 4단계(nginx 제거)는 30분. 서두르지 말 것이 가장 큰 팁.
마무리 — 다음 단계
Controller 교체는 "진입점을 건드리는 일" 중 제일 조심스럽지만, ingressClassName 하나가 격리 스위치 역할을 해주기 때문에 실제로는 감당할 만한 작업입니다. 중요한 건:
- 병렬 운영 기간을 아끼지 않는다 — 하루 쓰고 버릴 LB IP 아까워하지 마세요
- annotation → Middleware 맵핑을 미리 표로 만든다
- cert-manager는 재사용 — ClusterIssuer만 class 분리
- DNS TTL을 미리 낮춘다 — 롤백 속도의 핵심
- 한 번에 하나씩 전환하고 1시간 모니터링
오늘 정리 핵심:
- Controller 공존 +
ingressClassName이 전환의 스위치 - 기본 Ingress는 그대로 Traefik이 처리 — IngressRoute는 선택
- Middleware로 nginx annotation을 객체화·재사용
- cert-manager Secret은 Controller 교체 영향 받지 않음
- 4단계 계획 + 각 단계별 롤백 경로 준비
시리즈 다음 주제
- HPA·VPA로 오토스케일링 구성
- Prometheus + Grafana 모니터링 스택
- Gateway API 전환기 — 언제·어떻게 바꿀까 (Traefik native 경험 활용)
- ArgoCD ApplicationSet 심화 — 멀티 클러스터 대규모 운영
- K8S NetworkPolicy로 제로 트러스트 구성
여러분은 ingress-nginx에서 벗어날 생각이 있으신가요? 아니면 이미 Traefik·Istio Gateway·클라우드 관리형 ALB를 쓰고 계신지 댓글로 공유해주세요. 실전 전환 경험이 다음 편 소재가 됩니다.