homelab89 Docs Logs Legacy Files ☰ TOC 🌓
noteistio 2026-07-09istioegressdnsgslbservice-entrytls-passthroughenvoyresolution

SE resolution(STRICT/LOGICAL) × TLS passthrough(L4) — in-flight 요청의 운명

ABSTRACT

ServiceEntry.resolution(DNS→STRICT_DNS vs DNS_ROUND_ROBIN→LOGICAL_DNS)과 트래픽 경로(L7 TLS Origination vs L4 TLS Passthrough)를 조합하면 2×2 매트릭스가 나온다. GSLB가 flip을 걸었을 때 이미 진행 중(in-flight)인 요청은 네 칸 모두에서 완주한다 — 이건 이번 실측으로 확정됐다. 이 문서가 답하는 진짜 질문은 “안 끊긴다"가 아니라 **“안 끊기는 이유가 칸마다 다른가, 그리고 요청이 끝난 다음엔 무슨 일이 벌어지는가”**다. 네 칸의 실측 근거와 그 사이의 메커니즘 차이를 정리한다.


1. 질문

SE의 resolution 설정(DNS=STRICT_DNS vs DNS_ROUND_ROBIN=LOGICAL_DNS)에 따라, TLS passthrough(L4) 경로에서 GSLB flip이 일어날 때 진행 중인(in-flight) 요청은 어떻게 처리되는가?

이 질문은 두 축의 조합이다 — resolution(STRICT/LOGICAL)과 경로(L7 origination/L4 passthrough). 답은 이 문서 전체에 걸쳐 2×2 매트릭스 하나로 완결한다(§3). “L7이 L4보다 안전한가” 같은 넓은 보호-경계론은 배경(§2)으로만 다루고 본론으로 확장하지 않는다 — 그 주제의 정본은 이미 TLS passthrough(L4) 대조 리포트에 있다. dead-IP 유실 회복(D-1/D-3의 retry 이야기)도 “L4에는 무엇이 없는지"를 보여주는 배경으로만 짧게 인용한다(§4-4).

실측/추론 표기 규칙: 이 문서의 모든 주장은 클러스터 실측(홈랩, Istio 1.30.0)이거나 그 실측에서 논리적으로 따라오는 메커니즘 추론이다. 실측하지 못한 부분은 문장 끝에 **(메커니즘 추론 — 미실측)**을 명시한다. 표기가 없으면 실측 근거가 있는 문장이다.


2. 배경 — sidecar egress의 두 경로

resolution이 Envoy cluster type을 STRICT_DNS/LOGICAL_DNS 중 무엇으로 바꾸는지의 변환 규칙 자체는 이 문서의 범위가 아니다 — ServiceEntry resolution 정본 §3이 이미 정리했다. 여기서 필요한 건 그 위에 얹히는 두 번째 축, 경로다.

client가 sidecar에 평문 HTTP로 요청을 보내고 sidecar가 TLS를 origination하면(40/41 SE + 42 VS + 43 DR 조합), Envoy는 HTTP 요청/응답의 경계를 안다. 그래서 RDS route가 붙고, 그 route 위에 connection pool이 얹히고, pool 안에서 keepalive 연결이 여러 요청에 재사용된다. 반대로 client가 처음부터 TLS를 직접 맺고 sidecar는 SNI만 보고 통과시키면(48/50 SE, protocol: TLS, VS/DR 없음), Envoy에게는 그냥 바이트 스트림이다 — SNI 매치로 filter chain을 고른 다음 바로 tcp_proxy가 raw 소켓을 1:1로 잇는다. route도, pool도 없다.

L7 — TLS Origination(평문 HTTP 진입, 40/41 SE+42 VS+43 DR) L4 — TLS Passthrough(SNI, 48/50 SE만) Client(app) — 평문 HTTP Envoy: HTTP filter chain RDS route 매치(VS42) Connection pool(keepalive) 요청/응답 경계 O Upstream(A 또는 B, pool이 아는 host) Client(app) — 직접 TLS ClientHello Envoy: SNI 매치 → tcp_proxy Route 없음 raw TCP 소켓 1:1 (pool 없음) Upstream(연결 시점 IP에 고정)
그림 1. sidecar egress의 두 경로. L7은 HTTP filter chain → RDS route → connection pool을 거쳐 "요청 경계"가 있고, L4는 SNI 매치 직후 곧바로 tcp_proxy가 raw 소켓을 1:1로 잇는다 — 중간에 pool 단계 자체가 없다.

“요청 경계가 있다/없다"는 이 문서 전체를 관통하는 유일한 구조적 차이다. pool이 있으면 그 위에 drain 유예·재사용·stale pin 같은 커넥션 수명 정책이 얹힐 자리가 생긴다. pool이 없으면 그 자리 자체가 없다 — 있고 없고의 문제가 아니라 개념이 성립하지 않는다. §4가 이 차이를 4칸에 대입해서 푼다.


3. 실측 매트릭스

L7 origination(HTTP pool) L4 passthrough(raw socket) STRICT_DNS (resolution: DNS) LOGICAL_DNS (resolution: DNS_ROUND_ROBIN) 완주 · membership 즉시 반영 destroy는 완료 후에만(설계된 drain) → 다음 새 연결은 새 IP 실측 · part2 §1 완주 · membership 지연 반영 소켓은 애초에 불간섭(destroy=자연 종료) pool 없음 → stale-pin 개념 자체가 희석(추론) 실측 · D report §5 완주 · membership Δ0 연결 pool 잔존 → stale pin 지속 가능 실측 · part2 §2 완주 · membership 개념 자체 없음 flip을 이벤트로도 안 봄 — 가장 무관심 실측 · F-0 §3~4

네 칸 모두 curl 200 · SIZE 409610B(전량) 완주 — 차이는 “요청이 끝난 다음” 연결의 운명

그림 2. resolution × 경로 2×2 매트릭스 요약. 색은 "완료 후 다음 연결의 예측 가능성"을 뜻한다 — 기본(회색 테두리)은 다음 IP가 구조적으로 결정되고, 호박색(LOGICAL×L7)은 옛 IP로 재사용될 위험(stale pin)이 실측으로 확인된 칸이다.

네 칸의 근거를 표로 펼치면 다음과 같다. 모두 40초 in-flight 스트림(/slow, payload 409610바이트) + flip(+5s) + 4점 스냅샷(BEFORE/MID1 +12s/MID2 +30s/AFTER +43s) 하니스로 측정했다.

L7 origination(HTTP, pool) L4 passthrough(SNI, raw socket)
STRICT_DNS
resolution: DNS
완주(200·409610B·body=backend-a). membership_change는 flip 반영과 함께 +1(MID1=+12s 시점에 이미 host A→B 교체 완료). upstream_cx_destroy_local요청 완료 직후에만 +1 — host가 이미 목록에서 빠졌어도 in-flight 연결은 능동적으로 drain 유예된다. 다음 신규 연결은 반드시 새 IP(backend-b)로 붙는다.
(2026-07-02_112113_dns-inflight-part2.md §1)
완주(200·409610B·최초 접속 pod 유지). membership_change지연 반영(MID1=+12s엔 아직 pod-A만 보임, MID2=+30s에야 pod-B로 교체·membership_change +1). destroy도 요청 자연 종료 시에만 +1 — 단 이건 “유예"가 아니라 raw 소켓이 애초에 CDS/EDS 갱신과 무관하게 살아있었을 뿐이다.
(2026-07-09_134250_dns-passthrough-tcp.md §5, D-2)
LOGICAL_DNS
resolution: DNS_ROUND_ROBIN
완주(200·409610B·body=backend-a, 2/2 결정론적). membership_change는 BEFORE→AFTER 전 구간 Δ0(논리 host 1개 유지, flip을 이벤트로 취급 안 함). upstream_cx_active=1이 AFTER(+43s)에도 그대로 남는다 — destroy 자체가 관측 창 안에서 발생하지 않는다(연결이 pool에 잔존, 재사용 대기 → stale pin 지속 가능).
(2026-07-02_112113_dns-inflight-part2.md §2)
완주(200·409610B·최초 접속 pod 유지, 2/2 결정론적). membership_change는 BEFORE→AFTER 전 구간 11→11 고정 — flip이 반영돼도 “논리적으로는 여전히 host 1개"라 membership 갱신 개념 자체가 없다. admin의 endpoint 표시 IP는 MID1(+12s)에 이미 새 IP로 갱신되지만, 이건 다음 연결이 참조할 해석 캐시가 바뀐 것이지 membership 이벤트가 아니다.
(2026-07-09_144251_dns-passthrough-logical-inflight.md §3~4, F-0)

4. 칸 사이 차이의 메커니즘

flip 이슈(A→B, t=+5s)

STRICT_DNS × L7 origination (HTTP pool, 40/41+42+43) membership +1 destroy_local +1(완료 후)

LOGICAL_DNS × L7 origination (HTTP pool, 40/41+42+43) membership_change Δ0(전 구간) destroy 없음 → pool 잔존(재사용 대기)

STRICT_DNS × L4 passthrough (raw socket, 48 SE만) membership +1(지연, +12~+30s) destroy +1(자연 종료)

LOGICAL_DNS × L4 passthrough (raw socket, 50 SE만) membership_change Δ0(개념 없음) destroy +1(자연 종료)

0 +5s flip +12s MID1 +30s MID2 +43s AFTER
그림 3. 40초 in-flight 스트림 동안의 membership/destroy 이벤트 타임라인. 점(●)은 실제로 카운터가 틱한 지점이다 — L7 STRICT만 flip 직후(+12s 이내)에 membership이 갱신되고, 나머지 세 레인은 그보다 늦거나(L4 STRICT) 아예 갱신되지 않는다(LOGICAL 둘 다). destroy는 네 레인 중 셋에서 정확히 스트림이 끝나는 시점(+43s)에만 틱하고, LOGICAL×L7 레인만 관측 창 안에서 전혀 틱하지 않은 채 연결이 이어진다.

4.1 네 칸 모두 생존하지만, 보증 수준은 다르다

in-flight 생존은 네 칸 전부에서 성립한다(§3). 하지만 그 생존이 무엇에 의한 것인지가 다르다.

  • L7 origination(둘 다): Envoy가 요청 경계를 안다. connection pool이 “이 연결 위에 아직 활성 요청이 있다"를 추적하고, host가 제거돼도 그 요청이 끝날 때까지 destroy를 미룬다. 이건 설계된 동작이다 — HTTP1.1 keepalive·HTTP2 GOAWAY류의 graceful shutdown과 같은 계열.
  • L4 passthrough(둘 다): tcp_proxy는 한 번 맺은 커널 소켓을 그냥 릴레이할 뿐이다. CDS/EDS가 나중에 그 host를 지워도 이미 진행 중인 연결과는 애초에 무관하다 — 정리할 지점 자체가 없다. D report는 이 차이를 정확히 짚었다: “우연히 안전한 것이지 설계로 안전한 게 아니다” (2026-07-09_134250_dns-passthrough-tcp.md §5.3).

결과의 유사성(둘 다 완주)에 속아 “L4도 L7만큼 안전하다"고 일반화하면 안 된다. 완료까지 살아남는 것완료 후에도 신뢰할 수 있는 장애 복구 체계를 갖추는 것은 별개 질문이고, 후자는 §4.4에서 갈린다.

4.2 L4엔 재사용 pool이 없다 — LOGICAL의 stale pin이 구조적으로 희석된다

L7×LOGICAL 칸의 진짜 위험은 “이번 요청이 안 끊긴다"가 아니라 “다음 요청도 같은(옛) 커넥션을 재사용해서 계속 backend-a로 간다“는 stale pin이다 — 이건 connection pool이 있기 때문에 생기는 문제다. D report가 명시적으로 확인한 사실은 “TCP passthrough엔 connection pool 자체가 없다” (2026-07-09_134250_dns-passthrough-tcp.md §5.3)는 것이다. tcp_proxy는 다운스트림 연결마다 새 업스트림 소켓을 1:1로 여는 구조라, HTTP처럼 “여러 요청이 하나의 커넥션을 keepalive로 나눠 쓰는” 개념 자체가 없다.

이 사실에서 따라오는 결론 — L4에서는 매 신규 연결마다 그 시점의 DNS/EDS 해석을 새로 타므로, L7×LOGICAL에서 본 stale pin 문제가 구조적으로 희석된다(메커니즘 추론 — 미실측). 이번 실측은 전부 “이미 맺은 하나의 연결"의 생존만 봤을 뿐, flip 이후 새로 여는 L4 연결이 실제로 몇 초 만에 새 IP를 따라가는지는 별도로 측정하지 않았다. STRICT×L4에서 관측한 “membership 지연 반영”(§3)이 새 연결의 목적지 선택에 얼마나 영향을 주는지, LOGICAL×L4에서 admin 표시가 MID1(+12s)에 이미 갱신된다는 사실이 새 연결에도 그대로 적용되는지는 이 문서의 실측 범위 밖이다.

4.3 이 매트릭스가 성립하는 전제 — LOGICAL + multi-IP는 CDS NACK

네 칸 모두 flip은 “단일 IP → 단일 IP"로만 걸었다. LOGICAL_DNS cluster는 lb_endpoint가 1개여야 하는데, A record가 다중 IP가 되는 순간 이 SE를 받는 프록시가 CDS push를 통째로 NACK할 수 있다는 사고를 이전 세션에서 이미 겪었다(같은 push의 listener까지 드롭 — DNS/GSLB 재현 랩 §7). 그래서 LOGICAL이 들어간 두 칸 (L7×LOGICAL, L4×LOGICAL)은 이 제약 위에서만 유효하다 — GSLB가 실제로 멀티 IP A record를 돌려주는 환경이라면 LOGICAL_DNS 자체를 쓸 수 없는 경우가 생긴다는 뜻이고, 이건 이 매트릭스의 전제 조건이지 매트릭스 내부의 발견은 아니다.

4.4 dead-IP 구제(L7 기본 retry)는 L4에 없다 — 배경으로 짧게

이 문서의 중심은 in-flight(§3~4.3)이지만, “요청이 끝난 뒤"의 이야기를 완전히 빼면 왜 이 차이가 프로덕션에서 중요한지 감이 안 온다. D-1(같은 40초 창, 같은 dead-IP 조건)에서 L7 경로는 connect-fail 자동 재시도로 46/46 성공(유실 0%)했지만, L4 passthrough는 route 자체가 없어 이 재시도가 걸릴 자리가 없어 18/41(43.9%)이 client에게 그대로 실패로 전달됐다(2026-07-09_134250_dns-passthrough-tcp.md §3). outlier detection을 걸면 43.9%→0.21%까지 떨어지지만 정확히 0엔 안 닿는다(D-3, 같은 문서 §6). in-flight는 4칸 모두 보호되지만, 연결이 끊어진 뒤 새로 맺으려는 시도는 L4에서 훨씬 더 무방비다 — 자세한 수치와 원인은 원본 리포트를 참조.


5. 실측 함정 콜아웃 — ClusterIP 섀도잉

DNS가 Service ClusterIP를 가리키면 이 매트릭스 전체가 조용히 무효화된다

L4 passthrough SE(48/50)의 host를 backend Service의 ClusterIP로 두면 curl은 200을 반환하는데 passthrough cluster의 카운터(upstream_cx_total)는 전혀 늘지 않는다 — Istio sidecar가 in-mesh Service ClusterIP마다 만들어 둔 전용 리스너SO_ORIGINAL_DST로 목적지를 가로채, 0.0.0.0:443의 SNI 캐치올보다 항상 먼저 매치되기 때문이다. §3~4의 모든 L4 측정은 DNS를 backend pod IP로 돌려서 이 함정을 피한 뒤에 나온 값이다. 겉으로는 전혀 티가 안 나는 함정이라(curl 200), GSLB/외부 도메인을 흉내내는 랩·테스트 환경 전반에서 재발할 수 있다 — 상세 메커니즘과 해결책은 TLS passthrough 대조 리포트 §2, DNS/GSLB 재현 랩 §8-3에 있다.


6. 프로덕션 시사점

조합 조심할 것
STRICT × L7 기본값처럼 다루기 좋다 — flip 시 in-flight는 보호되고, 완료 후엔 반드시 새 IP로 넘어간다. GSLB 추종이 빠르고 예측 가능해야 하는 경로에 적합.
LOGICAL × L7 in-flight는 안전하지만, 완료된 연결이 pool에 남아 다음 요청도 옛 IP로 갈 수 있다(stale pin). GSLB가 장애 failover 목적이면 이 지연이 치명적일 수 있다 — pinned backend가 죽어야만 재연결이 강제된다(재현 랩 mode3 참조).
STRICT × L4 in-flight는 안전하지만 membership 반영 자체가 client 쪽 DNS 갱신보다 느리다(+12s에도 아직) — 운영 중 “flip이 반영 안 됐다"고 오판하기 쉽다. 신뢰할 진단은 client dig가 아니라 cluster 레벨 proxy-config endpoints/membership_change.
LOGICAL × L4 in-flight는 안전하지만 membership이라는 신호 자체가 없다 — flip을 관측하려면 endpoint 표시(해석 캐시)를 봐야 하고, “membership이 안 바뀌었으니 flip이 안 됐다"는 판단은 틀렸다.
L4 passthrough 전반(둘 다) dead-IP 재시도가 sidecar 레벨에 없다(§4.4) — outlier detection + 클라이언트 자체 재시도를 반드시 별도로 갖춰야 한다. “TLS passthrough니까 사이드카가 알아서 해줄 것"이라는 가정은 이번에도 틀렸다.

참조

아카이브 내부

원시 리포트(repo)

  • docs/test-reports/2026-07-02_112113_dns-inflight-part2.md — L7 STRICT/LOGICAL in-flight 4점 스냅샷(§3 매트릭스 좌측 열의 출처)
  • docs/test-reports/2026-07-09_134250_dns-passthrough-tcp.md — L4 STRICT 대조(D-1/D-2/D-3, §3 매트릭스 우측 상단 및 §4.4의 출처)
  • docs/test-reports/2026-07-09_144251_dns-passthrough-logical-inflight.md — L4 LOGICAL in-flight(F-0, §3 매트릭스 우측 하단의 출처, 매트릭스 마지막 칸)
  • docs/test-reports/2026-07-01_dns-resolution.md §14 — 종합 리포트(위 세 리포트를 흡수해 재정리)

다운로드 가능 사본(fileshare)

Files