환경: Ubuntu Server 24.04 LTS, k3s v1.31+, Windows 11 (호스트)
구축 동기
회사에서 Kubernetes 클러스터를 매일 사용하지만, 이미 만들어진 환경 위에서 배포만 해왔습니다. kubectl apply는 익숙한데, 그 아래 클러스터가 어떻게 구성되어 있는지는 막연하게만 알고 있는 상태였습니다. 직접 노드를 구성하고, 네트워크를 잡고, 클러스터를 올려보는 경험이 필요하다고 느꼈습니다.
또 하나의 이유는 배포 인프라의 확장성이었습니다. 지금은 프론트엔드 문서 사이트 하나를 Vercel에 올려두고 있지만, 앞으로 백엔드 API 서버, 데이터베이스, 사이드 프로젝트들을 계속 늘려갈 계획이 있었습니다. 그때마다 클라우드 서비스에 과금하기보다는, 물리 장비 위에 직접 운영하는 환경을 갖추는 편이 장기적으로 유리하다고 판단했습니다.
마침 회사에서 4코어 8GB 메모리 사양의 미니PC 2대를 받게 되면서 홈 클러스터 구축을 시작하게 되었습니다.
| 구성 | 사양 |
|---|---|
| CPU | 4코어 |
| RAM | 8GB |
| 수량 | 2대 (마스터 1 + 워커 1) |
| OS | Ubuntu Server 24.04 LTS |
| Kubernetes | k3s (경량 배포판) |
정보
k3s는 Rancher Labs에서 개발한 경량 Kubernetes 배포판으로, 전체 k8s 대비 바이너리 크기가 100MB 미만이며 메모리 사용량도 절반 이하입니다. IoT, Edge, 홈랩 환경에 적합합니다.
Ubuntu 부팅 USB 만들기
첫 단계는 Ubuntu Server를 설치할 부팅 USB를 만드는 것입니다. Windows 환경에서는 Rufus가 가장 널리 쓰이는 도구입니다.
Ubuntu Server ISO 다운로드
Ubuntu Server 다운로드 페이지에서 Ubuntu Server 24.04 LTS ISO 파일을 받습니다. LTS 버전은 5년간 보안 업데이트가 보장되므로 서버 용도에 적합합니다.
Rufus 다운로드 및 실행
rufus.ie에서 Rufus를 다운로드합니다. 설치 없이 바로 실행 가능한 포터블 버전을 제공하므로, 받아서 바로 실행하면 됩니다.
부팅 USB 작성
Rufus를 실행한 뒤 다음 항목을 확인합니다.
- 장치: USB 드라이브 선택
- 부트 유형: 다운로드한 Ubuntu Server ISO 파일 선택
- 파티션 구성: GPT (UEFI 부팅인 경우)
- 파일 시스템: FAT32
시작 버튼을 누르면 USB에 쓰기가 진행됩니다. USB 내 기존 데이터는 모두 삭제되므로 중요한 파일이 없는지 반드시 확인해야 합니다.
Rufus 외 대안
balenaEtcher도 많이 사용됩니다. ISO 파일을 선택하고 드라이브를 지정한 뒤 Flash 버튼만 누르면 끝이라 더 단순하지만, 파티션 구성 등 세부 옵션이 필요한 경우에는 Rufus가 더 적합합니다.
Ubuntu Server 설치
USB를 기기에 꽂고 BIOS에 진입합니다. BIOS 진입 키는 제조사마다 다르지만 F2, F12, Del, Esc 중 하나인 경우가 대부분입니다. Boot 메뉴에서 USB를 첫 번째 부팅 장치로 선택한 뒤 저장하고 재부팅하면 Ubuntu 설치 부팅 메뉴가 나타납니다.
검은 화면 문제와 nomodeset
USB로 부팅하면 Try or Install Ubuntu Server 항목이 선택된 부팅 메뉴가 나타납니다. 그런데 여기서 그대로 엔터를 누르면 화면이 검은색으로 멈춰버리는 경우가 있습니다. 커서도 깜빡이지 않고, 아무 입력에도 반응하지 않는 상태입니다.
이 현상은 Linux 커널의 KMS(Kernel Mode Setting)가 GPU를 제대로 인식하지 못할 때 발생합니다. 특히 NVIDIA 또는 일부 내장 GPU에서 흔히 나타나는 문제로, 커널이 그래픽 모드를 설정하려다 실패하면서 화면 출력 자체가 멈추는 것입니다. Ubuntu Desktop과 달리 Ubuntu Server에는 safe graphics 옵션이 없으므로, 직접 커널 파라미터를 수정해야 합니다.
Try or Install Ubuntu Server 가 선택된 상태에서 E 키를 눌러 편집 모드로 진입한 뒤, linux 로 시작하는 줄을 찾습니다.
linux /casper/vmlinuz ------ 앞에 nomodeset을 추가합니다.
linux /casper/vmlinuz nomodeset ---Ctrl + X 또는 F10을 누르면 수정된 파라미터로 부팅이 진행되며, 정상적으로 설치 마법사가 나타납니다.
정보
nomodeset은 커널에게 자체적인 디스플레이 모드 설정을 하지 말고 BIOS/UEFI가 설정한 모드를 그대로 사용하라고 지시하는 파라미터입니다. GPU 드라이버를 제대로 설치하기 전까지는 이 옵션이 가장 안정적인 화면 출력을 보장합니다.
설치 진행
설치 마법사는 GUI 없이 텍스트 기반으로 진행되며, 방향키와 엔터만으로 조작할 수 있습니다. 대부분의 항목은 기본값으로 진행해도 무방하지만, 아래 항목들은 주의가 필요합니다.
| 항목 | 권장 설정 | 비고 |
|---|---|---|
| 언어 | English | 에러 메시지 검색이 쉽고 폴더명이 영어로 생성됨 |
| 키보드 | English (US) | 기본값 유지 |
| 네트워크 | DHCP 자동 할당 | 이후 고정 IP로 변경 |
| 스토리지 | Use an entire disk | LVM으로 설치되며, 디스크 절반만 할당될 수 있음 |
| SSH | Install OpenSSH server | 반드시 체크. 미체크 시 원격 접속 불가 |
| Active Directory | 체크 안 함 | 개인 서버에서는 불필요 |
| 사용자 | username / password 설정 | SSH 접속에 계속 사용하므로 기억해둘 것 |
OpenSSH server 체크 필수
이 옵션을 놓치면 설치 후 모니터와 키보드를 직접 연결해서 sudo apt install openssh-server를 수동으로 실행해야 합니다. 설치 과정에서 반드시 체크하는 것이 좋습니다.
nomodeset 영구 적용 (재부팅 전)
설치가 완료되면 "Installation complete" 화면이 나타납니다. 여기서 바로 재부팅을 선택하면 안 됩니다. 앞서 USB 부팅 시 추가한 nomodeset은 일회성이었으므로, 영구 적용 없이 재부팅하면 다시 검은 화면에 빠지게 됩니다.
터미널 전환
"Installation complete" 화면에서 Alt + F2를 눌러 다른 터미널로 전환합니다.
마운트 포인트 확인
설치된 시스템이 어디에 마운트되어 있는지 확인합니다.
lsblkLVM 구성이면 아래와 같이 표시되며, 마운트 포인트가 /target인 것을 확인합니다.
sda 119.2G disk
├─sda1 1G part
├─sda2 2G part
└─sda3 116.2G part
└─ubuntu--vg-ubuntu--lv lvm /target
GRUB 설정 파일 편집
설치된 시스템의 GRUB 설정 파일을 직접 편집합니다.
sudo nano /target/etc/default/grubGRUB_CMDLINE_LINUX_DEFAULT 줄을 찾아 nomodeset을 추가합니다.
# 변경 전
GRUB_CMDLINE_LINUX_DEFAULT=""
# 변경 후
GRUB_CMDLINE_LINUX_DEFAULT="nomodeset"Ctrl + O → Enter → Ctrl + X로 저장하고 나옵니다.
GRUB 업데이트
설치된 시스템에 chroot로 진입하여 GRUB 설정을 반영합니다.
sudo mount --bind /dev /target/dev
sudo chroot /target update-grubdone이 출력되면 성공입니다.
재부팅
USB를 뽑고 재부팅합니다.
reboot재부팅 후 로그가 쭉 나오고 로그인 프롬프트가 정상적으로 나타나면 성공입니다.
Ubuntu 24.04 LTS server-name tty1
server-name login: _
설치 시 생성한 username과 password로 로그인한 뒤, 서버에 할당된 IP를 확인해둡니다. 이 IP는 이후 SSH 접속과 고정 IP 설정의 기준이 됩니다.
hostname -I네트워크 구성
Ubuntu 서버가 올라왔으면 다음 단계는 네트워크입니다. Windows 호스트 PC에서 SSH로 서버에 접근하려면, 두 기기가 같은 네트워크 대역에 있어야 합니다. 가정 환경에서는 크게 두 가지 구성이 가능합니다.
공유기를 사용하는 경우 (권장)
가장 단순한 구성입니다. Windows PC와 Ubuntu 서버 2대를 모두 같은 공유기에 유선으로 연결하면, DHCP가 자동으로 IP를 할당하여 같은 서브넷(예: 192.168.0.x)에 위치시켜 줍니다. 별도의 네트워크 설정 없이도 기기 간 통신이 가능하므로, k3s 클러스터의 마스터-워커 간 통신도 자연스럽게 이루어집니다.
| 기기 | 역할 | IP (예시) |
|---|---|---|
| Windows PC | 호스트 (SSH 접속용) | 192.168.0.10 |
| Ubuntu #1 | k3s 마스터 노드 | 192.168.0.20 |
| Ubuntu #2 | k3s 워커 노드 | 192.168.0.21 |
공유기 없이 직접 연결하는 경우
공유기 없이 Windows PC와 Ubuntu 서버를 직접 이더넷 케이블로 연결하는 경우에는 DHCP 서버가 없으므로, 양쪽 모두 고정 IP를 수동으로 설정해야 합니다.
Windows 측에서는 네트워크 어댑터 설정에서 IP를 지정하고, Ubuntu 측에서는 Netplan으로 설정합니다. 이 구성은 해당 PC에서만 서버에 접근할 수 있으므로 호스트 전용 네트워크처럼 동작합니다. 외부 인터넷 접근이 필요하다면 Windows PC에서 인터넷 연결 공유(ICS)를 활성화해야 하는 등 추가 작업이 필요해 권장하지 않습니다.
고정 IP 설정 (Netplan)
DHCP로 받은 IP는 공유기 재시작이나 리스 갱신 시 바뀔 수 있습니다. k3s 클러스터에서 마스터 노드의 IP가 바뀌면 워커 노드가 연결을 잃게 되므로, 고정 IP 설정은 필수입니다.
Ubuntu Server는 Netplan을 사용하여 네트워크를 관리합니다. 먼저 현재 네트워크 인터페이스 이름과 게이트웨이 정보를 확인합니다.
# 인터페이스 이름과 현재 IP 확인
ip addr
# 게이트웨이 확인
ip route | grep defaultip addr 출력에서 enp나 eth로 시작하는 인터페이스 이름을 확인하고, ip route에서 게이트웨이 IP를 확인한 뒤 Netplan 설정 파일을 편집합니다.
# Netplan 설정 파일명 확인
ls /etc/netplan/
# 편집 (파일명은 환경마다 다를 수 있음)
sudo nano /etc/netplan/50-cloud-init.yamlnetwork:
version: 2
ethernets:
enp1s0: # ip addr에서 확인한 인터페이스 이름
dhcp4: false
addresses:
- 192.168.0.20/24 # 원하는 고정 IP
routes:
- to: default
via: 192.168.0.1 # ip route에서 확인한 게이트웨이 IP
nameservers:
addresses:
- 8.8.8.8
- 8.8.4.4# 설정 적용
sudo netplan apply
# 확인 — valid_lft forever가 보이면 고정 IP 적용 성공
ip addr show enp1s0SSH 연결 끊김 주의
고정 IP로 변경하면 기존 DHCP IP로 잡아둔 SSH 세션이 끊어집니다. 새로 설정한 고정 IP로 다시 접속해야 하므로, IP 변경 전에 설정할 IP 주소를 메모해두는 것이 좋습니다.
두 번째 Ubuntu 서버도 같은 방식으로 고정 IP를 설정하되, 주소만 다르게 지정합니다(예: 192.168.0.21).
Windows에서 SSH 접속
네트워크가 잡히면 더 이상 모니터와 키보드를 서버에 직접 연결할 필요가 없습니다. Windows의 터미널에서 SSH로 원격 접속하면 됩니다.
Windows 10 이상에서는 OpenSSH 클라이언트가 기본 내장되어 있으므로, PowerShell이나 Windows Terminal에서 바로 사용할 수 있습니다.
# 마스터 노드 접속
ssh username@192.168.0.20접속할 때마다 비밀번호를 입력하는 것은 번거롭습니다. SSH 키 인증을 설정하면 비밀번호 없이 바로 접속할 수 있습니다.
먼저 Windows 터미널에서 SSH 키를 생성하고 서버에 복사합니다.
# Windows 터미널에서 실행
ssh-keygen -t ed25519
# 공개키를 서버에 복사
scp ~/.ssh/id_ed25519.pub username@192.168.0.20:~/그다음 서버에 SSH로 접속하여 복사한 공개키를 등록합니다.
# 서버(Ubuntu)에서 실행
mkdir -p ~/.ssh
cat ~/id_ed25519.pub >> ~/.ssh/authorized_keys
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
rm ~/id_ed25519.pub이후에는 ssh username@192.168.0.20만 입력하면 비밀번호 없이 바로 접속됩니다.
SSH config 활용
~/.ssh/config 파일에 호스트 별칭을 등록해두면 매번 IP를 입력하지 않아도 됩니다.
Host k3s-master
HostName 192.168.0.20
User username
Host k3s-worker
HostName 192.168.0.21
User username
이렇게 설정하면 ssh k3s-master만으로 접속할 수 있습니다.
k3s 마스터 노드 설치
SSH로 첫 번째 서버에 접속한 뒤, k3s를 설치합니다. k3s의 설치는 단 한 줄의 스크립트로 끝납니다.
curl -sfL https://get.k3s.io | sh -이 스크립트가 하는 일은 k3s 바이너리를 다운로드하고, systemd 서비스로 등록하여 자동 시작되도록 설정하는 것입니다. 설치가 완료되면 해당 노드가 곧바로 마스터(control plane) 역할을 수행하기 시작합니다.
설치 확인은 kubectl로 합니다. k3s를 설치하면 kubectl이 함께 포함되지만, 기본적으로 kubeconfig 파일이 root 소유이므로 sudo가 필요합니다.
# 노드 상태 확인
sudo kubectl get nodesNAME STATUS ROLES AGE VERSION
k3s-master Ready control-plane,master 30s v1.31.x+k3s1
STATUS가 Ready로 표시되면 마스터 노드가 정상적으로 동작하는 것입니다.
매번 sudo를 붙이는 것이 번거롭다면 kubeconfig 파일의 권한을 조정하거나, 환경 변수를 설정해둘 수 있습니다.
# kubeconfig를 현재 사용자가 읽을 수 있도록 설정
sudo chmod 644 /etc/rancher/k3s/k3s.yaml
# 셸 프로파일에 환경 변수 추가
echo 'export KUBECONFIG=/etc/rancher/k3s/k3s.yaml' >> ~/.bashrc
source ~/.bashrc
# 이제 sudo 없이 사용 가능
kubectl get nodes워커 노드 조인
두 번째 기기에도 동일한 과정으로 Ubuntu Server를 설치하고 고정 IP를 설정합니다(예: 192.168.0.21). 이 기기는 워커 노드로 사용할 것이므로 k3s를 agent 모드로 설치해야 합니다.
워커 노드가 마스터에 합류하려면 두 가지 정보가 필요합니다.
| 정보 | 설명 |
|---|---|
K3S_URL | 마스터 노드의 API 서버 주소 (https://마스터IP:6443) |
K3S_TOKEN | 마스터 노드의 인증 토큰 |
먼저 마스터 노드에서 토큰을 확인합니다.
# 마스터 노드에서 실행
sudo cat /var/lib/rancher/k3s/server/node-token긴 문자열이 출력됩니다. 이 값을 복사해둔 뒤, 두 번째 서버에 SSH로 접속하여 다음 명령을 실행합니다.
# 워커 노드에서 실행
curl -sfL https://get.k3s.io | K3S_URL=https://192.168.0.20:6443 K3S_TOKEN=<복사한 토큰> sh -K3S_URL로 마스터 노드의 API 서버 위치를 알려주고, K3S_TOKEN으로 합류 권한을 인증합니다. 설치가 완료되면 워커 노드가 자동으로 마스터에 등록됩니다.
마스터 노드로 돌아가서 클러스터 상태를 확인합니다.
# 마스터 노드에서 실행
kubectl get nodesNAME STATUS ROLES AGE VERSION
k3s-master Ready control-plane,master 10m v1.31.x+k3s1
k3s-worker Ready <none> 30s v1.31.x+k3s1
두 노드 모두 Ready 상태로 표시되면 클러스터 구성이 완료된 것입니다. 이제 이 클러스터 위에 Pod를 배포하면 k3s 스케줄러가 두 노드에 걸쳐 워크로드를 분산시킵니다.
방화벽 확인
워커 노드가 마스터에 조인되지 않는다면 방화벽이 원인일 수 있습니다. k3s는 API 서버(6443), kubelet(10250), flannel VXLAN(8472/UDP) 포트를 사용합니다. sudo ufw status로 방화벽 상태를 확인하고, 필요하다면 해당 포트를 열어주어야 합니다.
sudo ufw allow 6443/tcp
sudo ufw allow 10250/tcp
sudo ufw allow 8472/udp클러스터 동작 확인
클러스터가 정상적으로 구성되었는지 간단한 테스트 배포로 확인해볼 수 있습니다.
# nginx 테스트 배포
kubectl create deployment nginx-test --image=nginx --replicas=2
# Pod 상태 확인 (두 노드에 분산 배포되는지 확인)
kubectl get pods -o wideNAME READY STATUS RESTARTS AGE IP NODE
nginx-test-7c5b8d6b9-abc12 1/1 Running 0 10s 10.42.0.5 k3s-master
nginx-test-7c5b8d6b9-def34 1/1 Running 0 10s 10.42.1.3 k3s-worker
두 Pod가 각각 다른 노드에 배치되었다면 클러스터가 정상적으로 워크로드를 분산하고 있는 것입니다. 확인이 끝났으면 테스트 리소스를 정리합니다.
kubectl delete deployment nginx-test트러블슈팅
정리
1편 요약
- Ubuntu USB 부팅 디스크: Rufus로 Ubuntu Server ISO를 USB에 구워 설치 미디어를 준비합니다
- OS 설치 + nomodeset: 검은 화면이 발생하면 부팅 메뉴에서
nomodeset으로 설치를 진행하고, 설치 완료 후 chroot로 영구 적용합니다 - 네트워크 + SSH: 공유기 아래 고정 IP를 설정하고, SSH 키 인증으로 Windows에서 원격 접속 환경을 구성합니다
- k3s 클러스터: 마스터 노드에 k3s를 설치하고, 토큰 기반으로 워커 노드를 조인시켜 2노드 클러스터를 완성합니다
이것으로 물리 장비 위에 경량 Kubernetes 클러스터가 올라갔습니다. 다음 편에서는 이 클러스터를 집 공유기를 통해 외부 인터넷에 노출시키고, TLS 인증서를 적용하는 과정을 다룹니다.