EKS 노드 커널 튜닝
지원 버전: Amazon Linux 2023 (커널 6.1 / 6.12 / 6.18), Kubernetes 1.33+ (Amazon EKS) 마지막 업데이트: 2026년 9월 13일
이 문서에서 다루는 것
- EKS 노드에서 무엇을 건드려야 하고 무엇을 기본값으로 두어야 하는가 — 그리고 그 판단 기준
- 커널 파라미터를 EKS에서 실제로 적용하는 경로들과 각각의 함정
- AL2023의 커널 버전 전환(6.1 → 6.18)이 운영에 의미하는 것
먼저: 대부분은 건드리지 마십시오
이 문서의 가장 중요한 조언입니다.
커널 기본값은 광범위한 워크로드에서 합리적으로 동작하도록 정해져 있고, 상당수는 부하에 따라 커널이 자동 조정합니다(예: TCP 버퍼 자동 튜닝). 근거 없는 튜닝은 세 가지 방식으로 손해를 냅니다.
| 문제 | 예 |
|---|---|
| 재현 불가능한 구성 | 노드마다 값이 달라 장애 재현이 안 됨 |
| 커널 업그레이드 시 깨짐 | 6.1에서 유효했던 튜너블이 6.18에서 이름·위치가 바뀌거나 사라짐 |
| 자동 튜닝 방해 | Socket별 SO_RCVBUF/SO_SNDBUF 명시는 해당 socket 자동 크기 조정을 끄며 sysctl 범위와는 다른 제어 |
튜닝의 전제 조건은 측정입니다. 아래 순서를 지키십시오.
- 증상을 특정한다 (지연? 드롭? 처리량?)
- 드롭 카운터를 먼저 본다 —
conntrack -S의insert_failed,tc -s qdisc의dropped,ethtool -S의 NIC 드롭 - 그 카운터가 증가하는 지점의 파라미터만 건드린다
- 변경 전후를 같은 조건으로 측정한다
- 변경 사유와 근거를 코드로 남긴다 (아래 적용 경로)
측정 방법은 Pod 네트워크 실측 벤치마크의 픽스처를 참고하실 수 있습니다.
적용 경로 — EKS에서 커널 파라미터를 바꾸는 방법
바꾸는 방법이 여러 개이고 각각 범위와 함정이 다릅니다. 이것을 먼저 정리하는 것이 실무에서 더 중요합니다.
| 경로 | 범위 | 지속성 | 비고 |
|---|---|---|---|
노드 부트스트랩 스크립트 (User Data / nodeadm) | 노드 전체 | 노드 교체 시 재적용됨 | AL2023은 nodeadm 구성 사용. 가장 표준적 |
Bottlerocket 설정 (settings.kernel.sysctl) | 노드 전체 | 노드 설정으로 관리 | Bottlerocket은 불변 OS라 이 경로만 사용 |
Pod securityContext.sysctls | Pod의 net namespace만 | Pod 스펙 | namespace 지원 sysctl만 가능. net.* 다수가 해당 |
| privileged 초기화 DaemonSet | 노드 전체 | Pod 재시작 시 재적용 | 흔히 쓰이지만 privileged 필요 — 보안 심의 대상 |
kube-proxy-config ConfigMap | conntrack 관련 | kube-proxy가 관리 | EKS에서 이것이 CLI 인자보다 우선 |
Karpenter EC2NodeClass | 노드 그룹 | 노드 프로비저닝 시 | User Data를 선언적으로 관리 |
놓치기 쉬운 두 가지
첫째, namespace 지원 sysctl과 그렇지 않은 것의 구분입니다. net.* 중 상당수는 net namespace별로 설정 가능해서 Pod securityContext.sysctls로 바꿀 수 있습니다. 반면 vm.*, fs.*, 그리고 net.netfilter.nf_conntrack_max 같은 일부 값은 노드 전역이라 Pod 스펙으로는 바꿀 수 없습니다.
또한 kubelet은 기본적으로 "안전하지 않은" sysctl을 거부합니다. 필요하면 --allowed-unsafe-sysctls로 명시적으로 허용해야 하고, 이것은 노드 설정입니다.
둘째, conntrack은 kube-proxy가 덮어씁니다. 컨테이너 커널 기능에서 언급한 내용인데, 실무에서 가장 자주 걸리는 함정이라 다시 씁니다 — EKS에는 kube-proxy-config ConfigMap이 기본 존재하고 커맨드라인 인자보다 우선합니다. 부트스트랩에서 sysctl로 올려놔도 kube-proxy가 자기 값으로 되돌릴 수 있습니다.
확인 필요
Bottlerocket에서 settings.kernel.sysctl로 conntrack 상한을 올려도 적용되지 않는 이슈가 있습니다(bottlerocket-os/bottlerocket#4221, 2024년 9월 등록). 원인은 kube-proxy 설정 파일(/var/lib/kube-proxy-config/config)이 커맨드라인 인자보다 우선하기 때문입니다.
Kube-proxy 설정 파일을 사용하며 상한 관리를 node sysctl에 의도적으로 맡기려면 파일의 conntrack.maxPerCore/conntrack.min 필드를 변경합니다. 둘을 0으로 설정하더라도 --config가 무시하는 CLI flag에 의존하면 안 됩니다. Rollout 전 관리형 add-on 조정 동작과 메모리 여유를 확인합니다.
이 이슈가 특정 Bottlerocket 릴리스에서 해결되었는지는 확인하지 못했습니다. 어느 경로로 설정하든 적용 후 노드에서 실제 값을 직접 확인하십시오.
# 노드에서 실제 적용값 확인
cat /proc/sys/net/netfilter/nf_conntrack_max
cat /proc/sys/net/netfilter/nf_conntrack_count
conntrack -S | head커널 버전 — AL2023의 6.1 → 6.18 전환
운영상 지금 알아야 할 변화입니다.
| 시점 | 내용 |
|---|---|
| 2023년 3월 | AL2023 출시, 커널 6.1 |
| 2025년 4월 | 커널 6.12 지원 추가 |
| 2026년 8월 17일 | al2023-ami-kernel-default AMI의 기본 커널이 6.1 → 6.18로 변경 |
함의가 두 갈래입니다.
기본 kernel AMI 계열이 바뀌어도 교체 노드는 launch template/provisioning 정책이 선택한 AMI를 사용합니다. 고정 AMI ID의 kernel은 새 노드를 시작한다고 바뀌지 않습니다. Latest/default AMI를 다시 조회하면 새 kernel을 선택할 수 있으며 EKS-optimized AMI는 별도 release 선택을 확인해야 합니다.
특정 커널에 고정해야 하면 버전 지정 AMI(al2023-ami-kernel-6.1-* 등)를 명시적으로 쓰십시오.
커널이 바뀔 때 점검할 것들:
| 항목 | 이유 |
|---|---|
| 튜너블의 존재와 위치 | 커널 버전 간에 이름이 바뀌거나 sysctl → debugfs로 이동한 것이 있음 |
| 커널 모듈 의존 컴포넌트 | eBPF 프로그램, 특정 CNI 기능, GPU 드라이버, 커스텀 모듈 |
| 스케줄러 거동 | 6.6+ EEVDF (아래 참고) — 지연 민감 워크로드에서 체감될 수 있음 |
| 성능 회귀 | 벤치마크를 커널 버전별로 다시 측정 |
권고: 커널 전환은 Kubernetes 버전 업그레이드와 같은 무게로 다루십시오. 스테이징에서 같은 커널로 먼저 검증하고, 성능 기준선을 다시 측정한 뒤 프로덕션에 적용하는 것이 안전합니다.
CPU — 스케줄러와 throttling
EEVDF — 6.6에서 바뀐 것
Linux 6.6에서 CFS의 태스크 선택 로직이 EEVDF(Earliest Eligible Virtual Deadline First)로 교체되었습니다.
정확히 이해할 가치가 있는 지점은 무엇이 바뀌고 무엇이 안 바뀌었는가입니다.
| 바뀐 것 | 안 바뀐 것 |
|---|---|
| 다음에 실행할 태스크를 고르는 방식 (가상 데드라인 기반) | vruntime 메커니즘, weight 계산 |
깨어난 태스크의 선점 판단 — 휴리스틱(sched_wakeup_granularity_ns) 대신 데드라인 비교 | 그룹 스케줄링(cgroup cpu.weight), 로드 밸런싱 |
즉 CFS를 통째로 갈아낸 것이 아니라 선택 로직을 교체한 진화로 보는 것이 정확합니다. fair_sched_class 안에서의 변경입니다.
운영 관점의 의미: 지연 민감 워크로드의 깨우기 지연 특성이 달라질 수 있습니다. 대개 개선 방향이지만, 커널 6.1에서 6.18로 넘어갈 때 p99가 바뀌면 이 변화가 후보 중 하나입니다.
튜너블의 실제 위치
커널 소스(kernel/sched/debug.c)에서 확인한 결과입니다.
| 항목 | 상태 |
|---|---|
sched_latency_ns | 제거됨 — kernel/sched/fair.c에 참조가 남아 있지 않음 |
sched_wakeup_granularity_ns | 제거됨 — 동일 |
/sys/kernel/debug/sched/base_slice_ns | 현재의 대응 튜너블. 내부 변수는 sysctl_sched_base_slice이고 debugfs에 base_slice_ns로 노출됨 |
즉 CFS 시절의 지연·선점 휴리스틱 튜너블은 사라지고, **기본 타임슬라이스 하나(base_slice_ns)**로 정리되었습니다. 이름에 sched_ 접두어가 없다는 점에 주의하십시오 — 경로는 /sys/kernel/debug/sched/base_slice_ns입니다.
EEVDF는 이와 별개로 sched_setattr() 시스템 콜로 태스크가 자기 타임슬라이스를 직접 요청할 수 있게 했습니다. 지연 민감 애플리케이션에는 전역 튜너블을 건드리는 것보다 이 경로가 맞습니다.
그래도 스케줄러 튜너블은 권장 튜닝 대상이 아닙니다. debugfs는 커널 디버그 인터페이스라 프로덕션에서 마운트되지 않을 수 있고, 대부분의 경우 애플리케이션의 스레드 수 조정이나 cgroup limit 조정이 더 나은 답입니다.
CPU limit — throttling이 진짜 문제인 경우
컨테이너 커널 기능에서 다룬 대로, CPU limit은 대역폭 제한이라 사용률이 낮아도 지연을 만듭니다.
진단:
# cgroup v2 — 컨테이너의 cgroup 경로에서
cat cpu.stat
# nr_periods, nr_throttled, throttled_usecnr_throttled / nr_periods 비율이 유의미하게 높으면 limit이 원인입니다.
대응의 우선순위 (자세한 설계는 리소스 최적화):
- 애플리케이션이 인식하는 CPU 수를 limit에 맞춥니다. 가장 자주 놓치는 근본 원인입니다 — 컨테이너 안의 런타임이 노드의 전체 코어 수를 보고 그만큼 스레드를 만들면, 할당량을 순식간에 소진합니다. JVM
-XX:ActiveProcessorCount, GoGOMAXPROCS, Node.jsUV_THREADPOOL_SIZE등을 limit에 맞춰 설정합니다 - limit을 올립니다 — 노드 안정성과의 트레이드오프
- 지연에 극히 민감하면 limit 제거를 검토합니다 — 단 노이지 네이버 위험을 request와 노드 분리로 관리해야 합니다
cpu.pressurePSI로 실제 대기 시간을 확인합니다
CPU Manager의 정적 정책(전용 코어 할당)은 지연 민감 워크로드에 유효한 별개 수단입니다. 다만 노드 리소스 활용률이 내려가므로 근거가 필요합니다.
메모리 — OOM과 압박
무엇을 조정하고 무엇을 두는가
| 파라미터 | 권고 |
|---|---|
vm.swappiness | Kubernetes는 전통적으로 swap 비활성을 전제. swap 지원이 성숙해 왔으나 EKS에서 켜기 전에 지원 상태를 확인해야 함 |
vm.overcommit_memory | 기본값 유지 권장. 바꾸면 컨테이너 할당 실패 양상이 예측하기 어려워짐 |
vm.min_free_kbytes | 회수 여유 공간. 너무 낮으면 급격한 할당에서 OOM. 노드 메모리가 크고 버스트가 심할 때만 검토 |
vm.max_map_count | Elasticsearch/OpenSearch 등에서 실제로 필요한 조정. 기본값이 낮아 mmap 한도에 걸림 |
kernel.pid_max | 고밀도 노드에서 PID 고갈 시 |
vm.max_map_count가 실제 사례로 자주 등장합니다 — OpenSearch 계열은 많은 파일을 mmap하므로 기본값에서 시작 실패합니다. 이것은 "근거 있는 튜닝"의 좋은 예입니다: 증상이 명확하고, 해당 파라미터가 직접 원인이며, 벤더 문서가 값을 제시합니다.
kubelet의 예약 — 커널 파라미터보다 먼저
노드 안정성에서 커널 튜닝보다 효과가 큰 것이 kubelet의 리소스 예약입니다.
| 설정 | 용도 |
|---|---|
--system-reserved | OS·시스템 데몬용 예약 |
--kube-reserved | kubelet·컨테이너 런타임용 예약 |
--eviction-hard | 이 임계에 닿으면 Pod 축출 |
예약이 부족하면 Pod가 노드 메모리를 다 먹고 커널이나 kubelet 자체가 OOM에 걸립니다. 이 경우 노드가 NotReady가 되고 그 위의 모든 Pod가 영향을 받습니다 — 개별 Pod OOM보다 훨씬 나쁜 결과입니다.
축출이 OOM보다 낫습니다. 축출은 Kubernetes가 통제된 방식으로 Pod를 옮기는 것이고, OOM killer는 커널이 프로세스를 갑자기 죽이는 것입니다. --eviction-hard를 적절히 설정해 커널 OOM 전에 Kubernetes가 개입하게 만드는 것이 목표입니다.
PSI로 압박 관측
cgroup v2의 PSI가 사용량보다 나은 신호를 줍니다.
# 노드 전체
cat /proc/pressure/memory
cat /proc/pressure/cpu
cat /proc/pressure/io
# 특정 cgroup
: "${KERNEL_CGROUP_PATH:?조회할 cgroup directory를 지정하세요}"
cat "$KERNEL_CGROUP_PATH/memory.pressure"some avg10은 최근 10초간 최소 하나의 태스크가 그 자원 때문에 지연된 시간의 비율입니다. 사용량 그래프가 평온한데 이 값이 올라가고 있으면 회수나 경합에 시간을 쓰고 있다는 뜻입니다.
네트워크 — 근거 있는 조정 항목
conntrack
앞서 다룬 대로 가장 자주 실제 장애를 만드는 항목입니다.
| 항목 | 내용 |
|---|---|
| 증상 | 새 연결이 조용히 드롭. 애플리케이션은 타임아웃/refused만 봄 |
| 함께 확인할 증거 | conntrack -S의 insert_failed 증가. 이것만으로 table 포화를 확정하지 않음 |
| 보조 신호 | dmesg의 nf_conntrack: table full, nf_conntrack_count / nf_conntrack_max 비율 |
| 조정 경로 | kube-proxy-config ConfigMap의 conntrack.maxPerCore / conntrack.min (EKS에서 이것이 우선) |
| 비용 | 항목당 노드 메모리. 무한정 올릴 수 없음 |
| 근본 대응 | 연결 churn 감소와 dataplane/map 압력 조사. Headless DNS만으로 tracking이 꺼지지 않음 |
maxPerCore를 쓰는 이유를 알아둘 가치가 있습니다. 절대값이 아니라 코어당 값이라, 노드 크기가 달라도 같은 설정으로 비례 조정됩니다. 절대값(nf_conntrack_max)을 직접 박으면 작은 노드에서는 과다, 큰 노드에서는 부족해집니다.
타임아웃도 조정 대상입니다 — nf_conntrack_tcp_timeout_established(기본이 매우 길다)를 줄이면 항목이 빨리 회수됩니다. 단 정상적인 장수명 연결이 끊기지 않도록 주의해야 합니다.
소켓 버퍼와 큐
| 파라미터 | 언제 |
|---|---|
net.core.somaxconn | accept 큐 오버플로 시. 연결 폭주를 받는 서버에서 흔한 조정 |
net.ipv4.tcp_max_syn_backlog | SYN 폭주 시 |
net.core.netdev_max_backlog | 수신 softirq가 못 따라갈 때 |
net.ipv4.tcp_rmem / tcp_wmem | TCP 크기 범위/기본값. 변경 자체가 autotuning을 끄지는 않으며 BDP·메모리 실측 근거로만 조정 |
net.ipv4.ip_local_port_range | 출발지 포트 고갈 시. egress가 많은 노드에서 실제로 발생 |
net.ipv4.tcp_tw_reuse | TIME_WAIT 누적 시. 거동을 이해하고 적용 |
somaxconn과 ip_local_port_range가 실무에서 근거 있는 조정의 대표 사례입니다. 전자는 accept 큐 오버플로 카운터(nstat의 TcpExtListenOverflows)로 증거를 잡을 수 있고, 후자는 포트 고갈이 연결 실패로 직접 나타납니다.
실측 근거가 없으면 적절한 tcp_rmem/tcp_wmem 범위를 유지합니다. Linux 문서는 명시적 SO_RCVBUF/SO_SNDBUF socket 설정이 해당 socket autotuning을 끈다고 설명합니다. 이것을 sysctl min/default/max 설정과 혼동하면 안 됩니다.
qdisc
노드 내 드롭이 확인되면(tc -s qdisc의 dropped) 조정 대상입니다.
fq_codel: 버퍼블로트 완화 — 지연이 문제일 때fq: 페이싱 — bbr과 함께 쓸 때- 큐 길이(
txqueuelen)를 늘리면 드롭은 줄지만 지연이 늘어납니다. 트레이드오프를 인지하고 결정해야 합니다
인터럽트 분산
/proc/interrupts에서 특정 코어 편중이 보이고 mpstat -P ALL의 %soft가 그 코어에서 튀면 RSS/RPS/RFS 설정을 봅니다. 다만 최신 ENA 드라이버와 인스턴스 타입은 다중 큐와 RSS가 기본 구성이라, 대개 문제가 되지 않습니다.
kube-proxy 모드
노드 커널 파라미터는 아니지만 데이터패스 성능에 가장 큰 영향을 줍니다.
| 상황 | 권고 |
|---|---|
| Service 수가 많고 iptables 모드 | nftables 모드 검토 — 1.33에서 GA, O(1) 조회 + 증분 갱신. 커널 5.13+ 필요(AL2023은 충족) |
| IPVS 모드 사용 중 | 이전 계획 필요 — 1.35에서 deprecated; upstream은 1.40 기본 비활성화·1.43 제거 계획. 권장 대체는 nftables |
| 기본값 유지 | nftables가 GA여도 기본은 여전히 iptables — 전환은 명시적 결정 |
스토리지
| 파라미터 | 내용 |
|---|---|
| I/O 스케줄러 | NVMe는 none(또는 mq-deadline)이 일반적. NVMe에서 복잡한 스케줄러는 이점이 적음 |
vm.dirty_ratio / dirty_background_ratio | 쓰기 버퍼링 양. 쓰기 폭주 시 지연 특성에 영향 |
| ephemeral storage | 커널 파라미터보다 overlayfs copy-up 비용이 실제 문제 — 쓰기 많은 경로는 볼륨으로 분리 (컨테이너 커널 기능) |
| EBS 성능 | 커널이 아니라 볼륨 타입·IOPS·throughput 설정의 문제 (EBS gp2 vs gp3 실측) |
워크로드별 정리
증상에서 출발하는 표입니다.
| 워크로드 | 자주 필요한 조정 | 근거 카운터 |
|---|---|---|
| 고연결 게이트웨이·프록시 | conntrack 상한, somaxconn, ip_local_port_range | insert_failed, TcpExtListenOverflows, 포트 고갈 |
| 지연 민감 (거래·실시간) | CPU limit 재검토, CPU Manager 정적 정책, fq_codel | cpu.stat throttling, cpu.pressure |
| 대용량 처리 (배치·데이터) | 버퍼 상한(장거리만), netdev_max_backlog | qdisc dropped, softirq 편중 |
| 검색·색인 (OpenSearch 등) | vm.max_map_count, 파일 디스크립터 한도 | 시작 실패 로그 |
| 고밀도 노드 | kernel.pid_max, kubelet 예약, 축출 임계 | PID 고갈, 노드 NotReady |
| 블록체인 노드 | 파일 디스크립터, 디스크 IOPS, 소켓 버퍼 | 블록체인 노드 운영 |
변경을 어떻게 관리할 것인가
튜닝 자체보다 관리 방식이 장기적으로 더 중요합니다.
| 원칙 | 이유 |
|---|---|
코드로 관리 (Karpenter EC2NodeClass, 시작 템플릿, Bottlerocket 설정) | 노드마다 값이 다른 상황을 막음 |
| 변경 사유를 주석으로 남김 | "왜 이 값인가"를 6개월 뒤에 알 수 없으면 아무도 되돌리지 못함 |
| 노드 그룹을 분리 | 워크로드 성격이 다르면 튜닝도 달라야 함. 한 프로필을 전체에 강요하지 않음 |
| 커널 버전을 고정하거나 전환을 계획 | kernel-default AMI는 조용히 커널이 바뀜 |
| 적용 후 실제 값 검증 | 특히 conntrack — 다른 주체가 덮어쓸 수 있음 |
| 변경 전후 같은 조건으로 측정 | 측정 없는 튜닝은 미신이 됨 |
정리
- 대부분은 기본값을 두십시오. 커널은 부하에 따라 자동 조정하고 있고, 근거 없는 튜닝은 재현 불가능한 구성과 커널 업그레이드 시 파손을 만듭니다.
- 튜닝의 전제는 측정입니다. 드롭 카운터부터 보십시오 —
insert_failed, qdiscdropped, NIC 드롭. - 선택한 AMI와 실행 중 kernel을 확인합니다. 기본 AMI 계열은 바뀔 수 있지만 고정 AMI ID가 노드 교체 시 자동 변경되는 것은 아닙니다.
- CPU throttling의 근본 원인은 대개 애플리케이션이 인식하는 CPU 수와 할당량의 불일치입니다.
GOMAXPROCS/ActiveProcessorCount부터 맞추십시오. - 노드 안정성에는 커널 튜닝보다 kubelet 예약과 축출 임계가 효과적입니다. 축출이 커널 OOM보다 낫습니다.
- 근거 있는 조정의 대표 사례는 **conntrack 상한,
somaxconn,ip_local_port_range,vm.max_map_count**입니다. 모두 직접적인 증거 카운터가 있습니다. - TCP sysctl 범위와 socket별 autotuning override는 다르며 측정 근거로만 조정합니다.
- IPVS 모드를 쓰고 있으면 이전 계획이 필요합니다 (1.35 deprecated; 1.40 기본 비활성화·1.43 제거 계획). Rollout 전에 아래 KEP-5495 링크에서 최신 일정을 확인합니다.
참고 자료
- Amazon Linux 2023 — Updating the Linux Kernel
- Amazon EKS-Optimized Amazon Linux 2023 AMIs
- Increase nf_conntrack_max limit on EKS nodes
- Running kube-proxy in nftables Mode — EKS Best Practices
- KEP-5495: Deprecate IPVS mode in kube-proxy
- EEVDF Scheduler — Linux kernel documentation
- kernel/sched/debug.c — debugfs 튜너블 정의
- bottlerocket-os/bottlerocket#4221 — conntrack limit not applied
- PSI - Pressure Stall Information
- Reserve Compute Resources for System Daemons (Kubernetes)
- Using sysctls in a Kubernetes Cluster
- 리소스 최적화 / Pod 네트워크 실측 벤치마크