homelab89 Docs Logs Legacy Files ☰ TOC 🌓
guideistio 2026-07-02egresssysctlnetnstcp-tw-reusekubernetes

Pod 커널 파라미터 정본 — 호스트 sysctl은 왜 pod에 안 먹히고, unsafe sysctl은 어떻게 넣는가

ABSTRACT

TCP 병목 정본 §06은 완화 운영값이 4개 레이어(DR / pod sysctl / 노드 sysctl / Helm)에 분산된다고 정리했다. 이 문서는 그중 pod sysctl 레이어 하나를 메커니즘 레벨로 전개한다 — 호스트 /etc/sysctl.d가 pod에 전파되지 않는 이유(netns 초기화), tcp_tw_reuse=1이 안전한 근거(PAWS), 그리고 Kubernetes에서 unsafe sysctl이 통과해야 하는 3중 관문(kubelet allowlist → PSA → securityContext)까지.

대상 환경: Kubernetes ≥ 1.29 자체 구축(kubespray) 기준 — managed 환경 대안은 §06. 선행 문서: TCP 병목 정본 §02(포트 산술)·§06-4(이 문서의 원형이 된 2단계 요약).


01. 멘탈모델 — sysctl은 상속되지 않고 “초기화"된다

sysctl은 두 부류로 나뉜다:

  • netns 스코프 (net.ipv4.* 대부분, net.core.somaxconn 등): network namespace마다 독립 값을 가짐
  • 호스트 전역 (fs.*, vm.*, 그리고 실질적으로 conntrack): netns로 격리되지 않음

핵심은 이것이다 — 새 netns는 부모(호스트) netns의 현재 값을 복사하지 않는다. 커널 코드에 하드코딩된 기본값으로 초기화된다. tcp_tw_reuse는 커널의 netns 초기화 함수(tcp_sk_init())에 2로 박혀 있다.

flowchart LR
  H["host netns<br/>tcp_tw_reuse = 1<br/>(/etc/sysctl.d)"] -. "no inheritance ✗" .-> P["pod netns (new)<br/>tcp_tw_reuse = 2"]
  K["kernel tcp_sk_init()<br/>hardcoded defaults"] --> P

그래서 정확한 그림은:

  • 호스트에서 sysctl -w net.ipv4.tcp_tw_reuse=1호스트 netns만 바뀜. pod에는 무관.
  • pod 안의 값은 항상 기본값 20(끔)이 아니라 “loopback 목적지에 한해서만 재사용"(kernel 4.18+ — 이 삼중값 확장 패치가 병합된 버전. 4.15는 그보다 몇 달 앞선 릴리스라 이 동작이 없었다). loopback은 옛 중복 세그먼트가 되돌아올 물리 경로가 없어 무조건 안전하므로 커널이 기본으로 켜둔 것.
  • egress gateway의 외부행 연결은 loopback이 아니므로 실질적으로는 꺼진 것과 같다 → pod netns 안에서 직접 1로 바꿔야 한다.

같은 이유로 ip_local_port_range도 pod netns 값(기본 32768 60999)이 호스트와 별개로 존재한다.


02. TIME_WAIT는 왜 존재하고, tw_reuse는 왜 안전한가

값을 바꾸기 전에 그 값이 지키던 것부터. 먼저 close한 쪽(active closer — proxy는 대개 이쪽)은 두 가지 책임 때문에 4-tuple을 보존해야 한다:

  1. 상대의 마지막 FIN이 유실·재전송되면 다시 ACK해줄 것
  2. 같은 4-tuple로 새 연결이 즉시 열렸을 때, 네트워크에 떠돌던 이전 연결의 늦은 세그먼트가 새 연결의 데이터로 오인되지 않게 할 것

리눅스는 이 보존 시간을 60초로 하드코딩했다(TCP_TIMEWAIT_LEN — sysctl로 못 바꾼다). TCP 병목 정본 §02의 분수식 28,232 ÷ 60s ≈ 470 conn/s의 분모가 바로 이것.

flowchart LR
  A["port in use<br/>t=0 connect .. t=5s close"] --> B["TIME_WAIT 60s<br/>port locked"] --> C["port returned<br/>t=65s"]
  B --- R1["hazard 1: late FIN retransmit -> re-ACK"]
  B --- R2["hazard 2: stale segment must not hit new conn"]

tw_reuse=1의 안전 근거 — PAWS. TCP timestamps 옵션(기본 on)이 켜져 있으면 모든 세그먼트에 단조 증가하는 시계값이 실린다. 커널은 새 연결에서 이전 연결의 늦은 세그먼트를 timestamp 비교로 식별·폐기할 수 있다(PAWS, Protection Against Wrapped Sequences). 위 책임 ②가 timestamps로 대체되므로, 1초만 경과하면 그 4-tuple을 outgoing 연결에 재사용해도 안전하다. 수신(서버) 소켓에는 적용되지 않는다 — gateway는 외부행 발신자라 정확히 이 케이스에 해당한다.

flowchart LR
  O["old conn<br/>ts <= 1000"] -- "tw_reuse (after 1s)" --> N["new conn, same 4-tuple<br/>ts >= 2000"]
  S["stale segment ts=950"] --> P["PAWS check<br/>950 < 2000 -> drop"]
  P -. rejected .-> N

03. 파라미터별 정리 — 무엇이 어디에 사는가

패킷 경로를 그리면 각 파라미터의 적용 위치가 저절로 정해진다. pod netns 안에는 netfilter 룰이 없다. 패킷이 veth를 빠져나와 호스트 netns의 iptables/Calico 룰을 통과할 때 conntrack tracking이 일어난다 — conntrack이 pod가 아닌 노드 설정인 이유.

flowchart LR
  subgraph POD["gw pod netns"]
    APP["app socket"] --- SY["ip_local_port_range<br/>tcp_tw_reuse<br/>tcp_fin_timeout"]
  end
  subgraph HOST["host netns (node)"]
    FW["iptables / Calico"] --- CT["conntrack table<br/>nf_conntrack_max"]
  end
  POD -- veth --> HOST --> EXT["external"]
파라미터 스코프 (k8s 분류) 권장값 왜 필요한가
net.ipv4.ip_local_port_range pod netns (safe) "10240 60999" 포트 풀 28k→50k 순수 확장 — 유일하게 부작용 없는 완화. 하한 10240은 특권 포트(<1024)·registered port 관행 회피. pod netns라 노드의 NodePort 범위(30000–32767, 노드 netns 리스너)와 충돌하지 않음 — 노드 호스트에서 같은 확장을 하면 충돌 이슈가 있는 것과 대비되는 지점
net.ipv4.tcp_tw_reuse pod netns (unsafe) 1 60초 규칙을 PAWS가 안전을 보증하는 경우에 한해 우회 (§02). outgoing 전용 — gateway 워크로드에 정확히 부합
net.ipv4.tcp_fin_timeout pod netns (safe, k8s ≥1.29) 30 (선택) TIME_WAIT가 아니라 FIN_WAIT_2 orphan 소켓 보존 시간(기본 60s) 단축. 연결을 닫지 않고 방치하는 파트너 서버 대비책. 포트 고갈의 주범은 아니라 2순위
net.netfilter.nf_conntrack_max
..._tcp_timeout_time_wait
노드 /etc/sysctl.d 1048576 / 30 위 packet path 그림 참조 — tracking은 호스트 netns에서 발생. 테이블이 차면 거부가 아니라 silent drop(무응답)이라 선제 상향

04. 적용 — unsafe sysctl의 3중 관문

sysctlspod-level securityContext에만 존재한다 (netns가 pod 단위 공유 자원이라 container-level 불가). 그리고 Kubernetes는 sysctl을 두 등급으로 나눈다:

  • safe = 같은 노드의 다른 pod에 영향을 줄 수 없다고 검증된 것. ip_local_port_range는 아무 설정 없이 즉시 사용 가능. k8s 1.29+에서 tcp_fin_timeout·tcp_keepalive_* 계열도 safe로 승격됨.
  • unsafe = 그 외 전부. tcp_tw_reuse가 여기 해당 → kubelet이 노드 단위로 opt-in해야만 admit된다.
flowchart LR
  SPEC["pod spec<br/>securityContext.sysctls"] --> G1["gate 1: API server<br/>PSA namespace label"] --> G2["gate 2: kubelet<br/>allowedUnsafeSysctls"] --> OK["pod netns<br/>tw_reuse = 1"]
  G1 -. fail .-> F1["rejected (baseline violation)"]
  G2 -. fail .-> F2["SysctlForbidden"]

관문 1 — kubelet allowlist (gateway 전용 노드풀에만)

# kubespray라면 egress 노드 그룹 vars:
kubelet_config_extra_args:
  allowedUnsafeSysctls:
    - "net.ipv4.tcp_tw_reuse"

# 일반 환경이면 /var/lib/kubelet/config.yaml 에:
allowedUnsafeSysctls:
- net.ipv4.tcp_tw_reuse
# 이후 systemctl restart kubelet

노드 단위 설정이라는 게 포인트 — 전용 egress 노드풀(nodeSelector)을 쓰는 구성과 정확히 맞물린다. 전체 노드에 풀 필요가 없다.

관문 2 — Pod Security Admission

unsafe sysctl을 쓰는 pod는 PSS baseline 위반이다. 네임스페이스에 enforce=baseline/restricted 라벨이 걸려 있으면 kubelet 허용과 무관하게 API 단에서 거부된다:

kubectl label ns istio-egress pod-security.kubernetes.io/enforce=privileged --overwrite

이 완화는 “이 네임스페이스에 gateway만 산다"는 전제에서만 정당화된다. 범용 네임스페이스면 안 된다.

관문 3 — gateway Helm values 선언

# istio gateway 차트의 securityContext는 pod-level로 들어간다
securityContext:
  sysctls:
  - name: net.ipv4.ip_local_port_range   # safe — 관문 1·2 없이도 동작
    value: "10240 60999"
  - name: net.ipv4.tcp_tw_reuse          # unsafe — 관문 1·2 선행 필수
    value: "1"

05. 검증과 실패 모드

kubectl -n istio-egress exec deploy/istio-egressgateway -- \
  cat /proc/sys/net/ipv4/tcp_tw_reuse /proc/sys/net/ipv4/ip_local_port_range
# 기대 출력:
# 1
# 10240	60999
증상 원인 관문
kubectl apply 단계에서 admission 거부 PSA enforce 라벨이 baseline/restricted 2
pod STATUS SysctlForbidden 그 노드의 kubelet allowlist 미반영 (kubelet 재시작 누락 포함) 1
pod는 뜨는데 값이 여전히 2 securityContext.sysctls 누락, 또는 다른 워크로드에 적용함 3

SysctlForbidden은 스케줄까지는 되고 노드의 kubelet이 기동을 거부하는 상태라 kubectl get pod의 STATUS 컬럼에 그대로 보인다 — 이게 보이면 kubelet 설정이 그 노드에 반영 안 된 것.


06. kubelet을 못 건드리는 환경(managed)의 대안

privileged initContainer가 pod netns를 공유하는 성질을 이용한다:

initContainers:
- name: sysctl-tune
  image: busybox
  securityContext: { privileged: true }
  command: ["sh", "-c", "sysctl -w net.ipv4.tcp_tw_reuse=1"]

동작은 하지만 tcp_tw_reuse 하나를 위해 privileged(사실상 노드 root)를 주는 셈이라 권한 부여가 목적 대비 과도하다. kubelet을 제어할 수 있는 환경(자체 구축·kubespray)에서는 관문 1~3 정석이 맞다.


핵심 정리

항목 내용
netns 스코프 sysctl은 상속이 아니라 커널 기본값으로 초기화. tw_reuse의 pod 기본값은 0이 아닌 2(loopback 한정)
안전 근거 TIME_WAIT의 보호 ②(늦은 세그먼트 오인 방지)를 timestamps/PAWS가 대체 → outgoing 한정 재사용 가능
3중 관문 kubelet allowlist(노드) → PSA(네임스페이스) → securityContext.sysctls(pod). 하나라도 빠지면 거부
우선순위 앱 keep-alive(분자) > replica+antiAffinity(분모) > port range(safe) > tw_reuse(unsafe) — 싼 것부터

What you might be missing

  • tcp_tw_reuse는 TIME_WAIT 개수를 줄여주지 않는다. 소켓은 여전히 TIME_WAIT로 쌓이고(모니터링 곡선 그대로), 포트가 필요할 때 그중에서 빌려 쓸 수 있게 될 뿐이다. node_sockstat_TCP_tw 알람은 이 설정 후에도 유효한 신호이며, 그 알람의 처방은 여전히 “앱 keep-alive 캠페인"이다. homelab 실측(검증 기록 T77)에서도 tcp_tw_reuse 기본값(2) 그대로 60회 연결/종료를 반복하자 TIME_WAIT 소켓이 0→61개로 그대로 누적됐다.
  • 재사용은 상대측 timestamps에 의존한다. timestamps를 끈 상대(보안 장비 뒤 tcp_timestamps=0 강제 등)와의 연결에는 tw_reuse가 발동하지 못한다. 특정 채널만 EADDRNOTAVAIL이 계속되면 이 케이스를 의심할 것.
  • tcp_tw_recycle과 혼동 금지. NAT 뒤 클라이언트를 무차별로 깨뜨려 kernel 4.12에서 삭제된 설정이다. 지금 커널엔 존재하지도 않지만, 오래된 사내 위키 복사 시 딸려오는 단골 사고.
  • tcp_max_tw_buckets를 낮추는 튜닝은 안티패턴. 한도 초과분의 TIME_WAIT 소켓을 그냥 파괴해 보호 기능 자체를 무력화한다. 올바른 방향은 언제나 tw_reuse(안전 조건부 재사용) 쪽.

참조

아카이브 내부

외부


검증 기록 (2026-07-05 · Istio 1.30.0 / k8s 1.30.6)

검증 방법 요약: 공식 문서(커널/Kubernetes) 대조 + homelab 클러스터 실측(T77 — 새 pod netns 생성, unsafe sysctl 거부, TIME_WAIT 누적 관찰).

주장 판정 근거
C1. 새 pod netns는 호스트 sysctl을 상속하지 않고 커널 하드코딩 기본값(tcp_sk_init()tcp_tw_reuse=2)으로 초기화됨 ✅ 실측 확인 github.com/torvalds/linux net/ipv4/tcp_ipv4.c · T77 실측
C2. tcp_tw_reuse=2(loopback 한정 재사용)는 kernel 4.15+에서 도입 ❌ 오류 — 본문 교정 lore.kernel.org/netdev (실제로는 4.18에 병합, 본문 “kernel 4.18+“로 정정)
C3. ip_local_port_range도 pod netns 값(기본 32768 60999)이 호스트와 별개로 존재 ✅ 문헌 확인 kernel.org/doc/Documentation/networking/ip-sysctl.txt
C4. TIME_WAIT 보존 60초(TCP_TIMEWAIT_LEN)는 하드코딩, sysctl로 불가 ✅ 문헌 확인 github.com/torvalds/linux include/net/tcp.h
C5. tw_reuse=1의 안전 근거는 PAWS이며 outgoing 연결에만 적용, 수신 소켓엔 미적용 ✅ 문헌 확인 kernel.org/doc/Documentation/networking/ip-sysctl.txt
C6. conntrack tracking은 호스트 netns의 iptables/Calico 통과 시 발생 — pod가 아닌 노드 설정 ✅ 문헌 확인 kubernetes.io/docs/concepts/security/pod-security-standards/
C7. sysctls는 pod-level securityContext에만 존재(container-level 없음) ✅ 문헌 확인 kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/
C8. k8s 1.29+에서 tcp_fin_timeout·tcp_keepalive_* 계열이 safe로 승격 ✅ 문헌 확인 kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/
C9. kubelet allowedUnsafeSysctls 미등록 unsafe sysctl 요청 pod는 SysctlForbidden으로 기동 실패 ✅ 실측 확인 github.com/kubernetes/kubernetes/issues/72593 · T77 실측
C10. unsafe sysctl은 PSS baseline 위반 — enforce=baseline/restricted 라벨 시 kubelet 설정과 무관하게 admission 단에서 거부 ✅ 문헌 확인 kubernetes.io/docs/concepts/security/pod-security-standards/
C11. privileged initContainer는 이미 공유 중인 pod netns를 이용 — hostNetwork 불필요 ✅ 문헌 확인 kubernetes.io/docs/concepts/workloads/pods/init-containers/
C12. tcp_tw_reuse는 TIME_WAIT 발생 자체를 줄이지 않고 재사용만 가능하게 함 ✅ 실측 확인 kernel.org/doc/Documentation/networking/ip-sysctl.txt · T77 실측
C13. tcp_tw_recycle은 NAT 뒤 클라이언트를 깨뜨려 kernel 4.12에서 삭제 ✅ 문헌 확인 spinics.net/lists/linux-man/msg11931.html
C14. tcp_max_tw_buckets를 낮추는 튜닝은 안티패턴(한도 초과분 TIME_WAIT 소켓을 파괴) ✅ 문헌 확인 kernel.org/doc/Documentation/networking/ip-sysctl.txt
C15. istio gateway Helm 차트의 securityContext는 pod-level(containerSecurityContext와 별개) ✅ 문헌 확인 github.com/istio/istio manifests/charts/gateway/values.yaml

Files