파드 이벤트에 다음이 찍히고 ErrImagePull 로 넘어간다.
Warning Failed 67s kubelet spec.initContainers{hydrator}:
Failed to pull image "<REGISTRY>/<IMAGE>:<TAG>": pull QPS exceeded
Warning Failed 67s kubelet spec.initContainers{hydrator}: Error: ErrImagePull
Normal Pulling 53s (x2 over 67s) kubelet spec.initContainers{hydrator}: Pulling image ...
같은 파드의 다른 컨테이너는 정상적으로 내려받았거나 이미 캐시에 있는 상태이고, 특정 이미지 하나만 이 메시지를 낸다. 파드 수십 개가 한꺼번에 뜨는 초기 배포에서 주로 나온다.
레지스트리가 보낸 응답이 아니다. 노드의 kubelet 이 스스로 건 제한이다. kubelet 은 이미지 내려받기 요청에 토큰 버킷 방식의 속도 제한을 걸어 두고, 초과하면 레지스트리에 요청을 보내지도 않고 이 오류를 만든다.
레지스트리 쪽 제한은 메시지가 다르다. Docker Hub 의 익명 사용자 제한은 toomanyrequests: You have reached your pull rate limit 로, HTTP 429 로 온다. 둘을 섞어 보면 엉뚱한 곳을 손보게 된다.
| 메시지 | 주체 | 대응 |
|---|---|---|
pull QPS exceeded |
노드의 kubelet | kubelet 속도 제한 조정 또는 동시 기동 축소 |
toomanyrequests · HTTP 429 |
레지스트리 | 인증 추가, 미러 · 프록시 캐시 도입 |
unauthorized · 401 |
레지스트리 인증 | imagePullSecrets |
kubelet 은 실패 후 다시 시도한다. 이벤트의 Pulling (x2 over 67s) 가 재시도 중이라는 뜻이다. 대량 배포 초기에 잠깐 나타났다가 몇 분 안에 풀리는 경우가 대부분이고, 그때는 조치할 것이 없다.
근본 원인은 한 노드에서 동시에 시작한 파드 수다. 배포를 단계로 나누어 기동하면 사라진다. 대규모 제품(SAS Viya · 데이터 플랫폼 스택)을 처음 올릴 때는 상태 비저장 계층과 연산 계층을 나누어 순차로 올리는 편이 안전하다.
지속적으로 나온다면 KubeletConfiguration 을 조정한다. 두 값이 짝이다.
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
registryPullQPS: 10
registryBurst: 20
registryPullQPS 는 초당 허용 요청 수, registryBurst 는 순간 허용치다. 기본값은 각각 5 와 10 이다. registryPullQPS: 0 은 제한 해제를 뜻하지만, 노드의 네트워크와 디스크가 동시에 포화되어 다른 증상으로 옮겨 가므로 권하지 않는다.
관리형 클러스터에서는 파일을 직접 고치지 않는다. AKS 는 노드 풀의 kubelet 설정, EKS 는 노드 그룹의 부트스트랩 인자 또는 노드 설정으로 지정한다. 직접 구성한 클러스터라면 각 노드에서 파일을 고치고 재시작한다.
grep -n 'registryPullQPS\|registryBurst' /var/lib/kubelet/config.yaml
systemctl restart kubelet
이미 내려받은 이미지는 다시 받지 않으므로 kubelet 재시작이 진행 중인 파드에 큰 영향을 주지는 않지만, 배포가 몰리는 시간대는 피한다.
외부 레지스트리에서 노드가 직접 받는 구조는 이 문제뿐 아니라 인증 · 대역폭 · 가용성 문제를 모두 안고 간다. 사내 레지스트리에 미러하거나 프록시 캐시를 두면 내려받기 시간이 줄어 같은 QPS 제한 안에서도 훨씬 빨리 끝난다.
노드 → 사내 레지스트리(캐시) → 외부 레지스트리
큰 이미지를 쓰는 제품일수록 효과가 크다. 위 사례의 이미지는 하나가 수백 MB 였고 내려받기에만 1분 이상 걸렸다.
노드의 kubelet 로그에서 실제로 제한에 걸렸는지 본다.
journalctl -u kubelet --since '10 min ago' | grep -i 'pull QPS'
노드별 이미지 내려받기 상황은 이벤트를 모아 보면 드러난다.
kubectl get events -A --field-selector reason=Failed | grep -i 'pull QPS'
kubectl get events -A --field-selector reason=Pulling | wc -l