확장

처음에는 control-plane 1 + worker 2로 시작했습니다. 워커 둘은 PXE로 설치한 베어메탈 미니 PC였고, 곧 Terraform으로 VM으로 옮겼습니다. 그다음 노드가 하나 죽어도 서비스가 그대로 유지되는지 보고 싶어서 제어 노드를 셋으로 늘렸습니다. 워커는 VM 셋을 새로 만들면서 남아 있던 베어메탈 둘을 함께 붙여 다섯이 됐습니다. control-plane 3 + worker 5, 8노드입니다.

필요한 규모는 재지 않았습니다.

메모리 압박

k3s는 kubeadm과 달리 apiserver, etcd, controller-manager, scheduler를 k3s server 프로세스 하나에서 돌립니다. 스태틱 파드가 아니라서 파드 집계에 잡히지 않습니다. 실측 RSS는 2,531Mi였고, 제어 노드가 3대였습니다.

호스트 RAM이 먼저 바닥났습니다.

축소

제어 노드를 1대로 줄이고 워커도 통합했습니다. 회수한 메모리는 남은 노드에 다시 배정했습니다. 서비스는 그대로 돌았습니다.

구성은 Terraform으로 VM을 만들고 Ansible로 노드를 부트스트랩하는 코드로 관리하고 있었습니다. 줄이는 것도 정의를 지우고 다시 적용하면 됐습니다.

kube-reserved 재조정

줄이고 나서 문제가 하나 더 나왔습니다.

kube-reserved 기본값은 512Mi입니다. 워커라면 kubelet과 containerd 몫만 예약하면 되니 충분한 값이지만, 컨트롤 플레인에는 2,531Mi짜리 k3s server가 통째로 들어 있습니다. 예약을 실제보다 작게 잡으면 스케줄러는 노드가 한가하다고 보고 파드를 계속 배치합니다.

Allocatable 4,401Mi에 실사용이 4,383Mi(99%)까지 올라갔는데, 스케줄러가 보는 requests 합은 1,260Mi(28%)였습니다. MemoryPressure가 뜨면서 파드 12개가 축출됐습니다.

kube-reserved를 512Mi에서 2,560Mi로 올린 PR #79.

enforceNodeAllocatable["pods"]라 kube cgroup에는 상한이 없습니다. 예약은 한도가 아니라 장부입니다. 넘어간 만큼은 파드 몫에서 빠집니다. 그래서 예약값을 실측에 맞춰 넣었습니다.

# 실측
ssh <> ps -eo rss,args | grep "k3s server"
# 적용값 확인
kubectl get --raw /api/v1/nodes/<>/proxy/configz

적용 후 7일. Allocatable 2,353Mi · 파드 requests 716Mi · 실사용 약 3.7GiB · 전체 5,925Mi. 사건 당시(08-29) 데이터는 보존 기간 7일이 지나 남아 있지 않아 Prometheus에서 직접 그렸습니다.

현재 구성

control-plane 1 + worker 4, 5노드입니다. HA는 아닙니다.

워커 4대 중 2대는 베어메탈입니다. 그중 1대는 축소와 무관하게 CI 폴백용으로 나중에 붙인 arm64 보드입니다. 전체 구성은 홈랩 전체 구성에 정리해 두었습니다.

정리

세울 때 필요한 규모를 재지 않았고, 줄일 때도 메모리가 먼저 바닥났습니다. 지금은 구성을 바꾸기 전에 어느 값을 얼마 동안 볼지부터 정합니다. 기본값은 실측에 맞춥니다.

온도에서도 같은 일이 있었습니다. 알림 규칙을 여러개 만들어 두고도 하이퍼바이저가 100°C를 찍는 걸 몰랐습니다 → 랙을 바꿨습니다 — 온도 때문에