Pod 커널 파라미터 정본 — 호스트 sysctl은 왜 pod에 안 먹히고, unsafe sysctl은 어떻게 넣는가
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로 박혀 있다.
tcp_sk_init()의 하드코딩 기본값(tw_reuse=2, loopback 한정)으로 초기화되고, 호스트 /etc/sysctl.d는 호스트 netns에만 적용된다.그래서 정확한 그림은:
- 호스트에서
sysctl -w net.ipv4.tcp_tw_reuse=1→ 호스트 netns만 바뀜. pod에는 무관. - pod 안의 값은 항상 기본값
2—0(끔)이 아니라 "loopback 목적지에 한해서만 재사용"(kernel 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을 보존해야 한다:
- 상대의 마지막 FIN이 유실·재전송되면 다시 ACK해줄 것
- 같은 4-tuple로 새 연결이 즉시 열렸을 때, 네트워크에 떠돌던 이전 연결의 늦은 세그먼트가 새 연결의 데이터로 오인되지 않게 할 것
리눅스는 이 보존 시간을 60초로 하드코딩했다(TCP_TIMEWAIT_LEN — sysctl로 못 바꾼다). TCP 병목 정본 §02의 분수식 28,232 ÷ 60s ≈ 470 conn/s의 분모가 바로 이것.
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는 외부행 발신자라 정확히 이 케이스에 해당한다.
03. 파라미터별 정리 — 무엇이 어디에 사는가
패킷 경로를 그리면 각 파라미터의 적용 위치가 저절로 정해진다. pod netns 안에는 netfilter 룰이 없다. 패킷이 veth를 빠져나와 호스트 netns의 iptables/Calico 룰을 통과할 때 conntrack tracking이 일어난다 — conntrack이 pod가 아닌 노드 설정인 이유.
| 파라미터 | 스코프 (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중 관문
sysctls는 pod-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된다.
관문 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 캠페인"이다.- 재사용은 상대측 timestamps에 의존한다. timestamps를 끈 상대(보안 장비 뒤
tcp_timestamps=0강제 등)와의 연결에는 tw_reuse가 발동하지 못한다. 특정 채널만EADDRNOTAVAIL이 계속되면 이 케이스를 의심할 것. tcp_tw_recycle과 혼동 금지. NAT 뒤 클라이언트를 무차별로 깨뜨려 kernel 4.12에서 삭제된 설정이다. 지금 커널엔 존재하지도 않지만, 오래된 사내 위키 복사 시 딸려오는 단골 사고.tcp_max_tw_buckets를 낮추는 튜닝은 안티패턴. 한도 초과분의 TIME_WAIT 소켓을 그냥 파괴해 보호 기능 자체를 무력화한다. 올바른 방향은 언제나 tw_reuse(안전 조건부 재사용) 쪽.
참조
아카이브 내부 - 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을 금지하는 근거