Project: Shoong-Delivery

[네트워크]EKS 트래픽 설계: ALB + Istio를 선택한 이유

2-30 2026. 5. 19. 20:19

들어가며

EKS 위에 마이크로서비스를 올릴 때 가장 먼저 고민된 부분이 있었다.

"외부 트래픽을 어떻게 받고, 서비스끼리는 어떻게 통신시킬 것인가?"


단순히 "Ingress 하나 만들면 되는 거 아냐?" 라고 생각했다가, 선택지가 생각보다 많다는 걸 알게 됐다. 그리고 조합도 너무 많았다......
이 글은 각 게이트웨이를 비교하고, 최종적으로 ALB + Istio 를 선택한 이유와, 그로 인해 만들어진 트래픽 흐름을 정리한다.


1. 후보 기술 정리: 각 레이어에 뭐가 있는가

조합을 따져보기 전에 각 레이어에서 후보로 둘 만한 기술들의 특성을 먼저 정리한다. 트래픽이 인터넷에서 서비스 Pod까지 도달하려면 보통 세 개의 레이어를 거친다.

인터넷 → [① 로드밸런서] → [② 클러스터 내부 라우팅] → [③ 서비스 간 통신] → Pod

① 외부 진입: 로드밸런서 (ALB vs NLB)

기술 레이어 장점 단점
ALB L7 WAF·ACM 네이티브 연동, 경로/호스트 기반 라우팅, HTTPS 종단 가능 고정 IP 불가, 초저지연/대규모 동시 연결에 약함, TCP/UDP 처리 X
NLB L4 초저지연, 고정 IP, 대규모 동시 연결, TCP/UDP 패스스루 WAF 직접 연결 불가, HTTPS 종단·경로 라우팅을 LB 단에서 못 끝냄

② 클러스터 내부 라우팅 (Ingress / Gateway)

기술 위치 장점 단점
Nginx Ingress K8s Ingress 자료 풍부, 구성 단순, 학습 곡선 낮음 2026-03 프로젝트 retire (EOL) — 보안 패치 중단. 그 외에도 어노테이션 의존, East-West 미지원
Gateway API K8s 표준 K8s 공식 차세대 스펙, 인프라/앱 역할 분리(Gateway/HTTPRoute) 메시 기능 없음(East-West 맹점), 구현체 성숙도 편차, 리소스 종류 증가
Istio Gateway Istio 전용 Istio 메시와 통합 운영, VirtualService로 정교한 L7 제어 Istio 종속, 사이드카 운영 부담, 표준 스펙은 아님

참고 (2026-05 시점): ingress-nginx는 메인테이너 부족으로 2026년 3월 retire 됐고, 후속으로 발표됐던 InGate도 함께 retire 됐다. K8s 커뮤니티는 Gateway API 로의 마이그레이션을 공식 권고한다. Ingress API 스펙 자체가 deprecated는 아니지만 feature-frozen 상태이므로, 신규 설계에서 Nginx Ingress는 사실상 후보에서 빠진다.

③ 서비스 간 통신: 서비스 메시

기술 장점 단점
메시 없음 운영 부담 0, 리소스 비용 최소 mTLS·서킷브레이커·재시도·분산 추적을 모두 앱이 직접 구현
Istio 자동 mTLS, 트래픽 정책, 관측성(Kiali·Jaeger), 풍부한 생태계 사이드카 리소스·메모리 비용, 학습 곡선, 디버깅 난도
Linkerd 가벼운 사이드카, 단순성, 빠른 시작 기능 폭이 Istio보다 좁음 (Wasm 확장·고급 라우팅 일부 부재)

참고: Gateway API / Istio Gateway는 외부에 노출되는 네트워크 엔드포인트가 아니다. 인터넷에서 클러스터로 트래픽이 들어오려면 ①의 로드밸런서가 반드시 앞에 있어야 한다.

이제 이 후보들을 조합하면 의미 있는 시나리오가 어떻게 나오는지 본다.


2. 선택지 정리: 뭘 조합할 수 있는가

조합 매트릭스

# 로드밸런서 내부 라우팅 서비스 메시 한 줄 요약
1 ALB Nginx Ingress 가장 단순한 구조. 서비스 간 제어 없음
2 ALB Gateway API K8s 표준 + WAF 가능. 메시 없어서 East-West 맹점
3 ALB Istio Gateway Istio L7이 ALB·Istio 양쪽에 중복. 현실적 절충안
4 ALB Gateway API Istio Gateway API + Istio 동시 운영 → 오버엔지니어링
5 NLB Istio Gateway Istio Istio 정석 구조. WAF 직접 연결 불가
6 NLB Gateway API 표준 기반, 단순 MSA. 메시 없음

3. 내 서비스에 무엇이 필요한가 (요구사항 분석)

요구사항 이유
HTTPS + 인증서 자동 갱신 사용자 향 서비스 (ACM 활용)
WAF 연동 배달앱 = 결제 포함 → 앱 레벨 공격 방어 필요
서비스 간 암호화 (mTLS) order → kitchen → delivery 내부 통신 보안
외부 노출 최소화 kitchen / delivery는 외부에서 호출 불가해야 함
트래픽 제어 (서킷브레이커 등) 주문 폭주 시 downstream 장애 전파 차단

판단: 외부 진입 레이어와 내부 서비스 메시 레이어 둘 다 필요하다.


4. 1단계 의사결정: 로드밸런서는 ALB냐 NLB냐

외부에서 클러스터로 트래픽이 들어오려면 로드밸런서가 반드시 필요하다. 그럼 ALB와 NLB 중 무엇을 쓸 것인가?

NLB의 강점이 필요한 상황인가?

NLB는 L4라서 TCP/UDP 레벨 처리에 강하고, 초저지연이 필요한 대규모 연결 유지에 적합하다. 게임 서버나 실시간 통신처럼 동시 연결 수가 폭발적으로 많은 서비스라면 NLB가 더 적절하다고 생각된다.

하지만 Shoong Delivery는 그 정도 규모가 아니다. REST API 기반이고, 동시 연결 수도 NLB의 강점이 빛날 만한 수준이 아니다.

ALB가 제공하는 것 중 내가 진짜 필요한 것

배달앱은 주문, 결제, 배달 주소 같은 개인정보와 결제 정보를 REST API로 다룬다.(아직 Shoong-Delivery에는 결제 로직이 없긴하지만 추후 개발 가능성 염두) 이런 데이터를 다루는 API는 SQL Injection, XSS, 비정상 요청으로 결제 로직 우회 같은 애플리케이션 레벨 공격에 노출된다. 이 방어는 L4에서는 불가능하고 L7에서만 가능하다.

항목 NLB ALB 내 요구사항과 매칭
WAF 연동 결제 데이터 → 앱 레벨 공격 방어 필수
SSL 종단 (ACM) 인증서 자동 갱신 운영 부담 ↓
경로 기반 라우팅 /api/orders, /api/notification 분기 필요
고정 IP / 초저지연 배달앱 규모에선 불필요

결론: NLB가 필요할 만큼의 규모가 아니고, REST API 보안이 우선이므로 ALB 선택.


5. 2단계 의사결정: Gateway는 뭘 쓸 것인가

ALB는 정해졌다. 그럼 클러스터 내부 라우팅은? 여러가지 기술들이 있었고, 그 중 Nginx Ingress, Gateway API, Istio Gateway 3개로 추렸다.

사고 흐름: 요구사항이 선택을 강제한다

먼저 서비스 구성을 보자.

서비스 4~6개 (order, kitchen, delivery, notification, batch …)
서비스 간 직접 통신 존재 (order → kitchen → delivery)
개인정보/결제 데이터 취급(예정)

서비스 간 통신이 있다는 게 핵심이다. 이 구조에서 발생하는 문제는 외부 라우팅으로는 해결되지 않는다.

서비스 간 통신 구간에서 생기는 문제
  1. 결제 서비스 장애 → 주문 서비스로 전파(예정)
  2. 통신 구간 평문 → 암호화 없음
  3. 어느 구간에서 병목인지 추적 불가

Nginx Ingress 탈락

외부 라우팅은 가능하지만 거기까지다. 서비스 간 통신은 무방비로 남는다. 고급 트래픽 제어도 어노테이션에 의존해서 구현체 종속이 강하다.

덧붙여 ingress-nginx 프로젝트는 2026년 3월에 retire 되어 더 이상 보안 패치가 나오지 않는다. 결과적으로 기능 부족 이전에 유지보수 자체가 끝난 선택지가 됐다.

Gateway API 탈락

K8s 표준이라는 장점이 있고 외부 라우팅은 깔끔하게 해결한다. 하지만 서비스 메시 기능이 없다. 서비스 간 mTLS, 서킷브레이커, 분산 추적은 범위 밖이다. Gateway API만 쓰면 East-West 구간이 그대로 맹점으로 남는다.

Istio 선택

Istio는 외부 진입 라우팅(Gateway + VirtualService)과 서비스 간 통신 제어(사이드카 자동 mTLS, 서킷브레이커, 분산 추적의 hop span·trace context 전파)를 동시에 해결한다. 위에서 도출한 세 가지 문제를 전부 커버한다.

분산 추적은 사이드카만으로 완결되지 않는다. 앱 내부의 함수·DB·HTTP 호출 단위 span은 OTel SDK가 만들고, Istio 사이드카는 hop 단위 span과 trace context 전파를 담당하는 2-layer 구조다. 본 프로젝트는 4개 API 서비스에 @opentelemetry/sdk-node를 적용해 OTLP gRPC로 클러스터 내 OTel Collector에 송신한다. (각 서비스 src/instrumentation.ts 참조(https://github.com/shoong-delivery))


6. 3단계 의사결정: Gateway API + Istio 조합은?

여기서 한 번 더 고민이 생겼다. 요즘 권장되는 조합 중 하나가 Gateway API + Istio라는 것을 봤다. K8s 표준 스펙으로 외부 라우팅을 표현하고, 내부는 Istio가 처리하는 방식이다. 그럼 이걸 안 쓴 이유는?

L7 중복 문제

ALB         → L7 (HTTP 라우팅, WAF, TLS 종단)
Gateway API → L7 (HTTP 라우팅)
Istio       → L7 (HTTP 라우팅)

L7이 세 번 중복된다. ALB 앞단에서 이미 경로 기반 라우팅이 가능한데, 그 뒤에 Gateway API로 한 번 더 L7 라우팅을 정의하면 사실상 같은 일을 두 번 한다.

Gateway API + Istio

Gateway API + Istio 조합은 역할 분리다.

인프라팀 → Gateway 리소스 (진입점 관리)
앱팀     → HTTPRoute 리소스 (라우팅 규칙)

팀이 여러 개일 때 인프라팀과 앱팀이 각자 관심사를 분리해서 관리할 수 있다. 그리고 K8s 표준 스펙이라 나중에 Istio 대신 다른 구현체로 교체해도 스펙이 그대로 유지된다.

Shoong-Delivery 환경에선 의미가 없다

혼자 개발하는 포트폴리오다보니 인프라팀/앱팀 역할 분리가 의미 없고, 오히려 관리할 리소스 종류만 늘어난다. Gateway API + Istio는 단일 개발자 환경에서 진가를 발휘하지 못할 것 같다.

결론: Istio 리소스(Gateway, VirtualService, DestinationRule)로 외부/내부 트래픽을 통일해서 관리하는 게 더 단순하다. → Istio 단독 선택


7. 그래도 서비스 메시는 과스펙 아닌가?

Shoong Delivery 규모에 너무 과스펙이 아닌가하는 의문이 들었다. 서비스 4~6개 규모, 아직 결제 로직도 없는 포트폴리오에 Istio를 얹는 게 합당한가?

반론 1: 메시 없이 같은 보장을 얻으려면 더 비싸다

규모가 작아도 결제·개인정보 데이터가 흐르면 East-West 평문 통신은 리스크다. 메시 없이 같은 보장을 얻으려면 다음을 직접 구현해야 한다.

  • 앱마다 TLS 인증서 발급·회전·신뢰 체인 관리 코드
  • 서킷브레이커·재시도·타임아웃 로직을 SDK 단에서 구현 (서비스마다 언어/SDK 일관성 관리 필요)

서비스 4~6개에서 위를 모두 구현하면 결국 "직접 만든 메시"가 된다. 검증된 컴포넌트(Istio)로 위임하는 편이 오히려 단순할 것이라 판단했다.

반론 2: MSA의 진짜 비용은 "디버깅"이다

분리된 서비스 중 어디서 문제가 났는지 추적하기 어려운 게 MSA의 가장 큰 운영 비용이다. Kiali·Jaeger의 관측성은 규모와 무관하게 즉시 가치를 낸다. 오히려 작은 규모일수록 한 번 세팅으로 끝나서 ROI가 좋다.

반론 3: 포트폴리오의 목적은 "현실 운영 스택의 시연"이다

이 프로젝트는 "최소 비용으로 동작하는 앱"이 목표가 아니라 "실제 운영 환경에서 마주칠 DevOps 스택을 다뤄봤다"는 걸 보여주는 게 목표다. 메시가 필요 없는 규모라는 이유로 빼면, 그 학습 경험 자체가 없는 포트폴리오가 된다.

트레이드오프는 인정한다

  • 리소스 비용: 사이드카가 Pod마다 수십 MB 메모리를 추가 소비한다. dev 환경에선 감내 가능. 진짜 비용 민감한 환경이라면 Istio ambient mode(사이드카리스)나 Linkerd 같은 경량 옵션이 더 나을지도 모른다.
  • 학습 곡선: 한 번만 지불하면 되는 비용이고, 본 프로젝트의 학습 목표 자체와 일치한다.
  • 디버깅 난도: Envoy 레이어가 추가되니 "어디서 막혔는지" 찾는 깊이가 한 단계 늘어난다. Kiali로 어느정도 상쇄되지 않을까라고 생각했다.

만약 진짜로 메시가 부담스럽다면

단순화 경로는 있다.

  1. Linkerd로 교체 — Istio보다 가볍고 단순. 단, 고급 트래픽 제어·생태계 폭은 좁다.
  2. 메시 없이 가는 길 — mTLS는 클러스터 신뢰 도메인 가정으로 포기. 앱 단 OTel SDK는 어차피 적용 중이라 유지되지만, 사이드카 hop span과 Kiali 서비스 그래프 같은 메시 레이어 관측성은 잃는다.

Shoong-Delivery는 1,2 대신 Istio를 택했다. 운영에서 가장 자주 보게 될 메시이기도 하고, 학습 및 시연 가치가 높다고 판단했기 때문이다.


8. 그래서 탈락한 조합들 (정리)

NLB + Istio (정석이지만 탈락)

  • WAF를 직접 붙일 수 없음 → IP/포트 수준 방어만 가능 (SQL Injection, XSS 방어 안 됨)
  • Istio Gateway에서 TLS termination + 인증서 관리까지 담당해야 함 → 운영 복잡도 증가
  • 동시 연결 수가 많지 않은 서비스 규모에서 L4 pass-through의 장점이 없음

ALB + Gateway API (단순하지만 부족)

  • 서비스 간 mTLS, 서킷브레이커를 지원하지 않음
  • Istio를 추가로 선택하게 되면 어차피 아래 선택지로 이동

ALB + Gateway API + Istio (탈락)

  • L7 라우팅이 ALB · Gateway API · Istio 세 곳에서 중복
  • 역할 분리의 가치는 팀 구조가 있을 때 빛남, 단일 개발자 환경에선 관리 포인트만 늘어남
  • 오버엔지니어링

9. 최종 선택: ALB + Istio

인터넷 → CloudFront (WAF · edge ACM) → ALB (TLS 종단 · ACM) → istio-ingressgateway → Istio VirtualService → 서비스

ALB가 담당하는 것

  • HTTPS 종단 (ACM 인증서 자동 갱신, ELBSecurityPolicy-TLS13)
  • TargetGroupBinding으로 Pod IP 직접 등록 → 노드 hop 1단계 제거

WAF는 ALB 직접 attach가 아닌 앞단 CloudFront에 부착되어 있다.

앱 레벨 방어(SQLi·XSS·Bad Inputs 등)는 Edge 계층에서 1차로 차단되고, ALB는 TLS 종단과 클러스터로의 L7 라우팅에 집중한다.

Istio가 담당하는 것

  • South-North: Gateway + VirtualService로 경로 기반 라우팅
  • East-West: 사이드카 자동 mTLS — 메시 내부 통신은 모두 암호화 (앱 코드 0줄)
  • DestinationRule로 서킷브레이커(connectionPool + outlierDetection) 적용
  • kitchen / delivery에 VirtualService 없음 → 외부 호출 경로 자체가 없음

현재 PeerAuthentication은 mesh 기본값 PERMISSIVE 상태로 운영 중.
메시 내부 사이드카끼리는 auto-mTLS로 암호화되지만 평문 진입은 아직 차단되지 않는다.
STRICT으로의 점진 전환은 추후에 다룰 예정이다.


10. 실제 트래픽 흐름

아래 흐름이 실제로 동작 중임을 CLI와 Kiali 캡처로 함께 증명한다. 다이어그램 → 검증 캡처 순서로 본다.

South-North (외부 → 클러스터)

사용자
  │ HTTPS
  ▼
ALB  ─── TargetGroupBinding ip (NodePort 없음, Pod IP 직접)
  │ HTTP (TLS 종단 완료)
  ▼
istio-ingressgateway  ─── envoy sidecar
  │
  ├─ VS /api/orders/*         →  order
  ├─ VS /api/notifications/*  →  notification
  └─ VS *.internal.dev        →  argocd · grafana · kiali
      (VS 없음: kitchen · delivery → 외부 호출 불가)

검증 ① — ALB → Pod IP 직결 (NodePort 우회)

TargetGroupBindingtargetType: ip 설정으로 ALB가 istio-ingressgateway Pod에 직접 트래픽을 보낸다. 노드 hop 1단계가 제거된다. 세 가지 출력을 한 화면에 모으면 다음이 증명된다.

  1. Target Group 자체가 ip 모드 (TargetType: ip)
  2. ALB에 등록된 IP가 10.0.12.30
  3. kubectl로 확인한 istio-ingressgateway Pod IP도 10.0.12.30

→ 같은 IP가 AWS와 K8s 양쪽에 존재 = ALB가 노드를 거치지 않고 Pod로 직결.

검증 ② — VirtualService 라우팅 (kitchen/delivery 외부 노출 X)

shoong 네임스페이스의 VS 목록을 보면 dev-shoong-order, dev-shoong-notification 만 존재한다. kitchen·delivery에는 VS가 없으므로 게이트웨이가 라우팅할 경로 자체가 없고, 결과적으로 외부에서 호출 불가능하다.


East-West (서비스 간 자동 mTLS)

batch (CronJob)
  │ 자동완료 (PATCH status)
  ▼
order  ──[🔒 mTLS]──▶  kitchen  ──[🔒 mTLS]──▶  delivery
                          │                          │
                     PATCH status               PATCH status
                          └──────[🔒 mTLS]──▶  notification

포인트: 앱 코드는 평문 HTTP로 호출한다. sidecar ↔ sidecar 구간에서 Istio가 자동으로 mTLS를 처리하며, istiod가 인증서 발급·회전까지 담당한다.

검증 ③ — 전체 메시 트래픽 흐름 + mTLS 자물쇠

Kiali Graph(shoong 네임스페이스, Last 30m)로 실제 트래픽이 흐르는 모습. 빨간 표시 의미:

  • 좌측 빨간 박스: 외부 진입점 istio-ingressgateway
  • 빨간 굵은 화살표: order → kitchen → delivery → notification 비즈니스 체인 (East-West)
  • 빨간 동그라미: 메시 내부 통신의 mTLS 🔒 자물쇠 (다수)
  • 상단 빨간 박스 + "DB(RDS) - 메시 밖": PassthroughCluster — RDS로 빠지는 egress

→ 메시 내부 통신은 모두 mTLS로 암호화되고 있고 (PERMISSIVE auto-mTLS), DB는 메시 밖이라 PassthroughCluster로 잡힌다.

검증 ④ — 워크로드 디테일 (dev-shoong-order)

특정 워크로드를 들여다보면 사이드카 주입과 라이브 메트릭이 한 화면에 보인다.

  • 좌측 Pods 섹션: 2개 Pod (replica=2 적용, 둘 다 healthy)
  • 우측 미니 그래프: 라이브 트래픽 (rps + 평균 latency + 🔒)
  • 그래프의 자물쇠: order ↔ batch ↔ kitchen 사이의 mTLS

Kiali의 Pod 정보 팝업에서 Istio Container: Not found로 보이는 건 아직 원인을 찾지 못했다.


트래픽 제어 — 서킷브레이커 (DestinationRule)

mTLS와 함께 East-West 구간의 또 다른 핵심 보호 장치. shoong 네임스페이스의 4개 서비스에 DestinationRule이 적용되어 있다.

  • connectionPool: 동시 연결·대기 큐 cap → 폭주 시 즉시 fail fast (장애 전파 차단)
  • outlierDetection: 5xx 연속 5회 시 30초 격리, 최대 50% (replica=2 환경에서 한 쪽 Pod 격리 가능)
  • 앱 코드 변경 0줄 — DestinationRule manifest만으로 활성화

현재 운영 상태 — PERMISSIVE (점진 적용 중)

PeerAuthentication 미정의로 mesh 기본값 PERMISSIVE 운영 중. 메시 내부는 auto-mTLS로 암호화되지만 평문 진입도 허용된다. 운영 환경 STRICT 적용은 Istio 공식 마이그레이션 경로(PERMISSIVE → 호출자 식별 → namespace-level STRICT → AuthorizationPolicy)를 따라 단계적으로 전환 예정.


마치며

ALB + Istio 조합은 "현실적인 타협안"이다.
Istio 정석대로라면 NLB + Istio가 더 깔끔하지만, Edge 계층의 WAF(CloudFront)와 ALB의 ACM TLS 종단 등 AWS 매니지드 운영 편의성을 포기할 수 없었다
L7 라우팅 중복이라는 단점이 있지만, ALB는 외부 진입 보안에만 집중하고 Istio는 내부 서비스 메시에만 집중하도록 역할을 분리하니 구조가 명확해졌다.

선택 과정을 돌아보면 결국 두 가지가 핵심이었다.

  1. 로드밸런서 선택은 "내 서비스가 NLB의 강점이 필요할 만한 규모냐"의 답으로 갈렸다 → 아니오 → ALB
  2. Gateway 선택은 "서비스 간 통신 제어가 필요한가"의 답으로 갈렸다 → 예 → Istio

요구사항이 기술 선택을 강제하도록 흐름을 따라가니, 각 선택지에 명확한 근거가 붙었다.


참고 자료