[Kubernetes] Cluster: Kubespray를 이용해 클러스터 구성하기 - 8.1.0. 오프라인 배포 - 실습 환경 배포
서종호(가시다)님의 On-Premise K8s Hands-on Study 6주차 학습 내용을 기반으로 합니다.
TL;DR
이번 글에서는 폐쇄망 클러스터 배포 실습을 위한 Vagrant 환경을 구성하고, 네트워크 구조를 확인한다.
- Vagrant + VirtualBox로 admin 1대 + k8s-node 2대 구성
- admin: Bastion + Admin 역할 통합 (120GB 디스크), 이후 NTP/DNS/Repo/Registry 서빙 예정
- k8s-node1: Control Plane, k8s-node2: Worker
- 네트워크:
enp0s8(NAT, 인터넷 접근) +enp0s9(Host-Only, 내부 통신192.168.10.0/24)
실습 환경 아키텍처
실제 기업 환경에서는 DMZ의 Bastion Server와 내부망의 Admin Server가 분리되어 있지만, PC 스펙과 시간을 고려해 admin 1대가 Bastion + Admin 역할을 겸하도록 구성한다.

| 호스트 | IP | 역할 | 스펙 |
|---|---|---|---|
| admin | 192.168.10.10 | Bastion + Admin | 4 vCPU, 2GB RAM, 120GB Disk |
| k8s-node1 | 192.168.10.11 | K8s Control Plane | 4 vCPU, 2GB RAM |
| k8s-node2 | 192.168.10.12 | K8s Worker | 4 vCPU, 2GB RAM |
Vagrant 환경 파일
실습 환경은 3개의 파일로 구성된다.
| 파일 | 역할 |
|---|---|
Vagrantfile |
VM 정의 (스펙, 네트워크, 디스크) |
admin.sh |
admin 노드 프로비저닝 |
init_cfg.sh |
k8s-node 프로비저닝 |
Vagrantfile
# Base Image https://portal.cloud.hashicorp.com/vagrant/discover/bento/rockylinux-10.0
BOX_IMAGE = "bento/rockylinux-10.0"
BOX_VERSION = "202510.26.0"
N = 2 # max number of Node
Vagrant.configure("2") do |config|
# K8s Nodes
(1..N).each do |i|
config.vm.define "k8s-node#{i}" do |subconfig|
subconfig.vm.box = BOX_IMAGE
subconfig.vm.box_version = BOX_VERSION
subconfig.vm.provider "virtualbox" do |vb|
vb.customize ["modifyvm", :id, "--groups", "/Kubespary-offline-Lab"]
vb.customize ["modifyvm", :id, "--nicpromisc2", "allow-all"]
vb.name = "k8s-node#{i}"
vb.cpus = 4
vb.memory = 2048
vb.linked_clone = true
end
subconfig.vm.host_name = "k8s-node#{i}"
subconfig.vm.network "private_network", ip: "192.168.10.1#{i}"
subconfig.vm.network "forwarded_port", guest: 22, host: "6000#{i}", auto_correct: true, id: "ssh"
subconfig.vm.synced_folder "./", "/vagrant", disabled: true
subconfig.vm.provision "shell", path: "init_cfg.sh" , args: [ N ]
end
end
# Admin Node
config.vm.define "admin" do |subconfig|
subconfig.vm.box = BOX_IMAGE
subconfig.vm.box_version = BOX_VERSION
subconfig.vm.provider "virtualbox" do |vb|
vb.customize ["modifyvm", :id, "--groups", "/Kubespary-offline-Lab"]
vb.customize ["modifyvm", :id, "--nicpromisc2", "allow-all"]
vb.name = "admin"
vb.cpus = 4
vb.memory = 2048
vb.linked_clone = true
end
subconfig.vm.host_name = "admin"
subconfig.vm.network "private_network", ip: "192.168.10.10"
subconfig.vm.network "forwarded_port", guest: 22, host: "60000", auto_correct: true, id: "ssh"
subconfig.vm.synced_folder "./", "/vagrant", disabled: true
subconfig.vm.disk :disk, size: "120GB", primary: true
subconfig.vm.provision "shell", path: "admin.sh" , args: [ N ]
end
end
주요 포인트:
- Rocky Linux 10.0: RHEL 계열, 실제 기업 환경에서 많이 사용하는 배포판
- admin 120GB 디스크: 오프라인 패키지, 컨테이너 이미지 등을 로컬에 저장해야 하므로 용량을 크게 설정
- private_network:
192.168.10.0/24대역의 Host-Only 네트워크로 VM 간 내부 통신 - linked_clone: 디스크 공간 절약을 위한 linked clone 사용
admin.sh
admin 노드의 프로비저닝 스크립트다. 각 TASK가 하는 일을 정리하면:
#!/usr/bin/env bash
echo ">>>> Initial Config Start <<<<"
echo "[TASK 1] Change Timezone and Enable NTP"
timedatectl set-local-rtc 0
timedatectl set-timezone Asia/Seoul
echo "[TASK 2] Disable firewalld and selinux"
systemctl disable --now firewalld >/dev/null 2>&1
setenforce 0
sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config
echo "[TASK 3] Setting Local DNS Using Hosts file"
sed -i '/^127\.0\.\(1\|2\)\.1/d' /etc/hosts
echo "192.168.10.10 admin" >> /etc/hosts
for (( i=1; i<=$1; i++ )); do echo "192.168.10.1$i k8s-node$i" >> /etc/hosts; done
echo "[TASK 4] Delete default routing - enp0s9 NIC"
nmcli connection modify enp0s9 ipv4.never-default yes
nmcli connection up enp0s9 >/dev/null 2>&1
echo "[TASK 5] Config net.ipv4.ip_forward"
cat << EOF > /etc/sysctl.d/99-ipforward.conf
net.ipv4.ip_forward = 1
EOF
sysctl --system >/dev/null 2>&1
echo "[TASK 6] Install packages"
dnf install -y python3-pip git sshpass cloud-utils-growpart >/dev/null 2>&1
echo "[TASK 7] Install Helm"
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | DESIRED_VERSION=v3.20.0 bash >/dev/null 2>&1
echo "[TASK 8] Increase Disk Size"
growpart /dev/sda 3 >/dev/null 2>&1
xfs_growfs /dev/sda3 >/dev/null 2>&1
echo "[TASK 9] Setting SSHD"
echo "root:qwe123" | chpasswd
cat << EOF >> /etc/ssh/sshd_config
PermitRootLogin yes
PasswordAuthentication yes
EOF
systemctl restart sshd >/dev/null 2>&1
echo "[TASK 10] Setting SSH Key"
ssh-keygen -t rsa -N "" -f /root/.ssh/id_rsa >/dev/null 2>&1
sshpass -p 'qwe123' ssh-copy-id -o StrictHostKeyChecking=no root@192.168.10.10 >/dev/null 2>&1
ssh -o StrictHostKeyChecking=no root@admin-lb hostname >/dev/null 2>&1
for (( i=1; i<=$1; i++ )); do sshpass -p 'qwe123' ssh-copy-id -o StrictHostKeyChecking=no root@192.168.10.1$i >/dev/null 2>&1 ; done
for (( i=1; i<=$1; i++ )); do sshpass -p 'qwe123' ssh -o StrictHostKeyChecking=no root@k8s-node$i hostname >/dev/null 2>&1 ; done
echo "[TASK 11] Install K9s"
CLI_ARCH=amd64
if [ "$(uname -m)" = "aarch64" ]; then CLI_ARCH=arm64; fi
wget -P /tmp https://github.com/derailed/k9s/releases/latest/download/k9s_linux_${CLI_ARCH}.tar.gz >/dev/null 2>&1
tar -xzf /tmp/k9s_linux_${CLI_ARCH}.tar.gz -C /tmp
chown root:root /tmp/k9s
mv /tmp/k9s /usr/local/bin/
chmod +x /usr/local/bin/k9s
echo "[TASK 12] ETC"
echo "sudo su -" >> /home/vagrant/.bashrc
echo ">>>> Initial Config End <<<<"
| TASK | 설명 | 비고 |
|---|---|---|
| 1 | 타임존 설정 (Asia/Seoul) | NTP는 이후 별도로 구성 |
| 2 | firewalld / SELinux 비활성화 | 실습 편의상 |
| 3 | /etc/hosts에 노드 IP 등록 |
간이 DNS 역할 |
| 4 | enp0s9에서 default route 제거 |
Host-Only NIC이 기본 경로가 되지 않도록 |
| 5 | ip_forward 활성화 |
admin이 게이트웨이 역할을 할 수 있도록 |
| 6-7 | 필요 패키지 및 Helm 설치 | python3-pip, git, sshpass, growpart, helm |
| 8 | 디스크 확장 (120GB) | growpart + xfs_growfs |
| 9-10 | SSH 설정 및 키 배포 | root 로그인 허용, 모든 노드에 SSH 키 배포 |
| 11 | K9s 설치 | K8s TUI 관리 도구 |
init_cfg.sh
k8s-node 프로비저닝 스크립트다. admin.sh와 공통 부분이 있지만, K8s 워크로드를 실행할 노드이므로 추가 설정이 필요하다.
#!/usr/bin/env bash
echo ">>>> Initial Config Start <<<<"
echo "[TASK 1] Change Timezone and Enable NTP"
timedatectl set-local-rtc 0
timedatectl set-timezone Asia/Seoul
echo "[TASK 2] Disable firewalld and selinux"
systemctl disable --now firewalld >/dev/null 2>&1
setenforce 0
sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config
echo "[TASK 3] Disable and turn off SWAP & Delete swap partitions"
swapoff -a
sed -i '/swap/d' /etc/fstab
sfdisk --delete /dev/sda 2 >/dev/null 2>&1
partprobe /dev/sda >/dev/null 2>&1
echo "[TASK 4] Config kernel & module"
cat << EOF > /etc/modules-load.d/k8s.conf
overlay
br_netfilter
vxlan
EOF
modprobe overlay >/dev/null 2>&1
modprobe br_netfilter >/dev/null 2>&1
cat << EOF >/etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sysctl --system >/dev/null 2>&1
echo "[TASK 5] Setting Local DNS Using Hosts file"
sed -i '/^127\.0\.\(1\|2\)\.1/d' /etc/hosts
echo "192.168.10.10 admin" >> /etc/hosts
for (( i=1; i<=$1; i++ )); do echo "192.168.10.1$i k8s-node$i" >> /etc/hosts; done
echo "[TASK 6] Delete default routing - enp0s9 NIC"
nmcli connection modify enp0s9 ipv4.never-default yes
nmcli connection up enp0s9 >/dev/null 2>&1
echo "[TASK 7] Setting SSHD"
echo "root:qwe123" | chpasswd
cat << EOF >> /etc/ssh/sshd_config
PermitRootLogin yes
PasswordAuthentication yes
EOF
systemctl restart sshd >/dev/null 2>&1
echo "[TASK 8] Install packages"
dnf install -y python3-pip git >/dev/null 2>&1
echo "[TASK 9] ETC"
echo "sudo su -" >> /home/vagrant/.bashrc
echo ">>>> Initial Config End <<<<"
admin.sh와의 차이:
| 항목 | admin.sh | init_cfg.sh |
|---|---|---|
| SWAP | - | 비활성화 및 파티션 삭제 |
| 커널 모듈 | - | overlay, br_netfilter, vxlan 로드 |
| sysctl | ip_forward만 |
bridge-nf-call-iptables 등 K8s 필수 설정 포함 |
| SSH 키 | 생성 + 배포 | - (admin에서 배포받음) |
| 디스크 | 120GB 확장 | - |
| 추가 도구 | Helm, K9s, sshpass, growpart | - |
환경 배포
파일 다운로드 및 VM 생성
mkdir k8s-offline
cd k8s-offline
# 실습 파일 다운로드
curl -O https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/k8s-kubespary-offline/Vagrantfile
curl -O https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/k8s-kubespary-offline/admin.sh
curl -O https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/k8s-kubespary-offline/init_cfg.sh
# VM 생성
vagrant up
VM 생성 과정에서 각 노드가 순차적으로 프로비저닝된다.
Bringing machine 'week06-node1' up with 'virtualbox' provider...
Bringing machine 'week06-node2' up with 'virtualbox' provider...
Bringing machine 'week06-admin' up with 'virtualbox' provider...
각 노드의 프로비저닝 완료 로그:
# k8s-node (init_cfg.sh)
>>>> Initial Config Start <<<<
[TASK 1] Change Timezone and Enable NTP
[TASK 2] Disable firewalld and selinux
[TASK 3] Disable and turn off SWAP & Delete swap partitions
[TASK 4] Config kernel & module
[TASK 5] Setting Local DNS Using Hosts file
[TASK 6] Delete default routing - enp0s9 NIC
[TASK 7] Setting SSHD
[TASK 8] Install packages
[TASK 9] ETC
>>>> Initial Config End <<<<
# admin (admin.sh)
>>>> Initial Config Start <<<<
[TASK 1] Change Timezone and Enable NTP
[TASK 2] Disable firewalld and selinux
[TASK 3] Setting Local DNS Using Hosts file
[TASK 4] Delete default routing - enp0s9 NIC
[TASK 5] Config net.ipv4.ip_forward
[TASK 6] Install packages
[TASK 7] Install Helm
[TASK 8] Increase Disk Size
[TASK 9] Setting SSHD
[TASK 10] Setting SSH Key
[TASK 11] Install K9s
[TASK 12] ETC
>>>> Initial Config End <<<<
상태 확인
모든 VM이 running 상태인지 확인한다.
vagrant status
Current machine states:
week06-node1 running (virtualbox)
week06-node2 running (virtualbox)
week06-admin running (virtualbox)
접속 확인
각 노드에 SSH로 접속할 수 있는지 확인한다. 호스트 OS에 sshpass가 없으면 ssh로 접속 후 비밀번호(qwe123)를 입력한다.
# admin 접속
ssh root@192.168.10.10
# k8s-node1 접속
ssh root@192.168.10.11
# k8s-node2 접속
ssh root@192.168.10.12
네트워크 구조 확인
환경 배포가 완료되면, 네트워크가 의도한 대로 구성되었는지 확인한다. 이후 Network Gateway 구성(8.1.1)의 기반이 되는 내용이다. VirtualBox NAT/Host-Only 어댑터의 전체적인 동작 원리는 VirtualBox + Vagrant 네트워크 어댑터 이해하기를 참고하자.
라우팅 테이블
admin 노드에 접속하여 라우팅 테이블을 확인한다.
root@admin:~# ip -c route
default via 10.0.2.2 dev enp0s8 proto dhcp src 10.0.2.15 metric 100
10.0.2.0/24 dev enp0s8 proto kernel scope link src 10.0.2.15 metric 100
192.168.10.0/24 dev enp0s9 proto kernel scope link src 192.168.10.10 metric 101
| NIC | 네트워크 | 용도 |
|---|---|---|
enp0s8 |
10.0.2.0/24 (NAT) |
VirtualBox NAT, 인터넷 접근 (default route) |
enp0s9 |
192.168.10.0/24 (Host-Only) |
VM 간 내부 통신 |
NIC 설정 상세
두 NIC의 NetworkManager 설정을 비교하면, 각각의 역할이 명확히 드러난다.
enp0s8 — NAT (인터넷)
# /etc/NetworkManager/system-connections/enp0s8.nmconnection
[connection]
id=enp0s8
type=ethernet
interface-name=enp0s8
[ipv4]
method=auto # DHCP로 IP/서브넷/게이트웨이 자동 할당
method=auto: VirtualBox NAT 네트워크의 DHCP 서버에서 IP를 할당받는다- 이 NIC을 통해 인터넷에 접근할 수 있으며, default route가 여기를 가리킨다
enp0s9 — Host-Only (내부망)
# /etc/NetworkManager/system-connections/enp0s9.nmconnection
[connection]
id=enp0s9
type=ethernet
autoconnect-priority=-100 # 자동 연결 우선순위 낮음
autoconnect-retries=1 # 실패 시 1회만 재시도
interface-name=enp0s9
[ipv4]
address1=192.168.10.10/24
method=manual # 고정 IP
never-default=true # 절대 default route를 생성하지 않음
method=manual: Vagrantfile에서 지정한 IP(192.168.10.10)가 고정 설정never-default=true: 핵심 설정. 이 NIC이 default route를 생성하지 않도록 한다.admin.sh의 TASK 4에서nmcli connection modify enp0s9 ipv4.never-default yes로 설정한 결과다.
이 설정이 없으면 두 NIC 모두 default route를 생성하려 하면서 라우팅 충돌이 발생할 수 있다.
NetworkManager 서비스 구조
Rocky Linux 10에서 네트워크는 NetworkManager가 관리한다. 관련 systemd 서비스는 3개다.
| 서비스 | 역할 | 타입 |
|---|---|---|
NetworkManager.service |
네트워크 장치 관리, IP 할당의 핵심 데몬 | dbus |
NetworkManager-wait-online.service |
부팅 시 네트워크 연결 완료까지 대기 | oneshot |
NetworkManager-dispatcher.service |
NIC up/down, IP 변경 등 이벤트 시 사용자 스크립트 실행 | dbus |
# 서비스 상태 확인
systemctl status NetworkManager.service --no-pager
systemctl status NetworkManager-wait-online.service --no-pager
systemctl status NetworkManager-dispatcher.service --no-pager
# dispatcher 스크립트 디렉터리 (현재 비어 있음)
tree /etc/NetworkManager/dispatcher.d/
/etc/NetworkManager/dispatcher.d/
├── no-wait.d
├── pre-down.d
└── pre-up.d
4 directories, 0 files
참고: NetworkManager의 동작 원리와 네트워크 핵심 개념은 다음 글(8.1.1 Network Gateway)에서 자세히 다룬다.
정리
실습 환경의 현재 상태를 정리하면:
| 호스트 | 항목 | 상태 |
|---|---|---|
| admin (192.168.10.10) | enp0s8 (NAT) — 인터넷 접근 | 완료 |
| enp0s9 (Host-Only) — 내부 통신 | 완료 | |
| ip_forward = 1 — 패킷 포워딩 준비 | 완료 | |
| SSH 키 배포 — 모든 노드 접근 가능 | 완료 | |
| NTP, DNS, Repo, Registry | 미구성 | |
| k8s-node1/2 (192.168.10.11-12) | enp0s8 (NAT) — 인터넷 접근 | 완료 |
| enp0s9 (Host-Only) — 내부 통신 | 완료 | |
| K8s 커널 모듈 — overlay, br_netfilter 로드 | 완료 | |
| SWAP 비활성화 — K8s 요구사항 충족 | 완료 | |
| containerd, kubelet | 미설치 |
현재는 모든 노드가 enp0s8(NAT)을 통해 인터넷에 접근할 수 있다. 폐쇄망 시뮬레이션은 이후 Network Gateway 구성(8.1.1)에서 이 인터넷 접근을 차단하는 것으로 시작한다.
댓글남기기