목차
1. 준비물
2. 운영체제 설치
3. 홈 네트워크 세팅
4. 라즈베리 파이에 고정 IP 할당
5. 외부 접속 세팅 (SSH + VPN)
6. 쿠버네티스 클러스터 구축
7. 스토리지 세팅
1. 준비물
- Computing : 라즈베리파이 5 Raspberry pi 5 Model 8GB * 4EA
- Switch : 넷기어 GS108PP 8포트 기가비트 POE 스위칭 허브 (123W)
- SSD : 삼성전자 870 EVO SSD, MZ-77E1T0, 1TB
- Micro SD : 삼성전자 MicroSD 카드 PRO PLUS 128GB MB-MD 128SA/KR * 4EA
- SSD SATA 케이블 : 엠비에프 SATA to USB 컨버터
- Micro SD 리더기 : 블레이즈 마이크로 SD 카드리더기
- (중요) 어댑터 : 라즈베리파이5 전용 어답터 5V 5A 지원
- 멀티 포트 허브 : iptime USB0A 고속 충전기 5포트 QC3.0 동시 충전
- 그리고 멀티탭과 USB to C 케이블 3개
2. 운영체제 설치
https://www.raspberrypi.com/software/
Raspberry Pi OS – Raspberry Pi
From industries large and small, to the kitchen table tinkerer, to the classroom coder, we make computing accessible and affordable for everybody.
www.raspberrypi.com
- 위 링크에서 Raspberry Pi Imager라는 프로그램을 자신의 운영체제에 맞게 설치하자.
- 해당 프로그램은 라즈베리 파이가 구동하기 위한 운영체제를 설치하기 위한 저장매체를 만들어준다. ("굽는다"라고 표현함)


- 이 글에서는 [Other general-purpose OS] - [Ubuntu Desktop 24.04.2 LTS (64-bit)] 운영체제를 기준으로 설명한다.
- 이후, 4장의 마이크로 SD 카드에 설치가 끝나면 각 라즈베리 파이 보드 뒷편에 슬롯에 꽂아주면 끝난다.
3. 홈 네트워크 세팅

- IDC 환경이 아닌 가정용 홈 네트워크 환경에서, 외부에서 접속하려고 하면 제약사항이 많다.
- 이는 가정용 네트워크는 2번의 NAT(Network Address Traslation)를 거치기 때문이다. 내부 디바이스에서 아무리 퍼블릭 IP 주소를 조회해봤자 이미 변환된 주소가 나와서 실질적으로 접속 가능한 주소를 알기 어렵다.
- 고로, 외부 모뎀의 관리자 페이지에 접속하여 네트워크 모드를 NAT에서 Bridge모드로 활성화해야 한다. 변환을 제거하고 WAN을 통해 오는 네트워크 신호를 그대로 흘러주는 방식이라 생각하면 된다.
- 통신사마다 모뎀 관리자 접속 페이지나 설정 방식은 다를 것이라 상세한 설명은 생략한다.
- Bridge 모드를 활성화하고 나서 다시 내부 네트워크에 있는 디바이스로부터 퍼블릭 IP 주소를 조회하면 다른 주소가 나올 것이다.
4. 라즈베리 파이에 고정 IP 할당
- 라즈베리 파이도 공유기에 연결하면 Private Network에 속하게 될텐데, 할당되는 프라이빗 IP 주소는 DHCP에 의해 동적으로 할당되는 방식이다. 즉, 바뀔 수 있음을 의미하며 임대기간이 지나면 해당 주소는 만료된다.
- 이 글에서는 라즈베리 파이를 하나의 쿠버네티스 클러스터로 연동시켜야 하기 때문에 서로의 IP 주소 정보를 계속 유지해야 한다. 아래는 CLI, GUI 환경 2가지 기준으로 설명한다.
Ubuntu CLI 환경
# ifconfig 커맨드를 실행하기 위해 의존성 설치 (+ VIM 설치)
$ sudo apt install net-tools && sudo apt-get update && sudo apt-get install vim
$ cd /etc/netplan
# 아래에 있는 yaml 파일을 수정한다.
$ sudo vi <.yaml>
# 이전 상태
network:
version: 2
eth0:
match:
macaddress: "xxx"
dhcp4: true
...
# 아래에 있는 yaml 파일을 아래와 같이 수정
network:
version: 2
eth0:
match:
macaddress: "xxx"
dhcp4: no
# 고정적으로 할당하기 싶은 IP 주소와 prefix 입력
# ex) addresses: [192.168.45.100/24]
addresses: [<private-ip-addr>/<prefix>]
# 일반적으로 Gateway 주소는 공유기의 IP가 된다. (공유기 관리자 페이지가 게이트웨이 주소)
# ex) gateway4: 192.168.45.1
gateway4: <gateway-ip-addr>
# Resolving 하기 위한 DNS 서버 목록 (아래는 구글의 DNS 서버)
nameservers:
addresses: [8.8.8.8, 8.8.4.4]
...
# 변경사항을 적용하고 ifconifg로 확인하면 이더넷 인터페이스의 IP 주소가 바뀌어 있을 것이다.
$ sudo netplan apply
Ubuntu Desktop

- Ubuntu Desktop 운영체제에서는 직접 Settings 이더넷에서 고정 IP를 할당해주어야 한다.
- 이는 네트워크 관리 도구 백엔드를 CLI환경에서 networkd, 데스크탑 환경에서는 NetworkManager를 사용하기 때문에 재부팅 과정에서 변경사항을 적용하는 방식에서 차이가 있다고 한다.
5. 외부 접속 세팅 (SSH + VPN)
- 결론부터 얘기하자면 VPN을 적극 권장한다. VPN을 사용한다고 별도의 비용이 청구되는 것도 아니며, 보안적으로나, 관리 측면에서도 훨씬 유리하다.
SSH 서버 구동
$ sudo apt update && sudo apt install openssh-server
$ sudo systemctl status ssh
$ sudo systemctl enable ssh && sudo systemctl start ssh
- 위 커맨드는 우분투 환경에서 SSH 서버 모듈을 설치 및 구동한다. 기본 포트는 22번이지만, 찝찝하면 다른 포트로 /etc/ssh/sshd_config에서 원하는 포트로 Port 라인을 수정하고 서비스를 재시작하면 된다.
VPN 서버 세팅

- VPN을 설정하지 않는 경우, 외부에서 들어온 트래픽의 포트(External Port)에 대해 일일이 내부 네트워크의 특정 디바이스(Device IP)에서 구동되는 포트(Internal Port)로 매핑해주는 포트포워딩 규칙을 구성해주어야 한다.
- 이러한 방식은 규칙이 늘어남에 따라 선형적으로 테이블이 증가하기 때문에 관리 측면에서 불리하고, 퍼블릭하게 노출되는 포트도 늘어나 보안측면에서도 불리하다.

- 위와 같이 클라이언트가 다이렉트로 내부 디바이스에 접근하는 방식이 아닌 VPN 서버를 경유(우회)하여 타 디바이스에 접근하는 방식을 취하면, VPN 서버가 구동된 경로만 노출시켜도 되기 때문에 위 방식의 단점들을 상쇄할 수 있다. (물론 VPN 서버에 대한 포트포워딩 규칙은 필요하다.)
- 다만, 해당 VPN 서버에서 중앙집권적으로 외부접속이 관리되기 때문에 장애가 발생할 경우 일괄적으로 문제가 발생하는 단점이 있기는 하다.
# VPN 서버를 구동시킬 라즈베리 파이에서 아래 커맨드 수행
$ wget https://git.io/vpn -O openvpn-install.sh
$ ./openvpn-install.sh
Welcome to this OpenVPN road warrior installer!
# 대상 네트워크 대역
Which IPv4 address should be used?
1) <private-ip-addr>
2) <...>
IPv4 address [1]: <엔터>
# 공유기(NAT 활성화)에 속했다면 다음과 같이 뜹니다.
This server is behind NAT. What is the public IPv4 address or hostname?
Public IPv4 address / hostname [<public-ip-addr>]: <엔터>
Which protocol should OpenVPN use?
1) UDP (recommended)
2) TCP
Protocol [1]: <엔터>
# 리스닝할 VPN 서버 포트
What port should OpenVPN listen on?
Port [1194]: <엔터>
Select a DNS server for the clients:
1) Default system resolvers
2) Google
3) 1.1.1.1
4) OpenDNS
5) Quad9
6) AdGuard
7) Specify custom resolvers
DNS server [1]: 2
Enter a name for the first client:
Name [client]: <클라이언트 이름 입력>
- 설치가 완료되고 나면 <클라이언트 이름 입력>.ovpn 파일이 생겨있을 것이다. 해당 파일을 접속할 클라이언트 PC에 복사한다.
- 그 후, 클라이언트 PC에서 https://openvpn.net/client/ 사이트에 접속하여 프로그램을 설치한 뒤, [FILE] 탭에서 아까 다운로드 받은 .ovpn 파일을 주입하고 토글을 활성화시키면 외부 네트워크에서도 라즈베리 파이가 있는 네트워크(홈 네트워크)에 접속할 수 있게 된다.
6. 쿠버네티스 클러스터 구축
- kubeadm으로 클러스터를 구축하기에는 너무 귀찮고, minikube로 노드들을 컨테이너 단위로 관리할 계획이 없기 때문에 microk8s를 사용하려고 한다.
$ sudo vi /boot/firmware/cmdline.txt
# 아래 내용을 추가한다.
cgroup_enable=memory cgroup_memory=1
# microk8s 설치 (버전 변경 가능)
$ sudo apt update && sudo apt install -y snapd
$ sudo snap install microk8s --classic --channel=1.30
$ sudo usermod -a -G microk8s berry-0
$ mkdir ~/.kube && sudo chown -R berry-0 ~/.kube
$ sudo reboot
- 위 커맨드를 모든 라즈베리 파이에서 구동시킨다.
$ microk8s start
# 아래 커맨드를 추가할 워커 노드 (나머지 3개의 라즈베리 파이) 만큼 실행한다.
# 실행하면 나오는 출력에 microk8s join ~~ 커맨드를 복사하여 나머지 라즈베리 파이에서 실행시킨다.
$ microk8s add-node
- 마스터 노드가될 라즈베리 파이에서 위 커맨드를 실행시킨다. 총 3번의 add-node 커맨드를 실행시켜 나머지 라즈베리 파이들을 새로 만들어진 클러스터에 조인시킨다.
$ microk8s.kubectl config view --raw > ~/.kube/conifg
- 위 커맨드를 수행시킴으로써 접속하기 위한 컨텍스트 정보를 추출한다. 추출하지 않을 경우, 클러스터를 제어하기 위해 microk8s kubectl ~~ 처럼 번거롭게 커맨드를 수행해야 한다..
- 위에서 생성된 .kube/config 파일을 클라이언트 PC에 ~/.kube 경로에도 복사하면 OpenLens나 k9s과 같은 쿠버네티스 클라이언트에서도 접속할 수 있다. (물론 해당 컨텍스트 정보가 프라이빗 IP 주소에 대해 신뢰(SAN)하고 있기 때문에 OpenVPN을 활성화해야 한다.)
7. 스토리지 세팅
SSD 마운트
$ sudo fdisk -l
Disk /dev/sda: 931.51 GiB, 1000204886016 bytes, 1953525168 sectors
Disk model: 2115
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
# 931GiB에 해당하는 마운트된 파일 시스템이 아직 없는 상태
$ df -h
Filesystem Size Used Avail Use% Mounted on
tmpfs 794M 4.1M 790M 1% /run
/dev/mmcblk0p2 117G 9.7G 103G 9% /
tmpfs 3.9G 0 3.9G 0% /dev/shm
tmpfs 5.0M 12K 5.0M 1% /run/lock
/dev/mmcblk0p1 505M 181M 324M 36% /boot/firmware
tmpfs 794M 124K 794M 1% /run/user/1002
shm 64M 0 64M 0% /var/snap/microk8s/common/run/containerd/io.containerd.grpc.v1.cri/sandboxes/8fd20040813e28a802810b8fcd67b7e80e3cd0767e496b8030f46076c71d398a/shm
shm 64M 0 64M 0% /var/snap/microk8s/common/run/containerd/io.containerd.grpc.v1.cri/sandboxes/90c65f7463f8fe7cfdf5411575350d4dd4011d9fb08ef3a0d3000ec46daccc5c/shm
shm 64M 0 64M 0% /var/snap/microk8s/common/run/containerd/io.containerd.grpc.v1.cri/sandboxes/b53c7964dba33af6bf469d2750a36de15bc6a5683e6951a8c86636f08b6abd01/shm
shm 64M 0 64M 0% /var/snap/microk8s/common/run/containerd/io.containerd.grpc.v1.cri/sandboxes/bc891dbb035c60557c67e34381a10d9501c081b1e5c5667c114eb7520c7e5946/shm
- SSD를 연결한 라즈베리 파이에서 위 커맨드를 통해 물리적으로 인식된 디스크(/dev/sda)를 확인할 수 있다. 하지만, 이 상태는 아직 Ubuntu 운영체제에서 마운트 된 상태는 아니다. (sda가 아니라 sdb, c 등으로 인식될 수 있음)
$ sudo fdisk /dev/sda
Welcome to fdisk (util-linux 2.39.3).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.
This disk is currently in use - repartitioning is probably a bad idea.
It's recommended to umount all file systems, and swapoff all swap
partitions on this disk.
The device contains 'ext4' signature and it will be removed by a write command. See fdisk(8) man page and --wipe option for more details.
Device does not contain a recognized partition table.
Created a new DOS (MBR) disklabel with disk identifier 0x7c85155d.
# n(new) 파티션 생성
Command (m for help): n
Partition type
p primary (0 primary, 0 extended, 4 free)
e extended (container for logical partitions)
Select (default p): <엔터>
Using default response p.
# 나눌 파티션 수
Partition number (1-4, default 1): <엔터>
First sector (2048-1953525167, default 2048): <엔터>
# 파티션으로 나누기 원하는 크기 입력 (e.g. +100G)
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-1953525167, default 1953525167): <엔터>
Created a new partition 1 of type 'Linux' and of size 931.5 GiB.
# w 저장 및 종료
Command (m for help): w
The partition table has been altered.
Syncing disks.
- 인식된 디스크(/dev/sda)에 대해 파티션을 생성해주어야 한다. 파티션을 생성하는 방식은 크게 MBR(Master Boot Record) 방식과 GPT(GUID Partition Table) 방식이 있다.
- 2TB가 넘어가는 디스크에 대한 파티션 생성시 GPT 방식으로 생성해주어야 한다. 하지만, 이 글에서는 1TB를 기준으로 하기 때문에 해당 설명은 생략한다.
$ sudo fdisk -l
Disk /dev/sda: 931.51 GiB, 1000204886016 bytes, 1953525168 sectors
Disk model: 2115
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x7c85155d
Device Boot Start End Sectors Size Id Type
/dev/sda1 2048 1953525167 1953523120 931.5G 83 Linux
$ sudo mkfs.ext4 /dev/sda1
mke2fs 1.47.0 (5-Feb-2023)
Creating filesystem with 244190646 4k blocks and 61054976 inodes
Filesystem UUID: c0263f2d-47f7-4518-b46b-505e1d3753fc
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
4096000, 7962624, 11239424, 20480000, 23887872, 71663616, 78675968,
102400000, 214990848
Allocating group tables: done
Writing inode tables: done
Creating journal (262144 blocks):
done
Writing superblocks and filesystem accounting information: done
- 새로 생긴 /dev/sda1 파티션에 대해 ext4 파일시스템을 새롭게 생성해준다.
# 마운트 대상 디렉터리 생성 및 파티션과 마운트
$ sudo mkdir /mnt/ssd01
$ sudo mount /dev/sda1 /mnt/ssd01
$ sudo chmod 777 /mnt/ssd01
# 마운트를 마치고 나면 870G의 파일시스템이 생겨난것을 확인할 수 있다.
$ df -h
Filesystem Size Used Avail Use% Mounted on
tmpfs 794M 4.1M 790M 1% /run
/dev/mmcblk0p2 117G 9.7G 103G 9% /
tmpfs 3.9G 0 3.9G 0% /dev/shm
tmpfs 5.0M 12K 5.0M 1% /run/lock
/dev/mmcblk0p1 505M 181M 324M 36% /boot/firmware
tmpfs 794M 116K 794M 1% /run/user/1002
/dev/sda1 916G 28K 870G 1% /mnt/ssd01
shm 64M 0 64M 0% /var/snap/microk8s/common/run/containerd/io.containerd.grpc.v1.cri/sandboxes/977a0f6226024dc3f8737d4b85bd8cb6cf036f1293a9d215007a9e805c838674/shm
shm 64M 0 64M 0% /var/snap/microk8s/common/run/containerd/io.containerd.grpc.v1.cri/sandboxes/cf09946d18ab8b45796c58ba1644527a6a289e1cb9bcf1a062e123fddb4fae25/shm
shm 64M 0 64M 0% /var/snap/microk8s/common/run/containerd/io.containerd.grpc.v1.cri/sandboxes/82077e023d24f6cea7c5cdf9c9f5678e26afccd17bdbd57159d1cb774f09678b/shm
shm 64M 0 64M 0% /var/snap/microk8s/common/run/containerd/io.containerd.grpc.v1.cri/sandboxes/8b2b00f44ce63cc83bb7e3600981a1942ec40f5baccd7adffe88a25e2614dbb8/shm
sudo vi /etc/fstab
# 아래 내용을 추가한다.
# [파일시스템 장치] [마운트 포인트] [파일시스템 종류] [옵션] [덤프] [파일체크옵션]
/dev/sda1 /mnt/ssd01 ext4 defaults 0 0
- 재부팅시에도 마운트를 유지하고 싶으면 /etc/fstab에 마운트 규칙을 추가한다.

- 주의할 점
- 일반적으로 라즈베리 파이에는 5A의 전류가 인가되면 안정적으로 구동될 수 있다. 하지만, SSD가 추가된 라즈베리 파이의 경우, /var/log/kern.log를 확인해보면 "Undervolatge detected!" 로그가 찍힐 수 있다. 시간이 조금 지나면 마운트가 해제된다.
- 이는 멀티 USB 포트를 통해 해당 라즈베리 파이에 전원을 인가하게 되면 전압이 분산되어 약해지는 이슈로, 별도의 전용 어댑터를 따로 물려주면 해결된다. (준비물에서 어댑터를 별도로 구비한 이유)
- 마운트 해제를 할 때에는 반드시 /etc/fstab에서 추가한 마운트 규칙을 제거부터 해야한다. 해당 규칙을 제거하지 않고 sudo umount /mnt/ssd01 커맨드만 수행하고 재부팅하였더니 라즈베리 파이가 emergency mode에 돌입하면서 네트워킹을 아예 할 수 없게 되었다.
NFS 세팅
# NFS 서버 모듈 설치 및 구동
$ sudo apt install nfs-kernel-server
$ sudo systemctl start nfs-kernel-server.service
$ sudo vi /etc/exports
# 아래 내용을 추가한다.
# <공유할 디렉터리> <공유 대상>(rw, root_squash, sync)
# ex) /mnt/ssd01 192.168.45.0/24(rw,root_squash,sync)
# -a (apply, 파일시스템 노출 규칙 적용)
$ sudo exportfs -a
$ sudo exportfs -v
$ sudo systemctl restart nfs-kernel-server
- NFS 서버를 구동할 라즈베리 파이에서 위처럼 커맨드를 실행하면 된다. (SSD가 마운트된 라즈베리 파이)
$ sudo apt install nfs-common
$ sudo mkdir /mnt/ssd01 && sudo mount 192.168.45.100:/mnt/ssd01 /mnt/ssd01
# 재부팅시 NFS 서버에서 노출한 파일시스템을 지속적으로 연결하고 싶은 경우
$ sudo vi /etc/fstab
# 아래 내용 추가
192.168.45.100:/mnt/ssd01 /mnt/ssd01 nfs default 0 0
- 나머지 라즈베리 파이에서 NFS 서버가 구동된 라즈베리 파이의 SSD 공유 디렉터리를 마운트한다.
StorageClass & NFS Static PV
$ helm repo add openebs https://openebs.github.io/openebs
$ helm repo update
$ helm pull openebs/openebs --version 4.2.0
# 다운 받아진 tar.gz 파일을 압축 해제한다.
$ tar -zxvf <tar.gz>
$ kubectl create ns openebs
# values.yaml : engines.replicated.mayastor.enabled = false로 수정
$ helm install openebs . -f values.yaml -n openebs
- 클라우드 환경에서 관리형 쿠버네티스 서비스(e.g. EKS, GKE, ..)를 사용하고 있다면, 기본적으로 CSI 드라이버를 설치하면 지원하는 스토리지 클래스가 있다.
- 하지만, 온프레미스 환경에서 직접 구축하는 경우, 쿠버네티스에서 자체적으로 제공하는 스토리지 클래스가 없기 때문에 Ceph, GlusterFS, OpenEBS와 같은 솔루션을 이용해야 한다.
annotations:
helm.sh/images: |
- name: bats
image: bats/bats:1.8.2
- name: etcd
image: docker.io/bitnami/etcd:3.5.6-debian-11-r10 # 이 부분 수정
- 혹시라도 openebs-etcd 파드가 구동되었을 때, 재부팅이 진행될 경우, 로그를 확인해보면 알겠지만 CPU 아키텍처의 문제이다. 라즈베리 파이는 ARM64이며, OpenEBS 4.2.0 버전의 ETCD에서 의존하는 도커 이미지는 etcd:3.5.6-debian-11-r10(https://hub.docker.com/r/bitnami/etcd/tags?name=3.5.6-debian-11-r10)로 AMD64밖에 지원하지 않는다. 고로, 멀티 플랫폼을 지원하는 최신 버전의 도커 이미지로 갈아끼우면 된다.
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-ssd01-pv-01
spec:
capacity:
storage: 870Gi
accessModes:
- ReadWriteMany
nfs:
server: <private-raspberry-pi-ip-addr> # NFS 서버가 구동된 라즈베리 파이 IP 주소
path: /mnt/ssd01 # 노출시킨 파일 시스템 경로
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-ssd01-pvc-01
spec:
storageClassName: ""
accessModes:
- ReadWriteMany
resources:
requests:
storage: 870Gi
volumeName: nfs-ssd01-pv-01
volumeMode: Filesystem
- 위와 같은 PV, PVC 매니페스트를 작성하여 정적으로 프로비저닝한다. 공유 볼륨의 경우, 지속적으로 Pod가 마운트, 언마운트가 되더라도 데이터를 유지할 목적이기 때문이다.
- Pod에서 해당 공유 볼륨에 마운트 할 때에는 PVC 이름을 마운트 대상으로 명시하면 된다.

긴글 읽어주셔서 감사합니다. 앞으로도 지속적으로 해당 게시글에서 구축하는 과정을 업데이트할 예정입니다.
- MetalLB Helm Chart 배포
- Istio + Kiali 서비스 메시 구성
- (랙 도착시 재조립...)
'[DevOps] - Kubernetes' 카테고리의 다른 글
| Harbor 레지스트리 구성하기 (feat. Helm Chart 연동) (0) | 2025.05.26 |
|---|---|
| Istio 톺아보기 #2 - 트래픽 관리 관련 CRD (0) | 2025.05.17 |
| Istio 톺아보기 #1 - 개요 (0) | 2025.05.17 |
| 쿠버네티스 환경의 서비스 디스커버리 (0) | 2024.05.26 |