
K3s, Ansible, Argo CD, vLLM 및 NVIDIA GPU를 활용한 오프라인 AI 플랫폼 구축
요약
인터넷 연결이 불가능한 폐쇄망 환경에서 K3s, vLLM, Argo CD 등을 활용해 독립적인 AI 플랫폼을 구축하는 방법을 다룹니다. 준비 단계와 설치 단계를 분리하여 외부 네트워크 요청 없이 모든 아티팩트를 로컬에서 설치하는 아키텍처를 제안합니다.
핵심 포인트
- 연결된 머신에서 모든 패키지를 다운로드하는 준비 단계와 격리된 머신에 설치하는 단계 분리
- K3s, vLLM, Argo CD, NVIDIA GPU를 활용한 풀스택 AI 인프라 구성
- Docker 컨테이너를 활용해 대상 아키텍처(Ubuntu 26.04 AMD64)에 맞는 아티팩트 확보
- 중단된 다운로드를 재개할 수 있는 환경 핑거프린트 기반의 스크립트 설계
서론
모든 노드가 패키지 저장소, 컨테이너 레지스트리(Container Registry), GitHub 및 모델 허브(Model Hub)에 접속할 수 있는 클라우드 환경에서 Kubernetes를 실행하는 것은 간단합니다. 하지만 대상 서버가 인터넷 연결이 전혀 되지 않는 환경이라면 작업은 훨씬 더 흥미로워집니다.
이번 개념 증명(Proof of Concept)을 위해, 저는 단일 Ubuntu 서버 상에 독립적인 AI 플랫폼을 구축하고자 했습니다. 최종 환경에는 다음 요소들이 포함되어야 했습니다:
- K3s
- NVIDIA GPU 지원
- 로컬에 저장된 Qwen 모델을 사용하는 vLLM
- Argo CD 및 로컬 GitOps 저장소
- FastAPI 및 LangChain 챗봇
- Prometheus, Grafana, Loki, Tempo 및 OpenTelemetry
- 로컬 운영을 위한 k9s
대상 머신은 NVIDIA A10G GPU가 장착된 Ubuntu 26.04 AMD64 서버입니다. 검증을 위해 EC2 g5.2xlarge 인스턴스를 사용했지만, AWS는 테스트 도구일 뿐입니다. 설치 프로그램 자체는 인프라를 프로비저닝하지 않으며 AWS에 의존하지도 않습니다.
중요한 요구 사항은 간단했습니다: 번들을 서버로 복사한 후에는 설치 과정에서 어떠한 외부 네트워크 요청도 발생해서는 안 된다는 것이었습니다.
두 가지 환경 설계
이 솔루션은 준비(Preparation) 단계와 설치(Installation) 단계를 분리합니다. 연결된 머신(Connected machine)이 모든 것을 다운로드하고 패키징하며, 격리된 머신(Isolated machine)은 로컬 파일만 소비합니다.
이 경계 설정이 주요 아키텍처 결정 사항입니다. 모든 구성 요소가 연결되지 않은 네트워크를 견디는 방법을 가르치는 대신, 네트워크에 의존하는 모든 작업을 통제된 준비 단계로 이동시켰습니다.
페이로드(Payload) 준비
저장소는 하나의 통합 명령어를 제공합니다:
cd offline-bundle
./scripts/download-all-artifacts.sh
이 스크립트는 Docker와 최소 50GB의 여유 공간이 필요합니다. macOS 및 non-AMD64 시스템에서는 다운로드된 패키지가 대상 아키텍처와 일치하도록 Ubuntu 26.04 AMD64 컨테이너를 사용합니다.
각 아티팩트 (artifact) 그룹은 전용 스크립트에 의해 처리됩니다. 완료된 단계에는 콘텐츠 및 환경 핑거프린트 (fingerprint)가 포함되어 있어, 다운로드가 중단되더라도 모든 것을 다시 빌드하지 않고 재개할 수 있습니다. 완전히 새로운 페이로드 (payload)가 필요한 경우에는 --clean 옵션을 사용할 수 있습니다.
생성된 payload/ 디렉토리에는 바이너리 (binaries), .deb 패키지, OCI 이미지 아카이브 (image archives), Kubernetes 매니페스트 (manifests), 도구 및 모델 가중치 (model weights)가 포함됩니다. 이 디렉토리는 로컬에서 생성되며, 크기가 크고 재현 가능하기 때문에 의도적으로 Git에서 제외됩니다.
모델과 vLLM 이미지는 번들 (bundle)에서 가장 큰 부분을 차지합니다. 이 설정에서 vLLM 이미지는 압축 시 약 8 GB이며, Qwen2.5 7B 모델은 약 14~15 GB가 필요합니다. 디스크 계획은 선택 사항이 아닙니다. 대상 설치 프로그램은 시작하기 전에 최소 80 GB의 여유 공간이 있는지 확인합니다.
격리된 서버에서의 단일 명령
페이로드를 준비한 후, offline-bundle/ 디렉토리 전체를 대상 서버로 복사하고 다음을 실행합니다:
cd offline-bundle
./install.sh
설치 프로그램은 sudo 권한을 상승시키고, Ubuntu 버전과 CPU 아키텍처를 확인하며, 여유 공간을 체크하고, 모든 아티팩트의 SHA256 체크섬 (checksum)을 검증합니다. 그런 다음 로컬 .deb 파일로부터 Ansible을 설치하고 localhost 플레이북 (playbook)을 실행합니다.
플레이북은 ansible_connection=local을 사용하며, SSH는 설치 설계의 일부가 아닙니다. 역할 (roles)은 의도된 순서에 따라 실행됩니다:
- 바이너리 및 에어갭 (air-gap) 이미지 아카이브로부터 K3s 설치.
- NVIDIA 드라이버 및 컨테이너 툴킷 (container toolkit)을 설치한 후, Kubernetes에 GPU를 노출.
- 로컬 레지스트리 (registry) 및 Git 미러 (mirror)를 시작한 후, Argo CD 설치.
- k9s 설치.
- 관측성 스택 (observability stack) 배포.
- 모델을 로드하고 vLLM 시작.
설치 프로그램과 역할 (roles)은 멱등성 (idempotent)을 가집니다. 검증에 실패하더라도 문제를 수정하고 동일한 명령을 다시 실행할 수 있습니다.
서버 내부에서 실행되는 것
그 결과, 한 대의 머신 위에 완전한 플랫폼이 구축됩니다. Git 데몬 (daemon)과 같은 일부 지원 서비스는 호스트 (host)에서 실행됩니다. 애플리케이션 및 플랫폼 워크로드 (workloads)는 K3s 내부에서 실행됩니다.
K3s는 표준 에어갭 (air-gap) 아카이브를 직접 가져옵니다. 추가 이미지들은 containerd로 임포트되어 localhost:5000에서 대기 중인 레지스트리로 푸시(push)됩니다. 워크로드는 로컬 이미지 참조를 사용하며, vLLM 배포는 실수로 인한 풀(pull)을 방지하기 위한 추가적인 보호 장치로 imagePullPolicy: Never를 사용합니다.
Qwen2.5-7B-Instruct 스냅샷은 /opt/models로 복사되어 vLLM 포드 (pod)에 마운트됩니다. vLLM은 8000번 포트에서 OpenAI 호환 엔드포인트 (endpoint)를 노출하며, NVIDIA 디바이스 플러그인 (device plugin)에 의해 광고되는 단일 nvidia.com/gpu 리소스를 할당받습니다.
GitHub 없는 GitOps
Argo CD는 보통 외부 Git 제공업체로부터 원하는 상태 (desired state)를 가져옵니다. 격리된 네트워크에서는 이것이 불가능하므로, 이 번들(bundle)은 대상 서버에 베어 리포지토리 (bare repositories)를 생성하고 읽기 전용 Git 데몬으로 이를 서비스합니다.
클러스터 내부의 Service 및 Endpoints 객체는 호스트 데몬을 git://git-mirror.gitops.svc.cluster.local로 노출합니다. Argo CD는 이 주소로부터 app-of-apps 리포지토리를 읽어 에이전트 애플리케이션을 탐색하고, 해당 Helm 차트를 배포합니다.
이를 통해 플랫폼이 GitHub에 접속할 수 없는 상황에서도 GitOps 조정 (reconciliation) 모델을 유지할 수 있습니다. 오프라인 번들 (bundle)은 전달 메커니즘 역할을 하며, 로컬 Git 미러 (mirror)가 런타임의 신뢰할 수 있는 원천 (source of truth)이 됩니다.
로컬 AI 애플리케이션
이 스택이 단순한 포드 (pod)들의 집합이 아닌 하나의 플랫폼으로서 작동하는지 확인하기 위해, 작은 챗봇을 포함했습니다. 이 챗봇은 FastAPI, LangChain, 그리고 ChatOpenAI를 사용하지만, 클라이언트를 공개 API 대신 내부 vLLM 서비스로 연결합니다.
애플리케이션은 시스템 프롬프트 (system prompt)와 대화 기록 (conversation history)을 지원합니다. 또한 모든 모델 호출 주위에 OpenTelemetry 스팬 (span)을 추가합니다. 접속 가능한 Langfuse 인스턴스와 자격 증명이 제공될 경우, 선택적으로 Langfuse 통합을 활성화할 수 있습니다.
또한 OpenCode를 사용하여 OpenAI 호환 엔드포인트 (endpoint)를 테스트했습니다. 기존 OpenAI 클라이언트를 로컬 서비스로 연결하는 것은 vLLM의 유용한 특성 중 하나입니다. 즉, 애플리케이션이 별도의 커스텀 추론 프로토콜 (inference protocol)을 가질 필요가 없습니다.
모델 및 GPU 관측
오프라인 플랫폼이라 할지라도 정상적인 운영 가시성 (visibility)이 필요합니다. 번들에는 의도적으로 압축되었지만 완전한 스택이 포함되어 있습니다:
- 메트릭 (metrics)을 위한 Prometheus
- 대시보드 (dashboards)를 위한 Grafana
- 로그 (logs)를 위한 Loki 및 Promtail
- 트레이스 (traces)를 위한 Tempo
- OTLP 수집 (ingestion)을 위한 OpenTelemetry Collector
- Kubernetes 및 호스트 메트릭을 위한 kube-state-metrics 및 node-exporter
- GPU 메트릭을 위한 NVIDIA DCGM exporter
Prometheus는 실행 중인 요청 (running requests)과 같은 vLLM 메트릭과 DCGM exporter로부터의 GPU 사용률 (utilization)을 스크랩 (scrape) 합니다. Grafana는 Prometheus, Loki, Tempo를 데이터 소스 (datasources)로 제공하며, 번들된 vLLM/GPU 대시보드를 자동으로 로드합니다.
다음 Mermaid 다이어그램은 하나의 채팅 요청이 어떻게 세 가지 텔레메트리 (telemetry) 신호가 되는지 보여줍니다:
에어 갭 (air gap) 환경에서는 검증 (validation)이 더 중요합니다
연결된 환경에서는 누락된 이미지나 패키지를 나중에 다운로드할 수 있습니다. 하지만 격리된 환경 (isolated environment)에서는 누락된 전이적 의존성 (transitive dependency) 하나가 전체 설치를 중단시킬 수 있습니다. 그러한 이유로, 검증은 최종 체크리스트라기보다 설계의 일부입니다.
전송 전, 준비 스크립트가 페이로드 (payload) 구조와 체크섬 (checksums)을 확인합니다. 설치 중에는 Ansible 역할 (roles)이 다음 단계로 넘어가기 전에 각 레이어 (layer)를 검증합니다. 수락 확인 (acceptance checks) 항목에는 다음이 포함됩니다:
- K3s 노드가
Ready상태에 도달합니다. - 호스트가
nvidia-smi를 통해 NVIDIA A10G를 보고합니다. - Kubernetes가
nvidia.com/gpu: 1을 할당 가능 (allocatable) 자원으로 보고합니다. - vLLM 포드 (pod)가 외부 이미지 풀 (image pull) 없이 시작됩니다.
- 모델이
/opt/models/Qwen2.5-7B-Instruct에서 로드됩니다. /v1/models및 채팅 완성 (chat completion) API가 응답합니다.- Argo CD 애플리케이션이 로컬 Git 미러 (mirror)로부터 동기화됩니다.
- Prometheus 타겟 (targets)에 접근 가능합니다.
- Grafana에 프로비저닝된 데이터 소스 (datasources)와 대시보드 (dashboard)가 포함되어 있습니다.
- Loki가 로그를 반환하고 Tempo가 챗봇 트레이스 (trace)를 반환합니다.
저장소(repository)의 offline-bundle/VALIDATION.md에는 정확한 명령어가 포함되어 있어, 단순히 육안 검사에 의존하는 대신 테스트 절차를 재현 가능하게(reproducible) 만듭니다.
교훈 (Lessons learned)
오프라인 Kubernetes 설치에서 가장 어려운 부분은 K3s 자체가 아닙니다. 그 주변을 둘러싼 완전한 의존성 그래프 (dependency graph)입니다. 즉, OS 패키지, 컨테이너 이미지 (container images), GPU 커널 모듈 (kernel modules), 도구 (tools), 모델 파일, 매니페스트 (manifests), 그리고 일반적으로 인터넷 접속을 가정하는 런타임 서비스 (runtime services)들입니다.
몇 가지 선택이 설정을 관리 가능한 수준으로 만들어 주었습니다:
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기







