TT Lab
블로그 시작하기
배우기 러닝패스 코스

폐쇄망 k0s — 노드 1·2·3대와 그 위의 Ceph·PostgreSQL

k0s 폐쇄망 설치 — 바이너리 하나와 묶음 하나

TT Lab 에서 이어서 보기

한 줄 요약

k0s 는 쿠버네티스 전체를 바이너리 하나와 이미지 묶음 하나로 줄인 배포판입니다. 폐쇄망 설치는 그 둘을 들고 들어가 「데이터 디렉터리의 images/ 에 두고, 받으러 나가지 말라고 적는 것」 으로 끝납니다.

왜 이게 필요했나

kubeadm 으로 폐쇄망에 쿠버네티스를 세우면 들고 갈 것이 많습니다. OS 패키지(kubelet·kubeadm·containerd·runc·CNI 플러그인), 컨트롤 플레인 이미지 여섯 개 안팎, CNI 매니페스트와 그 이미지, 그리고 이것들의 판을 서로 맞추는 일입니다. 하나라도 빠지면 설치가 중간에 멈추고, 멈춘 자리에서 바깥으로 받으러 나가려다 시간 초과로 끝납니다.

k0s 는 이것을 두 개로 줄였습니다. 바이너리 하나(약 270MB)에 kube-apiserver·kubelet·containerd·runc·etcd·kine 까지 들어 있고, 나머지 시스템 이미지(CNI·CoreDNS·metrics-server·kube-proxy·pause·NLLB 의 envoy)는 릴리스마다 나오는 airgap 묶음(약 420MB) 하나에 들어 있습니다. 묶음 안의 목록은 k0s airgap list-images 가 보여 주고, 릴리스 페이지에도 같은 목록 파일이 있습니다.

어떻게 동작하나

묶음의 자리. k0s 는 <데이터 디렉터리>/images/ 를 지켜보다가 그 안의 OCI 묶음을 containerd 로 들여옵니다. 기본 데이터 디렉터리가 /var/lib/k0s 이므로 /var/lib/k0s/images/ 입니다. 앱 이미지도 같은 방법으로 들일 수 있습니다 — k0s airgap bundle-artifacts 가 이미지 목록을 받아 같은 형식의 묶음을 만듭니다.

받으러 나가지 않기. spec.images.default_pull_policy: Never 를 적으면 k0s 가 띄우는 파드의 당김 정책이 전부 Never 가 됩니다. 묶음이 빠졌을 때 시간 초과 대신 ErrImageNeverPull 이 바로 나와 원인이 보입니다. 이 설정은 k0s 가 관리하는 시스템 구성 요소에만 적용되고, 우리가 올리는 워크로드는 각자의 imagePullPolicy 를 따릅니다.

주소 두 가지. 노드에 NIC 가 둘이면 k0s 와 kubelet 은 기본 경로 쪽 NIC 를 고릅니다. 이 실습의 VM 은 첫 NIC 가 모두 같은 주소(10.0.2.2)라 API 주소(spec.api.address)와 kubelet 의 --node-ip 를 세션망(eth1) 주소로 적어야 합니다. 현장의 관리망·서비스망이 나뉜 서버도 같은 이유로 같은 두 줄을 적습니다.

--single 의 뜻. 컨트롤러와 워커를 한 프로세스에 담고, 저장소로 etcd 대신 kine(SQLite)을 씁니다. 그래서 가볍지만 다른 노드를 더할 수 없습니다. 나중에 노드를 늘릴 생각이면 처음부터 controller --enable-worker 로 시작해야 합니다.

반입 목록을 만드는 법. 반입은 「인터넷 쪽에서 받고 → 해시로 고정하고 → 매체로 옮기고 → 안쪽에서 다시 대조한다」 의 반복입니다. 바이너리와 묶음은 GitHub 릴리스의 sha256sums.txt 에 공식 해시가 있으므로 받은 직후 그 값과 대조합니다. 직접 만든 앱 묶음처럼 공식 해시가 없는 것은 받는 쪽에서 SHA256SUMS 를 만들어 함께 들고 갑니다. 안쪽에서 sha256sum -c 가 전부 OK 여야 설치를 시작합니다. 깨진 묶음을 그대로 두면 k0s 는 들여오기를 조용히 건너뛰고, 증상은 한참 뒤 ErrImageNeverPull 로만 나타납니다.

반입 서버를 두는 이유. 노드마다 USB 를 꽂는 대신 폐쇄망 안에 반입 서버(점프 호스트) 하나를 두고, 매체는 그 서버에만 옮깁니다. 나머지 노드는 반입 서버에서 scp·사내 레지스트리로 받습니다. 매체를 다루는 자리가 하나라 대조와 기록도 한 곳에서 하고, 노드는 처음부터 끝까지 바깥을 몰라도 됩니다. 이 실습의 bastion 이 그 역할이고, 반입이 끝나면 bastion 자신도 봉인합니다.

이 실습 환경에서 잰 값(2026-10-31). 반입 서버가 도구를 깔고 바이너리·묶음·k0sctl 을 받아 공식 해시와 대조하고 앱 묶음을 만들어 봉인하기까지 68~72초가 걸렸습니다. node01 에서 설치를 시작해 노드가 Ready 가 되기까지 144초, 시스템 파드 넷이 모두 Ready 가 되기까지 49초가 더 걸렸고, 그때 노드의 메모리 사용은 약 1GiB, 데이터 디렉터리는 1.7GiB 였습니다. 반입하지 않은 redis 이미지는 connect: connection refused 로 실패했고, 그 사이 노드가 바깥으로 보낸 것은 그 당기기 시도뿐이었습니다.

현장에서 만나는 모습

폐쇄망 설치가 실패하는 자리는 대개 명령이 아니라 「몰래 인터넷에 기대는 곳」 입니다. 설치 스크립트가 바이너리를 받으러 가고, 태그만 적힌 이미지를 Always 로 당기고, 도구가 새 판이 있는지 확인하러 나갑니다. 연결된 환경에서 만든 절차는 이 자리들이 숨어 있어도 통과합니다. 그래서 먼저 정말 막혔는지 재고, 반입물의 해시를 대조하고, 끝에 반입하지 않은 이미지가 정말 실패하는지 봅니다. 이 셋이 함께 있어야 「바깥 없이 설치했다」 가 증명됩니다.

다음 실습에서 할 것

반입 서버에서 node01 에 바이너리와 묶음을 옮기고, k0s.yaml 을 쓰고, --single 로 세운 뒤 nginx 와 반입하지 않은 이미지로 폐쇄망을 증명합니다.