본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 29. 17:02

Ollama, Open WebUI 및 커스텀 모델 라우터를 활용한 로컬 IT 어시스턴트 구축

요약

Ollama와 Open WebUI를 활용하여 SysOps 업무를 지원하는 로컬 IT 어시스턴트 구축 방법을 소개합니다. 단순 설치를 넘어 작업 유형에 따라 적절한 모델로 자동 라우팅하는 커스텀 파이프 함수 구현에 초점을 맞춥니다.

핵심 포인트

  • Ollama와 Open WebUI를 이용한 로컬 LLM 환경 구축
  • 작업 성격(일반 채팅 vs 코딩)에 따른 모델 자동 라우팅 구현
  • Open WebUI의 커스텀 파이프 함수를 활용한 사용자 경험 개선
  • Docker Compose 기반의 지속 가능한 로컬 AI 인프라 설정

로컬 LLM (Large Language Model)을 실행하는 것은 쉽습니다. 하지만 이를 일상적인 IT 관리 업무에 유용한 무언가로 만드는 것은 다른 이야기입니다.

이 글은 단순히 Docker에 Ollama와 Open WebUI를 설치하는 것에 관한 것이 아닙니다. 이미 그에 대한 많은 튜토리얼이 존재합니다. 제가 구축하고자 했던 것은 실제로 SysOps 작업을 도울 수 있는 로컬 어시스턴트였습니다. 예를 들어 로그 읽기, 에러 설명, 안전한 Bash 명령어 준비, Ansible 플레이북 작성, YAML 파일 검토, 그리고 Docker 관련 트러블슈팅(troubleshooting) 지원 등이 포함됩니다.

저의 목표는 나중에 전용 Actina 머신으로 옮겨 소규모 내부 IT 어시스턴트로 사용할 수 있는 로컬 AI 환경을 준비하는 것이었습니다.

흥미로운 부분은 설치 그 자체가 아니었습니다.

흥미로운 부분은 설치 후에 일어난 일들이었습니다.

저는 몇 가지 실질적인 문제들을 해결해야 했습니다:

  • 기본 모델을 어떻게 선택할 것인가,
  • 코딩 관련 프롬프트(prompt)를 더 나은 모델로 어떻게 라우팅(route)할 것인가,
  • UI에서 수동으로 모델을 전환하는 것을 어떻게 피할 것인가,
  • Open WebUI가 올바른 어시스턴트로 시작하게 하려면 어떻게 해야 하는가,
  • 전체 설정이 VM 재시작 후에도 유지되게 하려면 어떻게 해야 하는가,
  • 그리고 모델과 데이터를 어떻게 지속적으로 유지(persistent)할 것인가.

이것은 설정의 Part 1입니다.

최종 목표

최종 아이디어는 간단했습니다:

Open WebUI를 열고, 하나의 어시스턴트를 선택하여 IT 관리에 사용하는 것입니다.

하지만 내부적으로는 작업에 따라 서로 다른 모델이 사용되기를 원했습니다.

예를 들어:

일반 채팅, 로그, CSV 분석, 설명  -> qwen3:8b
Bash, Python, Docker, Ansible, YAML, 에러     -> qwen2.5-coder:7b

따라서 모델을 수동으로 전환하는 대신, Open WebUI에서 커스텀 파이프 함수(Pipe Function)를 생성했습니다.

사용자 관점에서는 다음과 같이 하나의 모델처럼 보였습니다:

AUTO Local IT Assistant

하지만 내부적으로는 단순한 모델 라우터(model router)로 작동했습니다.

환경 개요

로컬 설정은 Docker Compose를 기반으로 했습니다.

프로젝트 디렉토리 구조는 다음과 같습니다:

/home/kkadzielawa/ai-local
├── docker-compose.yml
├── .env
...

주요 컨테이너는 다음과 같습니다:

ollama
open-webui

사용된 로컬 모델은 다음과 같습니다:

qwen3:8b
qwen2.5-coder:7b

구상한 아이디어는 다음과 같습니다:

qwen3:8b           -> 일반적인 로컬 IT 어시스턴트 (general local IT assistant)
qwen2.5-coder:7b  -> 코딩 (coding), 스크립팅 (scripting), Docker, Ansible, YAML

처음에는 기본 모델 (default model)을 설정하는 것만으로 충분할 것이라고 생각했습니다.

하지만 그렇지 않았습니다.

문제 1: 기본 모델만으로는 충분하지 않다

첫 번째 접근 방식은 Open WebUI에 기본 모델을 설정하는 것이었습니다.

Docker Compose에서는 다음과 같이 환경 변수 (environment variables)를 사용하여 설정할 수 있습니다:

environment:
  - DEFAULT_MODELS=qwen3:8b
  - DEFAULT_PINNED_MODELS=qwen3:8b,qwen2.5-coder:7b

이 방식은 작동하지만, 단 한 가지 문제만 해결합니다:

어떤 모델을 기본값으로 선택해야 하는가?

더 중요한 문제는 해결하지 못합니다:

이 특정 프롬프트 (prompt)에는 어떤 모델이 가장 적합한가?

예를 들어, 다음 프롬프트는 일반 모델 (general model)로도 아마 충분할 것입니다:

Explain what should be checked before upgrading a Debian server in production.
(운영 중인 Debian 서버를 업그레이드하기 전에 무엇을 확인해야 하는지 설명해줘.)

하지만 다음 프롬프트는 코딩 지향 모델 (coding-oriented model)로 전달되어야 합니다:

Write an Ansible playbook that checks hostname, uptime, OS version and free disk space.
Read-only tasks only. Do not modify the system.
(호스트 이름, 업타임, OS 버전 및 여유 디스크 공간을 확인하는 Ansible 플레이북을 작성해줘. 읽기 전용 작업만 수행하고 시스템을 수정하지 마.)

저는 매번 어떤 모델을 선택해야 하는지 기억하고 싶지 않았습니다.

Open WebUI가 그 결정을 자동으로 내리기를 원했습니다.

따라서 사용자 프롬프트와 실제 모델 사이에 커스텀 레이어 (custom layer)가 필요했습니다.

문제 2: 도구 (Tools), 파이프라인 (Pipelines) 아니면 함수 (Functions)?

이 부분이 처음에 혼란스러웠던 지점 중 하나였습니다.

Open WebUI에는 처음 보면 비슷해 보이는 몇 가지 영역이 있습니다:

Workspace -> Tools (도구)
Settings  -> Pipelines (파이프라인)
Admin     -> Functions (함수)

저의 첫 번째 가정은 이러한 종류의 로직을 도구 (tool)나 파이프라인 (pipeline)으로 만들어야 한다는 것이었습니다.

하지만 그것은 올바른 방향이 아니었습니다.

모델 라우터 (model router)는 대화 중에 모델이 호출하는 일반적인 도구가 아닙니다. 계산기, 웹 검색 또는 커스텀 API 도구와 같은 것이 아닙니다.

제가 필요했던 것은 UI에서 모델처럼 동작하면서, 사용자 요청을 받고, 어떻게 처리할지 결정한 다음, 선택된 실제 모델로 이를 전달하는 무언가였습니다.

적절한 위치는 다음과 같습니다:

Admin Settings (관리자 설정)
-> Functions (함수)
-> New Function (새 함수)

그리고 적절한 함수 유형은 Pipe Function (파이프 함수)였습니다.

문제 3: Open WebUI는 Filter (필터) 예시를 보여주었지만, 저에게 필요한 것은 Pipe (파이프)였습니다

새로운 함수를 생성할 때, Open WebUI는 필터 (Filter)를 기반으로 한 예시를 보여줄 수 있습니다.

이는 오해를 불러일으킬 수 있습니다.

이번 유스케이스 (Use case)에서 필터 (Filter)는 제가 필요로 하는 것이 아니었습니다.

필터 (Filter)는 입력 또는 출력을 수정할 수 있지만, 저는 선택 가능한 모델처럼 나타나면서 요청을 다른 모델로 라우팅 (Routing)하는 무언가를 만들고 싶었습니다.

따라서 함수는 다음과 같이 작성되어야 했습니다:

class Pipe:

이 아니라:

class Filter:

이것은 중요한 세부 사항이었습니다.

커스텀 함수 이름은 다음과 같이 지정했습니다:

AUTO Local IT Assistant

함수에는 자체적인 설정 가능한 밸브 (Valves)가 있었습니다:

Default Model: qwen3:8b
Coder Model: qwen2.5-coder:7b
Show Selected Model: false

이를 통해 함수 전체를 다시 작성하지 않고도 나중에 기반 모델 (Underlying models)을 변경할 수 있었습니다.

라우팅 로직 (Routing logic)

라우터 (Router)의 첫 번째 버전은 의도적으로 단순하게 설계되었습니다.

사용자 메시지를 확인하여 프롬프트 (Prompt)가 코딩 (Coding), 스크립팅 (Scripting) 또는 인프라 자동화 (Infrastructure automation)와 관련이 있음을 시사하는 키워드나 패턴을 찾았습니다.

키워드 예시:

ansible
playbook
yaml
...

패턴 예시:

docker ...
systemctl
journalctl
...

만약 프롬프트가 코딩 또는 자동화 관련 패턴과 일치하면, 함수는 이를 다음 모델로 라우팅 (Route)했습니다:

qwen2.5-coder:7b

그렇지 않으면 다음 모델을 사용했습니다:

qwen3:8b

이것은 고도화된 에이전트 (Agent)가 아닙니다.

단순하고 예측 가능한 라우터 (Router)입니다.

그리고 그것이 바로 이 단계에서 제가 원했던 것이었습니다.

로컬 IT 어시스턴트 (Local IT assistant)의 경우, 디버깅하기 어려운 영리한 동작보다는 예측 가능한 동작이 종종 더 낫습니다.

안전 지향적 시스템 프롬프트 (System prompt) 추가

이 어시스턴트는 IT 관리 (IT administration)를 목적으로 하기 때문에, 무작위로 파괴적인 명령어를 제안하는 것을 원치 않았습니다.

그래서 실질적인 안전 규칙이 포함된 시스템 프롬프트 (System prompt)를 추가했습니다.

어시스턴트는 다음과 같이 동작해야 합니다:

  • 폴란드어로 답변할 것,
  • 명령어를 읽기 전용(read-only), 시스템 변경(system-changing) 또는 위험(risky)으로 분류할 것,
  • 운영 환경(production) 변경을 수행하기 전에 진단(diagnostics)을 제안할 것,
    ...

이는 매우 작은 추가 사항이지만, 동작을 크게 변화시킵니다.

예를 들어, 어시스턴트는 위험한 명령어를 즉시 제안하는 대신, 먼저 읽기 전용 진단 명령어를 제안해야 합니다:

df -h
free -m
uptime
...

저에게 있어, 이 부분은 로컬 SysOps 어시스턴트를 구축하는 데 있어 가장 중요한 부분 중 하나입니다.

모델은 단순히 답변만 해서는 안 됩니다.

관리자들이 실제로 운영 시스템(production systems)에서 작업하는 방식에 맞춰 답변해야 합니다.

문제 4: 라우터는 존재했지만, Open WebUI에는 여전히 qwen3가 표시됨

Pipe Function을 생성한 후, 저는 Open WebUI가 기본적으로 라우터가 선택된 상태로 시작하기를 원했습니다.

그래서 Docker Compose 환경 변수를 다음과 같이 변경했습니다:

environment:
  - DEFAULT_MODELS=auto-local-it-assistant
  - DEFAULT_PINNED_MODELS=auto-local-it-assistant,qwen3:8b,qwen2.5-coder:7b

처음에는 제대로 작동하지 않는 것처럼 보였습니다.

Open WebUI에는 여전히 다음과 같이 표시되었습니다:

qwen3:8b

설정이 올바른 것 같았기 때문에 이는 혼란스러웠습니다.

이유는 간단하지만 놓치기 쉬운 것이었습니다:

Open WebUI는 기존 채팅이나 사용자 세션에서 선택된 모델을 기억할 수 있습니다.

따라서 해결책은 또 다른 Docker Compose 변경이 아니었습니다.

해결 방법은 다음과 같았습니다:

1. CTRL + F5를 눌러 브라우저를 강력 새로고침(Hard refresh) 합니다.
2. 새로운 채팅을 시작합니다.
3. 모델 선택기에서 "auto"를 검색합니다.
...

그 후에 라우터가 예상대로 작동했습니다.

여기서 얻은 교훈은 중요했습니다:

DEFAULT_MODELS는 기본 상태에 영향을 미치지만,
기존 채팅이나 사용자 세션은 여전히 이전 모델을 기억할 수 있습니다.

이는 매우 실질적인 트러블슈팅(troubleshooting) 포인트입니다.

이를 모르면 컨테이너를 재시작하거나, YAML 파일을 변경하거나, Pipe Function이 고장 났다고 가정하며 시간을 낭비하기 쉽습니다.

문제 5: VM 재부팅 후 컨테이너가 시작되어야 함

또 다른 문제는 운영 측면의 문제였습니다.

이 설정은 VM (가상 머신)에서 실행되고 있었으며, 저는 재부팅 후에 로컬 어시스턴트가 사라지는 것을 원하지 않았습니다.

해결책은 두 부분으로 나뉩니다.

첫째, Docker와 containerd가 시스템과 함께 시작되어야 합니다:

sudo systemctl enable docker.service
sudo systemctl enable containerd.service

둘째, 컨테이너에 적절한 재시작 정책 (restart policy)이 있어야 합니다.

docker-compose.yml에서:

services:
  ollama:
    image: ollama/ollama:latest
...

저는 다음과 같이 사용했습니다:

restart: unless-stopped

다음 대신에 말이죠:

restart: always

로컬 관리 도구의 경우 저는 이 동작을 선호하기 때문입니다.

만약 제가 수동으로 컨테이너를 중지했다면, 그것은 대개 의도가 있는 행동입니다. 제 결정에 반하여 Docker가 즉시 컨테이너를 다시 실행하는 것을 원하지 않습니다.

재시작 정책을 확인하려면:

docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' ollama
docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' open-webui

예상 결과:

unless-stopped
unless-stopped

그다음 실제 테스트:

sudo reboot

VM이 다시 돌아온 후:

docker ps

두 컨테이너 모두 다시 실행 중이어야 합니다.

문제 6: 모델과 데이터는 컨테이너 재생성 시에도 유지되어야 함

Ollama와 Open WebUI에서 매우 중요한 또 다른 요소는 지속성 (persistence)입니다.

저는 다운로드된 모델이나 Open WebUI 데이터가 컨테이너 내부에만 머무는 것을 원하지 않았습니다.

그렇기 때문에 컨테이너에 마운트된 로컬 디렉토리를 사용했습니다:

volumes:
  - ./data/ollama:/root/.ollama

그리고:

volumes:
  - ./data/open-webui:/app/backend/data

이를 통해 깔끔한 프로젝트 구조를 가질 수 있습니다:

/home/kkadzielawa/ai-local
├── docker-compose.yml
├── .env
...

또한 설정을 백업하거나 다른 머신으로 옮기기가 더 쉬워집니다:

cd ~
tar -czf ai-local-backup-$(date +%F).tar.gz ai-local

이것은 향후 마이그레이션 (migration)을 염두에 두고 환경을 준비했기 때문에 중요했습니다.

VM은 첫 번째 단계일 뿐이었습니다.

목표는 로컬 AI 지원 관리를 위한 전용 머신을 구축하는 것이었습니다.

어시스턴트 테스트

저는 몇 가지 다른 프롬프트(prompt)로 라우터(router)를 테스트했습니다.

일반적인 SysOps 질문:

Explain what should be checked before upgrading a Debian server in production.

예상 모델:

qwen3:8b

Bash 관련 프롬프트:

Write a Bash script that checks CPU, RAM, disks, IP addresses and whether Docker is running.

예상 모델:

qwen2.5-coder:7b

Ansible 프롬프트:

Write an Ansible playbook that checks hostname, uptime, OS version and free disk space.
Read-only tasks only.

예상 모델:

qwen2.5-coder:7b

YAML 트러블슈팅(troubleshooting) 프롬프트:

Check this docker-compose.yml and explain why Open WebUI cannot reach Ollama.

예상 모델:

qwen2.5-coder:7b

로그 분석 프롬프트:

Analyze this log and tell me what looks suspicious.

예상 모델:

qwen3:8b

이 단계에서 라우팅(routing)이 완벽할 필요는 없습니다.

예측 가능하고 유용하기만 하면 됩니다.

이것으로 파트 1(Part 1)을 마칩니다.

배운 점

이 설정을 통해 얻은 가장 큰 교훈은 다음과 같습니다:

로컬 AI는 단순히 모델을 실행하는 것에 관한 것이 아닙니다.
모델을 중심으로 워크플로우 (workflow)를 구축하는 것에 관한 것입니다.

Ollama와 Open WebUI는 매우 좋은 시작점을 제공합니다.

하지만 진정한 가치는 자신의 사용 사례(use case)에 맞춰 환경을 커스텀(customize)할 때 시작됩니다.

제 경우, 가장 중요한 개선 사항은 다음과 같았습니다:

- UI 상의 가시적인 하나의 어시스턴트,
- 일반 모델과 코딩 모델 간의 자동 라우팅 (automatic routing),
- 안전 중심의 시스템 프롬프트 (system prompt),
...

가장 유용했던 부분은 뻔한 설치 단계가 아니었습니다.

가장 유용했던 부분은 진행 과정에서 발견한 작은 문제들이었습니다:

- 모델 라우터 (model router)는 일반적인 도구 (Tool)가 아니다,
- 올바른 Open WebUI 기능은 함수 (Function)이다,
- 함수는 필터 (Filter)가 아니라 파이프 (Pipe)여야 한다,
...

이것들은 깔끔한 설치 가이드에는 보통 나타나지 않는 세부 사항들입니다.

하지만 실제로 사용할 수 있는 무언가를 만들 때 중요한 것은 바로 이러한 세부 사항들입니다.

다음 단계

이것은 프로젝트의 첫 번째 파트일 뿐입니다.

현재 설정은 간단한 모델 라우팅 (model routing) 기능을 갖춘 로컬 IT 어시스턴트를 제공합니다.

하지만 가능한 다음 단계는 매우 많습니다.

가장 명확한 단계는 비전 모델 (vision model)을 추가하는 것입니다.

이를 통해 어시스턴트가 스크린샷, UI 오류, 다이어그램 또는 모니터링 뷰를 처리할 수 있게 됩니다.

예를 들어:

스크린샷 또는 이미지 -> 비전 모델 (vision model)
코드 또는 자동화 -> 코더 모델 (coder model)
일반적인 IT 질문 -> 일반 모델 (general model)

또 다른 중요한 단계는 RAG (검색 증강 생성)입니다.

저는 어시스턴트가 로컬 문서, 내부 노트, 절차서, PDF, CSV 내보내기 파일 및 문제 해결 가이드와 함께 작동하기를 원합니다.

이를 위해서는 임베딩 모델 (embedding model)과 로컬 지식 베이스 (knowledge base)를 추가해야 합니다.

향후 가능한 개선 사항:

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0