离线集群里的 ImagePullBackOff
한국어 원문으로 표시합니다.
이 실습은 진짜 k3s 에서 돕니다
VM 안에 k3s 한 대(v1.33.3+k3s1)가 떠 있고 containerd·kubelet 이 실제로 이미지를 끌어옵니다. 처음 뜨는 데 몇 분이 걸립니다. VM 은 처음에는 인터넷(80/443)에 닿습니다 — 1단계가 연결된 반입 준비 기계 역할이고, 2단계에서 여러분이 직접 바깥 송신을 막아 고객 폐쇄망으로 바꿉니다. 채점은 바깥에서 VM 의 8899 포트로 들어오는 에이전트가 하므로, 이미 맺은 연결과 루프백을 막으면 채점이 끊깁니다.
목표
연결된 곳에서 이미지를 아카이브로 받아 해시를 기록하고, 송신이 막힌 k3s 에서 ImagePullBackOff 를 증거로 남긴 뒤, k3s ctr images import 와 k3s 이미지 디렉터리 두 경로로 반입하고, 반입해도 실패하는 끌어오기 정책까지 확인해 반입 기록을 런타임과 대조해 남긴다.
왜 중요한가
폐쇄망 고객사에서 "파드가 안 떠요" 의 절반은 이미지다. 레지스트리에 닿지 않는 클러스터는 이미지를 스스로 구하지 못하고, 누군가 런타임 저장소에 넣어 줘야 한다. 그런데 넣었다고 끝이 아니다. 옮기는 사이 바뀐 파일을 넣으면 무엇이 도는지 아무도 모르고, 매니페스트가 imagePullPolicy: Always 이거나 태그가 latest 면 넣어 둔 이미지를 두고도 레지스트리에 물으러 나가다 실패한다. 반입은 "파일을 복사했다" 가 아니라 "해시가 맞는 이미지를 런타임이 들고 있고, 파드가 그것을 쓴다" 를 증명하는 일이다.
예상 60분이다. 세션이 끝나면 VM 이 사라지니 남기고 싶은 파일은 종료 전에 따로 보관한다.
단계
- 두 이미지
registry.k8s.io/e2e-test-images/busybox:1.36.1-1와registry.k8s.io/e2e-test-images/nginx:1.15-4를 이미지 아카이브로 받아/root/airgap/bundle/busybox.tar·/root/airgap/bundle/nginx.tar에 두고,/root/airgap/manifest.txt에sha256 파일이름 이미지를 한 줄씩 적는다. 아카이브 안의 이미지 이름이 위 이름 그대로여야 한다. - 다시 돌려도 규칙이 한 벌만 남는
/root/airgap/egress.sh를 써서 바깥 송신을 막는다. iptables 체인 이름은AIRGAP-EGRESS이고 OUTPUT 과 FORWARD 에서 한 번씩 그리로 보낸다. 루프백·이미 맺은 연결·k3s 파드 대역(10.42.0.0/16)·서비스 대역(10.43.0.0/16)은 살리고 나머지는 막는다. 막기 전과 막은 뒤에curl -s -o /dev/null -w '%{http_code}' https://registry.k8s.io/v2/로 잰 값을/root/airgap/egress-proof.txt에before=·after=로 적는다. - 네임스페이스
airgap에 busybox 이미지로sleep 3600을 도는 파드shop-api를 만든다(매니페스트/root/airgap/shop-api.yaml). 끌어오기에 실패하면/root/airgap/pull-failure.txt에uid=<파드 UID>와 상태·이벤트를 저장한다. - 해시를 대조한 뒤 busybox.tar 를
k3s ctr images import로 반입하고 출력을/root/airgap/import-busybox.txt에 저장한다.shop-api를 Running 으로 만든다. - nginx.tar 를 k3s 에이전트 이미지 디렉터리(
/var/lib/rancher/k3s/agent/images/)에 넣어 반입하고, 그 이미지로 파드shop-web(매니페스트/root/airgap/shop-web.yaml)을 Running 으로 만든다. - 반입해 둔 busybox 이미지로
imagePullPolicy: Always인 파드shop-always(매니페스트/root/airgap/shop-always.yaml)를 만들어 실패를 보고,/root/airgap/always.txt에uid=·policy=와 상태를 저장한다. - 송신 차단을 유지한 채
shop-always를imagePullPolicy: IfNotPresent로 다시 만들어 Running 으로 만든다. /root/airgap/import-record.json에 반입 기록을 남긴다.images배열의 항목마다ref·tar·tar_sha256·imported_via(ctr또는images-dir)·runtime_image_id(k3s crictl inspecti가 보고하는 id)·pods(지금 그 이미지로 Running 인 airgap 파드 이름 목록)를 적고, 최상위에egress_blocked를 true/false 로 적는다.
참고
- 아카이브로 받기:
skopeo copy docker://<이미지> docker-archive:<파일>:<이미지> - 런타임 저장소 보기:
k3s ctr -n k8s.io images ls -q, 이미지 상세:k3s crictl inspecti <이미지> - 파드 상태 이유:
kubectl -n airgap get pod <이름> -o jsonpath='{.status.containerStatuses[0].state.waiting.reason}' - kubelet 의 끌어오기 기록은 k3s 로그에도 남습니다:
journalctl -u k3s | grep ErrImagePull - 흔한 실수 1: 이미 맺은 연결(ESTABLISHED)을 살리지 않고 막기. 채점 에이전트의 응답이 끊겨 채점이 멈춥니다.
- 흔한 실수 2: 반입 전에 해시를 대조하지 않기. 옮기는 사이 바뀐 파일을 넣으면 무엇이 도는지 아무도 모릅니다.
- 흔한 실수 3: 반입이 안 되는 것 같아 차단을 잠깐 풀기. 고객 폐쇄망에는 그 선택지가 없습니다.
연결된 쪽에서 반입물 만들기
두 이미지를 이미지 아카이브로 받아 /root/airgap/bundle/busybox.tar·/root/airgap/bundle/nginx.tar 에 두고, /root/airgap/manifest.txt 에 sha256 파일이름 이미지 를 한 줄씩 적는다.
skopeo copy 의 대상에 docker-archive:경로:이름 을 주면 레지스트리 없이 파일 하나로 받습니다. 이름을 붙여 두어야 반입 뒤 파드가 같은 이름으로 찾습니다. 해시는 옮기기 전에 재 두어야 받은 쪽에서 대조할 수 있습니다.
바깥 송신을 막아 폐쇄망 만들기
다시 돌려도 규칙이 한 벌만 남는 /root/airgap/egress.sh 로 바깥 송신을 막고, 막기 전후의 HTTP 상태 코드를 /root/airgap/egress-proof.txt 에 before=·after= 로 적는다.
새 체인 하나를 만들어 OUTPUT 과 FORWARD 맨 앞에서 그리로 보내면, 비우고 다시 채우는 방식으로 몇 번을 돌려도 같은 모양이 됩니다. 살려야 할 것부터 RETURN 으로 적고 마지막에 막으세요. 이 VM 의 채점은 바깥에서 8899 로 들어오는 연결이라 이미 맺은 연결을 막으면 채점이 끊깁니다.
막힌 클러스터에서 ImagePullBackOff
네임스페이스 airgap 에 busybox 이미지로 파드 shop-api 를 만들어 끌어오기 실패를 보고, /root/airgap/pull-failure.txt 에 uid=<파드 UID> 와 상태·이벤트를 저장한다.
태그를 고정한 이미지는 imagePullPolicy 를 적지 않으면 IfNotPresent 입니다. 런타임에 없으니 끌어오러 나가다 막힙니다. 파드는 곧 다시 만들어지므로 UID 와 이벤트를 지금 남겨 두세요 — kubelet 의 기록은 k3s 로그에도 남습니다.
ctr 로 런타임에 직접 반입
busybox.tar 를 k3s ctr images import 로 반입해 출력을 /root/airgap/import-busybox.txt 에 저장하고, shop-api 를 Running 으로 만든다.
쿠버네티스가 쓰는 이미지는 containerd 의 k8s.io 네임스페이스에 있어야 보입니다. 반입해도 kubelet 은 실패가 거듭될수록 다음 시도까지 오래 기다리니(최대 5분), 기다리기 싫다면 파드를 지우고 같은 매니페스트로 다시 만드세요.
k3s 이미지 디렉터리로 반입
nginx.tar 를 k3s 에이전트 이미지 디렉터리에 넣어 반입하고, 그 이미지로 파드 shop-web 을 Running 으로 만든다.
k3s 는 agent 의 images 디렉터리에 놓인 tar 를 containerd 로 들입니다. 문서의 버전 표에서 실행 중에 넣은 파일도 들이는지 확인하세요. 런타임에 보일 때까지 잠깐 기다린 뒤 파드를 만듭니다.
반입했는데도 실패하는 파드
반입해 둔 busybox 이미지로 imagePullPolicy: Always 인 파드 shop-always 를 만들어 실패를 보고, /root/airgap/always.txt 에 uid=·policy= 와 상태를 저장한다.
Always 는 이미지가 로컬에 있어도 매번 레지스트리에 물어 태그를 다이제스트로 풀려고 합니다. 레지스트리에 닿지 않으면 그 단계에서 실패합니다. 태그가 latest 이거나 없으면 기본 정책이 Always 라는 점도 떠올려 보세요.
차단은 그대로 두고 정책을 고친다
송신 차단을 유지한 채 shop-always 를 imagePullPolicy: IfNotPresent 로 다시 만들어 Running 으로 만든다.
파드의 컨테이너 imagePullPolicy 는 만든 뒤에 바꿀 수 없는 필드입니다. 차단을 풀어 통과시키는 것은 고객 폐쇄망에서는 할 수 없는 해결이니, 매니페스트를 고쳐 다시 만드세요.
반입 기록을 런타임과 대조해 남기기
/root/airgap/import-record.json 에 반입한 두 이미지의 기록과 egress_blocked 를 남긴다.
기록의 값은 손으로 옮기지 말고 명령 출력에서 뽑으세요. 런타임 이미지 ID 는 crictl inspecti 가 알려 주고, 그 이미지를 쓰는 Running 파드는 kubectl 의 JSON 에서 모읍니다. 반입 경로는 여러분이 실제로 쓴 방법입니다.