AI Homelab 인프라 설계 및 구축: 클러스터 구성과 관리 접근 구조

집에서 만드는 AI 플랫폼 ② — 기존 장비와 경험을 바탕으로 구성한 인프라

이전 글에서는 집에 있는 장비와 클라우드 GPU를 활용해 어떤 프로젝트를 해보려는지 설명했다. 이번에는 그 기반이 되는 클러스터와 관리 환경을 어떤 구조로 구성했는지 정리하려고 한다.

먼저 가지고 있는 장비에 클러스터를 설치하고, 집 밖에서도 관리할 수 있는 접근 경로가 필요했다. 여기에 다른 사람이 클러스터에 접근해야 한다면 인증 정보를 어떻게 공유할 것인지, 앞으로 워크로드에서 사용할 시크릿은 어디에서 관리할 것인지도 함께 고민했다.

이번 글에서는 각 구성 요소를 선택한 이유와 실제로 어떻게 배치하고 연결했는지를 정리하려고 한다. 구축 과정에서 발생한 문제와 원인을 찾아 해결한 과정은 다음 글에서 따로 다룬다.

기존 장비로 클러스터 구성하기

클러스터를 구성할 장비는 이미 가지고 있던 minipc와 라즈베리파이다. 두 장비 모두 여유 있는 서버 사양은 아니다 보니, 일반적인 Kubernetes 구성을 그대로 올리면 클러스터 자체를 유지하는 데 드는 자원도 부담이 될 것 같았다.

그래서 Kubernetes 배포판 중 상대적으로 가벼운 K3s를 선택했다. 일반적인 Kubernetes 구성과 자원 사용량을 직접 비교한 결과라기보다는, 우선 가진 장비에서 클러스터를 운영하기 위한 선택이었다.

노드의 역할은 아래와 같이 나눴다.

장비 역할 CPU 아키텍처
minipc K3s control-plane 및 관리 서비스 실행 amd64
Raspberry Pi K3s worker arm64

minipc에서 클러스터를 관리하고 라즈베리파이를 worker로 연결했다. 이후 클라우드 GPU 노드도 worker로 연결하는 방향으로 확장하려고 한다. 두 장비의 CPU 아키텍처가 다르므로, 워크로드를 배포할 때 사용하는 이미지가 해당 아키텍처를 지원하는지도 확인해야 한다.

전체 인프라 구조

현재 구성의 중심은 K3s, Tailscale, Dex, Vault다. 각각 워크로드 실행 기반, 관리 접근 경로, 사람의 인증, 시크릿 관리를 담당한다.

관리 노트북
├─ Windows
│  ├─ Tailscale
│  └─ 브라우저
└─ WSL
   ├─ SSH
   └─ kubectl / OIDC 로그인 도구
          │
          │ Tailscale 기반 관리 접근
          ▼
집의 minipc
├─ K3s control-plane
├─ Tailscale / subnet router / Serve
├─ Dex   ─┐
└─ Vault ─┴─ Docker Compose로 실행
          │
          │ 기존 LAN 통신
          ▼
Raspberry Pi
└─ K3s worker

향후 확장
└─ 클라우드 GPU worker 연결

모든 요청이 위에서 아래로 같은 경로를 거친다는 의미는 아니다. 클라우드 GPU worker는 아직 현재 구성에 포함된 것이 아니라 이후 연결할 대상이다.

Dex와 Vault는 minipc에서 Docker Compose로 실행하고 있다. K3s 내부의 워크로드와는 별도로 관리하지만, 같은 물리 장비를 사용하므로 minipc 장애의 영향까지 분리되는 구조는 아니다.

아래에서는 각 도구를 선택한 이유와 실제로 연결한 구성을 나눠서 설명하려고 한다.

집 밖에서 클러스터에 접근하기

클러스터를 집에 두더라도 관리를 항상 집에서만 할 수는 없다. 외부에서 SSH나 Kubernetes API에 접근할 경로가 필요했고, 이를 위해 Tailscale을 사용했다.

직접 WireGuard를 구성하는 방법도 있지만, 피어와 연결 설정을 직접 관리하는 부분을 줄이고 필요한 서비스에 접근할 환경을 구축하길 원했다. Tailscale을 사용해 관리 노트북과 minipc를 연결하고, 접근 정책으로 필요한 대상과 포트를 허용하는 방향으로 구성했다.

관리 접근을 추가하되 기존 LAN은 유지하기

VPN을 도입하면서 기존 클러스터 노드의 주소까지 전부 바꾸지는 않았다. minipc와 라즈베리파이 사이의 통신은 기존 LAN을 유지하고, 외부 관리 접근에 Tailscale을 사용하는 방식이다.

관리 노트북에서는 기존 SSH 설정과 Kubernetes API 주소를 유지하기 위해 minipc의 LAN IP 하나를 대상으로 /32 subnet route를 사용했다. 집의 LAN 전체에 대한 경로를 제공하는 대신, 우선 필요한 호스트 하나에만 접근하도록 범위를 잡았다.

관리 노트북의 SSH / kubectl
    ↓
Tailscale 연결과 subnet route
    ↓
minipc의 기존 LAN 주소
    ↓
SSH / Kubernetes API

Windows의 VPN과 WSL의 관리 도구 사용하기

노트북에서는 Windows에 Tailscale을 설치하고, SSH와 kubectl은 WSL에서 사용한다. WSL에 Tailscale을 중복 설치하기보다 Windows의 VPN 경로를 이용하는 구성으로 시작했다.

SSH도 기존 OpenSSH와 SSH 키를 그대로 사용한다. Tailscale 위에서 SSH에 접속하는 것과 Tailscale SSH 기능을 사용하는 것은 다르며, 현재 구성에서는 후자를 사용하지 않는다.

또한 관리 경로를 VPN 중심으로 구성했다는 것과 기존 LAN 접근이나 모든 외부 노출을 차단했다는 것은 다르다. 이 글에서는 접근 구조를 설명하는 것이며, 모든 경로의 차단 검증까지 완료했다는 의미는 아니다.

kubeconfig를 어떻게 공유할 것인가

인증 구조를 고민하게 된 계기는 다른 사람이 이 클러스터에 접근해야 하는 상황을 생각해보면서였다. 지금 당장 공유할 일이 있었다기보다는, 그런 상황이 생긴다면 어떤 방식으로 접근 정보를 전달하는 것이 좋을지 고민했다.

이전에는 Kubernetes 접근이 필요한 경우 kubeconfig 파일 자체를 전달하는 방식을 사용해왔다. 그런데 kubeconfig는 클러스터 주소만 담고 있는 파일이 아니다. 사용자 인증 정보도 포함할 수 있고, 내가 사용하던 관리자 kubeconfig의 인증 정보는 관리자 권한을 가졌다.

이 파일을 그대로 전달하면 접속 방법만 공유하는 것이 아니라 같은 관리자 신원과 권한까지 공유하게 된다. 사용자별로 접근 범위를 다르게 두고 싶어도, 같은 인증 정보를 사용하면 개인 신원을 기준으로 권한을 구분하기 어렵다. 이 부분이 위험할 수 있다고 생각했다.

그래서 관리자 자격 증명을 전달하는 대신, 각자가 자신의 계정으로 로그인하고 해당 신원에 필요한 권한을 부여하는 구조를 선택했다.

GitHub 계정과 Dex OIDC 연결하기

GitHub 계정을 외부 인증 수단으로 사용하고, Dex를 Kubernetes가 신뢰하는 OIDC Provider로 구성했다.

사용자가 GitHub 계정으로 로그인
    ↓
Dex가 GitHub의 신원을 확인하고 OIDC 토큰 발급
    ↓
Kubernetes가 토큰과 신원을 검증
    ↓
사용자 또는 그룹에 매핑된 RBAC 적용

이 그림은 인증과 권한 판단의 관계를 단순화한 것이다. 로그인 이후의 모든 Kubernetes API 요청이 GitHub나 Dex를 프록시처럼 거친다는 의미는 아니다.

이 환경에서 필요한 것은 별도의 회원 관리 시스템보다 기존 계정을 Kubernetes 인증에 연결하는 기능이었다. Dex는 외부 신원을 OIDC로 연결하는 역할을 맡고, Kubernetes 내부에서 어떤 작업을 허용할지는 RBAC로 관리하도록 나눴다.

이 구조에서도 kubeconfig는 필요하다. 다만 다른 사람에게 전달할 설정에는 관리자 개인키를 넣는 대신, 클러스터 접속 정보와 자신의 계정으로 로그인하는 방법을 담을 수 있다. 로그인 과정에서 발급되는 토큰이나 로컬 로그인 캐시는 여전히 보호해야 하는 인증 정보다.

실제 배포와 연결 설정

Dex는 minipc에서 Docker Compose로 실행하고, Tailscale Serve를 통해 HTTPS로 접근하도록 구성했다. GitHub OAuth App의 callback은 Dex로 연결하고, K3s에는 Dex의 issuer와 client ID, 사용할 신원 claim을 설정했다. WSL의 kubectl에서는 OIDC 로그인 도구를 통해 개인 계정으로 인증하도록 구성했다.

최종적으로 Dex의 HTTPS endpoint는 8443 포트를 사용하고 있다. 이 포트를 사용하게 된 과정은 다음 트러블슈팅 글에서 다룰 예정이다.

인증과 권한은 따로 관리하기

설계 방향은 사용자 계정 또는 계정이 속한 그룹에 RBAC를 매핑하는 것이다. 같은 클러스터에 로그인하더라도 누구에게 어떤 작업을 허용할지는 별도로 정할 수 있게 하려는 목적이다.

현재는 본인의 OIDC 신원에 cluster-admin을 부여해 관리하고 있다. 사용자별로 제한된 역할을 모두 구성하거나 그룹 기반 권한 관리를 완료한 상태는 아니다. GitHub 조직이나 팀 정보를 그룹으로 사용하려면 Dex 설정과 실제 전달되는 claim을 확인하고, 그 값에 맞춰 RBAC를 연결해야 한다.

기존 관리자 자격 증명은 일상 로그인과 구분해 비상 접근 수단으로 남겨두는 방향이다. 개인 로그인을 도입했다는 것이 곧 관리자 권한을 줄였다는 의미는 아니고, 우선 공유된 인증 정보 대신 개인 신원을 기준으로 권한을 관리할 기반을 만든 것이다.

정리하면 각 계층의 역할은 아래와 같다.

계층 확인하는 것 구성 요소
네트워크 접근 서비스에 도달할 수 있는가? Tailscale 접근 정책
인증 요청자가 누구인가? GitHub와 Dex OIDC
인가 해당 신원으로 어떤 작업을 할 수 있는가? Kubernetes RBAC

시크릿을 사람에게 전달받지 않고 관리하기

Vault를 도입한 이유는 앞으로 워크로드를 서빙할 때 필요한 시크릿들을 중앙에서 관리하는 것이 좋겠다고 생각했기 때문이다. 여기에는 이전 프로젝트에서 느꼈던 불편함도 있었다.

이전에는 시크릿을 로컬의 .env 파일 형태로 관리하는 경우가 많았다. 필요한 값이 어느 환경이나 파일에 있는지 찾기 어려웠고, 다른 사람이 가지고 있는 값이 필요하면 그 사람에게 요청해서 전달받아야 했다.

작업을 진행하기 위해 특정 사람이 시크릿을 가지고 있는지, 지금 전달해줄 수 있는지에 의존하는 점이 불편했다. 이번에는 필요한 시크릿을 찾을 기준 위치를 만들고, 사람 간에 직접 값을 전달하는 대신 권한이 있는 주체가 접근하는 방식으로 바꿔보고 싶었다.

.env 자체가 잘못된 방식이라고 생각하는 것은 아니다. 다만 로컬 파일과 사람 간 전달을 중심으로 관리했던 방식에서 느낀 불편함을 줄이기 위해 Vault를 선택했다.

실제 배포 구성

Vault는 minipc에서 Docker Compose로 실행하며, 단일 노드 Raft 저장소와 TLS를 사용하는 구성으로 구축했다. 초기화와 unseal을 진행한 뒤 Dex OIDC 로그인을 연결했다.

최종적으로 컨테이너는 host network를 사용하고, API listener는 minipc의 Tailscale IP에 바인딩했다. 네트워크 구성을 이렇게 변경하게 된 이유는 다음 글에서 다룰 예정이다.

중앙 저장과 접근 권한 관리

Vault는 시크릿을 저장하는 위치이면서, 누가 어떤 시크릿을 사용할 수 있는지 정책을 적용하는 역할을 맡는다.

사람의 로그인에는 Kubernetes와 마찬가지로 Dex 기반 OIDC를 연결하고, 실제 Vault 내부의 접근 권한은 Vault policy로 관리한다. Kubernetes에서 관리자 권한을 가지고 있다고 Vault에서도 자동으로 관리자 권한을 얻는 구조는 아니다.

사람
└─ Dex OIDC로 로그인 → Vault policy에 따라 접근

워크로드 — 후속 연결 대상
└─ 워크로드 신원으로 인증 → 허용된 시크릿 사용

향후에는 워크로드가 필요한 시크릿을 사용하는 방식도 연결하려고 한다. 이 부분은 Vault를 설치하고 사람이 로그인하는 것과 별개의 작업이다. 워크로드의 인증 방법과 전달 방식은 실제 연동 단계에서 설계하고 검증할 예정이다.

운영 설정은 Git으로 관리하되 토큰, 개인키, OAuth client secret 같은 실제 비밀은 저장소에 넣지 않는다. 그렇다고 Vault를 도입하는 순간 모든 시크릿이 자동으로 중앙화되는 것은 아니다. 초기 구동에 필요한 인증 정보나 Vault 자체의 복구 자료는 별도로 관리해야 한다.

현재까지 구축한 범위와 남은 부분

현재까지 구성하고 동작을 확인한 부분과 이후 연결할 부분은 아래와 같다.

영역 현재까지 확인한 내용 후속 영역
K3s minipc와 Raspberry Pi로 클러스터 구성 클라우드 worker 연결
관리 접근 Tailscale 경로를 통한 SSH·API 접근 추가 접근 제한과 검증
사람 인증 GitHub·Dex OIDC 로그인 및 개인 RBAC 적용 다른 사용자·그룹별 권한 구성
시크릿 관리 Vault 초기화·unseal 및 OIDC 로그인 워크로드 인증과 시크릿 전달

이번 구성에서는 HA나 재해 복구까지 고려한 클러스터를 만드는 것을 목표로 두지는 않았다. 우선 현재 장비로 필요한 환경을 구성하고 운영해보는 데 집중하려고 한다.

현재는 minipc에 control-plane과 관리 서비스들이 함께 있기 때문에, 해당 장비에 문제가 생기면 여러 기능이 영향을 받는다. 두 개의 물리 노드를 사용한다고 해서 고가용성 구성이 되는 것은 아니다.

이후 관련 학습이나 장애 실험이 필요해지거나 운영 중 문제를 만나면, 그때 구조를 확장하고 개선해나갈 생각이다. 처음부터 모든 운영 요구사항을 충족하는 구조를 만들기보다는, 필요한 환경을 실제로 사용하면서 고려할 범위를 넓혀가려고 한다.

다음 글에서는 OIDC 로그인 과정에서 Kubernetes 인증이 실패했던 문제와, 호스트에서는 접근되지만 Vault 컨테이너에서는 연결되지 않았던 문제를 정리하려고 한다. 각각 어떤 경로로 원인을 확인했고, 왜 해당 해결 방법을 선택했는지 다룰 예정이다.

Share: X (Twitter) Facebook LinkedIn