노드의 여유 공간이 임계값 아래로 내려가면 kubelet 이 DiskPressure 조건을 올린다. 그러면 새 파드가 그 노드에 배치되지 않고, 압박이 이어지면 이미 도는 파드를 내보낸다. 파드가 Evicted 로 쌓이거나 ImagePullBackOff 가 나는 형태로도 나타난다.
kubectl get nodes
kubectl describe node <노드> | grep -A8 Conditions
kubectl get pod -A --field-selector status.phase=Failed
노드에 들어가 확인한다. 용량뿐 아니라 inode 도 본다. 작은 파일이 많으면 용량이 남아도 같은 증상이 난다.
df -h
df -i
du -sh /var/lib/containerd /var/lib/docker /var/log /var/lib/kubelet 2>/dev/null
du -h /var/log --max-depth=1 | sort -hr | head
| 경로 | 무엇이 쌓이나 |
|---|---|
/var/lib/containerd · /var/lib/docker |
이미지 레이어와 컨테이너 쓰기 레이어. 보통 가장 크다 |
/var/log/pods, /var/log/containers |
컨테이너 표준 출력 로그 |
/var/lib/kubelet/pods/<uid>/volumes |
emptyDir, ConfigMap, Secret |
/var/log/journal |
systemd 저널 |
컨테이너가 쓰는 임시 공간은 대부분 컨테이너 쓰기 레이어와 emptyDir 이다. 둘 다 노드의 ephemeral storage 로 계산되며, 파드 스펙에 ephemeral-storage 요청·상한을 주면 특정 파드가 노드를 혼자 채우는 일을 막을 수 있다.
resources:
requests:
ephemeral-storage: "1Gi"
limits:
ephemeral-storage: "4Gi"
emptyDir 에 medium: Memory 를 주면 디스크가 아니라 tmpfs 를 쓰므로 노드 메모리를 소비한다. 디스크 문제를 메모리 문제로 바꾸는 것일 뿐이니 크기 제한을 함께 준다.
kubelet 은 디스크 압박이 생기면 쓰지 않는 이미지를 스스로 지운다. 급할 때는 직접 지운다.
crictl images
crictl rmi --prune # 어떤 컨테이너도 쓰지 않는 이미지 제거
crictl rmi <이미지 ID>
Docker 를 런타임으로 쓰는 노드라면 docker system prune -a 가 같은 역할을 하지만, 이 명령은 멈춰 있는 컨테이너와 네트워크도 함께 지우므로 클러스터 노드에서는 범위를 좁혀 쓰는 편이 안전하다. 특정 이미지를 모든 노드에서 지워야 한다면 노드마다 crictl rmi 를 돌린다.
컨테이너 로그 파일을 직접 지우면 그 파일을 열고 있는 프로세스가 공간을 놓지 않아 효과가 없을 수 있다. 근본 해결은 kubelet 의 로그 회전 설정이다.
# /var/lib/kubelet/config.yaml
containerLogMaxSize: 50Mi
containerLogMaxFiles: 3
systemctl restart kubelet
저널도 상한을 정한다.
journalctl --disk-usage
journalctl --vacuum-time=2d
# /etc/systemd/journald.conf
[Journal]
SystemMaxUse=500M
SystemKeepFree=1G
이미 쫓겨난 파드는 API 에 계속 남아 목록을 어지럽힌다. 용량과는 무관하지만 함께 정리한다.
kubectl delete pod -A --field-selector status.phase=Failed
응용 프로그램이 컨테이너 안의 파일에 로그를 쌓고 있으면 표준 출력으로 바꾸고 회전 설정에 맡긴다. 이미지 태그를 latest 로 두고 자주 배포하는 환경은 노드에 태그 없는 레이어가 계속 쌓이므로 버전 태그를 쓴다. 그리고 /var/lib/containerd 와 /var/log 를 루트 파일 시스템과 분리된 디스크에 두면, 차더라도 노드 전체가 멈추지는 않는다.