mTLS Passthrough TCP 운영 종합 — connectionPool·port reuse·런북
egress mTLS Passthrough(HTTPS over mTLS, 용어 정본) 채널의 TCP 운영 설정을 한 문서에 모았다. 구성은 두괄식이다 — 문제 4가지(2장) → 답이 되는 전체 설정 한 벌(3장) → 문제별 원리·설정 상세(4~7장) 순서다. 각 주제의 정본은 문서 끝 참조 절에 있다.
대상 환경: Istio 1.30.0, sidecar mesh (homelab k8s 1.30.6 기준 컨벤션)
검증: 본문 주장 대부분은 소스 정본들의 클러스터 실측(T16·T52·T53·T55·T57·T91·T93 — 각 정본의 검증 기록 참조)을 인용한다. 이 문서 자체 실측은 gateway Helm chart의 securityContext 동작 1건(T94 — 문서 끝 “검증 기록”, 번들 files/verify/T94/)이다.
읽는 법: 바로 적용할 사람은 0장 요약과 3장(전체 설정)·8장(검증). 문제별 배경은 4~7장. 장애 대응은 9장 런북. 값을 조정하고 싶으면 부록 A.
0. 요약
egress gateway를 두면, 외부로 나가는 연결이 gateway 몇 대로 모인다. 그러면 전에는 없던 문제 4가지가 생긴다. 전부 “연결의 개수"와 “연결의 수명” 문제다.
| # | 문제 | 왜 생기나 | 해결 | 설정 위치 |
|---|---|---|---|---|
| P1 (4장) | 연결이 일정 개수에서 거부된다 | Envoy의 기본 상한이 목적지당 1,024개 | maxConnections를 직접 지정 |
DestinationRule |
| P2 (5장) | 새 연결을 못 만든다 (포트 부족) | 발신 포트가 pod당 약 28,000개뿐이고, 닫은 포트도 60초간 재사용 불가 | ① 앱 keep-alive ② gateway 증설 ③ 커널 설정(tcp_tw_reuse) |
gateway Helm chart |
| P3 (6장) | 한동안 안 쓰던 연결이 죽어 있다 | 방화벽이 유휴 연결을 조용히 끊는다. Envoy도 1시간이면 스스로 끊는다 | tcpKeepalive와 idleTimeout을 둘 다 설정 |
DestinationRule |
| P4 (7장) | 배포할 때마다 연결이 끊긴다 | 종료 전 대기(drain)가 기본 5초뿐 | drain 300초 + PDB | gateway Helm chart |
장애가 났을 때 넷 중 뭔지 구분하는 방법은 9장 런북에 있다. (다섯 번째 후보였던 conntrack은 위 한도들보다 훨씬 넓어서 일반 규모에선 도달하지 않는다 — 부록 B)
기억할 것 3개:
- DestinationRule은 2개 필요하다. 연결 설정은 “연결을 여는 쪽"에 붙는다. 앱→gateway 연결은 앱의 sidecar가 열고, gateway→외부 연결은 gateway가 연다. 그래서 구간마다 하나씩이다. (4장)
- 1,024는 “아무 설정도 안 했을 때” 기본값이다. 함정이 하나 있다: connectionPool을 일부만 설정하면, 나머지 상한은 1,024가 아니라 무제한으로 풀린다. 그래서
maxConnections는 항상 직접 적는다. (4장) - 시작값으로 시작한다. 이 문서의 4096, 300 같은 값은 일반 REST API 채널 기준의 시작값이다. 트래픽이 큰 채널만 부록 A의 계산식으로 다시 잡으면 된다.
0.1 용어 최소 사전 — 처음 읽는 사람용
| 용어 | 뜻 |
|---|---|
| sidecar | 앱 pod마다 함께 뜨는 프록시(Envoy). 앱의 모든 트래픽이 이걸 거쳐 나감 |
| egress gateway | 외부(파트너망·인터넷)로 나가는 트래픽을 한곳에 모아 내보내는 전용 프록시 pod |
| DestinationRule (DR) | “이 목적지로 갈 때는 이렇게 연결하라"를 정하는 Istio 리소스 — connectionPool이 여기 들어감 |
| connectionPool | 목적지로 여는 연결의 개수 상한·수명·타임아웃 규칙 묶음 |
| cluster (Envoy) | Envoy 내부에서 “목적지 하나"를 부르는 단위. DR 설정이 최종적으로 여기에 반영됨 |
| SNI | TLS 접속 첫 인사에 “이 도메인 찾아왔다"고 밝히는 필드. gateway가 채널을 구분하는 열쇠 |
| mTLS / SPIFFE 신원 | 메시 내부 상호 TLS — 워크로드마다 발급된 인증서로 “누가 보냈나"를 증명 |
| response flag | Envoy access log의 실패 원인 코드. 이 문서에선 UO(풀 초과)·UF(연결 실패) 두 개만 알면 됨 |
| TIME_WAIT | TCP 연결을 닫은 뒤 커널이 그 포트를 60초간 잠가두는 상태 — 포트 고갈의 주범 |
1. 패턴 개요 — 두 홉, 두 봉투
mTLS Passthrough(HTTPS over mTLS)가 푸는 문제는 하나다. 종단간 암호화는 건드리지 않으면서, 누가 나가는지만 egress에서 확인하고 싶다.
동작은 3단계다:
- 앱은 평소처럼
https://를 직접 호출한다. 이 TLS(inner)는 앱과 외부 서버 사이에서만 열린다. 중간의 누구도 풀지 않는다. - sidecar가 그 위에 메시 mTLS(outer)를 한 겹 더 입혀 gateway로 보낸다. (홉 1)
- gateway는 outer만 벗겨서 호출자의 신원(SPIFFE — 메시가 워크로드마다 발급하는 인증서 ID)을 확인한다. 안에 든 inner TLS는 풀지 않고 그대로 외부로 흘린다. (홉 2)
flowchart LR APP["app<br/>(inner TLS 생성)"] --> SC["sidecar<br/>(outer mTLS 래핑)"] SC -->|"홉 1: outer mTLS"| GW["egress gateway<br/>(outer만 벗겨 신원 확인)"] GW -->|"홉 2: TCP 그대로 전달"| EXT["외부 서버<br/>(inner TLS 종단)"]
와이어 어디에도 평문 구간은 없다. “홉 2가 일반 TCP"라는 말은 gateway가 TCP로 그대로 전달한다는 뜻이지, 암호화가 없다는 뜻이 아니다.
2. 문제 4가지 — 증상부터
gateway를 도입하면 이 넷 중 하나는 만난다. 증상으로 먼저 구분한다.
| # | 증상 | gateway 쪽 기록 | 원인 한 줄 |
|---|---|---|---|
| P1 | 특정 채널 호출이 일정 개수에서 거부된다 | access log flag UO |
Envoy 기본 상한 1,024에 전사 트래픽이 부딪힘 |
| P2 | 외부로 새 연결 자체가 안 열린다 | flag UF + 로그 EADDRNOTAVAIL |
발신 포트 고갈 — pod당 약 28,000개, 닫은 포트는 60초 잠김 |
| P3 | 유휴 후 첫 요청만 실패 / 정확히 1시간마다 절단 | 기록 없음 — 시간 패턴으로 구분 | 방화벽 유휴 절단 + Envoy idleTimeout |
| P4 | 배포·스케일 인마다 일괄 reset | upstream_cx_destroy 급증 |
종료 전 대기(drain)가 기본 5초 |
어느 문제부터 만나나 — 앱의 연결 사용 방식이 정한다
앱이 연결을 쓰는 방식은 둘 중 하나다.
- 요청마다 열고 닫는다 (단발성). 열린 연결은 안 쌓인다. 대신 닫은 포트(TIME_WAIT)가 쌓인다. → P2부터 만난다.
- 연결을 계속 들고 쓴다 (keep-alive). 포트는 안 마른다. 대신 열린 연결이 쌓인다. → P1부터 만난다.
근데 P2의 근본 처방이 “앱을 keep-alive로 바꾸는 것"이다(5장). 처방을 따르는 순간 앱은 2번 방식이 되고, 이번엔 P1 쪽 상한이 일하기 시작한다. P3·P4는 그렇게 수명이 길어진 연결의 후속 문제다. 그래서 골라 잡는 게 아니라 넷을 세트로 잡는다.
3. 답 — 전체 YAML 한 벌 (복사해서 시작)
2장의 문제 4가지를 전부 막는 설정이다. 예시 채널: api.partner-a.example.com:443. 값은 전부 시작값이다. (조정은 부록 A)
각 값이 왜 필요한지는 주석의 P1P4 표시를 따라 47장에서 찾는다.
채널마다 넣는 것(레이어 1 — CRD 5종)과, 전역으로 한 번 넣는 것(레이어 2 — gateway Helm chart)으로 나뉜다.
레이어 1 — 배선 + DR 2개 (채널당 1세트)
# 1) ServiceEntry — 외부 host 등록 (protocol TLS: gateway가 L7 파싱 안 함)
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata: { name: partner-a, namespace: istio-system }
spec:
hosts: [api.partner-a.example.com]
ports:
- { number: 443, name: tls, protocol: TLS }
resolution: DNS
location: MESH_EXTERNAL
---
# 2) Gateway — outer ISTIO_MUTUAL 종단 (protocol TLS — HTTPS 아님, inner는 opaque)
apiVersion: networking.istio.io/v1
kind: Gateway
metadata: { name: egress-partner-a, namespace: istio-system }
spec:
selector: { istio: egressgateway }
servers:
- port: { number: 8443, name: tls-partner-a, protocol: TLS }
hosts: [api.partner-a.example.com]
tls: { mode: ISTIO_MUTUAL } # client cert 강제 + SPIFFE 검증 + outer만 종단
---
# 3) DR-hop1 — outer mTLS 래핑 + 홉 1 풀 (호출자 sidecar cluster에 컴파일)
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata: { name: egressgateway-partner-a, namespace: istio-system }
spec: # exportTo 없음(기본 *) — 소비자 = 모든 클라이언트 ns sidecar
host: istio-egressgateway.istio-system.svc.cluster.local # 레지스트리 FQDN과 일치 필수
subsets:
- name: partner-a
trafficPolicy:
portLevelSettings:
- port: { number: 443 } # Service 포트 기준
tls:
mode: ISTIO_MUTUAL # sidecar가 SPIFFE cert 제시
sni: api.partner-a.example.com # gateway filter chain 매칭 키
connectionPool: # tls와 같은 포트 엔트리에 — 통째 교체 규칙(4장)
tcp:
maxConnections: 4096 # "클라이언트 pod당" 상한 — 여유 있게
connectTimeout: 3s # 클러스터 내부 홉이므로 짧게
tcpKeepalive: { time: 300s, interval: 30s, probes: 3 }
---
# 4) DR-hop2 — tls 블록 없음(raw TCP), 홉 2 풀 전용 (gateway cluster에 컴파일)
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata: { name: partner-a-tcp-pool, namespace: istio-system }
spec:
host: api.partner-a.example.com # ServiceEntry hosts와 정확 일치
exportTo: ["."] # 타 ns sidecar 오적용 차단
trafficPolicy:
connectionPool:
tcp: # http.* 는 TCP 라우트라 무의미 — tcp만
maxConnections: 4096 # P1: 전사 연결이 모이는 곳
connectTimeout: 3s # 외부 장애 시 빠른 실패 (기본 10s)
idleTimeout: 1800s # P3: 채널 최장 유휴보다 길게 (keepalive로 못 막음)
maxConnectionDuration: 3600s # P3/P4: 수명 상한 — 재분배·drain 자연 소멸
# (재연결 민감 채널은 제외하거나 길게)
tcpKeepalive: { time: 300s, interval: 30s, probes: 3 } # P3: 방화벽 유휴 제한의 1/3
---
# 5) VirtualService — leg-1 tls(SNI 생존) / leg-2 tcp(SNI 소비 후 raw relay)
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata: { name: partner-a-via-egress, namespace: istio-system }
spec:
hosts: [api.partner-a.example.com]
gateways: [mesh, istio-system/egress-partner-a]
tls:
- match:
- gateways: [mesh]
port: 443
sniHosts: [api.partner-a.example.com] # 앱 ClientHello의 SNI로 매치
route:
- destination:
host: istio-egressgateway.istio-system.svc.cluster.local
subset: partner-a # -> DR-hop1
port: { number: 443 }
tcp:
- match:
- gateways: [istio-system/egress-partner-a]
port: 8443
route:
- destination:
host: api.partner-a.example.com # -> DR-hop2 (tls 없음 = raw forward)
port: { number: 443 }
문자열 정렬 체크리스트 — 한 글자 어긋나면 에러 없이 조용히 깨진다:
- 외부 host:
SE.hosts=GW.servers.hosts=VS.hosts= VS leg-1sniHosts= DR-hop1sni= DR-hop2host - gateway 대상: DR-hop1
host(레지스트리 FQDN) = VS leg-1destination.host - subset: DR-hop1
subsets[].name= VS leg-1destination.subset - 포트 체인: Service 443 → targetPort 8443 =
GW.port= VS leg-2 match port
레이어 2 — gateway Helm chart values (전역 1벌)
replicaCount: 3 # 포트 공간 = 28k × replica (P2)
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 6
targetCPUUtilizationPercentage: 70 # 연결 중심 부하는 CPU와 상관이 약함 —
# upstream_cx_active 기반 custom metric 권장
affinity:
podAntiAffinity: # SNAT 환경에선 required가 필수 (5장)
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector: { matchLabels: { istio: egressgateway } }
topologyKey: kubernetes.io/hostname
nodeSelector: { node-role/egress: "true" } # 전용 노드풀 = 방화벽 등록 IP 안정화
tolerations:
- { key: node-role/egress, operator: Exists }
securityContext: # pod-level — P2 커널 파라미터 (차트 함정은 5장)
sysctls:
- { name: net.ipv4.ip_unprivileged_port_start, value: "0" }
- { name: net.ipv4.ip_local_port_range, value: "10240 60999" }
- { name: net.ipv4.tcp_tw_reuse, value: "1" } # kubelet·PSA 관문 선행 (5장)
podAnnotations:
proxy.istio.io/config: |
terminationDrainDuration: 300s # P4
proxyMetadata:
EXIT_ON_ZERO_ACTIVE_CONNECTIONS: "true"
service:
type: ClusterIP # egress는 외부 노출 불필요
ports:
- { name: status-port, port: 15021, targetPort: 15021 }
- { name: tls-egress, port: 443, targetPort: 8443 }
(+ P4의 PDB는 7장, tcp_tw_reuse 관문 2개는 5장 참조)
4. P1 — 연결이 일정 개수에서 거부된다
문제
특정 채널 호출이 갑자기 거부된다. gateway access log에 flag UO가 찍히고 envoy_cluster_upstream_cx_overflow 지표가 오른다.
원인: connectionPool을 안 걸면 Envoy 내장 기본 상한이 목적지당 동시 1,024개다. sidecar 시절엔 pod 하나가 한 목적지로 1,024개를 열 일이 없어서 한 번도 안 보이던 벽인데, gateway에선 전사 트래픽이 한곳에 모이니 바로 부딪힌다.
해결
maxConnections를 직접 지정한다. 시작값 4096. (근거와 재계산은 부록 A)
근데 한 군데가 아니라 두 군데에 적어야 한다. 이유는 아래.
상세 — DR이 2개인 이유
istiod는 DR을 “그 목적지를 향한 cluster 설정"으로 컴파일해서 프록시에 내려주는데, cluster는 프록시마다 따로 있다. 그래서 설정이 실제로 먹는 곳은 그 목적지로 연결을 여는 프록시다.
flowchart LR DR1["DR-hop1<br/>(host: gateway)"] -.->|컴파일| SC["호출자 sidecar의 cluster<br/>→ pod당 상한"] DR2["DR-hop2<br/>(host: 외부 host)"] -.->|컴파일| GW["gateway의 cluster<br/>→ 전사 합류 상한"]
| DR-hop1 (sidecar→gw) | DR-hop2 (gw→외부) | |
|---|---|---|
host |
gateway Service의 레지스트리 FQDN (= istioctl proxy-config cluster 출력에 보이는 전체 이름) |
ServiceEntry의 외부 host |
tls |
ISTIO_MUTUAL + sni (outer 봉투를 만든다) |
블록 자체가 없다 (TCP 그대로 전달) |
| connectionPool 적용 위치 | 호출자 sidecar — 값은 “클라이언트 pod당” 상한 | gateway — 전사 연결이 모이는 곳, 튜닝의 핵심 지점 |
exportTo |
안 적는다 (mesh 전체 공개 — 모든 호출 ns의 sidecar가 써야 함) | ["."] (쓰는 건 gateway뿐) |
정말 그런가? 확인하는 방법이 있다. 두 DR의 maxConnections에 서로 다른 식별값을 넣고 양쪽 프록시의 cluster를 덤프해 보면, 각 값이 자기 프록시에만 나타난다. (8장, 실측 T91)
규칙 3개:
tls.mode: tcp는 없다. enum은DISABLE/SIMPLE/MUTUAL/ISTIO_MUTUAL4개뿐이고, apply하면 CRD가 거부한다. 홉 2의 “일반 TCP"는 mode를 고르는 게 아니라 tls 블록을 빼는 것이다. (DR을 안 만들거나 / tls 없이 풀만 있는 DR /DISABLE명시 — 셋 다 같다)http.*풀 설정은 조용히 무시된다. 두 홉 다 TCP 라우트라 HTTP 풀 개념이 없다. 연결 관리는tcp.idleTimeout,tcp.maxConnectionDuration,tcp.tcpKeepalive로만 한다.- DR의
host가 Istio가 아는 이름과 다르면 에러 없이 통째로 무시된다. 증상은 “설정했는데 기본값"이다.istioctl proxy-config cluster에 보이는 이름 기준으로 맞춘다.
상세 — 기본값 함정 2개 (실측으로 확정)
-
기본값이 둘이다. “기본 1,024"는 connectionPool을 아예 안 걸었을 때 얘기다. connectionPool에 아무 필드든 하나라도 걸면, 생략한 상한은 1,024가 아니라 무제한으로 바뀐다:
상태 maxConnections 실효값 DR/connectionPool 자체가 없음 1,024 — Envoy 내장 기본 connectionPool을 걸고 maxConnections 생략 4294967295 ≈ 무제한 — Istio가 명시 주입 명시 명시한 값 idleTimeout하나 넣자고 connectionPool을 만들면 1,024 안전망이 조용히 사라진다. 그래서 connectionPool을 일부만 도입할 때도maxConnections는 같이 적는다. -
subset과 portLevelSettings는 병합이 아니라 통째 교체다. subset에 설정을 쓰면 상위 설정을 필드 단위로 물려받지 않는다.
connectionPool,tls같은 덩어리 단위로 교체된다. 포트 엔트리에tls만 적고 connectionPool을 빼먹으면 그 포트의 풀은 기본값으로 돌아간다. 트래픽이 타는 포트 엔트리에 전부 다시 적는다.
상세 — 이 상한은 평소에 걸리라고 두는 값이 아니다
maxConnections가 세는 건 “지금 열려 있는 연결"뿐이다. 닫고 버린 포트(TIME_WAIT)는 안 센다.
그래서 요청마다 열고 닫는 단발성 채널에선 이 상한이 평소에 놀고 있는 게 정상이다. (그 채널의 진짜 위험은 포트 고갈 — 5장. 그건 이 상한이 못 막는다)
그럼 왜 두나? 외부 서버가 느려지는 날 때문이다.
- 열려 있는 연결 수는 대략 “초당 새 연결 × 연결 수명"이다.
- 평소: 초당 500개 × 0.05초 = 동시 25개. 상한 근처에도 안 간다.
- 외부 응답이 5초로 늘어진 날: 500 × 5 = 2,500개. 순식간에 쌓인다.
응답이 안 오면 연결을 못 닫는다. 단발성 앱도 이 순간엔 “오래 들고 있는” 앱이 된다. 이때 gateway가 연결을 무한정 쌓아 같이 죽는 걸 이 상한이 끊는다. 재시도 폭풍(실패 → 앱 일제 재시도 → 연결 시도 급증)도 같은 방식으로 막는다. (퓨즈와 같다 — 평소에 안 끊어진다고 빼지 않는다. connectTimeout: 3s도 같은 목적의 세트다)
keep-alive 채널은 다르다. 평시에도 열린 연결이 쌓이니, 여기선 이 상한이 장애 대비가 아니라 평시 용량 설계값이다. (부록 A: 최대 동시 연결 × 2~3)
핵심 설정만 뽑으면 이렇다 (전체 YAML은 3장):
# DR-hop2 (gateway→외부) — 전사 연결이 모이는 곳, 핵심
connectionPool:
tcp:
maxConnections: 4096 # 시작값 — 큰 채널은 부록 A로 재계산
connectTimeout: 3s # 외부 장애 시 빠른 실패 (기본 10s)
# DR-hop1 (sidecar→gateway)의 subset에도 — pod당 상한이라 여유 있지만, 숨은 1,024는 동일
connectionPool:
tcp: { maxConnections: 4096 }
확인: istioctl proxy-config cluster로 양쪽 값 확인(8장). 알람은 upstream_cx_overflow(9장).
5. P2 — 새 연결을 못 만든다 (포트 고갈)
문제
gateway에서 외부로 connect가 실패한다. access log에 flag UF, gateway 로그에 EADDRNOTAVAIL.
원인은 포트다. 아래 원리 2개가 산수로 정해버린 한계라, 규모가 커지면 어떤 채널이든 만난다.
원리 1 — gateway는 연결을 재사용하지 못한다
gateway는 연결을 “통과"시키는 장치가 아니다. 양쪽에 연결을 하나씩 잡고, 가운데서 바이트를 복사하는 중계기다.
flowchart LR APP["app"] -->|"연결 A"| GW["gateway<br/>소켓 2개: A 받기 + B 열기"] GW -->|"연결 B (새로 엶)"| EXT["외부 서버"]
앱이 연결 1개를 만들면 gateway에는 소켓이 2개 잡힌다. A를 받는 소켓 하나, 외부로 새로 여는 B의 소켓 하나. 그리고 B를 열 때마다 발신 포트를 1개씩 쓴다.
L7 proxy라면 B 하나를 여러 앱의 요청이 나눠 탈 수 있다. (요청의 경계를 아니까) 근데 이 패턴의 gateway가 보는 건 암호화된 바이트 덩어리다. 내용을 모르니 섞을 수 없다. 그래서 A 1개당 B 1개, 1:1이 강제다. (PASSTHROUGH든 ISTIO_MUTUAL이든 같다)
이 1:1은 닫을 때도 그대로다. 앱이 A를 닫으면 gateway도 B를 닫는다. 앱이 연결을 아끼면 gateway 포트도 아껴지고, 앱이 낭비하면 gateway가 대신 아껴줄 방법이 없다.
원리 2 — 발신 포트는 유한하다
TCP 연결은 (출발 IP, 출발 포트, 목적지 IP, 목적지 포트) 4개로 구분된다. 외부 API 호출에서 목적지는 고정이고, 출발 IP도 gateway pod IP로 고정이다. 그러면 변수는 출발 포트 하나뿐이다.
-
동시 연결 한계: 커널이 발신 연결에 빌려주는 임시 포트(ephemeral port)는 기본 약 28,000개다 (범위 32768–60999). pod 1개, 목적지 1개당 이게 전부다.
-
초당 연결 한계: 닫은 포트는 바로 반납되지 않는다. TIME_WAIT 상태로 60초간 잠긴다. (커널 고정값 — 방금 닫은 연결의 지연 패킷이 같은 포트의 새 연결에 끼어드는 걸 막는 격리 시간) 그래서 초당 r개씩 새로 열면, 어느 순간이든 잠겨 있는 포트가 r × 60개다. 이게 28,000을 넘으면 빌려줄 포트가 없다:
r × 60초 ≤ 28,000 → r ≤ 약 470개/초(비유: 컵 28,000개짜리 대여점인데 반납된 컵은 소독에 60초 걸린다. 초당 470명까지만 지속 가능하다)
-
예: pod 500개가 keep-alive 없이 초당 1번씩 호출하면 gateway에 초당 500개가 모인다. gateway 1대면 몇십 초 안에 포트가 마른다.
해결 (우선순위 순)
- 앱 keep-alive — 유일한 근본 처방. keep-alive는 응답이 끝나도 연결을 닫지 않는 약속이다. 다음 요청이 그 연결을 그대로 탄다. 새 연결이 없으면 새 포트도 없고, TIME_WAIT도 안 쌓인다. 위 산술 자체가 무의미해진다. TIME_WAIT 알람이 울리면 인프라 튜닝 전에 호출 라이브러리의 connection reuse 설정부터 본다.
(여기서 keep-alive는 HTTP 연결 재사용이다. 6장의
tcpKeepalive— 유휴 연결에 보내는 생존 신호 — 와는 이름만 비슷한 다른 것이다) - gateway 증설 — 포트 공간이 replica 수만큼 늘어난다. 단 SNAT 환경이면 노드를 갈라야 효과가 있다 (아래).
- 커널 파라미터 — 포트 범위 확장(
ip_local_port_range) + TIME_WAIT 포트 재사용(tcp_tw_reuse).
처방 1을 따르면 앱은 “연결을 들고 쓰는” 쪽이 된다. 그때부터는 4장의 maxConnections가 평시에도 일하는 세상이다. P1·P2가 세트인 이유다(2장).
상세 1 — SNAT부터 확인한다
포트 계산은 “출발 IP = gateway pod IP"가 전제다. CNI가 노드를 나갈 때 SNAT하면(예: Calico natOutgoing: true) 출발 IP가 노드 IP로 바뀐다. 그러면 둘이 달라진다:
- 같은 노드의 gateway pod들이 노드 IP의 포트를 나눠 쓴다. replica를 늘려도 같은 노드면 효과가 없다. 그래서
podAntiAffinity가 필수가 된다. - 방화벽에 등록하는 단위가 바뀐다 (pod 대역 ↔ 노드 IP). 용량 계산과 보안 신청을 같은 설정이 동시에 결정하니, 도입 전에 가장 먼저 확인한다.
상세 2 — 커널 파라미터는 gateway Helm chart로 넣는다
커널 파라미터는 노드가 아니라 pod 네트워크 네임스페이스(netns) 값이다. 새 netns는 호스트 값이 아니라 커널 기본값으로 시작한다. 그래서 노드 /etc/sysctl.d에 넣어도 gateway pod에는 안 먹는다. 넣는 곳은 gateway Helm chart의 securityContext 하나다. (istio 1.30 istio/gateway 차트에서 pod securityContext로 그대로 렌더링되는 것 실측 확인 — T94)
차트 함정 (1.30 실측, T94):
securityContext를 안 넣으면 차트가 저권한 포트 바인딩용sysctls: [net.ipv4.ip_unprivileged_port_start=0]을 자동으로 넣어준다. 근데 values에securityContext를 직접 넣는 순간 이 자동 기본이 통째로 사라진다. 그래서 아래처럼ip_unprivileged_port_start를 같이 다시 적어야 한다. (빼먹으면 runAsNonRoot인 gateway가 1024 미만 포트를 못 연다)
# gateway Helm values 중 P2 관련 부분 (전체는 3장)
affinity:
podAntiAffinity: # SNAT 환경에선 required가 필수
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector: { matchLabels: { istio: egressgateway } }
topologyKey: kubernetes.io/hostname
securityContext: # pod-level 커널 파라미터
sysctls:
- { name: net.ipv4.ip_unprivileged_port_start, value: "0" } # 차트 자동 기본 재기재 (위 함정)
- { name: net.ipv4.ip_local_port_range, value: "10240 60999" } # safe: 포트 풀 28k -> 50k
- { name: net.ipv4.tcp_tw_reuse, value: "1" } # unsafe: 관문 2개 선행 (아래)
tcp_tw_reuse=1은 Helm values에 적는 것만으로 안 먹는다. 관문 2개를 먼저 열어야 하고, 빼먹으면 pod가 SysctlForbidden으로 뜬다:
# (1) kubelet: 해당 노드 그룹에 allowedUnsafeSysctls 허용 (safe 목록에 없는 sysctl이라서)
kubelet_config_extra_args:
allowedUnsafeSysctls: ["net.ipv4.tcp_tw_reuse"]
# (2) 네임스페이스 PSA(Pod Security Admission) 정책이 unsafe sysctl을 허용하는지 확인
# 의미: 나가는 연결에 TIME_WAIT 포트 재사용 허용 (TCP timestamps 켜져 있어야 함, 기본 on)
# 참고: ip_local_port_range·ip_unprivileged_port_start는 safe sysctl — 관문 없이 선언만으로 적용
확인:
kubectl -n istio-system exec deploy/istio-egressgateway -- \
cat /proc/sys/net/ipv4/tcp_tw_reuse /proc/sys/net/ipv4/ip_local_port_range
# 기대: 1 / 10240 60999
알람은 TIME_WAIT 개수(9장). 2만을 넘으면 커널 튜닝이 아니라 앱 keep-alive 캠페인 신호다.
6. P3 — 한동안 안 쓰던 연결이 죽어 있다
문제
증상이 두 갈래다. 원인이 다르니 구분해야 한다:
- 유휴 후 첫 요청만 실패한다 — 방화벽이 오래 조용한 연결을 세션 테이블에서 조용히 지운 것. 연결 양쪽은 살아 있다고 믿는데 중간이 끊겨 있다. 트래픽 적은 채널에서 먼저 터진다. (바쁜 채널은 유휴해질 틈이 없다)
- 정확히 N초(기본 1시간)에 끊긴다 — Envoy 자신의
idleTimeout이다.
keep-alive로 연결을 오래 쓰기 시작하면(5장) 반드시 따라오는 문제다. 연결이 오래 살수록 유휴 구간도 생기니까.
해결
원인이 둘이라 설정도 둘 다 필요하다:
- 방화벽 몫:
tcpKeepalive— 주기적으로 안부 패킷(probe)을 보내 방화벽 세션 타이머를 갱신한다. (5장의 앱 keep-alive와는 다른 물건이다 — 그건 연결 재사용, 이건 살아 있는 연결의 생존 신호) - Envoy 몫:
idleTimeout— keepalive probe는 데이터가 아니라서 Envoy의 idle 타이머를 못 돌려놓는다 (실측 확인). “keepalive 넣었는데 정확히 1시간마다 끊긴다"가 바로 이 경우다. 채널의 최장 유휴 시간보다 길게 직접 정한다.
상세 — keepalive 세 필드
| DR 필드 | 소켓 옵션 | 역할 | 시작값 |
|---|---|---|---|
time |
TCP_KEEPIDLE | 방화벽 세션 유지 — 유휴 time초 후 probe(빈 ACK)를 보내고, 그 왕복이 방화벽 타이머를 갱신 | 300 (방화벽 유휴 제한의 1/3 이하 — 부록 A) |
interval |
TCP_KEEPINTVL | 죽은 상대 감지 — 무응답이면 이 간격으로 재시도 | 30 |
probes |
TCP_KEEPCNT | 연속 무응답이 이 횟수면 죽었다고 보고 폐기 | 3 (30×3=90초 안에 죽은 연결 정리) |
Envoy는 연결을 열 때 소켓 옵션(setsockopt)만 걸고, probe는 커널이 보낸다. 커널 sysctl 값은 안 바뀐다. 그리고 이미 열려 있는 연결에는 소급되지 않는다. (기존 연결은 idleTimeout이나 maxConnectionDuration으로 자연 교체되게 한다)
# DR-hop2 (gateway→외부)에 추가 (전체는 3장)
connectionPool:
tcp:
idleTimeout: 1800s # Envoy 몫 — 채널 최장 유휴보다 길게
maxConnectionDuration: 3600s # 수명 상한 — 재분배·drain 자연 소멸 (재연결 민감 채널은 제외)
tcpKeepalive: { time: 300s, interval: 30s, probes: 3 } # 방화벽 몫
(선택) 채널별 DR 없이 전역으로
mesh 전체에 기본 keepalive를 깔 수도 있다. (DR이 있으면 DR이 이긴다)
meshConfig:
tcpKeepalive: { time: 300s, interval: 30s, probes: 3 }
# time은 "모든 채널의 방화벽 유휴 제한 중 최솟값의 1/3 이하"로 — 전역이라 채널별 조정 여지가 없다
주의: pod sysctl(net.ipv4.tcp_keepalive_*)만으로는 Envoy 연결에 keepalive가 아예 안 켜진다.
sysctl은 값만 정하고, 켜는 스위치(SO_KEEPALIVE)는 소켓을 만든 쪽이 걸어야 한다. Envoy는 설정 없이는 안 건다.
확인: 소켓의 timer 필드를 직접 본다 (8장의 ② — sysctl로 확인하면 보는 곳이 틀린 거다).
7. P4 — 배포할 때마다 연결이 끊긴다
문제
재배포나 스케일 인 시점마다 클라이언트가 일괄 reset을 받는다.
원인: gateway 종료 시 살아 있는 연결을 기다려주는 시간(terminationDrainDuration)이 기본 5초뿐이다. 수명이 긴 연결에겐 너무 짧다.
해결
drain을 늘리고, 동시에 죽는 pod 수를 제한하고(PDB), 연결 수명에 상한을 둔다. (P3의 maxConnectionDuration이 여기서도 일한다 — 수명 상한이 있으면 drain 시간 안에 자연 소멸된다)
상세
# gateway Helm values 중 P4 관련 부분 (전체는 3장)
podAnnotations:
proxy.istio.io/config: |
terminationDrainDuration: 300s # 기본 5s -> 300s
proxyMetadata:
EXIT_ON_ZERO_ACTIVE_CONNECTIONS: "true" # drain 중 연결 0이면 조기 종료
# PDB (gateway 차트가 안 만들어줌 — 별도 적용)
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata: { name: egressgateway, namespace: istio-system }
spec:
minAvailable: 2
selector: { matchLabels: { istio: egressgateway } }
확인: 카나리 배포 중 envoy_cluster_upstream_cx_destroy 급증 여부와 클라이언트 에러율.
8. 적용 검증 — 확인할 pod를 헷갈리지 말 것
홉 1은 클라이언트 sidecar에서, 홉 2는 gateway pod에서 확인한다. 반대로 잡으면 영원히 “미적용"으로 보인다.
설정은 이 순서로 흐른다. 순서대로 확인하면 어디서 끊겼는지 바로 나온다:
flowchart LR DR["DR"] --> XDS["istiod<br/>(xDS)"] --> CL["Envoy<br/>cluster"] --> SOCK["커널<br/>소켓"] --> WIRE["와이어<br/>probe"]
① xDS — Envoy cluster에 도달했나
# 홉 1 — 클라이언트 sidecar: 풀 + ISTIO_MUTUAL + sni가 subset cluster에 붙었나
istioctl proxy-config cluster deploy/<client> -n <app-ns> \
--fqdn istio-egressgateway.istio-system.svc.cluster.local -o json | \
jq '.[] | select(.name | contains("|partner-a|")) |
{name, dr: .metadata.filterMetadata.istio.config,
max: .circuitBreakers.thresholds[0].maxConnections,
ka: .upstreamConnectionOptions.tcpKeepalive,
sni: .transportSocket.typedConfig.sni}'
# 홉 2 — gateway pod: 풀은 붙고 transportSocket은 "없어야" 정상 (raw TCP의 표식)
istioctl proxy-config cluster deploy/istio-egressgateway -n istio-system \
--fqdn api.partner-a.example.com -o json | \
jq '.[] | {name, max: .circuitBreakers.thresholds[0].maxConnections,
ka: .upstreamConnectionOptions.tcpKeepalive,
transportSocket: (.transportSocket // "ABSENT (raw TCP)")}'
읽는 법:
dr: null이면 값 이전의 문제다. DR이 cluster에 아예 안 붙은 것이다. (host 불일치, exportTo, 중복 DR, pod를 잘못 잡음)max: 4294967295면 connectionPool이 그 cluster에 안 닿은 것이다. (미설정 상한에 주입되는 값)- subset 줄만
ka: null이면 통째 교체 함정이다. subset에 tcpKeepalive를 다시 안 적은 것이다.
② 소켓 — 커널에 실제로 박혔나
DR의 keepalive는 sysctl이 아니라 소켓 옵션으로 들어간다. 그래서 sysctl로 확인하면 보는 곳이 틀린 거다. ss -o의 timer 필드를 본다:
kubectl exec deploy/istio-egressgateway -n istio-system -- \
ss -tno state established 'dst <외부IP>'
# 기대: timer:(keepalive,4min32sec,0) ← 300초에서 카운트다운 = time:300 적용 확정
# 미적용: timer가 없거나(SO_KEEPALIVE off), 2시간 근처(커널 기본 7200s)
③ 경유 증명 — 200이 아니라 이게 완료 조건이다
VS 매칭이 어긋나면 sidecar가 gateway를 건너뛰고 직접 나간다 (outboundTrafficPolicy: ALLOW_ANY 기준).
호출은 200이라 멀쩡해 보이는데, 신원 확인은 한 겹도 안 된 상태다. 그래서 gateway access log로 경유를 증명해야 끝난 거다:
kubectl -n istio-system logs deploy/istio-egressgateway --tail=5 | \
grep 'outbound|443||api.partner-a.example.com'
④ (선택) 와이어 — probe가 실제로 나가나
# gw 노드에서: 유휴 300초 후 length 0 ACK → 상대의 즉시 ACK, 이후 300초 주기 반복
tcpdump -ni any host <외부IP> and port 443
9. 운영 — 장애 런북과 알람
9.1 원인 구분 — 클라이언트 증상은 다 똑같다
풀 초과, 포트 고갈, 인가 거부, 설정 오류가 클라이언트에선 전부 “SSL_ERROR_SYSCALL / Connection reset"류로 보인다. 구분은 gateway 쪽 기록으로만 가능하다. 이 표가 없으면 정상 통제 동작을 장애로 오인한다:
| gateway 쪽 기록 | 원인 | 대응 |
|---|---|---|
access log flag UO |
Envoy 연결 풀 초과 (P1) | maxConnections 재계산 |
access log flag UF |
gw→외부 connect 실패 — 포트 고갈(P2) 또는 외부 장애 | ss -tan state time-wait | wc -l 확인 |
rbac 로그 denied |
AuthorizationPolicy 거부 — 정상 통제 동작 | 정책 확인 |
gw 로그 미도달 + sidecar에 PassthroughCluster |
mesh 쪽 VS 미매칭 (sniHosts 오타, sidecar 미주입) | 정렬 체크리스트(3장) 대조 |
| handshake 즉시 실패 | DR sni 누락/오타, 비-mesh 클라이언트 |
정렬 체크리스트 대조 |
| 무응답 timeout (reset조차 없음) | conntrack 유실 — 대규모에서만 (부록 B) | 노드 dmesg + conntrack -C |
| 유휴 후 첫 요청만 실패 | 방화벽이 유휴 연결을 이미 끊어둠 (P3) | keepalive.time 재점검 |
| 정확히 N초(기본 1h)에 절단 | Envoy idleTimeout — keepalive로는 안 막힘 (P3) |
idleTimeout 직접 설정 |
진단 순서: ① gw access log(거쳐갔나) → ② rbac 로그(왜 거부됐나) → ③ istioctl proxy-config listeners·istioctl analyze(설정 정합) → ④ ss·conntrack(커널). tcpdump는 최후 수단이다.
9.2 알람 (Prometheus) — 문제별 1:1
| 알람 | 조건 | 의미 |
|---|---|---|
| 연결 거부 | rate(envoy_cluster_upstream_cx_overflow[5m]) > 0 |
P1 발동 — 재계산 필요 |
| 풀 사용률 | upstream_cx_active / maxConnections > 0.8 |
P1 선제 증설 신호 |
| connect 실패 | rate(envoy_cluster_upstream_cx_connect_fail[5m]) > 0 |
P2 또는 외부 장애 |
| TIME_WAIT | egress 노드 node_sockstat_TCP_tw > 20000 |
연결 생성·소멸 과다 → 앱 keep-alive 캠페인 |
| conntrack | node_nf_conntrack_entries / limit > 0.7 |
노드 전역 자원 경계 신호 (부록 B) — 울리기 전엔 조치 불필요 |
9.3 그 밖의 운영 주의 4개
- maxConnections를 무한정 키우면 격벽이 사라진다. 외부 기관 한 곳이 느려지면 그 cluster에 연결이 쌓이고, gateway 메모리가 마르면 다른 목적지까지 번진다. (메모리 부담 = 동시 연결 × 소켓 2개 × 버퍼 약 1MiB)
- 재연결에 민감한 채널(전문망, long-lived 스트림)에는 시작값을 그대로 쓰면 안 된다.
maxConnectionDuration을 빼거나 늘리고, drain도 더 늘린다. 시작값은 일반 REST API 파트너 기준이다. - 도입 전에 기준선을 잰다. 직접 나갈 때 대비 p99 연결 수립 시간, 초당 연결, TIME_WAIT 추이, gateway CPU. “gateway 때문에 느려졌다” 논쟁을 데이터로 끝내는 장치다.
- 상대 기관 입장도 있다. 파트너 서버가 보는 출발 IP가 몇 개로 줄어든다. 상대쪽 IP 기반 rate limit이나 이상탐지에 걸릴 수 있으니 미리 알려준다.
참고 둘: outlierDetection은 단일 IP 목적지에선 기본값(ejection 상한 10%)에 막혀 사실상 무력하다 — DNS가 IP를 여러 개 줄 때만 검토한다. connectTimeout은 HTTP 경로에선 Istio 기본 재시도 2회와 곱해져 최악 지연이 3배가 된다 — 이 패턴의 두 홉(TCP)에는 해당 없지만, 같은 값을 HTTP 경로에 재사용할 때는 같이 계산한다.
부록 A — 값을 조정하고 싶을 때 (측정과 계산)
시작값이 안 맞는 큰 채널은 3개를 측정해서 다시 계산한다. 측정은 gateway 도입 전에 sidecar 메트릭에서:
① 채널별 최대 동시 연결 + 초당 새 연결
istio_tcp_connections_opened_total{destination_service="<외부host>"} → 최대치, rate()로 conn/s
② 방화벽/중간장비의 유휴 제한 시간 — 네트워크팀 확인 (보통 30~60분)
③ SNAT 여부 — CNI 설정 확인 (5장)
flowchart LR I1["① 최대 동시 연결<br/>예: 1,500"] -->|"× 2~3 여유"| O1["maxConnections<br/>4096"] I2["① 초당 새 연결<br/>예: 250"] -->|"÷ 470 + 이중화"| O2["replicaCount<br/>3"] I3["② 방화벽 유휴 제한<br/>예: 1800s"] -->|"× 1/3 이하"| O3["keepalive.time<br/>300"]
idleTimeout은 채널의 최장 유휴 시간보다 길게 잡는다. (시작값 1800s)
채널 온보딩 시트 — 시트에는 값이 아니라 계산식을 적는다. 다음 사람이 숫자를 복사하는 사고를 막는 장치다:
| 채널 | 최대 동시 연결 (측정) | conn/s (측정) | maxConnections (=최대×2~3) | 방화벽 유휴 제한 → keepalive.time (=1/3) | idleTimeout (최장 유휴+여유) |
|---|---|---|---|---|---|
| partner-a | 1,500 | 250 | 4096 | 1800s → 300 | 1800s |
| partner-b | … | … | … | … | … |
온보딩 = “DR 1세트 추가 + 전역값 재검토”. 전역(레이어 2)은 모든 채널의 conn/s 합으로 다시 계산한다.
부록 B — conntrack (노드 sysctl): 다채널 합산 규모에서만
일반 규모에선 손댈 필요 없다. 기본 한도 약 26만은 Envoy 1,024나 포트 28,000보다 훨씬 넓어서, P1·P2를 잡았다면 여기까지 안 온다. 다만 알람 하나(9.2절)는 걸어둔다. 이게 터지면 reset도 없이 조용히 패킷이 사라져서, 알람 없이는 진단이 제일 어렵다.
- 계산식: 전 채널 합산 conn/s × 잔류 120초 = 상시 점유 엔트리 (예: 초당 250개 → 3만 ≈ 기본 한도의 12%)
- 알람이 70%에 접근하면 그때 조치한다 (전용 egress 노드풀의 노드
/etc/sysctl.d— conntrack은 패킷이 호스트 netns의 iptables/CNI를 지날 때 생기므로 pod sysctl이 아니다):
# /etc/sysctl.d/90-egress-gw.conf
net.netfilter.nf_conntrack_max = 1048576 # 기본 ~26만
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30 # 기본 120s 단축
부록 C — 검증 상태: 무엇이 실측이고, 무엇을 적용 환경에서 재확인해야 하나
실측 완료 (Istio 1.30.0 / k8s 1.30.6 — 원자료는 각 정본 문서의 검증 번들, “검증 기록” 절 참조):
| 주장 | 방법 |
|---|---|
| connectionPool이 홉별로 다른 프록시에 적용 (홉1=sidecar, 홉2=gateway) | 식별값 111/222 주입 후 양쪽 cluster 덤프 |
| 홉 2 cluster에 transportSocket 부재 = raw TCP, inner TLS end-to-end 보존 | E2E 호출 + config dump + 서버측 SNI/cert 검증 |
tls.mode: tcp 미존재 (CRD 거부) |
apply 실측 |
| keepalive probe가 Envoy idleTimeout을 리셋하지 못함 | 유휴 절단 실측 |
DR keepalive는 sysctl이 아니라 소켓 옵션 (ss -o timer로 검증) |
소켓 실측 |
| Envoy cluster 기본 1024 / connectionPool 부분 설정 시 무제한 주입 | config dump 실측 |
| subset/portLevelSettings 통째 교체 (deep-merge 아님) | config dump 실측 |
| TIME_WAIT 60s (커널 하드코딩) → pod당·목적지당 ~470 conn/s | 커널 소스 + 실측 |
적용 환경에서 재확인 필요 — 이 표를 채우기 전에는 시작값을 그대로 운영값으로 확정하지 말 것:
| 항목 | 확인 방법 | 값에 미치는 영향 |
|---|---|---|
| Istio 버전·설치 방식 | — | API·기본값 차이 (본 문서는 1.30 기준) |
| 방화벽/중간장비 유휴 제한 시간 | 네트워크팀 | keepalive.time·idleTimeout 계산 입력 |
| SNAT 여부 (CNI 설정) | CNI IPPool 설정 | antiAffinity 필수 여부, 방화벽 등록 단위, 포트 계산 전제 |
| 채널별 최대 동시 연결·conn/s | 도입 전 sidecar 메트릭 | maxConnections·replica 수 |
| kubelet unsafe sysctl 정책 | 클러스터 관리 주체 | tcp_tw_reuse 적용 가능 여부 |
| keepalive ACK를 세션 갱신으로 인정 안 하는 중간장비 | 유휴 30분 후 첫 요청 실측 | 드물지만 존재 — 채널별 확인 |
부록 D — 출처 (외부 공식 문서)
- Envoy circuit breaking 기본값(cluster당 1024): envoyproxy.io › intro › arch_overview › upstream › circuit_breaking
- Envoy tcp_proxy idleTimeout 기본 1h: envoyproxy.io › api-v3 › tcp_proxy.proto
- Istio DestinationRule 레퍼런스 (ConnectionPoolSettings·ClientTLSSettings): istio.io › docs › reference › config › networking › destination-rule
- TCP keepalive 소켓 옵션: man 7 tcp (TCP_KEEPIDLE/KEEPINTVL/KEEPCNT)
- 리눅스 ephemeral port 범위: docs.kernel.org › networking › ip-sysctl
- k8s sysctl 정책 (safe/unsafe): kubernetes.io › tasks › administer-cluster › sysctl-cluster
검증 기록 (2026-07-06 · Istio 1.30.0 / k8s 1.30.6)
이 문서는 종합본이라 실측 대부분을 소스 정본에서 인용한다 — 부록 C “실측 완료” 표의 원본:
| 인용 주장 | 원본 실측 |
|---|---|
connectionPool 홉별 적용(식별값 111/222) · 홉 2 transportSocket 부재 · tls.mode: tcp 미존재 |
mTLS Passthrough — 홉별 DR·connectionPool 구성 T91 |
| 이중 기본값(4294967295 명시 주입) · connectTimeout×retry 곱 | DR connectionPool 정본 T93 |
| keepalive ↔ 커널 소켓 옵션 1:1 매핑 · probe가 idleTimeout을 리셋 못함 | tcpKeepalive 필드 노트 T55·T16 |
| subset/portLevelSettings 통째 교체(deep-merge 아님) | DestinationRule 만들기 T53 |
| Envoy cluster 기본 1024 발동(UO) | circuit breaking 메커니즘 T52 |
| TIME_WAIT 60s(커널 하드코딩) → ~470 conn/s | Egress TCP 병목 정본 T57 |
이 문서 자체 실측 — T94: istio/gateway Helm chart 1.30.0의 pod securityContext 동작 (차트 렌더링 검증, 클러스터 불요). 원자료: run.sh · result.txt · verdict.json
| # | 주장 | 실측 결과 | 판정 |
|---|---|---|---|
| C1 | 차트가 pod-level securityContext value 노출 (기본 {}) |
helm show values에 존재 — 주석: “미설정 시 80/443 바인딩 최소 권한 자동 설정” |
✅ 지지 |
| C2 | 미설정 시 sysctls: [net.ipv4.ip_unprivileged_port_start=0] 자동 주입 |
기본 helm template 렌더링에서 해당 블록 확인 |
✅ 지지 |
| C3 | values 지정 시 자동 기본이 통째 대체 (merge 아님) | tcp_tw_reuse만 지정해 렌더링 → ip_unprivileged_port_start 소멸 |
✅ 지지 |
참조
아카이브 내부 — 각 주제의 정본
- mTLS Passthrough — 홉별 DR·connectionPool 구성 — 3장 레이어 1·4장의 정본 (T91)
- DR connectionPool 정본 — 필드별 동작·매핑·관측 — 필드 12종 상세·이중 기본값·retry 곱 (T93)
- tcpKeepalive 필드 노트 — time·interval·probes — 6장·8장 검증의 정본 (T55·T16)
- Egress TCP 문제별 처방전 — 4~7장 문제 중심 구조·부록 A 계산식의 정본
- Egress TCP 병목 정본 — 5장 포트 계산·SNAT·런북·알람의 정본 (T57)
- Egress mTLS Passthrough 해부 — 1장 패턴 자체의 정본 (T07)
- Egress HTTPS 패턴 지도 — 용어 정본
- Pod 커널 파라미터 정본 — 5장 sysctl netns 초기화·safe/unsafe 관문의 정본