로컬 LLM 보안 운영 — Ollama로 사내 데이터 유출 없이 AI 쓰는 법

목차

외부 LLM API로 전송된 프롬프트에 고객 개인정보가 섞여 나가는 문제가 생겼다. 보안 감사 후 ChatGPT, Claude API 등 외부 LLM 서비스가 전면 차단됐다.

코드 리뷰 보조, 에러 로그 분석, 문서 초안 작성 등 개발팀의 LLM 의존도가 이미 높은 상태였기 때문에 대안이 급했다. Ollama 기반 로컬 LLM이 그 대안이었다.

외부 LLM 차단 이후, 남는 선택지

데이터가 외부 서버로 나가면 안 된다. 보안팀의 유일한 조건이었다. 이걸 만족시키면서 LLM을 쓸 수 있는 방법은 현실적으로 세 가지였다.

첫째, 클라우드 VPC 안에 기업용 LLM 서비스를 두는 방법이다. AWS Bedrock이나 Azure OpenAI 같은 서비스는 데이터가 모델 학습에 사용되지 않는다는 계약 조건이 붙는다. 다만 월 비용이 수백만 원 단위고, 사내 조달 프로세스를 거치면 최소 2~3개월은 걸린다. 당장 내일부터 써야 하는 상황에는 안 맞았다.

둘째, 사내 서버에 오픈소스 모델을 직접 올리는 방법이다. GPU 서버가 필요하다. 마침 우리 팀에 ML 파이프라인 테스트용으로 RTX 4090이 꽂힌 Ubuntu 22.04 서버가 한 대 놀고 있었다.

셋째, 개발자 각자의 노트북에서 돌리는 방법이다. M1/M2 Mac이면 7B 모델 정도는 돌아간다. 다만 이 방식은 관리 자체가 불가능하다. 누가 어떤 모델을 돌리는지, 프롬프트 데이터가 로컬 디스크에 남는지 추적할 방법이 없어서 보안팀이 절대 허용하지 않는다.

둘째로 갔다. 사내 서버 한 대에 Ollama를 올리고, 개발자들이 API로 접속하는 구조. 여기까지 결정하는 데는 반나절이면 충분했다. 문제는 그다음이었다.

Ollama 기본 설정이 보안 구멍인 이유

Ollama 설치는 허무할 정도로 간단하다.

curl -fsSL https://ollama.com/install.sh | sh
ollama pull llama3.1:8b  # 8B 모델 하나 받기

2분이면 끝난다. 근데 이 상태로 두면 안 된다는 걸, 처음에는 몰랐다. 설치 가이드나 튜토리얼을 보면 전부 ollama pullollama run → 끝이라서, 보안 설정에 대한 언급이 거의 없다.

Ollama는 기본으로 0.0.0.0:11434에 바인딩된다. HTTP API가 인증 없이 열려 있고 CORS 제한도 없다. 같은 사내 네트워크에 있는 사람이면 누구든 curl http://서버IP:11434/api/generate를 쳐서 응답을 받을 수 있다.

문제가 되는 엔드포인트를 정리하면 이렇다:

  • /api/generate — 인증 없이 아무 프롬프트나 보내서 응답 받기
  • /api/pull — 외부에서 임의의 모델을 다운로드 (네트워크 대역폭을 잡아먹는다)
  • /api/delete — 운영 중인 모델 삭제
  • /api/show — 모델 시스템 프롬프트, 파라미터 설정 전체 노출

빌트인 인증 기능은 Ollama GitHub 리포지토리에서 이슈 #2547을 시작으로 꾸준히 요청되고 있지만, 2026년 4월 현재까지 제공되지 않는다. Ollama 측의 공식 입장은 "프로덕션 환경에서는 리버스 프록시를 써라"다 (출처: Ollama FAQ, GitHub).

설치 다음 날 아침, 보안팀 내부 네트워크 스캐너가 바로 잡았다. "11434 포트에 인증 없는 HTTP API가 노출되어 있다"는 메일이 도착했다. 예상은 했지만 이렇게 빠를 줄은 몰랐다.

보안팀이 승인한 구성 — 네트워크 격리부터 실행 격리까지

4일 걸렸다. 모델을 돌리는 것보다, 보안팀이 서명할 수 있는 수준으로 포장하는 데 시간이 3배 더 들었다.

최종적으로 세 가지를 적용해서 승인을 받았다.

포트 바인딩을 localhost로 제한

가장 먼저 할 일이다. Ollama가 외부에 직접 노출되지 않게 127.0.0.1에만 바인딩한다.

# /etc/systemd/system/ollama.service.d/override.conf
[Service]
Environment="OLLAMA_HOST=127.0.0.1:11434"

systemctl daemon-reload && systemctl restart ollama로 적용하면, 외부 IP로 11434를 찔러도 연결 자체가 안 된다. iptables로 한 겹 더 막으면 확실하다.

# 11434 포트를 localhost에서만 허용
sudo iptables -A INPUT -p tcp --dport 11434 -s 127.0.0.1 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 11434 -j DROP

nginx 리버스 프록시로 인증 추가

개발자들이 접근하는 경로는 nginx를 통해서만 열었다. Bearer Token을 검증하는 구조다. nginx 설정에서 $http_authorization 헤더 값을 검사하고, 일치하지 않으면 401을 반환한다. 토큰 값은 환경변수로 관리하며, 보안팀과 분기별 로테이션을 합의했다.

limit_req_zone으로 rate limiting도 걸었다. 분당 30회 초과 시 429 반환이다. 이건 보안보다는 GPU 자원 보호 목적이었다. 누군가가 스크립트로 대량 요청을 날려서 다른 사람이 대기하게 되는 상황을 막기 위해서다.

전체 구조를 정리하면 이렇다:

[개발자 PC] → HTTPS(443) → [nginx: Token 검증 + Rate Limit] → HTTP(127.0.0.1:11434) → [Ollama]

HTTPS 인증서는 사내 CA에서 발급받은 걸 사용했다. Let’s Encrypt도 가능하지만, 사내 전용 서비스라 내부 CA가 관리 편의상 낫다.

실행 사용자 격리

Ollama 프로세스가 root로 도는 건 말이 안 된다. 전용 시스템 사용자를 만들어서 최소 권한으로 격리했다.

# 전용 사용자 생성 (로그인 불가, 모델 저장소만 접근)
sudo useradd -r -s /bin/false -m -d /var/lib/ollama ollama-svc
sudo chown -R ollama-svc:ollama-svc /var/lib/ollama
sudo chmod 700 /var/lib/ollama

systemd 서비스에서 User=ollama-svc를 지정하면, 이 프로세스는 /var/lib/ollama 밖의 파일을 읽지 못한다. 같은 서버에 있는 프로젝트 소스코드나 DB 덤프 파일에 접근할 수 없다는 뜻이다.

이 세 가지를 적용한 뒤 구성도와 위협 분석 문서를 같이 보냈다. 보안팀에서 "이 정도면 된다"는 회신이 올 때까지 이틀 걸렸다. 솔직히 기술 설정보다 문서 작성이 더 시간을 잡아먹었다.

로컬 LLM 운영 3개월 차 메모

성능은 예상보다 쓸만하다

8B 모델(Llama 3.1) 기준으로 응답 시간은 보통 2~3초다. 코드 자동완성이나 짧은 질문에는 충분한 속도다. 개발자 10명이 몰리는 오후 시간대에도 체감 5초를 넘기는 경우는 드물었다.

현재 두 개 모델을 올려놓고 있다. 일반 질의용 Llama 3.1 8B, 코드 작업용 CodeLlama 13B. 13B 모델은 VRAM을 약 10GB 정도 잡는데, RTX 4090 24GB에서 두 모델을 동시에 올리면 여유가 빠듯하다. 실제로는 Ollama가 LRU 방식으로 모델을 로드/언로드하기 때문에 동시 점유 상황은 자주 발생하지 않는다.

GPT-4나 Claude Sonnet 수준을 기대하면 실망한다. 8B 모델의 한국어 능력은 영어 대비 확연히 떨어진다. 복잡한 로직 설명이나 긴 문서 생성에서는 답변 품질이 눈에 띄게 낮다. 이건 로컬 LLM 보안 운영에서 피할 수 없는 트레이드오프다. 보안을 얻는 대신 품질을 일부 포기하는 구조라는 걸 팀원들에게 미리 공유해둬야 불만이 줄어든다.

예상 못 한 문제 — 모델 업데이트

이건 진짜 예상 밖이었다. ollama pull로 모델을 받으려면 외부 인터넷 연결이 필요하다. 보안 정책상 서버의 아웃바운드 트래픽도 화이트리스트 기반으로 관리하고 있어서, registry.ollama.ai 도메인을 방화벽에 추가해달라는 별도 요청을 넣어야 했다.

지금은 모델 업데이트가 필요할 때마다 보안팀에 임시 오픈을 요청하고, 다운로드 끝나면 다시 닫는 식으로 운영 중이다. 에어갭 환경에서 모델 바이너리를 USB나 SCP로 직접 옮기는 방법도 있을 텐데, 아직 시도해보지 못했다.

로그 모니터링

nginx access log에 누가 언제 어떤 엔드포인트를 호출했는지 기록된다. 이걸 주 1회 보안팀에 리포트로 보내고 있다. 프롬프트 내용 자체는 로깅하지 않기로 했다. 프롬프트를 저장하면 그것 자체가 또 다른 데이터 보관 리스크가 되니까.

GPU 사용률은 nvidia-smi를 cron으로 5분마다 찍어서 간이 모니터링 중이다. Grafana 같은 걸 붙이면 좋겠지만, 서버 한 대짜리 운영에 거기까지는 과하다고 본다.

도입 여부 판단 기준

3개월 운영 끝에 나름의 기준이 생겼다.

로컬 LLM 보안 운영이 맞는 상황은 분명하다. 외부 API가 금지된 환경(금융, 의료, 공공), GPU 서버가 이미 있거나 확보 가능한 경우, 8B~13B 모델로 충분한 작업(코드 자동완성, 짧은 QA, 단순 번역), 동시 사용자 20명 이하. 이 네 가지가 겹치면 도입할 만하다.

반대로 안 맞는 상황도 있다. 70B급 대형 모델이 필요한 복잡한 추론 작업이면 GPU 한 대로는 부족하다. CPU만 있는 환경에서는 응답에 30초 이상 걸려서 실무에서 쓸 수가 없다. 사용자가 50명을 넘으면 요청 큐가 밀리기 시작하고, 모델 품질이 업무 품질에 직결되는 경우(고객 대면 챗봇 같은)에도 부적절하다.

한 가지 강조하자면, 로컬이라고 자동으로 안전한 게 아니다. 네트워크 격리와 인증, 실행 격리를 제대로 안 하면 관리되지 않는 섀도우 IT가 될 뿐이다. 로컬 LLM 보안 운영 환경 구축 자체는 GPU 서버가 있으면 일주일이면 되지만, 보안 정책 문서화와 승인까지 포함하면 2주 정도 잡는 게 현실적이다.

관련 글

Chiko
Chiko

Platform Engineer. Python, AI, Infra에 관심이 많습니다.