🏠 목록 Test Report — Ingress / Egress Gateway 동작 검증 📄 MD 원본 🌓 테마
istioingressegressgatewayreport

Test Report — Ingress / Egress Gateway 동작 검증

NOTE

홈랩 클러스터에서 Ingress·Egress gateway 동작을 실측 검증한 리포트. 하나의 그림: gateway는 메시 경계에 선 전용 Envoy proxy다. ingress는 자기가 TLS를 끝내므로(termination) L7 전체가 보여 host/path로 분기하고, egress는 나가는 HTTPS를 복호화하지 않고 SNI만 보고 L4로 한 chokepoint를 거치게 강제한다 — 이 L7 vs L4 비대칭이 두 gateway 검증법까지 갈라놓는다. 결론 — Ingress: host/path 라우팅 + TLS termination PASS. Egress: HTTPS SNI PASSTHROUGH로 외부 호출이 egress gateway를 강제 경유함을 access log로 입증(200). REGISTRY_ONLY 미등록 차단은 메시 전역 영향 탓에 의도적 보류.

Date: 2026-06-07 Cluster: homelab (kubespray bare-metal, k8s v1.30.6, Calico) Istio: 1.30.0 (istiod + ingress/egress gateway, Helm 설치) Scenario: 10-ingress, 20-egress NS: mesh-test (istio-injection=enabled)


0. 배경 — 왜 gateway가 따로 있고, 왜 둘의 검증법이 다른가

sidecar가 이미 모든 pod에 붙어 mTLS·라우팅·관측을 한다면, 경계에 또 하나의 Envoy(gateway)를 세우는 이유는 무엇인가. 답은 경계 트래픽은 sidecar가 다루기 곤란한 특성을 갖기 때문이다.

그래서 ingress와 egress는 같은 "경계 gateway"지만 푸는 문제가 정반대다. ingress는 외부 TLS를 끝내고 안을 들여다봐야 하고(그래야 path로 분기), egress는 나가는 TLS를 그대로 둔 채 어디로 가는지만 알면 된다. 이 한 줄의 차이가 이 리포트의 핵심 — ingress = L7 termination, egress = L4 SNI passthrough — 으로 굳는다.

ingress — TLS 종료 → L7 (복호화) external client no mesh identity ingress GW TLS termination → L7 httpbin internal HTTPS in Host/path route egress — SNI passthrough → L4 (암호화 유지) sleep sidecar in mesh egress GW SNI passthrough → L4 httpbin.org:443 external HTTPS HTTPS out (암호화) route by SNI only
그림 1. ingress/egress 비대칭. ingress GW는 TLS를 종료해 L7(Host/path)로 라우팅하므로 status code로 검증한다. egress GW는 payload를 복호화하지 않고 SNI만 보고 L4 passthrough하므로 status를 못 보고 access log로 "경유했는가"를 검증한다.

이 비대칭의 직접적 귀결: 검증법이 다르다. ingress는 "복호화가 됐나 + path가 맞게 갈렸나"를 status code(200/418/404)로 본다. egress는 payload가 암호화돼 gateway가 status를 볼 수 없으니, "200이 떴나"가 아니라 "정말 그 pod를 경유했나"를 access log로 본다. 이 차이를 모르면 egress의 200을 "gateway가 반환한 200"으로 오독한다(→ §2, What you might be missing).

선행 개념: Envoy listener/route/cluster, cluster 이름 규칙 direction|port|subset|fqdn, SNI(TLS handshake에 평문 노출되는 목적지 호스트명), ServiceEntry/Gateway/VirtualService/DestinationRule 4종 CRD. 깊은 레퍼런스는 Egress Gateway 정본, Cluster 해부.


1. 사전 확인 — 출발선이 깨끗한가

검증 전에 메시 자체가 정상이어야 결과를 믿을 수 있다.


2. Ingress Gateway — L7 termination 검증

메커니즘: ingress gateway는 외부에서 받은 HTTPS를 자기가 termination(복호화)한다. 일단 복호화하면 L7 전체(Host 헤더·path·method)가 보이므로, 그 정보로 내부 서비스에 분기할 수 있다. egress가 SNI만 보는 L4 라우팅인 것과 정확히 대비되는 지점이다 — 자기가 TLS를 끝내므로 L7 전체가 보인다. 그래서 검증 포인트는 둘로 갈린다: (1) TLS termination이 동작하는가, (2) 복호화된 L7로 host/path 분기가 올바른가(매칭 안 되면 404).

적용 manifest

검증 (NodePort http 31080 / https 31443, NODE=203.0.113.212)

각 테스트가 위 두 포인트 중 무엇을 입증하는지 보라 — 단순 200 나열이 아니다.

테스트 명령 기대 실제 입증
HTTP catch-all curl -H "Host: httpbin.example.com" http://NODE:31080/get 200 200 /* catch-all 도달
path match .../status/418 418 418 /status* 명시 route가 catch-all보다 우선
TLS termination curl -k --resolve httpbin.example.com:31443:NODE https://.../get 200 200 gateway가 외부 TLS 복호화
host 분기 curl -H "Host: nope.example.com" .../get 404 404 매칭 안 되는 host는 라우팅 거부

418200보다 중요하다: catch-all /*만 있었다면 /status/418도 200 본문을 받았을 것이다. 418이 떴다는 건 더 구체적인 /status* route가 먼저 매칭됐다는 뜻 — Envoy route 우선순위가 살아있음을 보여준다. 404(nope.example.com)는 그 반대편 증거: 정의되지 않은 host는 조용히 어디론가 가지 않고 거부된다.

합격 판정: PASS

외부→gateway 200, TLS termination 동작, host/path 라우팅 분기 정상.


3. Egress Gateway — L4 SNI passthrough + 경유 강제 검증

메커니즘과 "왜": 외부 대상이 HTTPS(httpbin.org:443)이면 sleep sidecar→egress 구간은 이미 암호화돼 있다 — gateway가 페이로드를 못 본다. 평문 HTTP였다면 gateway가 L7에서 헤더 보고 라우팅하겠지만, HTTPS면 그게 불가능하다. 그래서 헤더가 아니라 TLS handshake의 SNI(평문으로 노출되는 목적지 호스트명)로만 L4 라우팅한다. 이게 PASSTHROUGH 패턴이다 — gateway는 복호화 없이 SNI만 보고 흘려보낸다.

그러면 "굳이 왜 egw를 거치나?" §0의 답이 여기서 검증 질문을 정의한다: chokepoint를 만드는 게 목적이므로, 핵심 질문은 "호출이 성공하느냐"가 아니라 "호출이 정말 그 pod를 경유하느냐"다. 그래서 합격 판정도 단순 200이 아니라 200 + 경유 강제다. 개념·메커니즘 상세는 Egress Gateway 정본, passthrough vs TLS origination 비교는 Egress HTTP vs HTTPS 참조.

적용 manifest

이 3개 CRD의 값이 제각각이면 안 되고 한 줄로 정렬돼야 한다 — 셋 중 하나라도 L7(HTTP)을 가정하면 gateway가 복호화를 시도하다 깨진다.

SNI PASSTHROUGH 정합 3요소 — 셋이 모두 맞아야 L4 SNI 라우팅이 성립한다(상세: Egress Gateway 정본): - ServiceEntry 포트 protocol: TLS - Gateway server tls.mode: PASSTHROUGH - VirtualService를 http:가 아닌 tls: + sniHosts: [httpbin.org]로 작성

http: 라우팅으로 작성하면 gateway가 TLS를 복호화하려다 실패한다.

검증 — config가 깔렸나 → 호출 됐나 → 경유했나

검증은 세 층으로 내려간다: ① Envoy에 config가 반영됐나, ② 실제 호출이 200인가, ③ 그 호출이 정말 egw를 통과했나(이게 핵심).

항목 확인 방법 결과
sleep proxy에 egress cluster proxy-config clusters deploy/sleep.mesh-test ...egressgateway...443 httpbin subset cluster 존재
egress gateway listener proxy-config listeners deploy/istio-egressgateway 0.0.0.0:8443 SNI: httpbin.org → outbound|443||httpbin.org ¹
실제 호출 sleep -> curl -sI https://httpbin.org/get HTTP/2 200
경유 강제 egress gateway access log 신규 라인 outbound|443||httpbin.org 기록 (dest 44.213.156.185:443)

¹ Gateway server는 443으로 정의했으나 실제 리스너는 8443이다. 비권한 포트로 listen하기 위해 Service 포트 443 → pod targetPort 8443으로 매핑한 결과이며, 포트 불일치가 아니다. Ingress의 http.8080(Service 80→targetPort 8080)과 동일한 패턴.

access log 발췌:

[2026-06-07T01:49:40Z] "- - -" 0 ... "44.213.156.185:443" outbound|443||httpbin.org
  10.255.126.47:49456 10.255.126.47:8443 10.255.126.49:37006 httpbin.org -

gateway는 PASSTHROUGH이므로 이 로그는 HTTP 포맷이 아니라 TCP access log 포맷이다 — method/path/status code가 없고 SNI(httpbin.org)·바이트·duration·peer IP만 기록된다(L7 가시성 없음). 위 "- - -" 0을 위 표의 HTTP/2 200과 같은 라인으로 오해하지 말 것: 200은 egress가 본 status가 아니라 sleep→외부 end-to-end 호출 결과다. egress gateway pod IP 10.255.126.47:8443 수신 → 외부 44.213.156.185:443(httpbin.org) 송신 = 경유 확인. 로그에 이 라인이 새로 생겼다는 것 자체가 §1 baseline의 "egw 없이 직접 나가던" 경로가 egw를 통과하는 경로로 바뀌었다는 증거다.

sleep sidecar 10.255.126.49 egress GW :8443 PASSTHROUGH 10.255.126.47 httpbin.org:443 44.213.156.185 mesh leg SNI route (tls/sniHosts) egress leg outbound|443||httpbin.org
그림 1. 경유 강제 검증 경로 — sleep sidecar(.49)→egress GW :8443(.47)→외부(44.213.156.185), egress leg access log의 outbound 클러스터로 경유 확인.

합격 판정: PASS (경유 강제 + 200)

미수행 (의도적 보류)


4. 트러블슈팅


5. 재현 명령 요약

# 0. sample apps
kubectl apply -f scenarios/00-sample-apps/   # ns 의존으로 2회 또는 ns 먼저
# 1. ingress
kubectl -n istio-system create secret tls httpbin-tls --cert=tmp/certs/cert.pem --key=tmp/certs/key.pem
kubectl apply -f scenarios/10-ingress/
NODE=203.0.113.212
curl -H "Host: httpbin.example.com" http://$NODE:31080/get
# 2. egress
kubectl apply -f scenarios/20-egress/
kubectl -n mesh-test exec deploy/sleep -c sleep -- curl -sI https://httpbin.org/get
kubectl -n istio-system logs deploy/istio-egressgateway | grep httpbin.org

6. 다음 작업


핵심 정리


What you might be missing


관련 파일 · 참조

Ingress - 📎 gateway-ingress.yaml · 📎 virtualservice-httpbin.yaml · 📎 10-ingress/README.md

Egress - 📎 gateway-egress.yaml · 📎 serviceentry-httpbin-ext.yaml · 📎 virtualservice-egress.yaml · 📎 destinationrule-egress.yaml · 📎 20-egress/README.md

검증 스크립트 · 설치 - 📎 traffic.sh · 📎 proxy-dump.sh · 📎 install/helm/README.md · 📎 verify.sh - 📎 원본 test-report · 설치 선행: Helm 재설치 런북 - ↗ Istio: Egress Gateways