LLM 엔지니어링14장 · Streamlit 화면과 Docker 컨테이너11 / 22← 이전목차다음 →MetaContents

14장. Streamlit 화면과 Docker 컨테이너

docker compose 로 여러 서비스 띄우기

식당을 열려면 홀, 주방, 창고가 함께 문을 열어야 합니다. 하나씩 따로 여는 대신 "개점 순서표" 한 장으로 함께 엽니다. docker compose 는 화면, API, 모델 서버(Ollama) 컨테이너를 compose.yaml 한 파일로 함께 띄우고, 서로 서비스 이름으로 찾게 해 줍니다.

화면 컨테이너가 api 라는 이름으로 API 컨테이너를, API 컨테이너가 ollama 라는 이름으로 모델 서버를 부르는 세 서비스를 견준 카드

세 카드는 이 장의 프로젝트가 쓰는 서비스입니다. 화면(ui)은 8501 번 포트로 사용자를 받고 http://api:8014 로 API 를 부릅니다. API(api)는 http://ollama:11434 로 모델 서버를 부릅니다. 모델 서버(ollama)는 모델 파일을 볼륨에 둡니다. compose 가 만든 네트워크 안에서는 서비스 이름이 곧 주소입니다. 여기서 개점 순서표가 compose.yaml, 홀과 주방과 창고가 세 서비스입니다.

compose.yaml 의 칸

칸뜻예
services띄울 컨테이너들ui, api, ollama
buildDockerfile 이 있는 폴더./api
image만들지 않고 받아 쓸 이미지ollama/ollama
portsPC 포트:컨테이너 포트"8501:8501"
environment환경 변수API_URL: http://api:8014
depends_on먼저 떠야 할 서비스[api]
volumes남겨야 할 데이터ollama:/root/.ollama

compose.yaml 읽어 보기

Python
import yaml

compose = yaml.safe_load("""
services:
  api: {build: ./api, ports: ["8013:8013"]}
  ui: {build: ./ui, ports: ["8501:8501"], environment: {API_URL: "http://api:8013"}, depends_on: [api]}
""")
for name, svc in compose["services"].items():
    print(name, svc.get("ports"), svc.get("environment", {}))

코드에 나오는 함수

코드쉬운 뜻
import yamlYAML 파일을 읽는 PyYAML 라이브러리다. YAML 은 "YAML Ain't Markup Language" 의 머리글자로, 사람이 읽기 쉬운 설정 파일 꼴이다
yaml.safe_load(글)YAML 글을 파이썬의 사전과 목록으로 바꾼다. safe(안전한)는 글 안에 숨긴 파이썬 명령을 실행하지 않는다는 뜻이다
{build: ./api, ports: [...]}YAML 의 한 줄 꼴이다. 중괄호는 사전, 대괄호는 목록이 된다
svc.get("ports")서비스 설정에서 ports 칸을 읽는다. 없으면 None 이다
svc.get("environment", {})environment 칸이 없으면 빈 사전을 쓴다

이 코드가 뜻하는 것

식당의 개점 순서표를 읽고 "홀은 몇 번 문으로, 주방은 몇 번 문으로 손님을 받는지" 적어 보는 일과 같습니다. 여기서 개점 순서표가 compose.yaml, 문 번호가 ports, 홀 직원이 받은 주방 전화번호가 API_URL 입니다.

줄하는 일쉽게 말하면
compose = yaml.safe_load("""...""")compose 설정 글을 사전으로 읽는다순서표를 펼친다
compose["services"].items()서비스 이름과 설정을 하나씩 꺼낸다홀, 주방을 하나씩 본다
print(name, svc.get("ports"), ...)포트와 환경 변수를 찍는다문 번호와 받은 전화번호를 적는다

결과에서 api 는 ['8013:8013'] 포트만 있고 환경 변수가 없습니다. ui 는 {'API_URL': 'http://api:8013'} 을 받아, compose 안에서 서비스 이름 api 로 API 를 찾습니다.

작게 줄여서 눈으로 보기

depends_on 을 따라가면 어떤 차례로 띄워야 하는지 정해집니다. 파이썬의 graphlib 으로 그 차례를 구해 봅니다. compose 가 이 코드를 쓰는 것은 아니고, 생각을 흉내 낸 것입니다.

Python
from graphlib import TopologicalSorter

# 서비스 이름 → depends_on 에 적은 서비스들
depends_on = {"ui": ["api"], "api": ["ollama"], "ollama": []}
print(list(TopologicalSorter(depends_on).static_order()))
출력
['ollama', 'api', 'ui']
결과읽는 법
'ollama' 가 맨 앞아무에게도 기대지 않는 모델 서버를 먼저 띄운다
'api' 가 가운데ollama 에 기대므로 그다음이다
'ui' 가 맨 끝api 에 기대므로 마지막이다

graphlib 은 graph(점과 화살표로 이은 그림)를 다루는 표준 모듈이고, TopologicalSorter 는 "먼저 할 것부터 줄 세우는 도구"입니다. static_order() 가 그 줄을 돌려줍니다. 위의 주의할 점처럼, 이 차례는 "먼저 띄운다"일 뿐 "다 뜰 때까지 기다린다"가 아닙니다.

주의할 점

  • depends_on 은 "먼저 띄운다"일 뿐 "다 뜰 때까지 기다린다"가 아닙니다. 화면이 API 보다 먼저 준비되어 처음 몇 초 동안 연결 실패가 날 수 있으니, 화면이 연결 실패를 알리고 다시 시도하게 만듭니다.
  • 컨테이너 안의 localhost 는 그 컨테이너 자신입니다. 다른 서비스는 서비스 이름(api, ollama)으로 부릅니다.
← 이전 절14장 · Dockerfile 쓰기다음 절 →14장 · API 아키텍처