🏠 목록 tcpKeepalive 필드 노트 — time / interval / probes는 각각 무엇을 제어하는가 📄 MD 원본 📁 Files 🔒 Private 🌓 테마
istioegresstcpkeepalivedestinationruleconnection-poolenvoykernel

tcpKeepalive 필드 노트 — time / interval / probes는 각각 무엇을 제어하는가

NOTE

DR connectionPool.tcp.tcpKeepalive의 세 필드는 Envoy가 만든 개념이 아니라 리눅스 커널의 TCP keepalive 소켓 옵션 3개에 1:1 매핑된다. Envoy는 upstream 소켓에 옵션을 설정만 하고, probe를 보내는 주체는 커널이다. 이 한 설정이 서로 다른 두 역할(중간장비 세션 유지 / 죽은 상대 감지)을 겸한다는 것, 그리고 각 역할을 결정하는 필드가 다르다는 것이 이 노트의 본체다.

선행 문서: TCP 병목 정본 §04(병목 4: 중간장비 idle timeout)·§06-1(권장값 YAML).


01. 커널 소켓 옵션 매핑

DR 필드 소켓 옵션 의미 커널 기본값
time TCP_KEEPIDLE 마지막 데이터 송수신 이후 이만큼 유휴하면 첫 probe 발사 7200s (2시간)
interval TCP_KEEPINTVL probe에 응답이 없을 때 다음 probe까지의 재시도 간격 75s
probes TCP_KEEPCNT 연속 무응답 probe가 이 횟수에 도달하면 연결을 죽은 것으로 판정·폐기 9

probe의 정체는 데이터가 아니라 빈 ACK 세그먼트다(시퀀스 번호를 일부러 1 뒤로 물려 보내 상대의 ACK를 유도). 상대가 살아 있으면 즉시 ACK가 돌아오고, 그 왕복이 경로상 중간장비의 세션 타이머를 갱신한다.


02. 타임라인 — 권장값 (time=300, interval=30, probes=3)의 두 시나리오

repeat every 300s last data probe ACK received 300s idle ACK scenario A: peer alive - every probe/ACK round-trip refreshes the middlebox session timer last data probe 1 probe 2 probe 3 closedt = 390s 300s 30s 30s fail x3 scenario B: peer dead (silent) - no ACK at any step, detected after interval x probes = 90s probe = empty ACK segment (not data) -> does NOT reset Envoy idleTimeout
그림 1. time=300 / interval=30 / probes=3의 두 시나리오. 상대가 살아있으면(A) 첫 probe에 즉시 ACK가 와서 interval/probes는 발동하지 않고, 죽어 있으면(B) 30초 간격 3회 무응답 후 t=390s에 소켓이 폐기된다.

역할 분리가 핵심이다:

  1. 중간장비 세션 유지 (병목 4의 처방) — 상대가 살아있는 정상 케이스. probe/ACK 왕복이 FW 세션 idle 타이머를 갱신한다. 결정 규칙은 time < FW idle timeout(예: 300s < 1800s)이며, interval/probes는 이 역할과 무관하다 — 살아있는 상대는 첫 probe에 즉시 ACK하므로 재시도가 발생하지 않는다.
  2. 죽은 상대 감지 — 상대 서버가 소리 없이 사라진 경우(전원 단절, 경로 단절). interval × probes가 감지 지연을 결정한다. 커널 기본(75s × 9 ≈ 11분)은 너무 느려서, 30s × 3 = 90초 안에 죽은 연결을 정리하도록 줄인다. 이게 없으면 죽은 연결이 Envoy 풀에 유령으로 남아 maxConnections 슬롯을 점유한다.

한 줄 요약: time은 "얼마나 자주 안부를 묻나"(FW 대응), interval+probes는 "무응답을 얼마나 참고 사망 선고하나"(유령 연결 정리).


03. 값 결정 규칙

필드 규칙 근거
time FW idle timeout의 1/3 이하 (예: FW 1800s → 300) probe 유실·지연 1~2회를 견디고도 세션 갱신 보장
interval 수십 초 (예: 30) 짧을수록 감지 빠르지만 일시적 경로 flap에 과민해짐
probes 3 안팎 interval × probes = 사망 판정 지연이자 오판 방어 횟수

04. 적용 검증 — 설정은 sysctl이 아니라 소켓에 박힌다

KEY

DR tcpKeepalive는 커널 파라미터(net.ipv4.tcp_keepalive_*)를 바꾸지 않는다. istiod가 cluster 설정으로 컴파일하고, Envoy가 연결을 새로 열 때마다 그 소켓에 setsockopt(TCP_KEEPIDLE/KEEPINTVL/KEEPCNT)를 호출하는 방식이다. 소켓 단위 옵션은 netns 기본값(sysctl)을 소켓별로 덮어쓸 뿐, 기본값 자체는 영원히 그대로다. sysctl로 확인하는 것은 검증 지점 자체가 틀린 것이지 미적용의 증거가 아니다.

 [sysctl 경로]  net.ipv4.tcp_keepalive_time = 7200        (netns-wide DEFAULT)
                       |
                       v   applies only when socket has NO explicit option
 [DR 경로]      Envoy --- setsockopt(fd, TCP_KEEPIDLE=300) ---> per-SOCKET option
                            (overrides the netns default, socket by socket)

설정은 DR → istiod(xDS) → Envoy cluster → 소켓 옵션 → 와이어 probe 순으로 흐른다. 단계 순서대로 확인하면 실패 지점을 이분탐색할 수 있다. 확인할 pod에 주의: 외부 호스트 DR(레이어 1)은 gw→외부 연결이므로 egress gateway pod에서, sidecar→gw subset DR(레이어 2)은 앱 sidecar에서 확인한다.

① xDS — Envoy에 설정이 도달했는가 (default + subset cluster 나란히)

istioctl proxy-config cluster deploy/istio-egressgateway -n istio-egress \
  --fqdn api.partner-a.example.com -o json | \
  jq '.[] | {name, dr: .metadata.filterMetadata.istio.config,
             max: .circuitBreakers.thresholds[0].maxConnections,
             ka: .upstreamConnectionOptions.tcpKeepalive}'

기대 출력 — DR host의 모든 cluster가 한 줄씩 나온다. name의 세 번째 칸이 subset 이름(비어 있으면 default):

{ "name": "outbound|443||api.partner-a.example.com",          "dr": ".../destination-rule/partner-a-external", "max": 4096, "ka": { "keepaliveTime": 300, ... } }
{ "name": "outbound|443|partner-a|api.partner-a.example.com", "dr": ".../destination-rule/partner-a-external", "max": 4096, "ka": { "keepaliveTime": 300, ... } }

①-b (subset 전용) — 트래픽이 실제 그 subset cluster를 타는가

istiod는 VS 라우팅과 무관하게 DR의 subset마다 cluster를 만들어 두므로, cluster의 존재와 값은 "트래픽이 그걸 쓴다"의 증거가 아니다. VS가 subset으로 보내지 않으면 트래픽은 default cluster를 탄다:

kubectl exec <pod> -c istio-proxy -- pilot-agent request GET stats | \
  grep "outbound|443|partner-a|" | grep -E "cx_active|cx_total|cx_overflow"

② 소켓 — 커널 소켓에 실제로 박혔는가

ss -o의 timer 필드가 소켓 단위 keepalive 상태를 직접 보여준다:

kubectl exec deploy/istio-egressgateway -n istio-egress -- \
  ss -tno state established 'dst 203.0.113.10'
# Recv-Q Send-Q Local:Port      Peer:Port
# 0      0      10.244.1.5:53210  203.0.113.10:443  timer:(keepalive,4min32sec,0)

③ 와이어 — probe가 실제로 나가는가 (최종 확인, 선택)

probe = payload 0바이트, 시퀀스를 1 물린 빈 ACK(§01). 유휴 상태에서 time 간격마다 보이면 끝까지 검증된 것:

# 노드에서
tcpdump -ni any host 203.0.113.10 and port 443
# 유휴 300s 후: length 0 ACK -> 상대의 즉시 ACK, 이후 300s 주기 반복

랩에서 빨리 보려면 time: 10으로 임시 설정해 10초 주기를 눈으로 확인한다 — TCP 장애 재현 랩 병목 4 절차와 같은 요령.

①이 비어 있다면 — 미적용의 흔한 원인

원인 확인
0순위: 값이 애초에 클러스터에 저장 안 됨 — apply 누락, 다른 kubeconfig 컨텍스트/클러스터에 적용, 로컬 YAML만 수정 kubectl config current-context + kubectl get dr <name> -o yaml검증 0단계. 저장본에 connectionPool이 없으면 아래 전부 무의미
DR이 cluster에 아예 결부 안 됨 (dr: null + 전 항목 기본값) ①의 dr 필드. null이면 host 불일치·exportTo·중복 DR·잘못된 pod 중 하나
subset의 trafficPolicy가 상위를 통째로 교체 (아래 callout) subset cluster의 upstreamConnectionOptions 직접 확인
DR host ↔ 트래픽 목적지 FQDN 불일치 (ServiceEntry hosts와 대조) proxy-config cluster에 해당 cluster 이름 존재 여부
같은 host에 DR 여러 벌 — 하나만 선택되고 나머지는 조용히 무시 kubectl get dr -A \| grep <host> + istioctl analyze (IST0101류 경고)
portLevelSettings 포트 불일치 / VS가 subset으로 라우팅 안 함 VS의 destination.subset·포트 확인 + ①-b stats
DR namespace/exportTo 스코프 밖 istioctl analyze
확인하는 pod가 틀림 (레이어 1 설정을 sidecar에서 찾는 경우) 위 "확인할 pod" 기준
기존 연결에는 소급 적용 안 됨 — 소켓 옵션은 연결 생성 시 1회 ②에서 일부 소켓만 이상하면 설정 변경 전 연결. idleTimeout/maxConnectionDuration으로 자연 교체 또는 rollout restart
WARN

subset 병합 규칙 — deep-merge가 아니라 최상위 필드 단위 통째 교체. subset의 trafficPolicy는 상위 trafficPolicy와 필드 단위로 합쳐지지 않는다. istiod는 connectionPool·outlierDetection·loadBalancer·tls 각각을 통째로 subset 것으로 교체한다(subset에 없으면 상위 것 상속). 따라서 subset에 connectionPool.tcp.maxConnections 하나만 적어도 그 subset cluster에서는 상위의 tcpKeepalive사라진다. subset을 타는 트래픽에 keepalive를 적용하려면 subset(또는 그 portLevelSettings) 안에 다시 전부 적어야 한다TCP 병목 정본 §06-1/06-2 YAML이 레이어 1과 레이어 2 양쪽에 tcpKeepalive를 중복 기재하는 이유가 이것이다. portLevelSettings도 같은 규칙이 포트 단위로 한 번 더 적용된다.


05. 일괄 적용 — 채널별 DR 없이 통째로 하려면 (meshConfig vs sysctl)

WARN

"컨테이너 커널 파라미터로 통째 적용"은 절반만 맞다. 커널 keepalive는 SO_KEEPALIVE가 켜진 소켓에서만 동작하고, net.ipv4.tcp_keepalive_* sysctl은 on/off가 아니라 값의 기본값만 제공한다. 전 소켓 활성화를 강제하는 sysctl 스위치는 커널에 없다. Envoy는 설정 없이는 upstream 소켓에 SO_KEEPALIVE를 켜지 않으므로, pod sysctl만으로는 Envoy 경유 연결에 keepalive가 아예 발동하지 않는다 — 값이 300이든 7200이든 무관하게.

keepalive는 "스위치 + 다이얼" 2층 구조다:

 +-----------------------------------------------------------+
 |  per-socket SWITCH:  SO_KEEPALIVE          default: OFF   |  <- setsockopt로만 켜짐
 +-----------------------------------------------------------+
 |  DIALS (values):     time / interval / probes             |
 |    per-socket option (TCP_KEEPIDLE..)  >  netns sysctl    |  <- 스위치 ON일 때만 의미
 +-----------------------------------------------------------+

스위치 기본 OFF는 리눅스의 선택이 아니라 TCP 표준(RFC 1122)의 요구다 — probe도 대역폭을 쓰고, 일시적 경로 장애 중 멀쩡히 복구될 연결을 keepalive가 먼저 죽일 수 있어, 켤지 말지는 소켓을 소유한 애플리케이션의 opt-in으로만 허용된다. 커널은 스위치가 켜진 소켓에만 probe를 보낸다. 그래서 "이 연결에 keepalive가 도는가?"의 답은 언제나 sysctl이 아니라 그 소켓을 만든 주체가 뭘 설정했는가다 — Envoy 소켓이면 DR/meshConfig가 그 답이다.

방법 동작 판단
meshConfig.tcpKeepalive istiod가 mesh 전역 모든 cluster에 keepalive를 컴파일. DR에 tcpKeepalive가 있는 목적지는 DR이 우선 권장 — "전역 기본 + 채널별 예외" 구조
pod sysctl(값) + DR/mesh에 tcpKeepalive: {} 빈 블록 빈 블록이 SO_KEEPALIVE만 켜고, 세 값은 netns sysctl을 따름 값 관리가 sysctl과 Istio 설정 두 곳으로 갈라짐 — 비추천
pod sysctl 단독 Envoy 소켓엔 무효(스위치가 안 켜짐). 앱 자체 발신 소켓 중 앱이 SO_KEEPALIVE를 켠 것에만 값이 반영 DR/mesh가 못 미치는 소켓용 보조 수단
# IstioOperator 또는 istio configmap (meshConfig)
meshConfig:
  tcpKeepalive:
    time: 300s
    interval: 30s
    probes: 3

적용 우선순위: DR tcpKeepalive > meshConfig.tcpKeepalive > (SO_KEEPALIVE가 켜진 소켓에 한해) netns sysctl 기본값.

빈 블록(tcpKeepalive: {}) + pod sysctl 조합의 정확한 동작

스위치만 켜고 다이얼을 sysctl에 맡기는 조합은 동작하지만, 네 가지를 정확히 알아야 한다:

전역화의 대가 두 가지:

앱 런타임 층의 함정 하나: Go처럼 keepalive를 소켓 옵션으로 명시 설정(기본 15s)하는 런타임은 sysctl 값을 다시 덮어쓴다. "sysctl = 전 소켓 일괄 통제"라는 기대는 소켓 옵션 층(Envoy든 앱이든)에서 깨진다는 원리가 여기서도 반복된다.

결국 세 층의 분업으로 정리된다:

리소스 역할
전역 스위치·기본값 meshConfig.tcpKeepalive mesh 모든 proxy의 upstream 연결에 일괄
채널별 튜닝 DR tcpKeepalive (subset 재기재 주의 — §04) 특정 목적지만 override
pod별 값 pod securityContext.sysctls 빈 블록·미명시 필드의 다이얼 공급

What you might be missing


참조

아카이브 내부 - TCP 병목 정본 — §04 병목 4(half-open), §06-1 권장값이 놓이는 전체 YAML 맥락 - Pod 커널 파라미터 정본 — netns 초기화 규칙, safe/unsafe sysctl - TCP 장애 재현 랩 — 병목 4 재현 절차

작업 파일 (다운로드) - /files/istio-egress/tcp-keepalive/ — §04 검증 절차의 실행 스크립트(verify-tcp-keepalive.sh)와 이 노트 md 사본

외부 - man 7 tcp — TCP_KEEPIDLE / TCP_KEEPINTVL / TCP_KEEPCNT 정의 - Envoy TcpKeepalive proto — DR 필드가 컴파일되는 대상