Kubernetes Ingress란?
Kubernetes Ingress는 클러스터 외부에서 내부 Service로 HTTP/HTTPS 트래픽을 라우팅하는 API 오브젝트다. NodePort나 LoadBalancer Service와 달리, 호스트 기반·경로 기반 라우팅을 단일 진입점에서 처리할 수 있다. 하지만 Ingress 리소스만으로는 아무 일도 일어나지 않는다. 반드시 Ingress Controller가 필요하다.
Ingress Controller 선택
Ingress 리소스는 선언이고, 실제 동작은 Controller가 담당한다. 대표적인 선택지를 비교해보자.
| Controller | 프록시 엔진 | 특징 |
|---|---|---|
| NGINX Ingress | NGINX | 가장 범용적, 커뮤니티 표준 |
| Traefik | 자체 엔진 | 자동 인증서, 미들웨어 체인 |
| HAProxy Ingress | HAProxy | 고성능 TCP/HTTP 로드밸런싱 |
| Istio Gateway | Envoy | 서비스 메시 통합 |
| Gateway API | 다양 | 차세대 표준, 역할 분리 |
프로덕션에서는 NGINX Ingress Controller가 가장 많이 사용된다. 이 글에서도 NGINX 기반으로 설명한다.
경로 기반 라우팅
하나의 도메인에서 경로별로 다른 Service로 트래픽을 분배하는 가장 기본적인 패턴이다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /users(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: user-service
port:
number: 3000
- path: /orders(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: order-service
port:
number: 3000
rewrite-target: /$2 어노테이션은 캡처 그룹을 사용해 경로를 재작성한다. /users/123 요청이 user-service에는 /123으로 전달된다. 이 패턴을 모르면 백엔드 서비스에서 404가 발생하는 흔한 실수를 하게 된다.
호스트 기반 라우팅
서브도메인별로 서비스를 분리하는 패턴이다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: multi-host-ingress
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
- host: admin.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: admin-service
port:
number: 3000
- host: ws.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: websocket-service
port:
number: 8080
TLS 인증서 설정
HTTPS는 선택이 아니라 필수다. Ingress에서 TLS를 종료하는 두 가지 방법을 살펴보자.
수동 Secret 등록
# TLS Secret 생성
kubectl create secret tls example-tls
--cert=tls.crt
--key=tls.key
-n default
# Ingress에 적용
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tls-ingress
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.example.com
- admin.example.com
secretName: example-tls
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
cert-manager 자동 발급
Let’s Encrypt와 cert-manager를 연동하면 인증서 발급·갱신이 완전 자동화된다.
# ClusterIssuer 정의
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
class: nginx
# Ingress에 어노테이션 추가
metadata:
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- api.example.com
secretName: api-tls-auto # cert-manager가 자동 생성
Rate Limiting
API 남용을 방지하는 속도 제한을 Ingress 레벨에서 설정할 수 있다.
metadata:
annotations:
# 초당 요청 수 제한
nginx.ingress.kubernetes.io/limit-rps: "10"
# 분당 요청 수 제한
nginx.ingress.kubernetes.io/limit-rpm: "300"
# 동시 연결 수 제한
nginx.ingress.kubernetes.io/limit-connections: "5"
# 제한 초과 시 응답 코드
nginx.ingress.kubernetes.io/limit-rate-after: "10m"
# 버스트 허용량 (rps * burst-multiplier)
nginx.ingress.kubernetes.io/limit-burst-multiplier: "3"
IP 기반 제한이 기본이며, X-Forwarded-For 헤더로 실제 클라이언트 IP를 식별한다. CDN 뒤에 있다면 use-forwarded-headers ConfigMap 설정이 필수다.
CORS·보안 헤더
프론트엔드-백엔드 분리 아키텍처에서 CORS 설정은 Ingress에서 중앙 관리하는 것이 효율적이다.
metadata:
annotations:
# CORS 설정
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-origin: "https://app.example.com"
nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS"
nginx.ingress.kubernetes.io/cors-allow-headers: "Authorization, Content-Type"
nginx.ingress.kubernetes.io/cors-max-age: "86400"
# 보안 헤더
nginx.ingress.kubernetes.io/configuration-snippet: |
more_set_headers "X-Frame-Options: DENY";
more_set_headers "X-Content-Type-Options: nosniff";
more_set_headers "X-XSS-Protection: 1; mode=block";
more_set_headers "Strict-Transport-Security: max-age=31536000";
WebSocket·gRPC 프록시
실시간 통신 프로토콜도 Ingress에서 처리할 수 있다.
# WebSocket
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/upstream-hash-by: "$remote_addr"
# gRPC
metadata:
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
WebSocket은 기본 60초 타임아웃을 늘려야 하고, upstream-hash-by로 같은 클라이언트가 같은 Pod에 연결되도록 해야 한다. gRPC는 반드시 TLS가 필요하며 backend-protocol: GRPC를 명시해야 한다.
Canary 배포
NGINX Ingress의 canary 어노테이션으로 트래픽을 점진적으로 전환할 수 있다.
# 기존 Ingress (stable)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-stable
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-stable
port:
number: 8080
---
# Canary Ingress (새 버전)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-canary
annotations:
nginx.ingress.kubernetes.io/canary: "true"
# 가중치 기반: 10% 트래픽
nginx.ingress.kubernetes.io/canary-weight: "10"
# 헤더 기반: X-Canary: always 시 무조건 canary로
nginx.ingress.kubernetes.io/canary-by-header: "X-Canary"
# 쿠키 기반: 특정 사용자만 canary
nginx.ingress.kubernetes.io/canary-by-cookie: "canary-cookie"
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-canary
port:
number: 8080
우선순위는 header → cookie → weight 순이다. 헤더가 설정되면 가중치는 무시된다. QA 팀은 헤더로 canary에 직접 접근하고, 일반 사용자는 가중치로 점진 노출하는 전략이 실무에서 흔하다.
커스텀 에러 페이지
metadata:
annotations:
nginx.ingress.kubernetes.io/custom-http-errors: "404,502,503"
nginx.ingress.kubernetes.io/default-backend: error-page-service
# error-page-service는 요청 헤더에서 원래 상태 코드를 읽음
# X-Code: 원래 HTTP 상태 코드
# X-Original-URI: 원래 요청 경로
성능 튜닝 어노테이션
metadata:
annotations:
# 프록시 버퍼 크기 (큰 응답 헤더 처리)
nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
# 요청 본문 크기 제한 (파일 업로드)
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
# 백엔드 연결 타임아웃
nginx.ingress.kubernetes.io/proxy-connect-timeout: "10"
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
# Gzip 압축
nginx.ingress.kubernetes.io/enable-gzip: "true"
nginx.ingress.kubernetes.io/gzip-min-length: "1000"
# 로드밸런싱 알고리즘
nginx.ingress.kubernetes.io/upstream-hash-by: "$request_uri"
디버깅과 트러블슈팅
# Ingress 상태 확인
kubectl get ingress -A
kubectl describe ingress app-ingress
# NGINX 설정 확인 (실제 생성된 nginx.conf)
kubectl exec -n ingress-nginx deploy/ingress-nginx-controller
-- cat /etc/nginx/nginx.conf | grep -A 20 "api.example.com"
# 실시간 로그
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller -f
# 백엔드 연결 테스트
kubectl exec -n ingress-nginx deploy/ingress-nginx-controller
-- curl -s http://user-service.default.svc:3000/health
가장 흔한 실수는 ingressClassName 누락, Service 포트 불일치, pathType 오설정이다. describe 명령에서 Events 섹션을 반드시 확인하자.
정리
| 기능 | 핵심 어노테이션 |
|---|---|
| 경로 재작성 | rewrite-target |
| TLS 자동화 | cert-manager.io/cluster-issuer |
| Rate Limiting | limit-rps, limit-connections |
| Canary 배포 | canary, canary-weight, canary-by-header |
| WebSocket/gRPC | proxy-read-timeout, backend-protocol |
Ingress는 단순한 라우터가 아니라 TLS 종료, 속도 제한, CORS, Canary 배포, 보안 헤더까지 담당하는 클러스터의 관문이다. 어노테이션 기반 설정을 숙지하면 별도 API Gateway 없이도 상당한 트래픽 제어가 가능하다.