kubespray-offline도구를 사용해서 외부망에서 필요한 파일을 모두 내려받고, 폐쇄망 내부에 로컬 레포지토리와 레지스트리를 구성한 뒤 클러스터를 배포하는 전체 과정을 정리했다.
- 기본 kubespray contrib/offline 동작 일부 수정이 필요한 부분들이있다.
- 이를 커스텀해놓은 https://github.com/garlicKim21/kubespray-offline-install-guide 를 활용한 가이드
크게 두 단계로 나뉜다.

| 구분 | 버전 |
|---|---|
| RHEL / AlmaLinux / Rocky Linux | 9.x |
| Ubuntu | 22.04, 24.04 |
주의할 점은 Online Node와 Offline Target Node의 OS 종류와 버전이 반드시 같아야 한다는 것이다. 다르면 패키지 의존성 문제가 생긴다.
RHEL 8은 Kubespray 2.29.0부터 지원이 중단됐다.
| 구분 | 최소 사양 |
|---|---|
| Installer Node | RAM 4GB, Disk 50GB 이상 |
| Target Nodes | RAM 2GB, vCPU 2개 이상 |
인터넷이 되는 리눅스 서버에서 진행한다.
먼저 config.sh를 열어 버전과 런타임 설정을 확인한다.
# config.sh
# 설치할 Kubespray 버전
KUBESPRAY_VERSION=${KUBESPRAY_VERSION:-2.30.0}
# 컨테이너 런타임 (RHEL 계열은 podman, Ubuntu는 docker 권장)
docker=${docker:-podman}
$ ./download-all.sh
내부적으로 아래 순서로 동작한다.
prepare-pkgs.sh - pip, podman 등 필요한 도구 설치get-kubespray.sh - 지정 버전의 Kubespray 소스코드 다운로드pypi-mirror.sh - Python 라이브러리(.whl) 다운로드download-kubespray-files.sh - 컨테이너 이미지 및 바이너리(kubectl, cni 등) 다운로드create-repo.sh - OS 패키지(RPM/Deb) 다운로드 및 로컬 레포 메타데이터 생성완료되면 outputs/ 디렉토리에 모든 파일이 쌓인다.
outputs/ 디렉토리를 압축해서 폐쇄망 Installer Node로 옮긴다.
# 압축
$ tar czf kubespray-offline-outputs.tar.gz outputs/
# Installer Node에서 압축 해제
$ tar xzf kubespray-offline-outputs.tar.gz
USB나 망연계 솔루션을 통해 이동시키면 된다.
이제부터는 폐쇄망 Installer Node에서 진행한다.
$ cd outputs
$ ./setup-all.sh
이 스크립트가 하는 일은 다음과 같다.
setup-container.sh - containerd 설치, Nginx와 Registry 이미지 로드start-nginx.sh - 80번 포트로 Nginx 실행 (패키지 파일 서버)setup-offline.sh - Installer Node가 로컬 Nginx를 바라보도록 repo 설정 변경start-registry.sh - 35000번 포트로 Docker Registry 실행load-push-all-images.sh - 이미지를 containerd에 로드 후 로컬 Registry에 푸시# Kubespray 소스 압축 해제
$ ./extract-kubespray.sh
$ cd kubespray-{version}
# Python 가상환경 생성 및 활성화
$ source ../venv.sh
# 라이브러리 설치 (오프라인 PyPI 미러 사용)
$ pip install -r requirements.txt
인벤토리를 만들고 오프라인 설정을 적용한다.
# 인벤토리 생성
$ cp -rfp inventory/sample inventory/mycluster
$ declare -a IPS=(10.10.1.3 10.10.1.4 10.10.1.5)
$ CONFIG_FILE=inventory/mycluster/hosts.yaml \
python3 contrib/inventory_builder/inventory.py ${IPS[@]}
outputs/offline.yml을 inventory/mycluster/group_vars/all/offline.yml로 복사한 뒤 Installer Node IP를 수정한다.
# inventory/mycluster/group_vars/all/offline.yml
# YOUR_HOST를 Installer Node IP로 변경
http_server: "http://192.168.1.100"
registry_host: "192.168.1.100:35000"
# 로컬 레지스트리 미러 설정 (중요)
containerd_registries_mirrors:
- prefix: "{{ registry_host }}"
mirrors:
- host: "http://{{ registry_host }}"
capabilities: ["pull", "resolve"]
skip_verify: true
Step 1. 타겟 노드 레포지토리 설정
타겟 노드들이 Installer Node의 Nginx에서 패키지를 받도록 설정한다.
$ cp -r ../playbook/* .
$ ansible-playbook -i inventory/mycluster/hosts.yaml --become offline-repo.yml
Step 2. 클러스터 설치
$ ansible-playbook -i inventory/mycluster/hosts.yaml \
--become --become-user=root cluster.yml
기준 레포: garlicKim21/kubespray-offline-install-guide
원본 upstream: kubespray-offline/kubespray-offline
download-all.sh는 직접 로직을 수행하지 않는다. 아래 순서로 서브스크립트를 순차 호출하는 오케스트레이터 역할이다.
#!/bin/bash
run ./prepare-pkgs.sh || exit 1 # 실패 시 전체 중단
run ./prepare-py.sh
run ./get-kubespray.sh
if $ansible_in_container; then
run ./build-ansible-container.sh # 컨테이너 방식
else
run ./pypi-mirror.sh # 일반 방식
fi
run ./download-kubespray-files.sh
run ./download-additional-containers.sh
run ./create-repo.sh
run ./copy-target-scripts.sh
|| exit 1은 prepare-pkgs.sh에만 적용된다. 이 스크립트가 실패하면 이후 모든 과정이 불가능하기 때문이다.
| 항목 | 내용 |
|---|---|
| 역할 | 다운로드 작업에 필요한 시스템 도구 설치 |
| 실행 환경 | Online Node (인터넷 연결 필수) |
| 실패 시 | exit 1 — 전체 프로세스 중단 |
| 주요 작업 | pip, git, podman (또는 docker/nerdctl) 설치 |
| OS 분기 | RHEL 계열: yum install / Ubuntu: apt install |
| 런타임 선택 | config.sh의 docker 변수로 podman / docker / nerdctl 결정 |
| 출력물 | 없음 (시스템 패키지 설치만 수행) |
| 주의사항 | Online Node와 Target Node의 OS가 동일해야 함 |
핵심 설치 패키지:
| 패키지 | 용도 |
|---|---|
podman (기본) | 컨테이너 이미지 pull/save |
git | Kubespray 소스 클론 |
python3, pip | Python 환경 구성 |
createrepo / dpkg-dev | 로컬 repo 메타데이터 생성용 |
| 항목 | 내용 |
|---|---|
| 역할 | Python 가상환경(venv) 생성 및 기본 패키지 설치 |
| 실행 환경 | Online Node |
| 실패 시 | 경고만, 프로세스는 계속 진행 |
| 주요 작업 | venv 생성, pip 업그레이드, 기본 의존성 설치 |
| Python 버전 | OS별 자동 감지 (target-scripts/pyver.sh 활용) |
| 출력물 | outputs/venv/ 디렉토리 |
| 주의사항 | Ubuntu 24.04는 Python 3.12 → 호환성 주의 |
동작 예시:
python3 -m venv outputs/venv
source outputs/venv/bin/activate
pip install -U pip setuptools wheel
| 항목 | 내용 |
|---|---|
| 역할 | 지정 버전의 Kubespray 소스코드 확보 |
| 실행 환경 | Online Node |
| 실패 시 | 경고 후 계속 (이미 존재하면 스킵) |
| 주요 작업 | GitHub에서 Kubespray tarball 다운로드 및 압축 해제 |
| 버전 결정 | config.sh의 KUBESPRAY_VERSION 변수 |
| 조건 | KUBESPRAY_DIR가 없을 때만 실행 |
| 다운로드 소스 | https://github.com/kubernetes-sigs/kubespray/archive/refs/tags/v{VERSION}.tar.gz |
| 출력물 | outputs/kubespray-{VERSION}/ 디렉토리 |
주의사항:
master 브랜치나 release 브랜치를 직접 사용하면 안 된다.| 항목 | 내용 |
|---|---|
| 역할 | Kubespray 실행에 필요한 Python 라이브러리 오프라인 미러 구성 |
| 실행 환경 | Online Node |
| 실패 시 | 경고 후 계속 |
| 주요 작업 | requirements.txt 기반 .whl 파일 전체 다운로드 |
| 다운로드 소스 | PyPI (pypi.org) |
| 출력물 | outputs/pypi/ 디렉토리 (.whl 파일 다수) |
| 분기 조건 | config.sh의 ansible_in_container=true이면 build-ansible-container.sh 실행 |
다운로드되는 주요 패키지:
| 패키지 | 용도 |
|---|---|
ansible, ansible-core | Playbook 실행 |
jinja2 | 템플릿 렌더링 |
netaddr | IP 주소 처리 |
cryptography | SSH 키 관련 |
jsonpatch | K8s manifest 패치 |
ruamel.yaml | YAML 파싱 |
build-ansible-container.sh (대안):
| 항목 | 내용 |
|---|---|
| 역할 | K8s 구성에 필요한 바이너리 및 컨테이너 이미지 전체 다운로드 |
| 실행 환경 | Online Node |
| 실패 시 | 경고 후 계속 (개별 파일 단위로 재시도) |
| 주요 작업 1 | Kubespray의 generate_list.sh 실행 → files.list, images.list 생성 |
| 주요 작업 2 | files.list 기반 바이너리 HTTP 다운로드 |
| 주요 작업 3 | images.list 기반 컨테이너 이미지 pull → .tar 저장 |
| 출력물 | outputs/files/ (바이너리), outputs/images/ (이미지 tar) |
다운로드되는 바이너리:
| 파일 | 출처 |
|---|---|
kubectl, kubelet, kubeadm | dl.k8s.io |
etcd tarball | GitHub releases |
cni-plugins | GitHub releases |
crictl | GitHub releases |
helm | get.helm.sh |
calico, cilium 등 CNI 바이너리 | 각 프로젝트 GitHub |
다운로드되는 컨테이너 이미지:
| 이미지 | 레지스트리 |
|---|---|
kube-apiserver, kube-scheduler 등 | registry.k8s.io |
coredns | registry.k8s.io |
pause | registry.k8s.io |
calico/node, calico/cni 등 | docker.io |
quay.io/cilium/* | quay.io |
etcd | registry.k8s.io |
| 항목 | 내용 |
|---|---|
| 역할 | 사용자 정의 추가 컨테이너 이미지 다운로드 |
| 실행 환경 | Online Node |
| 실패 시 | 경고 후 계속 |
| 주요 작업 | imagelists/*.txt에 명시된 이미지 pull → .tar 저장 |
| 출력물 | outputs/images/ (추가 이미지 tar) |
| 커스터마이징 | imagelists/ 디렉토리에 .txt 파일 추가로 원하는 이미지 지정 가능 |
imagelists/*.txt 형식:
# 예시: imagelists/custom.txt
nginx:1.25
redis:7.0
myregistry.example.com/myapp:latest
| 항목 | 내용 |
|---|---|
| 역할 | OS 패키지(RPM/DEB) 다운로드 및 로컬 레포지토리 메타데이터 생성 |
| 실행 환경 | Online Node |
| 실패 시 | 경고 후 계속 |
| 주요 작업 (RHEL) | reposync로 RPM 다운로드 → createrepo로 메타데이터 생성 |
| 주요 작업 (Ubuntu) | apt-mirror로 DEB 다운로드 → dpkg-scanpackages로 메타데이터 생성 |
| 출력물 | outputs/rpms/ 또는 outputs/debs/ + repodata/ |
| 주의사항 | OS 종류에 따라 자동 분기 처리됨 |
다운로드 대상 패키지:
| 카테고리 | 패키지 예시 |
|---|---|
| Container Runtime | containerd, runc |
| Network | socat, conntrack, ipset, ipvsadm |
| Storage | nfs-utils, open-iscsi |
| 기타 | curl, rsync, unzip |
| 항목 | 내용 |
|---|---|
| 역할 | 폐쇄망 Installer Node에서 사용할 셋업 스크립트들을 outputs/로 복사 |
| 실행 환경 | Online Node (로컬 복사 작업) |
| 실패 시 | 경고 후 계속 |
| 주요 작업 | target-scripts/ 디렉토리의 파일들을 outputs/에 복사 |
| 출력물 | outputs/ 내 각종 *.sh 파일 |
| 인터넷 | 불필요 (로컬 파일 복사만 수행) |
복사되는 주요 스크립트:
| 스크립트 | 역할 |
|---|---|
setup-container.sh | containerd 설치 + Nginx/Registry 이미지 로드 |
start-nginx.sh | Nginx 컨테이너 실행 (:80) |
setup-offline.sh | yum/apt repo를 로컬 Nginx로 변경 |
setup-py.sh | Python3 + venv 설치 |
start-registry.sh | Docker Registry 실행 (:35000) |
load-push-all-images.sh | 이미지 tar → containerd 로드 → Registry 푸시 |
extract-kubespray.sh | Kubespray tarball 압축 해제 + 패치 적용 |
offline.yml | Ansible 오프라인 설정 템플릿 |
offline-repo.yml | 타겟 노드 repo 설정 Playbook |
| 순서 | 스크립트 | 실행 환경 | 인터넷 필요 | 주요 출력물 | 실패 시 |
|---|---|---|---|---|---|
| 1 | prepare-pkgs.sh | Online Node | O | 없음 (시스템 설치) | 전체 중단 |
| 2 | prepare-py.sh | Online Node | O | outputs/venv/ | 계속 |
| 3 | get-kubespray.sh | Online Node | O | outputs/kubespray-{VER}/ | 계속 |
| 4-a | pypi-mirror.sh | Online Node | O | outputs/pypi/*.whl | 계속 |
| 4-b | build-ansible-container.sh | Online Node | O | Ansible 컨테이너 이미지 | 계속 |
| 5 | download-kubespray-files.sh | Online Node | O | outputs/files/, outputs/images/ | 계속 |
| 6 | download-additional-containers.sh | Online Node | O | outputs/images/*.tar | 계속 |
| 7 | create-repo.sh | Online Node | O | outputs/rpms/ or outputs/debs/ | 계속 |
| 8 | copy-target-scripts.sh | Online Node | X | outputs/*.sh, outputs/offline.yml | 계속 |
outputs/
├── venv/ # Python 가상환경
├── kubespray-2.30.0/ # Kubespray 소스코드
├── pypi/ # Python 패키지 (.whl)
│ ├── ansible-9.x.x-py3-none-any.whl
│ ├── jinja2-3.x.x-py3-none-any.whl
│ └── ...
├── files/ # K8s 바이너리
│ └── kubernetes/
│ └── v1.30.x/
│ ├── kubeadm
│ ├── kubectl
│ └── kubelet
├── images/ # 컨테이너 이미지 tar
│ ├── kube-apiserver_v1.30.x.tar
│ ├── coredns_v1.11.x.tar
│ ├── calico-node_v3.x.x.tar
│ └── ...
├── rpms/ (또는 debs/) # OS 패키지 + repodata
│ ├── containerd.io-x.x.x.rpm
│ ├── repodata/
│ └── ...
├── setup-container.sh # copy-target-scripts로 복사된 파일들
├── start-nginx.sh
├── start-registry.sh
├── setup-offline.sh
├── setup-py.sh
├── load-push-all-images.sh
├── extract-kubespray.sh
└── offline.yml
| 변수 | 기본값 | 설명 |
|---|---|---|
KUBESPRAY_VERSION | 2.30.0 | 다운로드할 Kubespray 버전 |
docker | podman | 컨테이너 런타임 (podman / docker / nerdctl) |
ansible_in_container | false | Ansible을 컨테이너로 실행할지 여부 |
NGINX_PORT | 80 | Nginx 포트 번호 |
REGISTRY_PORT | 35000 | Docker Registry 포트 번호 |
| 항목 | 내용 |
|---|---|
| OS 일치 | Online Node와 Target Node의 OS 및 버전이 반드시 동일해야 함 |
| RHEL 8 | Kubespray 2.29.0부터 지원 중단. RHEL 9 이상 사용 권장 |
| Python 버전 | Ubuntu 24.04는 Python 3.12 → pyver.sh가 자동 감지 |
| 디스크 용량 | outputs/ 완성 시 수 GB (이미지 포함 시 10~30GB 수준) |
| 방화벽 | 폐쇄망 Installer Node의 80, 35000 포트 오픈 필요 |
| 태그 버전 | Kubespray는 반드시 정식 태그(v2.xx.y) 기준으로 확보 |
Python 버전 문제
Ubuntu 24.04는 Python 3.12를 쓴다. prepare-pkgs.sh 실행 시 호환성 문제가 생길 수 있는데, target-scripts/pyver.sh에서 자동 감지하도록 되어 있어서 대부분 자동으로 처리된다.
방화벽
Installer Node의 80번(Nginx)과 35000번(Registry) 포트가 타겟 노드들에서 접근 가능한지 반드시 확인한다.
디스크 용량
outputs/ 디렉토리는 수 GB가 된다. Installer Node에 여유 공간이 충분한지 미리 확인해둔다.
처음에는 설정 파일이 많아서 복잡해 보이지만, 결국 흐름은 단순하다.
다운로드 → 이관 → 서버 구성 → 배포
kubespray-offline이 대부분의 번거로운 작업을 자동화해줘서 직접 처리할 부분은 IP 설정과 인벤토리 구성 정도다.