FDE 캡스톤: 창고가 같은 주문을 세 번 받았다 · 폐쇄망 클러스터에 이미지 반입 · 실습
폐쇄망 클러스터에서 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 이 사라지니 남기고 싶은 파일은 종료 전에 따로 보관한다.
단계
1. 두 이미지 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 파일이름 이미지 를 한 줄씩 적는다. 아카이브 안의 이미지 이름이 위 이름 그대로여야 한다.
2. 다시 돌려도 규칙이 한 벌만 남는 /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= 로 적는다.
3. 네임스페이스 airgap 에 busybox 이미지로 sleep 3600 을 도는 파드 shop-api 를 만든다(매니페스트 /root/airgap/shop-api.yaml). 끌어오기에 실패하면 /root/airgap/pull-failure.txt 에 uid=<파드 UID> 와 상태·이벤트를 저장한다.
4. 해시를 대조한 뒤 busybox.tar 를 k3s ctr images import 로 반입하고 출력을 /root/airgap/import-busybox.txt 에 저장한다. shop-api 를 Running 으로 만든다.
5. nginx.tar 를 k3s 에이전트 이미지 디렉터리(/var/lib/rancher/k3s/agent/images/)에 넣어 반입하고, 그 이미지로 파드 shop-web(매니페스트 /root/airgap/shop-web.yaml)을 Running 으로 만든다.
6. 반입해 둔 busybox 이미지로 imagePullPolicy: Always 인 파드 shop-always(매니페스트 /root/airgap/shop-always.yaml)를 만들어 실패를 보고, /root/airgap/always.txt 에 uid=·policy= 와 상태를 저장한다.
7. 송신 차단을 유지한 채 shop-always 를 imagePullPolicy: IfNotPresent 로 다시 만들어 Running 으로 만든다.
8. /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: 반입이 안 되는 것 같아 차단을 잠깐 풀기. 고객 폐쇄망에는 그 선택지가 없습니다.
단계 8개
- 연결된 쪽에서 반입물 만들기
- 바깥 송신을 막아 폐쇄망 만들기
- 막힌 클러스터에서 ImagePullBackOff
- ctr 로 런타임에 직접 반입
- k3s 이미지 디렉터리로 반입
- 반입했는데도 실패하는 파드
- 차단은 그대로 두고 정책을 고친다
- 반입 기록을 런타임과 대조해 남기기