3년간 먼지 쌓인 라즈베리 파이 4대로 Kubernetes 클러스터를 만들었다

TL;DR

  • 3년간 서랍 속에 있던 하드웨어 7대(RPi 4 × 4, NUC, ODYSSEY X86, NAS)로 K3s 클러스터를 구축했다
  • Claude Code와 함께 3.5개월, 123 커밋, 53 PR로 완성
  • Cloudflare Tunnel로 포트 포워딩 없이 외부 접근, Ansible로 모든 설정을 코드화
  • 현재 4개 서비스(Airflow, videokeeper, todo-hub, openclaw)가 프로덕션 운영 중

배경: 서랍 속 하드웨어, 꺼내다

라즈베리 파이를 살 때는 항상 거창한 계획이 있었습니다. NAS 만들기, 미디어 서버 구축, IoT 허브… 하지만 현실은 전원 한번 넣어보고 다시 서랍으로 직행이었죠.

전환점은 단순했습니다. 개인 프로젝트들이 늘어나면서 “이걸 어디서 돌리지?”라는 질문이 생긴 것입니다. 클라우드? 토이 프로젝트 몇 개에 월 수만 원은 아까웠거든요. 그때 서랍 속 하드웨어들이 떠올랐습니다.


장비 구성

장비스펙역할
Raspberry Pi 4B 8GB × 4ARM Cortex-A72 4코어, SSD 장착K3s worker 노드
PoE HAT × 4공식 PoE HAT이더넷으로 전력+데이터 동시 공급
NETGEAR GS305PP5포트 PoE+ 스위치worker 네트워크
SeeedStudio ODYSSEY X86Celeron J4105, 8GBK3s master (control plane)
Intel NUCCeleron J4025, 8GBPostgreSQL 전용 서버
Synology DS218play2베이 NASNFS 스토리지 + 백업

총 7대, 합산 RAM 56GB, CPU 22코어. 클라우드에서 이 스펙을 빌리면 월 10만 원은 나올 구성입니다.

구매 삽질기

PoE HAT은 깔끔한 선택이었지만, PoE+(802.3at)를 요구한다는 걸 뒤늦게 알았습니다. 기존 스위치로는 전력 공급이 안 돼서 PoE+ 스위치를 강제 추가 구매했죠. “전원 어댑터 안 사려고” 시작한 게 스위치를 사게 만든 셈입니다.

NAS도 아쉬운 선택이 있었습니다. DS218play는 Docker를 지원하지 않는 모델이거든요. 상위 모델을 샀더라면 NAS 위에 직접 PostgreSQL을 올릴 수 있었고, Intel NUC를 따로 쓸 필요가 없었을 겁니다. 결과적으로 DB 분리가 안정성 면에서 나은 선택이 됐을 수도 있지만, 장비 한 대를 줄일 수 있었다는 아쉬움은 남습니다.


기술 스택과 아키텍처

계층기술비고
자동화Ansible + justIaC, 30+ 자동화 명령어
오케스트레이션K3s경량 Kubernetes (단일 바이너리)
로드밸런서MetalLBL2 모드, 베어메탈 전용
인그레스TraefikHTTP/HTTPS 라우팅
인증서cert-manager로컬 CA (자동 발급/갱신)
스토리지Synology NAS + csi-driver-nfsNFS RWX
DBPostgreSQL 16전용 서버 분리
외부 접근Cloudflare Tunnel + WARPZero Trust
시크릿Ansible Vault암호화 git 추적
보안 스캔gitleaks글로벌 git hook
graph TB
    Internet[인터넷] --> CF[Cloudflare Tunnel + Access]

    CF --> Traefik

    subgraph K3s["K3s 클러스터"]
        Traefik[Traefik Ingress]
        MetalLB[MetalLB L2]
        Traefik --> Services
        subgraph Services["서비스"]
            Airflow
            videokeeper
            todo-hub
            openclaw
        end
        CM[cert-manager]
        NFS[csi-driver-nfs]
    end

    subgraph Hardware["하드웨어"]
        Master["ODYSSEY X86\nK3s master"]
        Workers["RPi 4B × 4\nK3s worker"]
        DB["Intel NUC\nPostgreSQL 16"]
        NAS["Synology NAS\nNFS + 백업"]
    end

    Services --> DB
    Services --> NAS
    DB --> NAS

    style CF fill:#f0f4ff,stroke:#0066cc,color:#333
    style Services fill:#efe,stroke:#3a3,color:#333
    style Hardware fill:#f5f5f7,stroke:#999,color:#333

왜 K3s인가

라즈베리 파이에서 풀 스펙 K8s는 너무 무겁습니다. K3s는 단일 바이너리로 동작하면서도 Kubernetes API를 완벽히 지원하죠. master 1대가 K3s server + embedded etcd를 담당하고, 라즈베리 파이 4대가 worker로 붙는 구조입니다.

K3s 내장 Traefik과 servicelb는 비활성화하고, Helm으로 직접 설치한 Traefik과 MetalLB를 씁니다. 버전 관리와 설정 자유도 때문이죠.

Cloudflare Tunnel — 포트 포워딩 없는 외부 접근

공유기 포트 포워딩, DDNS, SSL 인증서 관리 대신, cloudflared Pod 하나가 Cloudflare Edge로 아웃바운드 연결을 맺습니다. 인바운드 포트를 하나도 열지 않아도 되고, TLS도 Cloudflare가 처리합니다.

여기에 Cloudflare Access로 이메일 OTP 인증을 걸어서 허용된 사용자만 접근 가능합니다. WARP를 추가로 설정하면 외부에서 홈랩 사설망에 직접 접근하는 것도 가능하죠. 카페에서도 kubectl과 SSH를 쓸 수 있습니다.

DB는 클러스터 밖으로

PostgreSQL은 K3s 클러스터 안이 아니라 전용 NUC에서 독립 실행합니다. 클러스터를 날려도 데이터는 살아있어야 하니까요. Synology NAS가 일별/주별 자동 백업(pg_dump → NFS)을 받아서 이중 안전망을 구성합니다.

모든 설정을 코드로

“다시 구축할 수 있어야 한다”를 원칙으로 삼았습니다. 모든 설정은 Ansible 플레이북으로 코드화하고, justfile에 30개 이상의 명령어를 정의했습니다.

just infra-setup       # 전체 인프라를 처음부터 구축
just status            # 전체 호스트 상태 확인
just reboot-sequential # 순차 재부팅 (worker → master → postgres)
just briefing-detail   # 인프라 전체 브리핑

시크릿은 Ansible Vault로 암호화해서 git에 추적합니다. 평문 .env 파일 없이, 누가 언제 어떤 시크릿을 변경했는지 히스토리가 남죠.


Claude Code와 바이브 코딩

이 프로젝트 이름이 infra-copilot인 이유가 있습니다. Claude Code가 진짜 인프라 코파일럿이었거든요.

K3s, Ansible, MetalLB, Traefik, Cloudflare Tunnel, cert-manager… 하나하나 공부하고 나서 시작했으면 3년이 아니라 30년이 걸렸을 겁니다.

숫자로 보는 3개월

지표수치
전체 커밋123개
Pull Request53개 (51개 머지)
기간2026년 2월 ~ 5월 (3.5개월)
피크3월에 79 커밋 (전체의 64%)

3월 한 달에 전체 커밋의 64%가 집중됐습니다. 이 시기에 네트워킹, 보안, 외부 접근, 백업까지 핵심 인프라가 전부 올라갔죠.

인프라 코드와 AI의 궁합

인프라 코드는 바이브 코딩과 궁합이 좋습니다. Ansible 플레이북, Kubernetes 매니페스트, Helm values — 구조가 정형화되어 있고, 베스트 프랙티스가 명확하거든요.

트러블슈팅 루프도 빨랐습니다. videokeeper 배포 과정이 대표적이죠:

  • 이미지 pull 실패 → “arm64 빌드 안 했네” → 멀티아키텍처 빌드로 해결
  • NFS 마운트 실패 → 자동 프로비저닝 → 고정 경로 PV/PVC로 수정
  • healthz 타임아웃 → Django ALLOWED_HOSTS 설정 → probe에 httpHeaders 추가
  • OOMKilled → 메모리 상향 + nginx 분리

에러 로그를 붙여넣으면 원인 분석부터 플레이북 수정, 적용까지 한 세션 안에서 끝났습니다.

그래도 사람이 해야 하는 것

  • 아키텍처 결정 — MetalLB IP 풀 범위, Cloudflare Tunnel 라우팅 구조 같은 설계
  • 보안 판단 — CVE 패치 우선순위, 시크릿 관리 전략, Access 정책
  • 하드웨어 문제 — SSH 타임아웃, SSD 마운트 순서, NAS NFS 권한 같은 물리 환경 이슈

AI는 **“어떻게(How)“**를 빠르게 구현하는 데 탁월하지만, **“무엇을(What)“**과 **“왜(Why)“**는 여전히 사람의 몫입니다.


돌아가는 서비스들

서랍 속 장비들 위에서 4개의 서비스가 돌아가고 있습니다. 전부 *.jaypy.dev 도메인으로 외부에서 접근 가능하죠.

  • Airflow — 데이터 수집·처리 파이프라인 스케줄링
  • videokeeper — Django + Celery 기반 영상 다운로드/관리. tusd로 최대 5GB 업로드 지원
  • todo-hub — 여러 소스에서 TODO를 수집해 한 곳에서 관리
  • openclaw — 개인 비서 테스트용 서비스

운영: 만드는 것보다 유지하는 게 어렵다

4월부터는 구축보다 유지보수에 더 많은 시간을 쓰고 있습니다.

보안 패치는 끝이 없다

3개월 동안 적용한 보안 패치만 해도 상당합니다.

  • Traefik v3.6.11 → v3.6.14 (CVE 6건, 연속 패치)
  • Helm v3.20.1 → v3.20.2 (Chart.yaml 경로 순회 취약점)
  • PostgreSQL 16.13 → 16.14
  • Linux 커널 수차례 (CISA KEV 등재 CVE 포함)

Cloudflare Tunnel로 외부에 서비스를 노출하는 순간, 프로덕션과 같은 보안 책임이 따릅니다.

순차 재부팅의 교훈

초기에 모든 노드를 한번에 재부팅했더니 MetalLB speaker가 22회나 재시작하면서 네트워크가 불안정해졌습니다. 멤버십 프로토콜이 동시에 끊기면서 리더 선출이 꼬인 것이죠.

순차 재부팅(worker 한 대씩 → master → postgres)을 도입한 후 재시작 횟수가 1~2회로 안정됐습니다. 각 단계 사이에 K3s API 서버 ready 확인, pg_isready 체크 같은 안전 장치도 넣었죠.

라즈베리 파이는 재부팅이 느려서 SSH 타임아웃을 600초로 올려야 했습니다. 이런 건 직접 겪어봐야 알죠.


배운 것들

  1. “어디서부터”의 장벽이 가장 높다 — 3년간 서랍에 방치한 이유는 기술이 부족해서가 아니라, 어디서부터 시작해야 할지 몰라서였다. AI 코파일럿이 이 진입 장벽을 크게 낮춰준다.
  2. 인프라 코드는 AI와 궁합이 좋다 — 정형화된 구조, 명확한 베스트 프랙티스. “이런 걸 하고 싶다”에서 출발해서 필요한 만큼만 파고들 수 있다.
  3. 구축보다 운영이 본질이다 — 보안 패치, 모니터링, 백업 검증. 만드는 건 3개월이지만 유지하는 건 계속된다.
  4. 하드웨어 구매는 신중하게 — PoE HAT → PoE+ 스위치 강제 구매, Docker 미지원 NAS. 스펙 시트를 꼼꼼히 읽자.

마치며

123개 커밋, 53개 PR, 3.5개월.

서랍 속 먼지 쌓인 라즈베리 파이 4대가 이제 매일 데이터를 수집하고, 영상을 관리하고, TODO를 정리합니다.

집에 놀고 있는 라즈베리 파이가 있다면, 한번 꺼내보시길.


이 글에서 다루지 못한 개별 서비스(Airflow 파이프라인, videokeeper 아키텍처 등)의 구축기는 별도 글로 다룰 예정입니다.