파드 Pending — volume node affinity 충돌
데이터를 디스크에 들고 있어야 하는 앱이라 StatefulSet 으로 띄우고, 파드마다 EBS 볼륨을 PVC 로 하나씩 붙여 씁니다. 클러스터는 노드 12대를 세 가용영역에 나눠 두고 있었습니다.
그 StatefulSet 파드 하나가 6분째 Pending 에서 안 넘어갔습니다. 노드는 12대 전부 Ready 상태.
$ kubectl get pod app-0
NAME READY STATUS RESTARTS AGE
app-0 0/1 Pending 0 6m
스케줄러의 거절 사유 — 개수까지 기록
$ kubectl describe pod app-0
Events:
Warning FailedScheduling 0/12 nodes are available:
9 node(s) had volume node affinity conflict,
3 Insufficient memory.
12대를 전부 후보로 놓고 하나씩 떨어뜨리면서 이유별 개수를 남깁니다. 9대는 볼륨, 3대는 메모리. 원인이 두 개입니다.
taint 상태 확인 — 원인 아님
$ kubectl get nodes -o json | jq -r '.items[] | "\(.metadata.name) \(.spec.taints // [])"'
taint가 붙은 노드는 3대. 개수는 맞아떨어지지만 그 3대는
Insufficient memory로 잡힌 쪽입니다. taint가 원인이면 메시지는
node(s) had untolerated taint로 나옵니다.
EBS 볼륨은 가용영역을 못 넘음
EBS 볼륨은 만들어진 가용영역에 묶여서 다른 존의 노드에는 붙지 않습니다. 그래서
EBS CSI 드라이버는 PV를 만들 때 그 존을 nodeAffinity에 박아두고, 파드가 이
PV를 쓰면 스케줄러는 해당 존의 노드만 후보로 봅니다.
$ kubectl get pv pvc-8f3a... -o jsonpath='{.spec.nodeAffinity}' | jq
{
"required": {
"nodeSelectorTerms": [
{
"matchExpressions": [
{
"key": "topology.ebs.csi.aws.com/zone",
"operator": "In",
"values": ["ap-northeast-2a"]
}
]
}
]
}
}
ap-northeast-2a 하나로 고정.
처음 스케줄된 존이 파드 수명 내내 제약
$ kubectl get nodes -L topology.kubernetes.io/zone --no-headers | awk '{print $NF}' | sort | uniq -c
3 ap-northeast-2a
5 ap-northeast-2b
4 ap-northeast-2c
2a에 3대, 그 3대는 전부 메모리가 꽉 찬 상태. 나머지 9대는 볼륨이 붙을 수
없는 존이라 후보에서 빠집니다. 0/12의 내역이 이렇게 나옵니다.
한 번 바인딩된 PVC는 파드가 재생성돼도 그대로 재사용됩니다. 처음 스케줄된 존이 파드가 삭제될 때까지 따라붙습니다. 오토스케일링이나 노드 교체로 그 존의 여유가 사라지면 클러스터 전체에 자리가 남아도 이 파드만 못 뜹니다.
응급 처치 — 해당 존에 노드 추가
$ aws autoscaling set-desired-capacity \
--auto-scaling-group-name <asg-name> \
--desired-capacity 4
2a에 자리가 생기면 Pending 파드가 바로 스케줄됩니다.
근본 대응 — volumeBindingMode
Immediate면 PVC가 생기는 시점에 볼륨이 만들어지고 존이 확정됩니다.
파드가 어느 노드로 갈지 정해지기 전에 존이 먼저 정해지는 구조입니다.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer # Immediate 가 아니라 이것
parameters:
type: gp3
WaitForFirstConsumer는 순서를 뒤집습니다. 스케줄러가 파드를 놓을 노드를
먼저 정하고, 그 노드가 속한 존에 볼륨을 만듭니다. 충돌 조건 자체가
없어집니다.
존별 노드 수도 같이 맞춰야 하는데, 한 존에 3대뿐이면 그 3대가 차는 순간 같은 상황이 그대로 재현되기 때문입니다. 존마다 최소 3대가 남도록 ASG min을 조정했습니다.
흔한 오답 — PVC 재생성
“PVC를 지우고 다시 만들라”는 답이 자주 보입니다. 존이 다시 잡히니 파드는
뜹니다. 다만 StorageClass의 reclaimPolicy가 기본값 Delete면 PVC를 지울 때
PV와 EBS 볼륨까지 같이 삭제되고, 안에 있던 데이터도 사라집니다.
StatefulSet의 PVC는 대부분 지우면 안 되는 대상입니다. 노드를 한 대 늘리는 쪽이 안전합니다.
남은 것
이미 Immediate로 만들어져 바인딩된 PV에는 StorageClass 변경이 소급
적용되지 않습니다. nodeAffinity는 PV 생성 시점에 확정되고 이후 수정할 수
없습니다. 옮기려면 스냅샷으로 다른 존에 새 볼륨을 만들고 PVC 를 교체해야
하는데, 그 사이 파드를 내려야 하니 다운타임이 따라옵니다.
댓글 0