🏠 목록 Pod 커널 파라미터 정본 — 호스트 sysctl은 왜 pod에 안 먹히고, unsafe sysctl은 어떻게 넣는가 📄 MD 원본 📁 Files 🔒 Private 🌓 테마
istioegresssysctlnetnstcp-tw-reusetime-waitpawskubernetes

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

NOTE

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는 부모(호스트) netns의 현재 값을 복사하지 않는다. 커널 코드에 하드코딩된 기본값으로 초기화된다. tcp_tw_reuse는 커널의 netns 초기화 함수(tcp_sk_init())에 2로 박혀 있다.

host netns tcp_tw_reuse = 1set via /etc/sysctl.d pod netns (new) tcp_tw_reuse = 2kernel default (loopback-only) ✗ no inheritance kernel tcp_sk_init()hardcoded defaults initialized from
그림 1. 새 netns는 호스트 값을 상속하지 않는다 — 커널 tcp_sk_init()의 하드코딩 기본값(tw_reuse=2, loopback 한정)으로 초기화되고, 호스트 /etc/sysctl.d는 호스트 netns에만 적용된다.

그래서 정확한 그림은:

같은 이유로 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의 분모가 바로 이것.

in uset=0 connect .. t=5s close TIME_WAIT 60s - port lockedTCP_TIMEWAIT_LEN: fixed, not a sysctl freet=65s+ t=0t=5st=65s 1. late FIN retransmitpeer resends FIN -> must re-ACK 2. stale segment hazardold bytes must not hit a new conn
그림 2. TIME_WAIT 60초가 지키는 두 책임. 포트 하나가 "사용 시간 + 60s"를 점유하므로 분수식 28,232 ÷ 60s ≈ 470 conn/s의 분모가 된다. tw_reuse는 이 중 ②(amber)를 PAWS로 대체해 우회한다 — 그림 3.

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

old connectionts <= 1000 new conn (same 4-tuple)ts >= 2000 tw_reuse: reopen after 1s stale segment arrivests = 950 PAWS check950 < 2000 -> drop rejected ✗ requires TCP timestamps on both peers (default on). PAWS = Protection Against Wrapped Sequences
그림 3. tw_reuse=1이 안전한 이유 — timestamps가 단조 증가하므로 이전 연결의 늦은 세그먼트(ts=950)는 새 연결의 기대값(≥2000)보다 작아 PAWS가 폐기한다. 그림 2의 보호 ②가 timestamps로 대체되는 지점.

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

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

gw pod netns app socket pod-scoped sysctlsip_local_port_range (safe)tcp_tw_reuse (unsafe)tcp_fin_timeout (safe 1.29+) host netns (node) iptables / Calico rules conntrack tablenf_conntrack_max=1048576 tracks here veth external
그림 4. 파라미터 스코프 맵. pod netns 안에는 netfilter 룰이 없어 conntrack tracking은 패킷이 호스트 netns의 iptables/Calico를 통과할 때 일어난다 — conntrack이 pod securityContext가 아닌 노드 /etc/sysctl.d 설정인 이유.
파라미터 스코프 (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을 두 등급으로 나눈다:

pod specsecurityContext.sysctls: tw_reuse=1 gate 1: API serverPSA namespace label(enforce level) gate 2: kubeletallowedUnsafeSysctls(per-node opt-in) appliedtw_reuse = 1in pod netns rejectedbaseline violation SysctlForbiddenvisible in pod STATUS fail fail safe sysctl (e.g. ip_local_port_range): passes without gate 1-2 configuration
그림 5. unsafe sysctl의 3중 관문. PSA는 API 서버 admission에서, kubelet allowlist는 노드에서 각각 별도로 거부한다 — 실패 지점에 따라 증상이 다르므로(admission 거부 vs 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 병목 정본 — §02 포트 산술(분수식), §06 완화 운영값 YAML 전체. 이 문서는 그 §06-4의 확장 - TCP 병목 한계 축소 재현 랩 — 병목을 테스트 클러스터에서 직접 재현 - Egress 운영 정본 — 모니터링·graceful shutdown 일반론

외부 - kernel ip-sysctl 문서 — tcp_tw_reuse 값 의미(0/1/2) - Kubernetes: Using sysctls in a cluster — safe/unsafe 분류와 allowedUnsafeSysctls - Pod Security Standards — baseline이 unsafe sysctl을 금지하는 근거