클라우드 LLM API 청구서가 무서워지기 전에 — Ollama + Open WebUI로 팀 전용 온프레미스 AI 서버 운영하기
개발팀에서 LLM을 도입하면 초기엔 괜찮아 보입니다. API 키 하나 발급받아서 호출하면 되니까요. 그런데 팀 규모가 커지고 사용량이 늘면, 크게 두 가지 문제가 동시에 떠오릅니다.
첫째는 비용입니다. 월 수백만 원씩 빠져나가는 API 청구서가 예산 회의 단골 의제가 됩니다. 둘째는 데이터입니다. 고객 정보나 내부 문서가 포함된 쿼리가 외부 클라우드로 전송되고 있다는 사실이 법무팀과 보안팀의 레이더에 걸리기 시작합니다. EU AI Act가 2026년 8월 전면 시행되면서 GDPR 준수 이슈가 더해지면 "API 대신 직접 돌리는 게 낫지 않을까"라는 논의가 자연스럽게 나옵니다.
여기에 하나 더 있습니다. 팀원들이 이미 개인적으로 ChatGPT나 클라우드 API를 업무에 활용하는 상황, 이른바 Shadow AI입니다. 데이터 흐름을 통제할 수 없는 Shadow AI를 방치하는 것보다, 사내 LLM 서버로 이를 공식 인프라로 흡수하는 편이 보안·컴플라이언스 양쪽에서 훨씬 유리합니다.
이 글은 Ollama와 Open WebUI를 조합해 팀 공용 AI 인프라를 구성하는 경험을 담고 있습니다. "이 명령어 치면 됩니다" 수준을 넘어서 모델 관리 전략, 역할 기반 접근 제어(RBAC) 설계, GPU 사용량 모니터링 파이프라인 구성까지, 프로덕션에서 실제로 부딪히는 지점들을 다룹니다. Ollama API 포트를 인터넷에 통째로 노출하거나 VRAM이 꽉 차서 서비스가 조용히 망가지는 상황을 반복하지 않도록 주의할 지점들도 함께 짚습니다.
대상은 백엔드·DevOps 개발자입니다. Docker와 Linux 기본 명령어에 익숙하고, GPU 서버 하나를 손에 쥔 상태에서 읽으시면 가장 잘 맞습니다.
핵심 개념
아키텍처 전체 그림 먼저
세부 설정보다 전체 구조를 먼저 보는 편이 훨씬 이해가 빠릅니다.
사내 LLM 서버는 크게 네 레이어로 구성됩니다.
- Ollama — 모델 서빙 백엔드.
localhost:11434에 OpenAI 호환 REST API를 노출합니다. - Open WebUI — 인증·사용자 관리·채팅 UI 레이어. Ollama 위에 얹히는 형태입니다.
- Nginx — 리버스 프록시. TLS 종료와 외부 트래픽 중계를 담당합니다.
- Prometheus + Grafana — GPU 사용량과 추론 성능을 모니터링합니다.
Ollama API는 외부에 직접 노출하지 않고, 반드시 Open WebUI 인증 레이어를 통해서만 접근하는 구조입니다. 이 원칙이 보안 설계의 핵심입니다.
Ollama: 명령 하나로 모델을 띄우는 런타임
Ollama의 가장 큰 매력은 진입 장벽이 낮다는 것입니다. 모델 다운로드, 양자화, GPU/CPU 자원 할당을 자동으로 처리하기 때문에 아래 한 줄로 Llama 3.3 70B를 로컬에서 바로 실행할 수 있습니다.
ollama run llama3.3:70bOpenAI 호환 API를 그대로 노출하기 때문에 기존 코드에서 엔드포인트 URL만 바꾸면 됩니다. 단, Ollama API에 직접 연결하는 방식은 로컬 개발 환경 전용입니다.
# 로컬 개발 환경 전용 — 팀 서버에서는 Open WebUI 엔드포인트를 사용하세요
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:11434/v1",
api_key="ollama",
)
response = client.chat.completions.create(
model="llama3.3:70b",
messages=[{"role": "user", "content": "코드 리뷰해줘"}],
)팀 서버 환경에서는 Open WebUI가 발급한 API 키와 함께 Open WebUI의 OpenAI 호환 엔드포인트를 사용합니다. API 키는 Open WebUI 관리자 패널 → Settings → Account에서 발급받을 수 있습니다.
# 팀 서버 환경 — Open WebUI API 키로 인증
from openai import OpenAI
client = OpenAI(
base_url="https://llm.yourcompany.internal/api",
api_key="sk-...", # Open WebUI 관리자 패널에서 발급
)
response = client.chat.completions.create(
model="llama3.3:70b",
messages=[{"role": "user", "content": "코드 리뷰해줘"}],
)주의할 점: Ollama는 기본적으로 약 4개의 병렬 요청을 처리합니다. 팀 규모가 커지면 vLLM이나 TGI(Text Generation Inference)로의 전환을 고려할 수 있습니다.
Open WebUI: 인증과 UI를 한 번에
Open WebUI는 ChatGPT와 비슷한 웹 인터페이스를 제공하면서, 팀에서 실제로 필요한 기능들을 내장합니다.
| 기능 | 설명 |
|---|---|
| 멀티유저 관리 | 가입 승인, 역할 부여, 사용자 비활성화 |
| RBAC | Admin / User / Pending 역할 + 세부 권한 |
| RAG 내장 | Knowledge Base 벡터 인덱싱, 문서 기반 응답 |
| SSO | OIDC/OAuth2, SCIM 2.0 Okta·Azure AD 연동 |
| 모델 공유 | 팀 내 Modelfile·시스템 프롬프트 공유 |
| 완전 오프라인 | 인터넷 연결 없이 동작, 에어갭 환경 지원 |
실전 적용
1단계: Docker Compose로 스택 올리기
GPU 서버에 NVIDIA Container Toolkit이 설치돼 있다는 전제하에 아래 구성을 사용합니다. 시작 전에 .env 파일을 먼저 만들어야 합니다.
echo "WEBUI_SECRET_KEY=$(openssl rand -hex 32)" > .env# docker-compose.yml
services:
ollama:
image: ollama/ollama:0.5.13 # 최신 릴리스 태그로 고정 — hub.docker.com에서 확인
container_name: ollama
restart: unless-stopped
ports:
- "127.0.0.1:11434:11434" # 로컬 바인딩만 — 외부 노출 금지
volumes:
- ollama_data:/root/.ollama
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
open-webui:
image: ghcr.io/open-webui/open-webui:v0.5.16 # 최신 릴리스 태그로 고정 — GitHub Releases에서 확인
container_name: open-webui
restart: unless-stopped
ports:
- "127.0.0.1:3000:8080"
environment:
- OLLAMA_BASE_URL=http://ollama:11434
- WEBUI_SECRET_KEY=${WEBUI_SECRET_KEY}
- WEBUI_AUTH=true
- DEFAULT_USER_ROLE=pending # 신규 가입자 기본값을 Pending으로 설정
volumes:
- open_webui_data:/app/backend/data
depends_on:
- ollama
volumes:
ollama_data:
open_webui_data:127.0.0.1:11434로 바인딩한 부분이 핵심입니다. 0.0.0.0:11434로 열면 인증 없이 외부에서 모델을 마음대로 호출할 수 있습니다. 공식 Ollama 이미지(ollama/ollama)는 NVIDIA Container Toolkit이 설치된 환경에서 자동으로 GPU를 활용하며, 별도의 :cuda 태그는 필요하지 않습니다.
이미지 버전은 :latest나 :main 대신 특정 릴리스 태그로 고정해야 재현 가능한 배포와 롤백이 가능합니다. 위 버전 번호는 예시이므로, 실제 배포 전에 각 저장소의 최신 릴리스를 확인하세요.
2단계: Nginx + TLS 구성
# /etc/nginx/sites-available/llm-server
server {
listen 443 ssl http2;
server_name llm.yourcompany.internal;
ssl_certificate /etc/ssl/certs/llm-server.crt;
ssl_certificate_key /etc/ssl/private/llm-server.key;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_buffering off; # 이 줄이 없으면 스트리밍 응답이 동작하지 않음
proxy_cache off;
proxy_read_timeout 300s;
}
}
server {
listen 80;
server_name llm.yourcompany.internal;
return 301 https://$host$request_uri;
}proxy_buffering off 설정이 없으면 스트리밍 토큰 응답이 한꺼번에 뭉쳐서 옵니다. 타이핑되는 효과가 왜 안 나오나 싶을 때 가장 먼저 확인할 항목입니다.
SSL 인증서 발급: 내부망 전용 서버라면 mkcert로 로컬 CA를 구성하거나, openssl로 자체 서명 인증서를 발급할 수 있습니다.
# 방법 1: mkcert — 팀 PC에 로컬 CA 설치, 브라우저 경고 없음
mkcert -install
mkcert llm.yourcompany.internal
# 방법 2: openssl 자체 서명 — 브라우저에 인증서 예외 추가 필요
openssl req -x509 -newkey rsa:4096 -keyout llm-server.key \
-out llm-server.crt -days 365 -nodes \
-subj "/CN=llm.yourcompany.internal"3단계: 모델 관리 전략
RBAC 설계에서 "어떤 그룹이 어떤 모델에 접근할 수 있게 할지"를 정하려면 먼저 어떤 모델을 운영할지 결정해야 합니다.
# 모델 다운로드
ollama pull llama3.3:70b
ollama pull qwen2.5-coder:32b
ollama pull mistral:7b
# 현재 메모리에 로드된 모델 확인
ollama ps
# 보유 모델 목록
ollama list팀 공용 서버라면 Modelfile을 통해 시스템 프롬프트를 고정한 커스텀 모델을 만드는 것이 유용합니다. 아래는 코드 리뷰 전용 모델 예시입니다. Modelfile은 Dockerfile과 유사한 문법을 갖지만 Ollama 전용 형식입니다.
# Modelfile.codereview (Ollama Modelfile 형식)
FROM qwen2.5-coder:32b
SYSTEM """
당신은 시니어 백엔드 개발자입니다. 코드 리뷰 요청에 대해 보안 취약점, 성능 이슈, 가독성 문제를 구체적으로 짚어줍니다.
한국어로 응답하며, 문제가 있는 라인을 명시적으로 인용합니다.
"""
PARAMETER temperature 0.3
PARAMETER num_ctx 8192ollama create codereview -f Modelfile.codereviewOpen WebUI에서 이 모델을 팀 전체 공유 모델로 등록하면, 모든 팀원이 동일한 컨텍스트의 코드 리뷰 어시스턴트를 사용할 수 있습니다.
VRAM 계획: 70B 모델을 Q4 양자화로 로딩하면 약 40GB VRAM이 필요합니다. 서버 VRAM 용량에 맞게 상시 로딩할 모델 수를 제한하고, 사용 빈도가 낮은 모델은 온디맨드로 로딩하는 방식을 권장합니다.
4단계: RBAC 설계 — 가산적 권한 모델 이해하기
Open WebUI의 권한 모델은 **가산적(additive)**입니다. 그룹 멤버십은 권한을 추가하기만 하고 제거하지 않습니다. 이 원칙을 이해하면 설계 실수를 막을 수 있습니다.
설계 원칙은 단순합니다. 기본 권한을 최대한 낮게 설정하고, 그룹으로 권한을 올려주는 방식입니다. 이 다이어그램에서 "기본 역할 Pending 설정"은 1단계의 Docker Compose 환경변수 DEFAULT_USER_ROLE=pending으로 제어합니다. Open WebUI 관리자 패널(Admin Panel → Settings → General → Default User Role)에서도 동일한 값을 변경할 수 있습니다.
기본 역할 구조:
| 역할 | 주요 권한 |
|---|---|
| Admin | 전체 설정, 사용자 관리, 모델 관리, 모든 대화 열람 |
| User | 채팅, 허용된 모델 사용, 개인 Knowledge Base |
| Pending | 로그인만 가능, Admin 승인 대기 |
실무에서 많이 쓰는 구성은 Admin이 신규 가입자를 Pending → User로 수동 승인하고, 개발팀·데이터팀 같은 그룹에 따라 접근 가능한 모델을 다르게 설정하는 방식입니다. LDAP 그룹 기반 자동 승인 등 고급 연동 기능은 Open WebUI 버전에 따라 지원 범위가 다르므로, 공식 문서와 저장소의 최신 릴리스 노트를 확인하세요. 엔터프라이즈 IdP 연동이 필요하다면 OIDC/OAuth2 또는 SCIM 2.0을 통한 외부 IdP 연동을 권장합니다.
5단계: GPU 모니터링 파이프라인
GPU 모니터링을 나중으로 미루면, VRAM이 가득 차거나 온도가 임계값을 넘어선 상황을 뒤늦게 알게 됩니다. 아무 증상 없이 레이턴시가 수십 배 늘어날 수 있으니 처음부터 붙여두는 것이 좋습니다.
Ollama에는 Prometheus가 직접 스크레이프할 수 있는 /metrics 엔드포인트가 없습니다. GPU 메트릭은 NVIDIA DCGM Exporter로, Ollama 상태 메트릭은 별도 사이드카 익스포터로 수집합니다.
사이드카 익스포터는 간단한 Python 스크립트로 직접 구현할 수 있습니다.
# ollama-exporter/main.py
import time
import requests
from prometheus_client import start_http_server, Gauge
OLLAMA_URL = "http://ollama:11434"
loaded_models_count = Gauge(
"ollama_loaded_models_total",
"현재 메모리에 로드된 모델 수"
)
def collect():
while True:
try:
resp = requests.get(f"{OLLAMA_URL}/api/ps", timeout=5)
if resp.ok:
models = resp.json().get("models", [])
loaded_models_count.set(len(models))
except Exception:
loaded_models_count.set(0)
time.sleep(15)
if __name__ == "__main__":
start_http_server(8000)
collect()# ollama-exporter/Dockerfile
FROM python:3.12-slim
RUN pip install prometheus-client requests
COPY main.py .
CMD ["python", "main.py"]P99 레이턴시, 토큰 처리량 등 요청 단위 메트릭이 필요하다면 Ollama API 앞에 FastAPI 미들웨어 프록시를 두고 거기서 계측하는 방식을 권장합니다. 이 글에서는 범위 밖이므로 별도 글에서 다룰 예정입니다.
모니터링 스택 전체를 Docker Compose에 추가합니다.
# docker-compose.yml에 추가
ollama-exporter:
build: ./ollama-exporter
container_name: ollama-exporter
restart: unless-stopped
ports:
- "127.0.0.1:8000:8000"
depends_on:
- ollama
dcgm-exporter:
image: nvcr.io/nvidia/k8s/dcgm-exporter:3.3.5-3.4.0-ubuntu22.04 # NGC에서 최신 태그 확인
container_name: dcgm-exporter
restart: unless-stopped
ports:
- "127.0.0.1:9400:9400"
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
cap_add:
- SYS_ADMIN
prometheus:
image: prom/prometheus:v2.53.0 # 최신 태그로 고정
container_name: prometheus
restart: unless-stopped
ports:
- "127.0.0.1:9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
grafana:
image: grafana/grafana:11.2.0 # 최신 태그로 고정
container_name: grafana
restart: unless-stopped
ports:
- "127.0.0.1:3001:3000"
volumes:
- grafana_data:/var/lib/grafana
volumes:
prometheus_data:
grafana_data:# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: dcgm
static_configs:
- targets: ["dcgm-exporter:9400"]
- job_name: ollama_sidecar
static_configs:
- targets: ["ollama-exporter:8000"]메트릭 흐름을 정리하면 이렇습니다.
Grafana에서 핵심적으로 살펴볼 패널입니다.
| 패널 | 메트릭 | 권장 알림 임계값 |
|---|---|---|
| VRAM 사용률 | DCGM_FI_DEV_FB_USED |
85% 초과 시 알림 |
| GPU SM 활용률 | DCGM_FI_DEV_GPU_UTIL |
참고용 |
| GPU 온도 | DCGM_FI_DEV_GPU_TEMP |
85°C 초과 시 알림 |
| 로드된 모델 수 | ollama_loaded_models_total |
이상값 감지 |
장단점 분석
클라우드 API vs 온프레미스 비교
| 항목 | 클라우드 LLM API | 온프레미스 자체 호스팅 |
|---|---|---|
| 초기 비용 | 없음 | GPU 서버 구매·임대 비용 |
| 운영 비용 | 토큰당 종량제 | 고정 인프라 비용 |
| 손익분기점 | — | 팀·GPU·API 단가에 따라 크게 다름 |
| 데이터 보안 | 외부 전송, DPA 필요 | 내부 처리, 외부 유출 없음 |
| 모델 성능 | 최신 프론티어 모델 | 오픈 모델, 복잡한 추론에서 격차 존재 |
| 커스터마이징 | 제한적 | 파인튜닝·파라미터 조정 자유 |
| 운영 부담 | 없음 | 내부 팀이 업데이트·패치 담당 |
| 벤더 종속 | 높음 | 없음 |
| 실제 TCO | 청구서 = 비용 | 인프라비 외에 인건비·유지보수 추가 |
손익분기점에 대해 한 마디: "월 API 비용 $5,000" 또는 "월 1억 토큰"처럼 특정 수치가 자주 언급되는데, 이는 GPU 임대 단가, API 모델 가격, 팀 엔지니어링 공수를 특정하게 가정했을 때 나오는 예시입니다. A100 80GB GPU를 월 $2,000에 임대하고 GPT-4o 입력 단가를 기준으로 계산하면 그 범위가 나오지만, 서버 스펙·계약 형태·사용 패턴에 따라 크게 달라집니다. 실제 TCO 계산에는 엔지니어 공수, 보안 패치 대응, 모니터링 운영 시간을 반드시 포함해야 합니다. 이를 합산하면 단순 인프라 비용만으로 계산할 때보다 총비용이 크게 늘어나는 경우가 많습니다.
온프레미스가 적합한 경우
- 민감 데이터(고객 정보, 의료·법률 문서)를 LLM으로 처리하는 경우
- 현재 API 비용이 충분히 높아 전환 ROI가 뚜렷하게 계산되는 경우
- GDPR·HIPAA·개인정보보호법 준수가 필수인 환경
- 에어갭(인터넷 차단) 내부망에서 AI 서비스가 필요한 경우
- Shadow AI를 거버넌스 범위 안으로 흡수해야 하는 경우
실무에서 흔한 실수
실수 1: Ollama 포트를 인터넷에 직접 노출
0.0.0.0:11434로 바인딩하면 인증 없이 누구나 모델을 호출하고 VRAM을 점유할 수 있습니다. Docker Compose에서 반드시 127.0.0.1:11434:11434로 로컬 바인딩을 사용하고, 외부 접근은 Nginx + TLS + Open WebUI 인증 레이어를 통해서만 허용하는 구조를 갖춰야 합니다.
실수 2: VRAM 상황을 예측하지 못함
70B 모델을 Q4 양자화로 로딩하면 약 40GB VRAM이 필요합니다. 팀원 여러 명이 동시에 요청을 보내면 KV 캐시 크기가 누적되면서 VRAM이 고갈되고, 이 시점에서 다른 모델을 추가로 로드하려 하면 기존 모델이 스왑되며 레이턴시가 급격히 늘어납니다. DCGM Exporter로 VRAM 85% 초과 알림을 설정해두고, 상시 로딩할 모델 수를 VRAM 용량에 맞게 제한하는 것이 좋습니다.
실수 3: RBAC 기본 권한을 처음부터 넓게 설정
Open WebUI의 권한은 가산적이므로, 기본 User 권한을 처음부터 넓게 설정하면 나중에 특정 그룹만 제한하기 어려워집니다. DEFAULT_USER_ROLE=pending으로 시작해 그룹 단위로 필요한 권한을 추가하는 방향으로 설계하는 것이 안전합니다.
실수 4: TCO를 인프라 비용으로만 계산
GPU 임대비만 보면 "클라우드 API보다 훨씬 싸다"고 느낄 수 있지만, 엔지니어링 인건비·모델 업데이트·보안 패치·모니터링 대응을 합산하면 실제 총비용은 단순 인프라 비용을 크게 상회합니다. 전환 의사결정 전에 팀 공수 비용을 반드시 포함해 계산하세요.
실수 5: proxy_buffering off 빠뜨리기
Nginx 설정에서 이 한 줄이 없으면 스트리밍 응답이 동작하지 않습니다. 생성이 끝난 후에야 응답이 한꺼번에 뜨는 것처럼 보여서 체감 성능이 나빠집니다.
마치며
VRAM 24GB 이상의 GPU 서버와 내부 DNS가 갖춰진 환경이라면 Ollama + Open WebUI 조합으로 팀 단위 AI 인프라를 구성할 수 있습니다. 클라우드 LLM API 대비 데이터 주권, 비용 예측 가능성, 벤더 독립성이라는 세 가지 가치를 얻을 수 있고, 민감 데이터를 처리하거나 API 비용이 ROI를 넘어선 팀에서 전환 효과가 두드러집니다.
지금 시작해볼 수 있는 3단계를 정리하면 이렇습니다.
1단계 — 로컬 검증: GPU가 있는 개발 머신에서 docker compose up -d로 Ollama + Open WebUI를 띄우고, 팀에서 자주 쓰는 작업(코드 리뷰, 문서 요약, 사내 FAQ 응답 등)에서 오픈 모델의 성능을 직접 확인합니다. Llama 3.3 70B나 Qwen 2.5 72B는 코드 보조, 문서 요약 같은 사내 루틴 작업에서 실용적인 결과를 냅니다.
2단계 — 팀 파일럿: 팀 공용 GPU 서버에 Nginx + TLS까지 구성해 소수 인원(510명)을 대상으로 24주 파일럿을 진행합니다. RBAC 설계와 모델 선택 기준이 이 과정에서 자연스럽게 정립됩니다.
3단계 — 모니터링 붙이기: DCGM Exporter + 사이드카 익스포터 + Prometheus + Grafana를 연동해 VRAM 85% 초과 알림을 설정합니다. 이게 없으면 문제가 생겼을 때 뒤늦게 알게 됩니다.
팀원들이 이미 업무에서 Shadow AI를 쓰고 있다면, 이를 공식 인프라로 흡수하는 시점을 늦출수록 데이터 거버넌스 공백이 커집니다. 처음 세팅이 다소 손이 가더라도, 돌아가기 시작하면 팀 전체가 유용하게 활용하게 됩니다.
참고 자료
- Open WebUI 공식 문서 — 인증 및 접근 제어
- Open WebUI 공식 문서 — RBAC 개요
- Open WebUI 공식 문서 — 역할 정의
- Open WebUI 공식 문서 — 권한 관리
- Open WebUI 공식 문서 — SSO
- GitHub — open-webui/open-webui 공식 저장소
- Open WebUI Sovereign AI Platform
- Monitor Ollama with Prometheus & Grafana: Production Dashboards
- Monitoring Self-Hosted LLM with Prometheus and Grafana — DEV Community
- GPU 모니터링: Prometheus & Grafana로 GPU 메모리 및 추론 처리량 모니터링
- Ollama Production Monitoring: 로깅 설정과 Prometheus 알림 실전 가이드
- Local LLM Ops: Docker & Grafana로 GPU 가속 AI 클라우드 구축 — DEV Community
- The Complete Ollama Enterprise Deployment Guide
- Self-Hosted LLM vs API: Enterprise Cost & Security
- Self-Hosted LLM Guide: Costs, Architecture & Breakeven Point
- Ollama Production Deployment: Docker-Compose 설정 가이드
- Ollama in Production: Docker, SSL, Auth & Monitoring
- SUSE AI 1.0 — Open WebUI 사용자 계정 관리 공식 문서