SE resolution(STRICT/LOGICAL) × TLS passthrough(L4) — in-flight 요청의 운명
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도 없다.
“요청 경계가 있다/없다"는 이 문서 전체를 관통하는 유일한 구조적 차이다. pool이 있으면 그 위에 drain 유예·재사용·stale pin 같은 커넥션 수명 정책이 얹힐 자리가 생긴다. pool이 없으면 그 자리 자체가 없다 — 있고 없고의 문제가 아니라 개념이 성립하지 않는다. §4가 이 차이를 4칸에 대입해서 푼다.
3. 실측 매트릭스
네 칸의 근거를 표로 펼치면 다음과 같다. 모두 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_DNSresolution: 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_DNSresolution: 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. 칸 사이 차이의 메커니즘
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 섀도잉
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니까 사이드카가 알아서 해줄 것"이라는 가정은 이번에도 틀렸다. |
참조
아카이브 내부
- ServiceEntry
resolution정본 —resolution→ cluster type 변환 규칙, dead-IP 처리, GSLB 권장 - DNS/GSLB resolution 재현 랩 §8-1(A, STRICT in-flight)·§8-2(B, dead-IP 지연세)·§8-3(D, TLS passthrough 대조) — 이 문서가 인용하는 실측의 랩 컨텍스트
- circuit-breaking 메커니즘(outlier detection 정본)
원시 리포트(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)
- F-0 리포트 · 50-serviceentry-passthrough-logical.yaml · dns-passthrough-inflight.sh(수정본) · 종합 리포트(§14-7 반영본)