포스트

Kubernetes Ingress & Spring Cloud Gateway 완전 가이드

Kubernetes Ingress & Spring Cloud Gateway 완전 가이드

이 문서는 helm/templates/ingress.yaml 예시를 기반으로,
왜 진입점이 필요한지부터 시작해 K8s IngressSpring Cloud Gateway의 개념·장단점·사용 방식,
두 기술의 심층 비교, 함께 사용하는 패턴, 그리고 Best Practices까지 순서대로 설명합니다.


목차


Part 1. 문제 정의 — 왜 진입점이 필요한가?

마이크로서비스 아키텍처에서 외부 클라이언트(브라우저, 모바일 앱, 외부 API)가 내부 서비스에 접근하려면 반드시 단일 진입점(Entry Point) 이 필요합니다.

진입점 없이 발생하는 문제

Ingress 없이 여러 서비스를 모두 외부에 직접 노출하면 다음 구조가 됩니다.

graph LR
    Internet --> LB1["LoadBalancer\nfrontend\n💰 추가 비용"]
    Internet --> LB2["LoadBalancer\ngame-server\n💰 추가 비용"]
    Internet --> LB3["LoadBalancer\nadmin\n💰 추가 비용"]

    LB1 --> SVC1[frontend:3000]
    LB2 --> SVC2[game-server:8080]
    LB3 --> SVC3[admin:3001]

    style LB1 fill:#f44336,color:#fff
    style LB2 fill:#f44336,color:#fff
    style LB3 fill:#f44336,color:#fff
  • 서비스 3개 = 클라우드 LoadBalancer 3개 → 비용 폭증
  • 각 서비스가 별도 IP/포트 → TLS 인증서를 각각 관리
  • URL 기반 라우팅(/api, /ws) 불가능
  • 도메인 기반 라우팅(api.myapp.com, admin.myapp.com) 불가능

진입점 전략 선택지

graph TD
    Client["클라이언트<br/>Browser / Mobile / API"] --> EP{진입점 전략}

    EP --> A["직접 노출<br/>NodePort / LoadBalancer"]
    EP --> B["K8s Ingress<br/>인프라 레벨 라우팅"]
    EP --> C["Spring Cloud Gateway<br/>애플리케이션 레벨 게이트웨이"]
    EP --> D["혼합 사용<br/>Ingress + SCG"]

    A --> A1["❌ 보안 취약<br/>❌ 비용 폭증<br/>❌ 기능 부족"]
    B --> B1["✅ 인프라 표준<br/>✅ 비용 효율<br/>⚠️ 앱 로직 불가"]
    C --> C1["✅ 비즈니스 로직 통합<br/>✅ 세밀한 제어<br/>⚠️ 인프라 의존성"]
    D --> D1["✅ 각각의 장점 결합<br/>✅ 엔터프라이즈 표준<br/>⚠️ 복잡도 증가"]

    style B fill:#2196F3,color:#fff
    style C fill:#4CAF50,color:#fff
    style D fill:#9C27B0,color:#fff

💡 Rummikub 프로젝트의 도입 타이밍 가이드

“현재 NodePort로 충분. 단일 도메인(rummikub.localhost) 통합 접근이 필요해지거나 클라우드 배포 시 도입”

이 메모는 Ingress를 언제 도입해야 하는가에 대한 실용적인 가이드입니다.

NodePort만으로 충분한 상황 (로컬 개발 초기)

로컬 개발 환경에서는 각 서비스를 포트로 직접 접근합니다.

1
2
3
4
http://localhost:3000   → frontend
http://localhost:8080   → game-server (REST API)
ws://localhost:8080/ws  → game-server (WebSocket)
http://localhost:3001   → admin

각 서비스가 독립 포트를 쓰는 것이 개발 중엔 오히려 편리하며, Traefik 설치·TLS 인증서 발급·ingress.yaml 관리 같은 오버헤드를 감수할 이유가 없습니다.

Traefik(Ingress) 도입이 필요해지는 시점

graph TD
    Q1{"단일 도메인으로<br/>통합 접근이 필요한가?<br/>rummikub.localhost/api<br/>rummikub.localhost/ws"}
    Q2{"클라우드 배포를<br/>준비 중인가?<br/>AWS EKS / Azure AKS"}
    Q3{"HTTPS TLS가<br/>필수인가?<br/>인증서 관리 필요"}
    Q4{"여러 서비스를<br/>하나의 URL로<br/>묶어야 하는가?"}

    Q1 -->|Yes| Introduce[✅ Traefik Ingress 도입]
    Q2 -->|Yes| Introduce
    Q3 -->|Yes| Introduce
    Q4 -->|Yes| Introduce

    Q1 -->|No| Q2
    Q2 -->|No| Q3
    Q3 -->|No| Q4
    Q4 -->|No| Skip["⏸️ NodePort 유지<br/>지금은 불필요"]

    style Introduce fill:#4CAF50,color:#fff
    style Skip fill:#9E9E9E,color:#fff
상황NodePortIngress (Traefik)
로컬 개발, 포트별 접근✅ 충분오버엔지니어링
rummikub.localhost 단일 도메인으로 통합❌ 불가✅ 필요
HTTPS TLS 적용❌ 복잡✅ 자동 처리
클라우드(AWS/Azure) 배포❌ 보안 취약✅ 필수
/api, /ws, /admin 경로 분기❌ 불가✅ 핵심 기능
CI/CD 파이프라인·스테이징 환경⚠️ 임시방편✅ 권장

결론: 지금의 ingress.yaml은 로컬 K3s 환경에서 rummikub.localhost 하나로 프론트엔드·API·WebSocket·관리자를 모두 묶기 위해 작성된 것입니다. 클라우드 배포 단계에서는 선택이 아닌 필수입니다.


Part 2. Kubernetes Ingress

Kubernetes Ingress는 클러스터 외부 HTTP/HTTPS 트래픽을 내부 서비스로 라우팅하는 인프라 레벨 진입점입니다.

🏢 Ingress를 호텔 프런트에 비유하면

호텔(= 쿠버네티스 클러스터)에 들어오는 손님(= 외부 요청)은 반드시 프런트 데스크(= Ingress Controller)를 거칩니다.

  • 손님이 “레스토랑 가고 싶어요” → /apigame-server 로 안내
  • 손님이 “수영장 가고 싶어요” → /wsgame-server (WebSocket) 로 안내
  • 손님이 “사장님 찾아요” → /adminadmin 으로 안내
  • 그냥 들어온 손님 → /frontend 로 안내

NodePort 방식은 호텔 출입구가 서비스마다 따로 있는 것(정문 30080, 후문 30001, 비상구 30081…)이고,
Ingress 방식은 출입구 하나(rummikub.localhost)에서 프런트가 목적지에 따라 안내해 주는 것입니다.

Ingress Resource = 프런트 데스크의 안내 규칙표 (어디서 오면 어디로 보내라)
Ingress Controller = 실제로 안내를 수행하는 프런트 직원 (Traefik, Nginx 등)
⚠️ 규칙표(Resource)만 있고 직원(Controller)이 없으면 아무것도 동작하지 않습니다.

graph LR
    Internet -->|"rummikub.localhost"| LB["LoadBalancer<br/>단일 진입점<br/>1개만 필요"]
    LB --> IC["Ingress Controller<br/>Traefik / Nginx / ALB"]

    IC -->|"Path: /"| SVC1["frontend<br/>Service:3000"]
    IC -->|"Path: /api"| SVC2["game-server<br/>Service:8080"]
    IC -->|"Path: /ws"| SVC2
    IC -->|"Path: /admin"| SVC3["admin<br/>Service:3001"]

    SVC1 --> Pod1[frontend Pods]
    SVC2 --> Pod2[game-server Pods]
    SVC3 --> Pod3[admin Pods]

    style IC fill:#2196F3,color:#fff
    style LB fill:#4CAF50,color:#fff

2.1 핵심 개념

구성 요소 3가지

구성 요소역할
Ingress Resource라우팅 규칙을 정의하는 Kubernetes 오브젝트 (YAML)
Ingress Controller실제 트래픽을 처리하는 프록시 서버 (Nginx, Traefik, HAProxy 등)
IngressClass어떤 Ingress Controller를 사용할지 지정

⚠️ 중요: Ingress Resource만 배포한다고 동작하지 않습니다. 반드시 Ingress Controller가 클러스터에 설치되어 있어야 합니다.

라우팅 유형

graph TD
    A[Ingress 라우팅 유형] --> B[Host 기반 라우팅]
    A --> C[Path 기반 라우팅]
    A --> D[혼합 사용]

    B --> B1["api.example.com → api-service\nadmin.example.com → admin-service"]
    C --> C1["example.com/api → api-service\nexample.com/admin → admin-service"]
    D --> D1["api.example.com/v1 → api-v1-service\napi.example.com/v2 → api-v2-service"]

PathType 종류

PathType동작 방식예시
Prefix지정된 경로로 시작하는 모든 요청 매칭/api/api, /api/users, /api/v2/... 모두 매칭
Exact정확히 일치하는 경로만 매칭/api/api만 매칭, /api/users는 불일치
ImplementationSpecificIngress Controller가 자체 규칙 적용컨트롤러에 따라 다름

2.2 장점과 단점

장점 ✅

비용 절감

1
2
LoadBalancer 방식:  서비스 N개 = 클라우드 LB N개 = 월 $N × LB단가
Ingress 방식:       서비스 N개 = 클라우드 LB 1개 = 월 $1 × LB단가

중앙 집중식 TLS 관리

  • TLS 인증서를 Ingress 레벨에서 한 번에 관리
  • cert-manager 연동으로 Let’s Encrypt 자동 갱신 가능
  • 각 Pod/Service에서 TLS를 처리할 필요 없음 (SSL Termination)

유연한 라우팅

  • URL 경로 기반 라우팅 (/api, /ws, /admin)
  • 호스트 기반 라우팅 (Virtual Hosting)
  • 헤더, 쿠키 기반 라우팅 (컨트롤러에 따라 지원)
  • Canary 배포, A/B 테스트 지원

부가 기능 통합

  • Rate Limiting
  • 인증/인가 (OAuth, Basic Auth)
  • WAF(Web Application Firewall) 연동
  • 요청/응답 헤더 조작
  • 리다이렉션 및 리라이팅

단점 ❌

단일 장애점 (Single Point of Failure)

  • Ingress Controller가 다운되면 전체 외부 트래픽 차단
  • → HA 구성(다중 레플리카)으로 완화 가능

운영 복잡성 증가

  • Ingress Controller 자체를 설치/관리/업그레이드해야 함
  • 컨트롤러마다 Annotation 문법이 다름 → 이식성 문제

HTTP/HTTPS에 최적화

  • 기본적으로 L7(HTTP/HTTPS) 트래픽만 처리
  • TCP/UDP 트래픽(예: 데이터베이스 포트)은 별도 처리 필요
    • Nginx: ConfigMap으로 TCP 스트림 설정
    • Traefik: IngressRouteTCP CRD 사용

WebSocket 주의사항

  • WebSocket(/ws)은 일부 컨트롤러에서 별도 Annotation 필요
  • 연결 유지(keep-alive) 타임아웃 설정 필요

비즈니스 로직 부재

  • 인증 토큰 검증, 사용자 컨텍스트 주입, 동적 라우팅 등 애플리케이션 레벨 로직 구현 불가
  • Annotation 기반 선언적 설정만 가능 → 복잡한 조건 처리 한계

디버깅 어려움

  • 트래픽이 외부 → LB → Ingress Controller → Service → Pod 여러 레이어를 거침
  • 문제 발생 시 어느 레이어 문제인지 추적이 복잡

2.3 예시 분석 — rummikub ingress.yaml

아래는 Rummikub 게임 서비스의 Ingress 설정입니다. 파일은 helm/templates/ingress.yaml에 생성되어 있지만, 현재는 NodePort로 운영 중이라 아직 활성화하지 않은 상태입니다.

💡 Ingress를 아직 적용하지 않은 이유

kubectl apply로 이 파일을 배포해도 Traefik이 자동으로 처리하지는 않습니다.
실제로 https://rummikub.localhost가 동작하려면 아래 4단계 준비가 먼저 필요합니다.

Step 1. mkcert로 TLS Secret 생성

1
2
3
4
5
mkcert rummikub.localhost
kubectl create secret tls rummikub-tls \
  --cert=rummikub.localhost.pem \
  --key=rummikub.localhost-key.pem \
  -n rummikub

Step 2. /etc/hosts에 도메인 추가

1
2
# /etc/hosts
127.0.0.1  rummikub.localhost

Step 3. Traefik websecure entrypoint 활성화

1
# helm/traefik/values.yaml 에서 websecure(443) entrypoint 활성화 확인

Step 4. frontend 환경변수 변경

1
2
3
4
5
# NodePort 방식 (현재)
NEXT_PUBLIC_WS_URL=ws://localhost:30080/ws

# Ingress 방식 (전환 후)
NEXT_PUBLIC_WS_URL=wss://rummikub.localhost/ws

지금 당장 적용할 필요는 없습니다. 클라우드 배포나 단일 도메인 통합이 필요한 시점에 위 4단계를 진행하면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# helm/templates/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rummikub-ingress
  namespace: rummikub
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: websecure   # HTTPS 전용
    traefik.ingress.kubernetes.io/router.tls: "true"              # TLS 강제 활성화
spec:
  ingressClassName: traefik                                        # Traefik 컨트롤러 사용
  tls:
    - hosts:
        - rummikub.localhost
      secretName: rummikub-tls                                     # TLS 인증서 Secret
  rules:
    - host: rummikub.localhost
      http:
        paths:
          - path: /                 # 프론트엔드 (React/Next.js 등)
            pathType: Prefix
            backend:
              service:
                name: frontend
                port:
                  number: 3000
          - path: /api              # REST API 서버
            pathType: Prefix
            backend:
              service:
                name: game-server
                port:
                  number: 8080
          - path: /ws               # WebSocket (실시간 게임 통신)
            pathType: Prefix
            backend:
              service:
                name: game-server
                port:
                  number: 8080
          - path: /admin            # 관리자 페이지
            pathType: Prefix
            backend:
              service:
                name: admin
                port:
                  number: 3001

트래픽 흐름 분석

sequenceDiagram
    participant Browser
    participant Traefik as Traefik Ingress Controller
    participant Frontend as frontend:3000
    participant GameServer as game-server:8080
    participant Admin as admin:3001

    Browser->>Traefik: GET https://rummikub.localhost/
    Traefik-->>Frontend: TLS Termination 후 /로 전달
    Frontend-->>Browser: HTML/CSS/JS 응답

    Browser->>Traefik: GET https://rummikub.localhost/api/rooms
    Traefik-->>GameServer: /api/rooms로 전달
    GameServer-->>Browser: JSON 응답

    Browser->>Traefik: WebSocket Upgrade https://rummikub.localhost/ws
    Traefik-->>GameServer: /ws로 WebSocket 터널링
    Note over Traefik,GameServer: 지속 연결 유지 (게임 실시간 통신)

    Browser->>Traefik: GET https://rummikub.localhost/admin
    Traefik-->>Admin: /admin으로 전달
    Admin-->>Browser: 관리자 UI

경로 매칭 우선순위

1
2
3
4
5
6
7
8
요청 URL                매칭되는 규칙        백엔드 서비스
/                   →   path: /          →   frontend:3000
/game               →   path: /          →   frontend:3000  (Prefix 매칭)
/api                →   path: /api       →   game-server:8080
/api/rooms/1        →   path: /api       →   game-server:8080
/ws                 →   path: /ws        →   game-server:8080
/admin              →   path: /admin     →   admin:3001
/admin/users        →   path: /admin     →   admin:3001

⚠️ Prefix 순서 주의: 더 구체적인 경로(/api, /ws, /admin)를 먼저 정의해야 합니다.
Kubernetes는 가장 긴 경로를 우선 매칭합니다.


2.4 TLS / HTTPS 처리 방식

TLS Termination 모드 3가지

graph TD
    A[TLS 처리 방식] --> B["Edge Termination<br/>(일반적인 방식)"]
    A --> C["Passthrough<br/>(End-to-End TLS)"]
    A --> D["Re-encryption<br/>(이중 TLS)"]

    B --> B1["클라이언트 ↔ Ingress: HTTPS<br/>Ingress ↔ Pod: HTTP<br/><br/>✅ 성능 좋음, 인증서 관리 단순<br/>⚠️ 클러스터 내부는 평문"]
    C --> C1["클라이언트 ↔ Pod: HTTPS 직통<br/>Ingress: 내용 보지 않고 전달<br/><br/>✅ End-to-End 암호화<br/>⚠️ Ingress에서 내용 검사 불가"]
    D --> D1["클라이언트 ↔ Ingress: HTTPS<br/>Ingress ↔ Pod: HTTPS<br/><br/>✅ 내부도 암호화<br/>⚠️ 인증서 2중 관리"]

    style B fill:#4CAF50,color:#fff
    style C fill:#2196F3,color:#fff
    style D fill:#FF9800,color:#fff

cert-manager와 자동 TLS 발급

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# cert-manager ClusterIssuer (Let's Encrypt)
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
    solvers:
      - http01:
          ingress:
            class: traefik   # 또는 nginx
---
# Ingress에서 cert-manager 사용
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rummikub-ingress
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod   # 이 Annotation 하나로 자동 발급 및 갱신
spec:
  tls:
    - hosts:
        - rummikub.example.com
      secretName: rummikub-tls   # cert-manager가 자동으로 이 Secret에 인증서 저장
  rules:
    - host: rummikub.example.com
      # ...

Part 3. 플랫폼별 Ingress 구현

같은 Ingress Resource라도 어떤 Ingress Controller를 사용하느냐에 따라 Annotation 문법과 아키텍처가 크게 달라집니다. 현재 예시의 Traefik부터 시작합니다.

3.1 K3s / 로컬 개발 — Traefik

현재 helm/templates/ingress.yaml이 바로 이 방식입니다. K3s는 Traefik을 기본으로 내장합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 표준 Ingress 방식 (현재 예시 파일과 동일)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rummikub-ingress
  namespace: rummikub
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: websecure
    traefik.ingress.kubernetes.io/router.tls: "true"
    # 미들웨어 체인 적용 (별도 Middleware CRD 정의 후 사용)
    # traefik.ingress.kubernetes.io/router.middlewares: rummikub-auth@kubernetescrd
spec:
  ingressClassName: traefik
  # ... (나머지 동일)

Traefik 고유 기능 — IngressRoute CRD (더 강력한 네이티브 방식):

표준 Ingress YAML보다 표현력이 풍부하며, Traefik을 사용한다면 이 방식을 권장합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: rummikub-route
  namespace: rummikub
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`rummikub.localhost`) && PathPrefix(`/api`)
      kind: Rule
      services:
        - name: game-server
          port: 8080
    - match: Host(`rummikub.localhost`) && PathPrefix(`/ws`)
      kind: Rule
      services:
        - name: game-server
          port: 8080
          # WebSocket 자동 지원 (별도 설정 불필요)
    - match: Host(`rummikub.localhost`) && PathPrefix(`/admin`)
      kind: Rule
      services:
        - name: admin
          port: 3001
      middlewares:
        - name: admin-auth    # 인증 미들웨어 체인 적용
    - match: Host(`rummikub.localhost`)
      kind: Rule
      services:
        - name: frontend
          port: 3000
  tls:
    secretName: rummikub-tls

3.2 순수 Kubernetes — Nginx Ingress Controller

가장 널리 사용되는 표준 Ingress Controller입니다.

🚨 Ingress-NGINX 커뮤니티 OSS 지원 종료(EOL) 안내

Kubernetes 커뮤니티는 2025년 11월, kubernetes/ingress-nginx(커뮤니티 오픈소스)의 지원이 2026년 3월부로 공식 종료된다고 발표했습니다.

배경:

  • 수년간 소수 개발자(1~2명)가 야간·주말에 유지보수를 이어온 구조적 한계
  • 2025년 초 발생한 중대 보안 취약점 IngressNightmare(CVE-2025-1974) 대응 과정에서 프로젝트 리소스의 한계 노출

영향 범위:

  • 지원 종료 이후 업데이트, 버그 수정, 보안 패치 완전 중단
  • 기존에 배포된 ingress-nginx는 계속 동작하지만 보안 패치 없는 운영은 중대한 보안 리스크

⚠️ 지원 종료 대상은 커뮤니티 OSS kubernetes/ingress-nginx 이며, F5가 제공하는 상용 NGINX Plus Ingress Controller와는 별개의 제품입니다.

공식 권장 대안:

대안특징
F5 NGINX Plus Ingress Controller국내외 엔터프라이즈 레퍼런스 다수. 정기 보안 패치·SLA 지원. 기존 ingress-nginx에서 가장 매끄러운 전환 경로
F5 NGINX Gateway FabricKubernetes 차세대 표준인 Gateway API 기반 신형 아키텍처. Ingress + 내부 트래픽 제어까지 확장 가능
Traefik오픈소스 무료. K3s 기본 내장. 활발한 커뮤니티
AWS ALB / Azure AGIC클라우드 네이티브 환경이라면 클라우드 관리형으로 전환

참고: Ingress NGINX 은퇴 공지 (kubernetes.io)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rummikub-ingress
  namespace: rummikub
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/use-regex: "true"
    # WebSocket 지원 (Nginx는 명시적으로 설정 필요)
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "Upgrade";
    # Rate Limiting
    nginx.ingress.kubernetes.io/limit-rps: "100"
    # Basic Auth (선택사항)
    # nginx.ingress.kubernetes.io/auth-type: basic
    # nginx.ingress.kubernetes.io/auth-secret: basic-auth
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - rummikub.example.com
      secretName: rummikub-tls
  rules:
    - host: rummikub.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend
                port:
                  number: 3000
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: game-server
                port:
                  number: 8080
          - path: /ws
            pathType: Prefix
            backend:
              service:
                name: game-server
                port:
                  number: 8080
          - path: /admin
            pathType: Prefix
            backend:
              service:
                name: admin
                port:
                  number: 3001

설치 방법:

1
2
3
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx --create-namespace

3.3 OpenShift — Route

OpenShift는 Kubernetes 표준 Ingress 대신 자체 Route 오브젝트를 사용합니다.
(OpenShift 4.x부터는 표준 Ingress도 지원하지만, Route가 더 OpenShift 네이티브합니다.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# OpenShift Route — 프론트엔드
apiVersion: route.openshift.io/v1
kind: Route
metadata:
  name: rummikub-frontend
  namespace: rummikub
spec:
  host: rummikub.apps.cluster.example.com
  path: /
  to:
    kind: Service
    name: frontend
    weight: 100
  port:
    targetPort: 3000
  tls:
    termination: edge           # edge: Ingress에서 TLS 종료
    insecureEdgeTerminationPolicy: Redirect
---
# REST API Route
apiVersion: route.openshift.io/v1
kind: Route
metadata:
  name: rummikub-api
  namespace: rummikub
spec:
  host: rummikub.apps.cluster.example.com
  path: /api
  to:
    kind: Service
    name: game-server
    weight: 100
  port:
    targetPort: 8080
  tls:
    termination: edge
    insecureEdgeTerminationPolicy: Redirect
---
# WebSocket Route (장기 연결 타임아웃 설정 필수)
apiVersion: route.openshift.io/v1
kind: Route
metadata:
  name: rummikub-ws
  namespace: rummikub
  annotations:
    haproxy.router.openshift.io/timeout: 1h
spec:
  host: rummikub.apps.cluster.example.com
  path: /ws
  to:
    kind: Service
    name: game-server
  port:
    targetPort: 8080
  tls:
    termination: edge
---
# Admin Route
apiVersion: route.openshift.io/v1
kind: Route
metadata:
  name: rummikub-admin
  namespace: rummikub
spec:
  host: rummikub.apps.cluster.example.com
  path: /admin
  to:
    kind: Service
    name: admin
  port:
    targetPort: 3001
  tls:
    termination: edge
    insecureEdgeTerminationPolicy: Redirect

OpenShift IngressController 설정:

1
2
3
4
5
6
7
8
9
10
apiVersion: operator.openshift.io/v1
kind: IngressController
metadata:
  name: default
  namespace: openshift-ingress-operator
spec:
  replicas: 2
  domain: apps.cluster.example.com
  endpointPublishingStrategy:
    type: LoadBalancerService

OpenShift vs 표준 Ingress 비교:

항목OpenShift Route표준 Kubernetes Ingress
TLS 종료 방식edge / passthrough / reencryptSecret 기반
자동 호스트명.apps.cluster.domain 자동 부여수동 설정
내장 LBHAProxy 내장별도 설치 필요
이식성OpenShift 전용표준 K8s 호환
경로별 RouteRoute 1개당 경로 1개Ingress 1개에 다중 경로

3.4 AWS EKS — ALB Ingress Controller

AWS에서는 Ingress 리소스가 Application Load Balancer(ALB) 를 자동으로 생성합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rummikub-ingress
  namespace: rummikub
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip

    # TLS: ACM 인증서 ARN 지정 (자동 갱신)
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:ap-northeast-2:123456789:certificate/xxxx
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP":80},{"HTTPS":443}]'
    alb.ingress.kubernetes.io/ssl-redirect: "443"

    # 헬스체크
    alb.ingress.kubernetes.io/healthcheck-path: /healthz
    alb.ingress.kubernetes.io/success-codes: "200"

    # WebSocket — idle timeout 연장 필수
    alb.ingress.kubernetes.io/load-balancer-attributes: |
      idle_timeout.timeout_seconds=3600

    # WAF 연동 (선택사항)
    alb.ingress.kubernetes.io/wafv2-acl-arn: arn:aws:wafv2:ap-northeast-2:123456789:regional/webacl/...

    # 리소스 태그
    alb.ingress.kubernetes.io/tags: Environment=prod,Team=gaming
spec:
  ingressClassName: alb
  rules:
    - host: rummikub.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend
                port:
                  number: 3000
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: game-server
                port:
                  number: 8080
          - path: /ws
            pathType: Prefix
            backend:
              service:
                name: game-server
                port:
                  number: 8080
          - path: /admin
            pathType: Prefix
            backend:
              service:
                name: admin
                port:
                  number: 3001

AWS 아키텍처:

graph LR
    Internet --> R53[Route53\nDNS]
    R53 --> ALB[Application Load Balancer\nAWS 관리형]

    ALB --> |"Target Group 1"| Frontend[frontend Pods\nEKS Node]
    ALB --> |"Target Group 2"| GameServer[game-server Pods\nEKS Node]
    ALB --> |"Target Group 3"| Admin[admin Pods\nEKS Node]

    ALB -.- ACM[ACM\n인증서 자동 관리]
    ALB -.- WAF[AWS WAF\n보안 필터링]

    style ALB fill:#FF9900,color:#fff
    style ACM fill:#FF9900,color:#fff
    style WAF fill:#FF9900,color:#fff

EKS 설치:

1
2
3
4
5
6
helm repo add eks https://aws.github.io/eks-charts
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
  -n kube-system \
  --set clusterName=my-cluster \
  --set serviceAccount.create=false \
  --set serviceAccount.name=aws-load-balancer-controller

3.5 Microsoft Azure AKS — AGIC

Azure에서는 Application Gateway 를 Ingress Controller로 사용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rummikub-ingress
  namespace: rummikub
  annotations:
    kubernetes.io/ingress.class: azure/application-gateway

    # SSL (Key Vault 인증서 참조)
    appgw.ingress.kubernetes.io/ssl-redirect: "true"
    appgw.ingress.kubernetes.io/appgw-ssl-certificate: "rummikub-cert"

    # 백엔드 설정
    appgw.ingress.kubernetes.io/backend-protocol: "http"
    appgw.ingress.kubernetes.io/connection-draining: "true"
    appgw.ingress.kubernetes.io/connection-draining-timeout: "30"

    # WAF 정책 연동
    appgw.ingress.kubernetes.io/waf-policy-for-path: /subscriptions/.../applicationGatewayWebApplicationFirewallPolicies/myWafPolicy

    # 헬스체크
    appgw.ingress.kubernetes.io/health-probe-path: /healthz
    appgw.ingress.kubernetes.io/health-probe-status-codes: "200-399"

    # WebSocket 타임아웃
    appgw.ingress.kubernetes.io/request-timeout: "3600"
spec:
  ingressClassName: azure/application-gateway
  tls:
    - hosts:
        - rummikub.example.com
      secretName: rummikub-tls
  rules:
    - host: rummikub.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend
                port:
                  number: 3000
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: game-server
                port:
                  number: 8080
          - path: /ws
            pathType: Prefix
            backend:
              service:
                name: game-server
                port:
                  number: 8080
          - path: /admin
            pathType: Prefix
            backend:
              service:
                name: admin
                port:
                  number: 3001

Azure 아키텍처:

graph LR
    Internet --> DNS[Azure DNS]
    DNS --> AppGW[Azure Application Gateway\n관리형 L7 LB + WAF]
    AppGW --> AKS[AKS Cluster]
    AKS --> AGIC[AGIC Pod\n설정 동기화]
    AGIC --> |ARM API| AppGW

    AppGW --> Frontend[frontend Pods]
    AppGW --> GameServer[game-server Pods]
    AppGW --> Admin[admin Pods]

    AppGW -.- KV[Azure Key Vault\n인증서 관리]
    AppGW -.- WAF[Azure WAF Policy]

    style AppGW fill:#0078D4,color:#fff
    style KV fill:#0078D4,color:#fff
    style WAF fill:#0078D4,color:#fff

3.6 Ingress Controller 비교표

항목Nginx (OSS)TraefikAWS ALBAzure AGICOpenShift HAProxy
설치 복잡도보통낮음낮음 (EKS)낮음 (AKS)없음 (내장)
WebSocket 지원Annotation 필요자동자동Annotation 필요자동
TLS 관리cert-manager 연동ACME 내장ACM 자동Key Vault 연동자동
WAF 지원별도 (ModSecurity)없음 (Enterprise 유료)AWS WAF 연동Azure WAF 내장없음
L4 TCP/UDPConfigMapIngressRouteTCPNLB 별도별도별도
비용무료무료 (Enterprise 유료)ALB 비용AppGW 비용포함
이식성높음높음AWS 전용Azure 전용OpenShift 전용
성능높음높음관리형관리형높음
Canary 배포Annotation 지원Middleware 지원제한적제한적없음
대시보드없음내장 UIAWS ConsoleAzure PortalOpenShift Console
지원 상태⚠️ EOL 2026.03✅ 활발한 개발✅ AWS 관리형✅ Azure 관리형✅ Red Hat 지원

⚠️ Nginx OSS 컬럼 주의: 위 표의 “Nginx”는 커뮤니티 OSS kubernetes/ingress-nginx 기준입니다. 2026년 3월부로 공식 EOL이며, 신규 프로젝트라면 Traefik, 엔터프라이즈라면 F5 NGINX Plus Ingress Controller 또는 NGINX Gateway Fabric을 권장합니다.


Part 4. Spring Cloud Gateway

4.1 왜 필요한가 — Ingress의 한계

K8s Ingress는 인프라 레벨 라우팅에 탁월하지만, 애플리케이션 레벨의 복잡한 로직을 처리하는 데는 구조적 한계가 있습니다.

graph TD
    A[Ingress의 한계] --> B[인증/인가 로직]
    A --> C[동적 라우팅]
    A --> D[응답 변환]
    A --> E[서킷브레이커]
    A --> F[요청 집계 BFF]

    B --> B1["JWT 검증, OAuth2 토큰 파싱\n사용자 역할 기반 라우팅\n→ Ingress Annotation으로 구현 불가"]
    C --> C1["DB 쿼리 결과 기반 라우팅\nA/B 테스트 가중치 동적 변경\n→ 선언적 YAML로 표현 불가"]
    D --> D1["응답 필드 필터링/변환\n다국어 응답 처리\n→ 코드로만 구현 가능"]
    E --> E1["Resilience4j 연동\n폴백 응답 커스터마이징\n→ Ingress 지원 안 됨"]
    F --> F1["여러 서비스 응답 집계\nBFF 패턴\n→ 별도 애플리케이션 필요"]

Spring Cloud Gateway는 Spring 생태계 기반의 애플리케이션 레벨 API Gateway로, 이러한 복잡한 요구사항을 Java/Kotlin 코드로 직접 구현할 수 있습니다.

graph LR
    Client --> Ingress[K8s Ingress\n인프라 레벨 진입점]
    Ingress --> SCG[Spring Cloud Gateway\n애플리케이션 레벨 게이트웨이]
    SCG --> |"JWT 검증 후"| Service1[game-server]
    SCG --> |"역할 확인 후"| Service2[admin-service]
    SCG --> |"토큰 주입 후"| Service3[user-service]

    style Ingress fill:#2196F3,color:#fff
    style SCG fill:#4CAF50,color:#fff

4.2 핵심 개념 및 아키텍처

Spring Cloud Gateway는 Spring WebFlux 기반의 논블로킹(Non-Blocking) 리액티브 게이트웨이입니다.

3대 핵심 요소

graph TD
    SCG[Spring Cloud Gateway] --> Route[Route 라우트]
    SCG --> Predicate[Predicate 조건자]
    SCG --> Filter[Filter 필터]

    Route --> R1["요청을 어떤 서비스로 보낼지\n목적지 URI + 조건 + 필터 묶음"]
    Predicate --> P1["요청이 Route에 매칭되는 조건\nPath, Header, Method, Query,\nHost, Weight, RemoteAddr 등"]
    Filter --> F1["요청/응답을 변형하는 로직\nGatewayFilter: 특정 Route 적용\nGlobalFilter: 모든 Route 적용"]

요청 처리 파이프라인

sequenceDiagram
    participant Client
    participant SCG as Spring Cloud Gateway
    participant GF as GlobalFilter Chain
    participant RF as Route Filter Chain
    participant DS as Downstream Service

    Client->>SCG: HTTP 요청
    SCG->>SCG: Route 매칭 (Predicate 평가)
    SCG->>GF: GlobalFilter 실행 (인증, 로깅 등)
    GF->>RF: Route별 GatewayFilter 실행 (헤더 추가 등)
    RF->>DS: 변형된 요청 전달
    DS-->>RF: 응답
    RF-->>GF: 응답 필터 실행
    GF-->>Client: 최종 응답

4.3 장점과 단점

장점 ✅

코드 기반의 세밀한 제어

  • Java/Kotlin으로 복잡한 비즈니스 로직 구현 가능
  • 조건부 라우팅, 동적 필터 추가, 런타임 설정 변경

Spring 생태계 완벽 통합

  • Spring Security로 OAuth2/JWT 인증/인가
  • Spring Cloud LoadBalancer로 서비스 디스커버리
  • Resilience4j로 서킷브레이커/Retry
  • Micrometer + Prometheus로 메트릭
  • Spring Boot Actuator로 헬스체크/관리

리액티브 아키텍처

  • WebFlux 기반 논블로킹 처리
  • 적은 스레드로 높은 동시성 처리
  • 백프레셔(Backpressure) 지원

플랫폼 독립성

  • K8s Ingress Controller 종류에 무관하게 동일한 동작
  • 온프레미스/클라우드 모두 동일한 로직

동적 라우팅

  • DB, Config Server, 환경 변수 기반 런타임 라우팅 변경
  • DiscoveryClient 연동으로 서비스 자동 발견

단점 ❌

운영 부담 증가

  • 추가적인 마이크로서비스(SCG Pod) 관리 필요
  • JVM 기반으로 메모리 사용량이 Nginx 대비 높음 (일반적으로 256MB~512MB+)
  • 애플리케이션 배포/업그레이드 사이클 존재

K8s 기능과 중복

  • Service Discovery, Load Balancing은 K8s가 이미 제공
  • 단순 라우팅만 필요하면 오버엔지니어링

Spring 의존성

  • Spring 생태계 지식 필요
  • 비 Spring 프로젝트에서는 이질적

학습 곡선

  • 리액티브 프로그래밍(WebFlux) 패러다임 이해 필요
  • 필터 체인, Predicate 조합의 복잡성

4.4 예시 — Rummikub 라우팅 구현

Rummikub 게임 서비스와 동일한 라우팅을 Spring Cloud Gateway로 구현하는 예시입니다.

application.yml — 선언적 설정 방식

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# src/main/resources/application.yml
spring:
  application:
    name: rummikub-gateway
  cloud:
    gateway:
      # 글로벌 CORS 설정
      globalcors:
        cors-configurations:
          '[/**]':
            allowed-origins:
              - "https://rummikub.localhost"
            allowed-methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
            allowed-headers: ["*"]
            allow-credentials: true

      # 기본 필터 (모든 라우트에 적용)
      default-filters:
        - DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials
        - name: RequestRateLimiter
          args:
            redis-rate-limiter.replenishRate: 100   # 초당 100 요청
            redis-rate-limiter.burstCapacity: 200
            key-resolver: "#{@userKeyResolver}"

      routes:
        # WebSocket 라우트 — 실시간 게임 통신 (필터 최소화)
        - id: game-websocket
          uri: ws://game-server:8080    # ws:// 스킴 사용
          predicates:
            - Path=/ws/**
          filters:
            - AddRequestHeader=X-Gateway-Source, rummikub-gateway

        # REST API 라우트 — game-server
        - id: game-api
          uri: http://game-server:8080
          predicates:
            - Path=/api/**
            - Method=GET,POST,PUT,DELETE,PATCH
          filters:
            - StripPrefix=0           # /api 경로 유지
            - name: CircuitBreaker
              args:
                name: gameServerCB
                fallbackUri: forward:/fallback/game
            - AddRequestHeader=X-Gateway-Source, rummikub-gateway
            - name: Retry
              args:
                retries: 3
                statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE
                methods: GET
                backoff:
                  firstBackoff: 100ms
                  maxBackoff: 1000ms

        # 관리자 라우트 — 역할 기반 접근 제어
        - id: admin-panel
          uri: http://admin:3001
          predicates:
            - Path=/admin/**
          filters:
            - name: RequestHeaderToRequestUri
            # Spring Security에서 ROLE_ADMIN 검증 (별도 SecurityConfig에서 설정)

        # 프론트엔드 라우트 (나머지 모든 요청)
        - id: frontend
          uri: http://frontend:3000
          predicates:
            - Path=/**
          filters:
            - name: CircuitBreaker
              args:
                name: frontendCB
                fallbackUri: forward:/fallback/frontend

server:
  port: 8090

management:
  endpoints:
    web:
      exposure:
        include: health,info,gateway,metrics,prometheus
  metrics:
    export:
      prometheus:
        enabled: true

Java 코드 기반 설정 방식 (RouteLocatorBuilder)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class GatewayConfig {

    @Bean
    public RouteLocator rummikubRoutes(RouteLocatorBuilder builder,
                                       AuthenticationFilter authFilter,
                                       LoggingFilter loggingFilter) {
        return builder.routes()
            // WebSocket 라우트
            .route("game-websocket", r -> r
                .path("/ws/**")
                .filters(f -> f
                    .addRequestHeader("X-Gateway-Source", "rummikub-gateway")
                )
                .uri("ws://game-server:8080")         // WebSocket URI
            )

            // REST API 라우트
            .route("game-api", r -> r
                .path("/api/**")
                .and()
                .method(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT,
                        HttpMethod.DELETE, HttpMethod.PATCH)
                .filters(f -> f
                    .filter(loggingFilter)
                    .filter(authFilter)               // JWT 검증
                    .addRequestHeader("X-Gateway-Source", "rummikub-gateway")
                    .addRequestHeader("X-User-Id", "#{T(ThreadLocal).get()}")
                    .circuitBreaker(c -> c
                        .setName("gameServerCB")
                        .setFallbackUri("forward:/fallback/game"))
                    .retry(r2 -> r2
                        .setRetries(3)
                        .setStatuses(HttpStatus.BAD_GATEWAY,
                                     HttpStatus.SERVICE_UNAVAILABLE))
                )
                .uri("http://game-server:8080")
            )

            // 관리자 라우트 (ROLE_ADMIN 검증은 SecurityConfig에서)
            .route("admin-panel", r -> r
                .path("/admin/**")
                .filters(f -> f
                    .filter(authFilter)
                    .addRequestHeader("X-Admin-Request", "true")
                )
                .uri("http://admin:3001")
            )

            // 프론트엔드 (catch-all)
            .route("frontend", r -> r
                .path("/**")
                .filters(f -> f
                    .circuitBreaker(c -> c
                        .setName("frontendCB")
                        .setFallbackUri("forward:/fallback/frontend"))
                )
                .uri("http://frontend:3000")
            )
            .build();
    }
}

4.5 주요 기능 상세

JWT 인증 필터

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class AuthenticationFilter implements GatewayFilter, Ordered {

    private final JwtTokenValidator jwtValidator;
    private static final List<String> PUBLIC_PATHS =
        List.of("/api/auth/login", "/api/auth/register", "/ws");

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getURI().getPath();

        // 공개 경로는 인증 스킵
        if (PUBLIC_PATHS.stream().anyMatch(path::startsWith)) {
            return chain.filter(exchange);
        }

        String authHeader = exchange.getRequest()
            .getHeaders().getFirst(HttpHeaders.AUTHORIZATION);

        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }

        String token = authHeader.substring(7);
        return jwtValidator.validate(token)
            .flatMap(claims -> {
                // 검증된 사용자 정보를 헤더에 주입 → 다운스트림 서비스에서 활용
                ServerHttpRequest mutatedRequest = exchange.getRequest()
                    .mutate()
                    .header("X-User-Id", claims.getSubject())
                    .header("X-User-Roles", String.join(",", claims.getRoles()))
                    .build();
                return chain.filter(exchange.mutate()
                    .request(mutatedRequest).build());
            })
            .onErrorResume(e -> {
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            });
    }

    @Override
    public int getOrder() { return -100; } // 가장 먼저 실행
}

서킷브레이커 + 폴백

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class FallbackController {

    @GetMapping("/game")
    public Mono<Map<String, Object>> gameFallback(ServerWebExchange exchange) {
        return Mono.just(Map.of(
            "status", "degraded",
            "message", "게임 서버가 일시적으로 불안정합니다. 잠시 후 다시 시도해주세요.",
            "timestamp", Instant.now().toString()
        ));
    }

    @GetMapping("/frontend")
    public Mono<Void> frontendFallback(ServerWebExchange exchange) {
        exchange.getResponse().setStatusCode(HttpStatus.SERVICE_UNAVAILABLE);
        return exchange.getResponse().writeWith(
            Mono.just(exchange.getResponse().bufferFactory()
                .wrap("서비스 점검 중입니다.".getBytes()))
        );
    }
}

Rate Limiting (Redis 기반)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class RateLimiterConfig {

    // 사용자 ID 기반 Rate Limiting
    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> {
            String userId = exchange.getRequest()
                .getHeaders().getFirst("X-User-Id");
            return Mono.just(userId != null ? userId : "anonymous");
        };
    }

    // IP 기반 Rate Limiting (인증 전 단계)
    @Bean
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(
            Objects.requireNonNull(
                exchange.getRequest().getRemoteAddress()
            ).getAddress().getHostAddress()
        );
    }
}

동적 라우팅 (런타임 변경)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class GatewayAdminController {

    private final RouteDefinitionWriter routeDefinitionWriter;
    private final ApplicationEventPublisher publisher;

    // 런타임에 새 라우트 추가 (재배포 불필요)
    @PostMapping("/routes")
    public Mono<ResponseEntity<String>> addRoute(
            @RequestBody RouteDefinition routeDefinition) {
        return routeDefinitionWriter
            .save(Mono.just(routeDefinition))
            .then(Mono.fromRunnable(() ->
                publisher.publishEvent(new RefreshRoutesEvent(this))))
            .thenReturn(ResponseEntity.ok("Route added: " + routeDefinition.getId()));
    }
}

4.6 Kubernetes 환경에서의 배포

Spring Cloud Gateway 자체가 K8s 위에서 동작하는 Pod입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# helm/templates/gateway-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-gateway
  namespace: rummikub
spec:
  replicas: 2                  # HA 구성
  selector:
    matchLabels:
      app: api-gateway
  template:
    metadata:
      labels:
        app: api-gateway
    spec:
      containers:
        - name: api-gateway
          image: rummikub/api-gateway:latest
          ports:
            - containerPort: 8090
          env:
            - name: SPRING_PROFILES_ACTIVE
              value: "kubernetes"
            - name: SPRING_REDIS_HOST
              value: "redis-service"    # Rate Limiter용 Redis
          resources:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "512Mi"
              cpu: "500m"
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8090
            initialDelaySeconds: 30
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8090
---
apiVersion: v1
kind: Service
metadata:
  name: api-gateway
  namespace: rummikub
spec:
  selector:
    app: api-gateway
  ports:
    - port: 8090
      targetPort: 8090

Ingress → SCG 연계 구조:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# Ingress가 SCG 하나만 바라보도록 단순화
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rummikub-ingress
  namespace: rummikub
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: websecure
    traefik.ingress.kubernetes.io/router.tls: "true"
spec:
  ingressClassName: traefik
  tls:
    - hosts:
        - rummikub.localhost
      secretName: rummikub-tls
  rules:
    - host: rummikub.localhost
      http:
        paths:
          - path: /              # 모든 트래픽을 SCG로 집중
            pathType: Prefix
            backend:
              service:
                name: api-gateway   # SCG Service
                port:
                  number: 8090
          # 정적 파일은 직접 frontend로 (선택사항 — 성능 최적화)
          # - path: /static
          #   backend:
          #     service:
          #       name: frontend
          #       port:
          #         number: 3000

Part 5. 비교와 선택

5.1 레이어 비교

두 기술은 같은 L7 레이어에서 동작하지만, 누가 관리하고 어떤 방식으로 구성하는지가 근본적으로 다릅니다.

graph TD
    Internet --> LB[Cloud Load Balancer]
    LB --> Ingress["K8s Ingress Controller<br/>L7 인프라 레이어<br/>(Nginx / Traefik / ALB)"]
    Ingress --> SCG["Spring Cloud Gateway<br/>L7 애플리케이션 레이어<br/>(Spring WebFlux)"]
    SCG --> SVC1[game-server]
    SCG --> SVC2[admin]
    SCG --> SVC3[frontend]

    subgraph infra ["인프라 영역 (Ops 팀 관리)"]
        LB
        Ingress
    end
    subgraph app ["애플리케이션 영역 (Dev 팀 관리)"]
        SCG
        SVC1
        SVC2
        SVC3
    end

    style Ingress fill:#2196F3,color:#fff
    style SCG fill:#4CAF50,color:#fff

5.2 기능 상세 비교표

기능K8s IngressSpring Cloud Gateway
동작 레이어L7 인프라 (네트워크)L7 애플리케이션 (코드)
구성 방식YAML / Annotation (선언적)YAML + Java/Kotlin 코드
경로 기반 라우팅✅ 기본 지원✅ 기본 지원
호스트 기반 라우팅✅ 기본 지원✅ 기본 지원
헤더/쿼리 기반 라우팅⚠️ 일부 컨트롤러만✅ 완전 지원
JWT 인증/인가⚠️ 외부 플러그인 필요✅ Spring Security 통합
비즈니스 로직 구현❌ 불가✅ 완전 지원
서킷브레이커❌ 미지원✅ Resilience4j 통합
Rate Limiting⚠️ 컨트롤러 의존 (단순)✅ Redis 기반 정밀 제어
요청/응답 변환⚠️ 헤더 조작만 가능✅ Body 포함 완전 변환
BFF 패턴 (응답 집계)❌ 불가✅ 구현 가능
WebSocket⚠️ 설정 필요✅ 기본 지원
SSE (Server-Sent Events)✅ 지원✅ 지원
Canary 배포⚠️ 컨트롤러별 제한✅ Weight Predicate
동적 라우팅❌ 재배포 필요✅ 런타임 변경 가능
서비스 디스커버리✅ K8s Service 기반✅ DiscoveryClient 연동
TLS 종료✅ 클러스터 경계에서⚠️ 내부 처리 (보통 Ingress에 위임)
리소스 사용량🟢 매우 낮음 (Nginx: ~50MB)🟡 보통~높음 (JVM: 256MB+)
처리 성능🟢 매우 높음🟡 높음 (논블로킹이나 JVM 오버헤드)
콜드 스타트🟢 없음🔴 JVM 기동 시간 (GraalVM으로 개선 가능)
플랫폼 이식성⚠️ 컨트롤러에 따라 다름✅ JVM 어디서나 동일
관찰가능성⚠️ 컨트롤러 기능에 의존✅ Micrometer/Sleuth/Zipkin 통합
Spring 의존성❌ 없음✅ Spring 생태계 필요
학습 곡선🟢 낮음 (YAML)🟡 중간 (리액티브 이해 필요)

5.3 SCG만 있으면 Ingress 없어도 되나?

결론: 기술적으로는 가능합니다. 하지만 상황에 따라 트레이드오프가 있습니다.

K8s Ingress의 본질적 역할은 “외부 트래픽을 클러스터 내부로 끌어들이는 진입점” 입니다.
이 역할은 Ingress Controller 없이도 LoadBalancer 타입의 Service로 대체할 수 있습니다.

graph TD
    subgraph A["방식 1: Ingress + SCG (권장)"]
        direction LR
        I1[Internet] --> LB1[Cloud LB]
        LB1 --> IC["Ingress Controller<br/>TLS 종료 · 도메인 라우팅"]
        IC --> SCG1["Spring Cloud Gateway<br/>인증 · 서킷브레이커"]
        SCG1 --> SVC1[backend Services]
    end

    subgraph B["방식 2: SCG 단독 (LoadBalancer Service)"]
        direction LR
        I2[Internet] --> LB2[Cloud LB\nLoadBalancer Service]
        LB2 --> SCG2["Spring Cloud Gateway<br/>TLS + 인증 + 라우팅 모두 처리"]
        SCG2 --> SVC2[backend Services]
    end

    subgraph C["방식 3: SCG 단독 (NodePort, 로컬 개발)"]
        direction LR
        I3[localhost:8090] --> NP[NodePort]
        NP --> SCG3[Spring Cloud Gateway]
        SCG3 --> SVC3[backend Services]
    end

    style IC fill:#2196F3,color:#fff
    style SCG1 fill:#4CAF50,color:#fff
    style SCG2 fill:#FF9800,color:#fff
    style SCG3 fill:#9E9E9E,color:#fff
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 방식 2: Ingress 없이 SCG를 직접 외부에 노출
apiVersion: v1
kind: Service
metadata:
  name: api-gateway
  namespace: rummikub
spec:
  type: LoadBalancer        # 클라우드 LB 자동 생성 (Ingress Controller 불필요)
  selector:
    app: api-gateway
  ports:
    - name: https
      port: 443
      targetPort: 8443      # SCG 내부에서 TLS 처리
    - name: http
      port: 80
      targetPort: 8090
1
2
3
4
5
6
7
8
9
# SCG application.yml — TLS를 SCG 자체에서 처리 (Ingress 없는 경우)
server:
  port: 8443
  ssl:
    enabled: true
    key-store: classpath:keystore.p12
    key-store-password: ${SSL_KEYSTORE_PASSWORD}
    key-store-type: PKCS12
    key-alias: rummikub

방식별 장단점 비교

항목Ingress + SCGSCG 단독 (LoadBalancer)SCG 단독 (NodePort)
구조 복잡도높음 (2레이어)낮음 (1레이어)매우 낮음
TLS 관리Ingress에서 중앙 관리SCG 자체 처리 (keystore)선택적
cert-manager 연동✅ 자연스러운 연동❌ 별도 구성 필요
클라우드 WAF 연동✅ ALB/AGIC WAF 활용 가능❌ 불가
클라우드 ACM/Key Vault✅ 자동 인증서 관리❌ 수동 keystore 관리
여러 도메인 멀티 테넌시✅ Ingress 다중 host 지원⚠️ SCG에서 직접 처리
비용Cloud LB 1개Cloud LB 1개추가 비용 없음
로컬 개발 적합성⚠️ Ingress Controller 설치 필요⚠️✅ 가장 단순
운영 포트 관리Ingress가 80/443 표준 포트SCG가 80/443 직접 수신커스텀 포트

SCG 단독으로 충분한 상황

1
2
3
4
5
✅ 도메인이 단일 서비스 하나뿐 (멀티 테넌시 불필요)
✅ 클라우드 관리형 WAF, ACM 인증서 자동 갱신이 불필요
✅ TLS 인증서를 SCG keystore로 직접 관리할 수 있음
✅ Spring 팀이 인프라까지 통합 관리 (Ops/Dev 분리 불필요)
✅ 온프레미스 또는 클라우드 관리형 기능을 쓰지 않는 환경

Ingress가 반드시 필요한 상황

1
2
3
4
5
6
🔴 AWS ACM / Azure Key Vault 인증서 자동 관리가 필요한 경우
🔴 클라우드 관리형 WAF를 Ingress 레벨에서 적용해야 하는 경우
🔴 cert-manager로 Let's Encrypt 자동 갱신을 사용하는 경우
🔴 여러 네임스페이스/팀이 서로 다른 도메인을 하나의 LB IP로 공유하는 경우
🔴 Ops 팀과 Dev 팀의 관리 영역을 명확히 분리해야 하는 조직 구조
🔴 Kubernetes Gateway API로의 마이그레이션을 계획 중인 경우

한 줄 요약: SCG는 Ingress의 기능을 코드 레벨에서 모두 구현할 수 있지만, 클라우드 인프라 연동·인증서 자동 관리·Ops/Dev 역할 분리 측면에서는 Ingress와 함께 쓰는 것이 여전히 더 실용적입니다.


5.4 의사결정 가이드

graph TD
    Q1{"복잡한 인증/인가 로직이<br/>필요한가?"}

    Q1 -->|Yes| SCG[Spring Cloud Gateway 고려]
    Q1 -->|No| Q2{"서킷브레이커, Retry,<br/>폴백이 필요한가?"}

    Q2 -->|Yes| SCG
    Q2 -->|No| Q3{"요청/응답 Body를<br/>변환해야 하는가?"}

    Q3 -->|Yes| SCG
    Q3 -->|No| Q4{"Spring 생태계<br/>기반 프로젝트인가?"}

    Q4 -->|Yes| Q5{"팀이 리액티브<br/>프로그래밍에 익숙한가?"}
    Q4 -->|No| Ingress[K8s Ingress만 사용]

    Q5 -->|Yes| Both[Ingress + SCG 함께 사용]
    Q5 -->|No| Q6{"리소스 제약이<br/>있는가?"}

    Q6 -->|Yes| Ingress
    Q6 -->|No| Both

    style SCG fill:#4CAF50,color:#fff
    style Ingress fill:#2196F3,color:#fff
    style Both fill:#9C27B0,color:#fff

5.5 Rummikub 서비스에서의 선택 기준

요구사항K8s IngressSpring Cloud Gateway
/ → frontend 라우팅✅ 충분✅ 가능
/api → game-server✅ 충분✅ 가능
/ws WebSocket✅ (설정 필요)✅ 기본 지원
/admin 관리자 접근⚠️ IP 제한만✅ 역할 기반 세밀 제어
게임 세션 토큰 검증
게임방 상태 기반 라우팅
동시 접속 Rate Limiting⚠️ 단순 RPS만✅ 사용자별 세밀 제어
서버 점검 폴백

환경별 권장 구성:

1
2
3
로컬 개발:    NodePort (포트 직접 접근) 또는 K3s Traefik Ingress
스테이징:     Traefik Ingress + SCG (현재 ingress.yaml 구조)
클라우드 운영: ALB/AGIC Ingress + SCG (TLS·WAF는 클라우드에, 인증·로직은 SCG에)

결론: Rummikub처럼 실시간 게임 서비스는 Ingress + SCG 병행 사용이 적합합니다.
TLS 종료·외부 노출은 Ingress가, 인증·게임 로직 라우팅은 SCG가 담당합니다.


Part 6. 함께 사용하는 패턴 — Ingress + SCG

실제 프로덕션 환경에서는 두 기술을 역할에 따라 계층적으로 결합하는 것이 엔터프라이즈 표준입니다.

6.1 권장 아키텍처와 역할 분담

graph LR
    Internet --> DNS["DNS<br/>Route53 / Azure DNS"]
    DNS --> LB["Cloud LB<br/>외부 IP 단일화"]
    LB --> Ingress["K8s Ingress<br/>① TLS 종료<br/>② 도메인 라우팅<br/>③ 기본 보안"]

    Ingress --> SCG["Spring Cloud Gateway<br/>④ JWT 인증/인가<br/>⑤ Rate Limiting<br/>⑥ 서킷브레이커<br/>⑦ 헤더 변환"]

    SCG --> Frontend["frontend:3000<br/>정적 컨텐츠"]
    SCG --> GameServer["game-server:8080<br/>REST API + WS"]
    SCG --> Admin["admin:3001<br/>관리자 UI"]

    style Ingress fill:#2196F3,color:#fff
    style SCG fill:#4CAF50,color:#fff
책임담당이유
TLS/HTTPS 종료K8s Ingress인프라 레벨에서 인증서 중앙 관리 효율
도메인/경로 기반 외부 라우팅K8s Ingress선언적 YAML, 운영팀 독립적 관리
DDoS 기본 방어, WAFK8s Ingress (클라우드 ALB/AGIC)클라우드 관리형 서비스 활용
JWT 토큰 검증Spring Cloud Gateway비즈니스 로직, 개발팀이 코드로 관리
사용자 컨텍스트 주입Spring Cloud Gateway다운스트림에 사용자 정보 전달
서킷브레이커/RetrySpring Cloud Gateway서비스 복원력 패턴
세밀한 Rate LimitingSpring Cloud Gateway사용자/역할별 제어
BFF 패턴 응답 집계Spring Cloud Gateway코드로만 구현 가능

6.2 완전한 예시 YAML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# K8s Ingress: TLS 종료 + 모든 트래픽을 SCG로 전달
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rummikub-ingress
  namespace: rummikub
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: websecure
    traefik.ingress.kubernetes.io/router.tls: "true"
spec:
  ingressClassName: traefik
  tls:
    - hosts:
        - rummikub.localhost
      secretName: rummikub-tls
  rules:
    - host: rummikub.localhost
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api-gateway    # Spring Cloud Gateway
                port:
                  number: 8090
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# Spring Cloud Gateway: application.yml — 내부 라우팅 및 비즈니스 로직
spring:
  cloud:
    gateway:
      routes:
        # WebSocket — SCG에서 직접 game-server로 (필터 최소화)
        - id: game-websocket
          uri: ws://game-server:8080
          predicates:
            - Path=/ws/**

        # API — JWT 인증 후 game-server로
        - id: game-api
          uri: http://game-server:8080
          predicates:
            - Path=/api/**
          filters:
            - name: AuthenticationFilter   # JWT 검증
            - name: CircuitBreaker
              args:
                name: gameServerCB
                fallbackUri: forward:/fallback/game

        # 관리자 — ROLE_ADMIN 확인 후 admin으로
        - id: admin
          uri: http://admin:3001
          predicates:
            - Path=/admin/**
          filters:
            - name: AdminAuthFilter        # ROLE_ADMIN 검증

        # 프론트엔드 — 인증 없이
        - id: frontend
          uri: http://frontend:3000
          predicates:
            - Path=/**

6.3 전체 트래픽 흐름

sequenceDiagram
    participant Browser
    participant Ingress as K8s Ingress (Traefik)
    participant SCG as Spring Cloud Gateway
    participant GameServer as game-server:8080

    Browser->>Ingress: HTTPS POST /api/rooms\nAuthorization: Bearer eyJ...
    Note over Ingress: TLS 복호화\nHTTP로 SCG에 전달
    Ingress->>SCG: HTTP POST /api/rooms
    Note over SCG: 1. JWT 검증\n2. X-User-Id 헤더 주입\n3. Rate Limit 확인\n4. Circuit Breaker 확인
    SCG->>GameServer: HTTP POST /api/rooms\nX-User-Id: user-123\nX-User-Roles: PLAYER
    GameServer-->>SCG: 200 OK {roomId: "abc"}
    SCG-->>Ingress: 200 OK
    Ingress-->>Browser: HTTPS 200 OK

Part 7. 대안과 미래 방향

7.1 Gateway API — 차세대 K8s 표준

Kubernetes는 Ingress의 한계를 극복하기 위해 Gateway API를 개발했습니다 (GA: v1.0, 2023년).
F5 NGINX Gateway Fabric도 이 Gateway API 기반으로 구축됩니다.

graph LR
    A[Kubernetes 네트워킹 진화] --> B["Ingress<br/>현재 표준<br/>(K8s v1.0, 2015~)"]
    A --> C["Gateway API<br/>차세대 표준<br/>(GA: 2023~)"]

    B --> B1["단순함<br/>광범위한 지원<br/>L7 HTTP 중심<br/>Annotation 의존"]
    C --> C1["표현력 풍부<br/>L4/L7 모두 지원<br/>Role 기반 분리<br/>CRD 기반<br/>SCG와 더 잘 어울림"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Gateway API 예시 (Ingress를 대체하는 미래 방향)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: rummikub-route
spec:
  parentRefs:
    - name: rummikub-gateway
  hostnames:
    - rummikub.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /api
      backendRefs:
        - name: api-gateway    # SCG로 전달
          port: 8090
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: frontend
          port: 3000

참고: Spring Cloud Gateway 4.x부터 Gateway API(HTTPRoute)를 직접 지원합니다.


7.2 Service Mesh — Istio와의 비교

항목IngressSpring Cloud GatewayService Mesh (Istio)
역할외부 진입점앱 레벨 게이트웨이서비스 간 통신 제어
트래픽 방향North-SouthNorth-SouthEast-West
주요 기능라우팅, TLS 종료인증, 서킷브레이커mTLS, 트레이싱
복잡도낮음중간높음 (사이드카)
함께 사용✅ (Ingress → SCG → Mesh)

7.3 진화 로드맵

graph LR
    Past["과거<br/>Ingress Only<br/>단순 라우팅"] --> Present["현재<br/>Ingress + SCG<br/>계층적 게이트웨이"]
    Present --> Future["미래<br/>Gateway API + SCG<br/>표준화된 계층 구조"]

    style Past fill:#9E9E9E,color:#fff
    style Present fill:#2196F3,color:#fff
    style Future fill:#4CAF50,color:#fff

Part 8. Best Practices

8.1 Ingress 보안

1
2
3
4
5
6
7
8
9
10
11
# 관리자 경로 IP 화이트리스트 (Nginx)
metadata:
  annotations:
    nginx.ingress.kubernetes.io/whitelist-source-range: "192.168.1.0/24,10.0.0.0/8"

# HSTS 및 보안 헤더 추가
nginx.ingress.kubernetes.io/configuration-snippet: |
  more_set_headers "Strict-Transport-Security: max-age=31536000; includeSubDomains";
  more_set_headers "X-Frame-Options: DENY";
  more_set_headers "X-Content-Type-Options: nosniff";
  more_set_headers "Referrer-Policy: strict-origin-when-cross-origin";

8.2 WebSocket 안정성

WebSocket은 장기 연결을 유지하기 때문에 타임아웃 설정이 가장 중요합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Nginx — WebSocket 장기 연결 설정
annotations:
  nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
  nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
  nginx.ingress.kubernetes.io/configuration-snippet: |
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";

# AWS ALB — idle timeout 연장
alb.ingress.kubernetes.io/load-balancer-attributes: |
  idle_timeout.timeout_seconds=3600

# OpenShift HAProxy
haproxy.router.openshift.io/timeout: 1h
1
2
3
4
5
6
7
# Spring Cloud Gateway — WebSocket 라우트 최적화
- id: game-websocket
  uri: ws://game-server:8080
  predicates:
    - Path=/ws/**
  # ⚠️ WebSocket 라우트에는 필터를 최소화할 것
  # Body 변환 필터, 버퍼링 필터 등은 WebSocket 연결을 끊을 수 있음

8.3 SCG 성능 튜닝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# application.yml — 연결 풀 및 타임아웃 설정
spring:
  cloud:
    gateway:
      httpclient:
        connect-timeout: 2000          # 연결 타임아웃 2초
        response-timeout: 30s          # 응답 타임아웃 30초
        pool:
          type: elastic
          max-idle-time: 15s
          max-life-time: 60s
      filter:
        request-rate-limiter:
          deny-empty-key: false

# JVM 튜닝 (컨테이너 환경)
# JAVA_OPTS: "-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"

8.4 모니터링

Ingress 모니터링:

1
2
3
4
5
6
7
# Nginx Ingress 메트릭 확인 (Prometheus 연동)
kubectl get pods -n ingress-nginx
kubectl exec -n ingress-nginx <pod> -- nginx -T | grep upstream

# Traefik 대시보드 (포트포워딩)
kubectl port-forward -n kube-system svc/traefik 8080:8080
# 브라우저에서 http://localhost:8080/dashboard 접속

Spring Cloud Gateway 모니터링:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Prometheus + Grafana 연동
management:
  endpoints:
    web:
      exposure:
        include: health,gateway,prometheus,info
  metrics:
    tags:
      application: rummikub-gateway
    export:
      prometheus:
        enabled: true

# 유용한 SCG 메트릭
# spring_cloud_gateway_requests_seconds_count — 요청 수
# spring_cloud_gateway_requests_seconds_sum  — 응답 시간 합계
# spring_cloud_gateway_routes_count          — 등록된 라우트 수
1
2
3
4
5
# 런타임 라우트 확인
curl http://api-gateway:8090/actuator/gateway/routes | jq .

# 특정 라우트 삭제
curl -X DELETE http://api-gateway:8090/actuator/gateway/routes/{routeId}

8.5 멀티 네임스페이스 전략

1
2
3
4
5
6
7
# 각 팀/서비스마다 별도 Ingress 리소스 사용
# namespace: rummikub → ingress: rummikub-ingress → SCG: api-gateway
# namespace: billing  → ingress: billing-ingress  → SCG: billing-gateway
# namespace: auth     → ingress: auth-ingress

# 단, 모든 Ingress는 동일한 Ingress Controller(외부 IP)를 공유
# SCG는 namespace별로 독립 배포 (서로 다른 인증/라우팅 로직)

8.6 Helm 환경별 설정 분리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# values.yaml (로컬/개발)
ingress:
  enabled: true
  className: traefik
  host: rummikub.localhost
  tls:
    enabled: true
    secretName: rummikub-tls
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: websecure

gateway:
  enabled: true          # 로컬에서는 SCG 옵셔널
  replicas: 1
  image: rummikub/api-gateway:dev
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# values-prod.yaml (운영)
ingress:
  className: nginx
  host: rummikub.example.com
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    cert-manager.io/cluster-issuer: letsencrypt-prod

gateway:
  enabled: true
  replicas: 3            # HA 구성
  image: rummikub/api-gateway:1.2.0
  resources:
    requests:
      memory: "512Mi"
      cpu: "500m"
    limits:
      memory: "1Gi"
      cpu: "1000m"

8.7 GraalVM Native Image로 SCG 콜드 스타트 개선

JVM 기동 시간은 SCG의 대표적인 단점입니다. GraalVM Native Image로 이를 크게 개선할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
# Dockerfile — GraalVM Native Image 빌드
FROM ghcr.io/graalvm/native-image:22 AS builder
WORKDIR /app
COPY . .
RUN ./mvnw -Pnative native:compile -DskipTests

FROM ubuntu:22.04
WORKDIR /app
COPY --from=builder /app/target/api-gateway .
EXPOSE 8090
ENTRYPOINT ["./api-gateway"]
# 결과: JVM 기동 수 초 → Native 수십 밀리초
# 메모리: JVM 512MB+ → Native ~80MB

요약 마인드맵

mindmap
  root(("API Gateway<br/>전략"))
    K8s Ingress
      필요성
        비용 절감 LB 1개
        중앙 TLS 관리
        유연한 라우팅
      핵심 개념
        Ingress Resource
        Ingress Controller
        IngressClass
      플랫폼별
        K3s Traefik 예시
        순수 K8s Nginx EOL
        OpenShift Route
        AWS ALB Controller
        Azure AGIC
      TLS 처리
        Edge Termination
        Passthrough
        Re-encryption
    Spring Cloud Gateway
      필요성
        인증/인가 로직
        서킷브레이커
        동적 라우팅
        응답 변환 BFF
      핵심 개념
        Route
        Predicate
        Filter GlobalFilter
      주요 기능
        JWT 검증
        Rate Limiting Redis
        Resilience4j
        동적 라우트 변경
      운영
        Actuator 모니터링
        GraalVM Native
    함께 사용
      역할 분담
        Ingress TLS 종료
        SCG 앱 로직
        Ops팀 Dev팀 분리
      SCG 단독 가능
        LoadBalancer Service
        트레이드오프 존재
    미래 방향
      Gateway API
        HTTPRoute
        SCG 4x 지원
      Service Mesh Istio
        East-West 트래픽

이 문서는 helm/templates/ingress.yaml 예시 기반으로 작성되었습니다.
Kubernetes v1.29+ · Traefik v3.x · Spring Cloud Gateway 4.x 기준

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.