노드의 스왑 메모리 관리
노드의 스왑 메모리 관리 (Manage Swap on your Node)
쿠버네티스는 노드에서 스왑 메모리를 사용하도록 구성할 수 있어요. 이렇게 하면 커널이 페이지를 백킹 스토리지로 스왑아웃해 물리 메모리를 확보할 수 있습니다. 이는 여러 사용 사례에 유용합니다. 예를 들어 스왑 사용의 이점을 얻을 수 있는 워크로드를 실행하는 노드 — 메모리 풋프린트는 크지만 주어진 시점에 그 메모리의 일부만 접근하는 워크로드 — 같은 경우가 있습니다. 또한 메모리 압력 급증 중 파드가 종료되는 것을 방지하고, 노드를 시스템 수준의 메모리 급증으로부터 보호하며, 노드의 더 유연한 메모리 관리를 허용하는 등 여러 이점이 있습니다.
클러스터에서 스왑을 구성하는 방법을 배우려면 쿠버네티스 노드에서 스왑 메모리 구성하기를 읽어보세요.
출처: 문서
본문
운영 체제 지원
- Linux 노드는 스왑을 지원하며, 각 노드를 구성해 활성화해야 합니다. 기본적으로 kubelet은 스왑이 활성화된 Linux 노드에서 시작하지 않습니다.
- Windows 노드는 스왑 공간이 필요합니다. 기본적으로 kubelet은 스왑이 비활성화된 Windows 노드에서 시작하지 않습니다.
어떻게 동작하나요?
노드에서 스왑 사용을 상상할 수 있는 여러 가지 방법이 있습니다. 노드에서 kubelet이 이미 실행 중이라면, 스왑이 프로비저닝된 후 식별하려면 재시작해야 합니다.
kubelet이 스왑이 프로비저닝되고 사용 가능한 노드(failSwapOn: false 구성)에서 시작하면, kubelet은:
- 이 스왑 활성화 노드에서 시작할 수 있습니다.
- 컨테이너 런타임으로 흔히 불리는 Container Runtime Interface (CRI) 구현이 기본적으로 쿠버네티스 워크로드에 0 스왑 메모리를 할당하도록 지시합니다.
노드의 스왑 구성은 KubeletConfiguration의 memorySwap을 통해 클러스터 관리자에게 노출됩니다. 클러스터 관리자로서 memorySwap.swapBehavior를 설정해 스왑 메모리가 있을 때 노드의 동작을 지정할 수 있어요.
스왑 동작 (Swap behaviors)
사용할 스왑 동작을 선택해야 합니다. 클러스터의 다른 노드는 다른 스왑 동작을 사용할 수 있습니다.
Linux 노드에서 선택할 수 있는 스왑 동작은 다음과 같습니다:
NoSwap (기본값)
: 이 노드에서 파드로 실행되는 워크로드는 스왑을 사용하지 않으며 사용할 수 없습니다.
LimitedSwap
: 쿠버네티스 워크로드는 스왑 메모리를 활용할 수 있습니다.
NoSwap 동작을 선택하고 kubelet이 스왑 공간을 허용하도록 구성하면(failSwapOn: false), 워크로드는 어떤 스왑도 사용하지 않습니다.
그러나 쿠버네티스가 관리하는 컨테이너 밖의 프로세스 — systemd 서비스(심지어 kubelet 자체!) — 는 스왑을 활용할 수 있습니다.
클러스터의 스왑 활성화에 대해 배우려면 쿠버네티스 노드에서 스왑 메모리 구성하기를 읽을 수 있습니다.
컨테이너 런타임 통합
kubelet은 컨테이너 런타임 API를 사용해, 컨테이너의 원하는 스왑 구성을 활성화하는 방식으로 특정 구성(예: cgroup v2의 경우 memory.swap.max)을 적용하도록 컨테이너 런타임에 지시합니다. 제어 그룹 또는 cgroups를 사용하는 런타임의 경우, 컨테이너 런타임은 이 설정을 컨테이너 수준 cgroup에 쓰는 역할을 합니다.
스왑 사용에 대한 관측성
노드 및 컨테이너 수준 메트릭 통계
Kubelet은 이제 노드 및 컨테이너 수준 메트릭 통계를 수집하며, 이는 주로 Prometheus 같은 모니터링 도구가 사용하는 /metrics/resource와 주로 Autoscaler가 사용하는 /stats/summary kubelet HTTP 엔드포인트에서 접근할 수 있습니다. 이는 kubelet에 직접 요청할 수 있는 클라이언트가 LimitedSwap 사용 시 스왑 사용량과 남은 스왑 메모리를 모니터링하게 해줍니다. 또한 머신의 총 물리 스왑 용량을 보여주는 machine_swap_bytes 메트릭이 cadvisor에 추가되었습니다. 자세한 내용은 이 페이지를 참조하세요.
예를 들어 다음 /metrics/resource가 지원됩니다:
node_swap_usage_bytes: 노드의 현재 스왑 사용량(바이트).container_swap_usage_bytes: 컨테이너 스왑 사용량의 현재 양(바이트).container_swap_limit_bytes: 컨테이너 스왑 제한의 현재 양(바이트).
kubectl top --show-swap 사용하기
메트릭 조회는 가치가 있지만 다소 번거롭습니다. 이 메트릭들은 사람보다 소프트웨어가 사용하도록 설계됐기 때문입니다. 이 데이터를 더 사용자 친화적인 방식으로 소비하기 위해 kubectl top 명령이 --show-swap 플래그로 스왑 메트릭을 지원하도록 확장되었습니다.
노드의 스왑 사용량에 대한 정보를 얻으려면 kubectl top nodes --show-swap을 사용할 수 있어요:
kubectl top nodes --show-swap
다음과 유사한 출력이 나옵니다:
NAME CPU(cores) CPU(%) MEMORY(bytes) MEMORY(%) SWAP(bytes) SWAP(%)
node1 1m 10% 2Mi 10% 1Mi 0%
node2 5m 10% 6Mi 10% 2Mi 0%
node3 3m 10% 4Mi 10% <unknown> <unknown>
파드별 스왑 사용량 정보를 얻으려면 kubectl top pods --show-swap을 사용할 수 있어요:
kubectl top pod -n kube-system --show-swap
다음과 유사한 출력이 나옵니다:
NAME CPU(cores) MEMORY(bytes) SWAP(bytes)
coredns-58d5bc5cdb-5nbk4 2m 19Mi 0Mi
coredns-58d5bc5cdb-jsh26 3m 37Mi 0Mi
etcd-node01 51m 143Mi 5Mi
kube-apiserver-node01 98m 824Mi 16Mi
kube-controller-manager-node01 20m 135Mi 9Mi
kube-proxy-ffgs2 1m 24Mi 0Mi
kube-proxy-fhvwx 1m 39Mi 0Mi
kube-scheduler-node01 13m 69Mi 0Mi
metrics-server-8598789fdb-d2kcj 5m 26Mi 0Mi
노드 상태의 일부로 스왑 용량 보고하기
이제 노드 상태 필드 node.status.nodeInfo.swap.capacity가 추가되어 노드의 스왑 용량을 보고합니다.
예를 들어 다음 명령으로 클러스터의 노드 스왑 용량을 검색할 수 있습니다:
kubectl get nodes -o go-template='{{range .items}}{{.metadata.name}}: {{if .status.nodeInfo.swap.capacity}}{{.status.nodeInfo.swap.capacity}}{{else}}<unknown>{{end}}{{"\n"}}{{end}}'
다음과 유사한 출력이 나옵니다:
node1: 21474836480
node2: 42949664768
node3: <unknown>
<unknown> 값은 해당 노드에 .status.nodeInfo.swap.capacity 필드가 설정되지 않았음을 나타냅니다. 이는 대개 노드에 스왑이 프로비저닝되지 않았거나, 가능성은 낮지만 kubelet이 노드의 스왑 용량을 결정할 수 없음을 의미합니다.
Node Feature Discovery (NFD)를 사용한 스왑 발견 {#node-feature-discovery}
Node Feature Discovery는 하드웨어 기능과 구성을 감지하는 쿠버네티스 애드온입니다. 이는 스왑이 프로비저닝된 노드를 발견하는 데 활용할 수 있어요.
예를 들어 어떤 노드에 스왑이 프로비저닝됐는지 알아내려면 다음 명령을 사용합니다:
kubectl get nodes -o jsonpath='{range .items[?(@.metadata.labels.feature\.node\.kubernetes\.io/memory-swap)]}{.metadata.name}{"\t"}{.metadata.labels.feature\.node\.kubernetes\.io/memory-swap}{"\n"}{end}'
다음과 유사한 출력이 나옵니다:
k8s-worker1: true
k8s-worker2: true
k8s-worker3: false
이 예시에서 스왑은 k8s-worker1과 k8s-worker2 노드에 프로비저닝되어 있지만 k8s-worker3에는 없습니다.
위험과 주의사항
스왑 공간을 암호화할 것을 강력히 권장합니다. 자세한 내용은 메모리 기반 볼륨 memory-backed volumes을 참조하세요.
시스템에 스왑이 있으면 예측 가능성이 낮아집니다. 스왑은 더 많은 RAM을 사용할 수 있게 만들어 성능을 향상시킬 수 있지만, 데이터를 메모리로 다시 스와핑하는 것은 무거운 작업이며 때로는 수십 배 더 느려 예상치 못한 성능 회귀를 일으킬 수 있습니다. 또한 스왑은 메모리 압력 하에서 시스템의 동작을 바꿉니다. 스왑을 활성화하면 시끄러운 이웃(noisy neighbors)의 위험이 커지는데, RAM을 자주 사용하는 파드가 다른 파드를 스왑하게 만들 수 있기 때문입니다. 또한 스왑은 쿠버네티스에서 예측 가능하게 회계할 수 없는 워크로드의 더 큰 메모리 사용을 허용하고, 예상치 못한 패킹 구성 때문에 스케줄러는 현재 스왑 메모리 사용을 회계하지 않습니다. 이는 시끄러운 이웃의 위험을 높입니다.
스왑 메모리가 활성화된 노드의 성능은 기반 물리 스토리지에 달려 있습니다. 스왑 메모리가 사용 중일 때는, I/O 스로틀링이 있는 클라우드 VM처럼 I/O 작업(IOPS)이 제약된 환경에서 성능이 솔리드 스테이트 드라이브나 NVMe 같은 더 빠른 스토리지 매체와 비교해 크게 나빠집니다. 스왑이 IO 압력을 유발할 수 있으므로, 시스템에 중요한 데몬에게 더 높은 IO 지연 우선순위를 주는 것이 권장됩니다. 아래 권장 사례 섹션의 관련 부분을 참조하세요.
메모리 기반 볼륨
Linux 노드에서 메모리 기반 볼륨(예: secret 볼륨 마운트, 또는 medium: Memory를 가진 emptyDir)은 tmpfs 파일시스템으로 구현됩니다. 그러한 볼륨의 내용은 항상 메모리에 남아 있어야 하므로, 디스크로 스왑되어서는 안 됩니다. 해당 볼륨의 내용이 메모리에 남도록 보장하기 위해 noswap tmpfs 옵션이 사용됩니다.
Linux 커널은 버전 6.3부터 noswap 옵션을 공식 지원합니다(자세한 내용은 Linux 커널 버전 요구 사항에서 확인). 그러나 여러 배포판은 이 마운트 옵션을 더 오래된 Linux 버전에도 백포트하는 경우가 많습니다.
노드가 noswap 옵션을 지원하는지 검증하기 위해 kubelet은 다음을 수행합니다:
- 커널 버전이 6.3보다 위면
noswap옵션이 지원된다고 가정합니다. - 그렇지 않으면 kubelet은 시작 시
noswap옵션으로 더미 tmpfs를 마운트하려 시도합니다. 알 수 없는 옵션이라는 오류로 kubelet이 실패하면noswap이 지원되지 않는다고 가정하고 사용하지 않습니다. 메모리 기반 볼륨이 디스크로 스왑될 수 있다는 경고 로그 항목이 출력됩니다. kubelet이 성공하면 더미 tmpfs를 삭제하고noswap옵션을 사용합니다.noswap옵션이 지원되지 않으면 kubelet은 경고 로그 항목을 출력한 뒤 실행을 계속합니다.
스왑 설정 예시는 쿠버네티스 노드에서 스왑 메모리 구성하기를 참조하세요. 그러나 암호화 스왑 처리는 kubelet의 범위가 아니며, 일반적인 OS 구성 문제이므로 그 수준에서 다뤄야 합니다. 이 위험을 완화하도록 암호화 스왑을 프로비저닝하는 것은 관리자의 책임입니다.
축출 (Evictions)
스왑 활성화 노드의 메모리 축출 임계값 구성은 까다로울 수 있습니다.
스왑이 비활성화된 상태에서는 노드의 메모리 용량보다 약간 낮게 kubelet의 축출 임계값을 구성하는 것이 합리적입니다. 그 이유는 노드가 메모리가 부족해져 OOM(Out Of Memory) 킬러를 호출하기 전에 쿠버네티스가 파드를 축출하기 시작하길 원하기 때문입니다. OOM 킬러는 쿠버네티스를 인지하지 못하므로 QoS, 파드 우선순위 같은 쿠버네티스 특정 요소를 고려하지 않습니다.
스왑이 활성화되면 상황은 더 복잡합니다. Linux에서 vm.min_free_kbytes 파라미터는 커널이 적극적으로 메모리를 회수(페이지 스왑아웃 포함)하기 시작하는 메모리 임계값을 정의합니다. kubelet의 축출 임계값이 커널이 메모리 회수를 시작하기 전에 축출이 일어나도록 설정되면, 노드 메모리 압력 중 워크로드가 절대 스왑아웃될 수 없을 수 있습니다. 그러나 축출 임계값을 너무 높게 설정하면 노드 메모리가 부족해져 OOM 킬러를 호출할 수 있는데, 이것도 이상적이지 않습니다.
이를 해결하려면 kubelet의 축출 임계값을 vm.min_free_kbytes 값보다 약간 낮게 설정하는 것이 권장됩니다. 이렇게 하면 kubelet이 파드를 축출하기 전에 노드가 스와핑을 시작할 수 있어, 워크로드가 사용하지 않는 데이터를 스왑아웃하고 축출이 일어나는 것을 방지할 수 있습니다. 반면에 약간만 낮기 때문에 kubelet은 노드 메모리가 부족해지기 전에 파드를 축출하기 시작할 가능성이 높아 OOM 킬러를 피할 수 있습니다.
vm.min_free_kbytes의 값은 노드에서 다음 명령을 실행해 결정할 수 있습니다:
cat /proc/sys/vm/min_free_kbytes
미사용 스왑 공간
LimitedSwap 동작에서 Pod에 사용 가능한 스왑 양은 노드의 총 메모리에 대한 요청된 메모리 비율을 기반으로 자동 결정됩니다(자세한 내용은 아래 섹션 참조).
이 설계는 보통 일부 스왑이 쿠버네티스 워크로드에 제한된 채 남는 것을 의미합니다. 예를 들어 쿠버네티스 v1.37은 Guaranteed QoS의 파드에 스왑 사용을 허용하지 않으므로, Guaranteed 파드의 메모리 요청에 비례하는 스왑 양은 쿠버네티스 워크로드에 의해 사용되지 않고 남습니다.
이 동작은 스왑 자격이 없는 파드가 많을 때 어떤 위험을 지닙니다. 반면에 이는 시스템 예약 스왑 메모리 양을 효과적으로 유지하며, 시스템 데몬과 kubelet 자체 같은 쿠버네티스 범위 밖의 프로세스가 사용할 수 있습니다.
쿠버네티스 클러스터에서 스왑 사용을 위한 모범 사례
시스템 중요 데몬의 스왑 비활성화
테스트 단계와 사용자 피드백에서 시스템 중요 데몬과 서비스의 성능이 저하될 수 있음이 관찰되었습니다. 이는 kubelet을 포함한 시스템 데몬이 평소보다 느리게 동작할 수 있음을 의미합니다. 이 문제가 발생하면 시스템 슬라이스의 cgroup을 스와핑을 방지하도록 구성(즉, memory.swap.max=0 설정)하는 것이 좋습니다.
I/O 지연에 대해 시스템 중요 데몬 보호
스왑은 노드의 I/O 부하를 증가시킬 수 있습니다. 메모리 압력이 커널로 하여금 페이지를 빠르게 스왑 인/아웃하게 하면, I/O 작업에 의존하는 시스템 중요 데몬과 서비스가 성능 저하를 겪을 수 있습니다.
이를 완화하기 위해 systemd 사용자는 I/O 지연 측면에서 시스템 슬라이스에 우선순위를 주는 것이 권장됩니다. non-systemd 사용자는 시스템 데몬과 프로세스를 위한 전용 cgroup을 설정하고 같은 방식으로 I/O 지연에 우선순위를 주는 것이 좋습니다. 이는 시스템 슬라이스에 io.latency를 설정해 더 높은 I/O 우선순위를 부여함으로써 달성할 수 있습니다. 자세한 내용은 cgroup 문서를 참조하세요.
스왑과 컨트롤 플레인 노드
쿠버네티스 프로젝트는 어떤 스왑 공간도 구성하지 않고 컨트롤 플레인 노드를 실행할 것을 권장합니다. 컨트롤 플레인은 주로 Guaranteed QoS 파드를 호스팅하므로, 보통 스왑을 비활성화할 수 있습니다. 주요 우려는 컨트롤 플레인의 중요한 서비스 스와핑이 성능에 부정적인 영향을 줄 수 있다는 것입니다.
스왑용 전용 디스크 사용
쿠버네티스 프로젝트는 스왑을 활성화한 노드를 실행할 때마다 암호화 스왑을 사용할 것을 권장합니다. 스왑이 파티션이나 루트 파일시스템에 있으면, 워크로드가 디스크에 써야 하는 시스템 프로세스와 간섭할 수 있습니다. 같은 디스크를 공유하면 프로세스가 스왑을 압도해 kubelet, 컨테이너 런타임, systemd의 I/O를 방해하고, 이는 다른 워크로드에 영향을 줍니다. 스왑 공간이 디스크에 있으므로 의도한 사용 사례에 충분히 빠른 디스크인지 확인하는 것이 중요합니다. 또는 단일 백킹 장치의 서로 다른 매핑 영역 사이에 I/O 우선순위를 구성할 수 있습니다.
스왑 인지 스케줄링
쿠버네티스 v1.37은 스왑 메모리 사용을 회계하는 방식으로 파드를 노드에 할당하는 것을 지원하지 않습니다. 스케줄러는 보통 인프라 리소스의 요청 을 사용해 파드 배치를 안내하며, 파드는 스왑 공간을 요청하지 않고 memory만 요청합니다. 즉, 스케줄러는 스케줄링 결정을 할 때 스왑 메모리를 고려하지 않습니다. 이는 우리가 적극적으로 작업 중인 것이지만 아직 구현되지 않았습니다.
관리자가 특별히 사용할 의도가 없으면 파드가 스왑 메모리가 있는 노드에 스케줄링되지 않도록 보장하기 위해, 관리자는 스왑이 있는 노드에 테인트(taint)를 적용해 이 문제로부터 보호할 수 있습니다. 테인트는 스왑을 허용하는 워크로드가 부하 시 스왑이 없는 노드로 넘쳐나지 않도록 보장합니다.
최적 성능을 위한 스토리지 선택
스왑 공간으로 지정된 스토리지 장치는 높은 메모리 사용 중 시스템 응답성을 유지하는 데 중요합니다. 회전 자기 하드 디스크 드라이브(HDD)는 기계적 특성이 상당한 지연을 도입해 심각한 성능 저하와 시스템 스래싱(thrashing)을 일으키므로 이 작업에 부적합합니다. 현대 성능 요구를 위해 솔리드 스테이트 드라이브(SSD) 같은 장치가 스왑에 적절한 선택일 것입니다. 낮은 지연의 전자 접근이 속도 저하를 최소화하기 때문입니다.
스왑 동작 세부 사항
LimitedSwap으로 스왑 제한이 어떻게 결정되나요?
스왑 메모리의 구성(제한 포함)은 상당한 도전 과제입니다. 잘못 구성되기 쉬울 뿐만 아니라, 시스템 수준 속성으로서 어떤 잘못된 구성도 특정 워크로드뿐만 아니라 전체 노드를 손상시킬 수 있습니다. 이 위험을 완화하고 노드의 건강을 보장하기 위해 우리는 자동 제한 구성과 함께 Swap을 구현했습니다.
LimitedSwap에서 Burstable QoS 분류에 속하지 않는 파드(즉 BestEffort/Guaranteed QoS 파드)는 스왑 메모리 사용이 금지됩니다. BestEffort QoS 파드는 예측 불가능한 메모리 소비 패턴을 보이며 메모리 사용에 대한 정보가 부족해, 안전한 스왑 메모리 할당을 결정하기 어렵습니다. 반대로 Guaranteed QoS 파드는 보통 워크로드가 지정한 리소스의 정밀한 할당에 의존하는 애플리케이션에 사용되며, 메모리가 즉시 사용 가능합니다. 위에서 언급한 보안과 노드 건강 보장을 유지하기 위해 LimitedSwap이 적용되는 동안 이 파드들은 스왑 메모리를 사용할 수 없습니다. 또한 높은 우선순위의 파드는 소비하는 메모리가 항상 RAM에 있도록(따라서 즉시 사용 가능하도록) 스왑 사용이 허용되지 않습니다.
스왑 제한 계산을 자세히 설명하기 전에 다음 용어를 정의할 필요가 있습니다:
nodeTotalMemory: 노드에서 사용 가능한 총 물리 메모리 양.totalPodsSwapAvailable: 파드가 사용할 수 있는 노드의 총 스왑 메모리 양(일부 스왑 메모리는 시스템 사용을 위해 예약될 수 있음).containerMemoryRequest: 컨테이너의 메모리 요청.
스왑 제한은 다음과 같이 구성됩니다:
( containerMemoryRequest / nodeTotalMemory ) × totalPodsSwapAvailable
다시 말해, 컨테이너가 사용할 수 있는 스왑 양은 메모리 요청, 노드의 총 물리 메모리, 파드가 사용할 수 있는 노드의 총 스왑 메모리 양에 비례합니다.
중요한 점은 Burstable QoS 파드의 컨테이너는 메모리 요청을 메모리 제한과 같게 지정해 스왑 사용을 선택 해제할 수 있다는 것입니다. 이런 방식으로 구성된 컨테이너는 스왑 메모리에 접근할 수 없습니다.
더 알아보기 (Learn more)
- Linux 노드에서 스왑을 관리하는 방법을 배우려면 쿠버네티스 노드에서 스왑 메모리 구성하기를 읽어보세요.
- 쿠버네티스와 스왑에 관한 블로그 게시물을 확인할 수 있습니다.
- 배경 정보는 원래 KEP, KEP-2400과 그 설계를 참조하세요.