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

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

실습 4 — 화면과 API 를 실제 서버로 띄우기

지금까지는 AppTest 로 화면을 이 프로그램 안에서 실행했습니다. 이번에는 API(8013 번 포트)와 화면(8501 번 포트)을 실제 서버 두 개로 띄워, 브라우저로 열 수 있는지 확인하고 끕니다. 실습 3의 파일이 있어야 합니다.

1단계. 두 서버 띄우기

Python
import os, subprocess, sys, time, urllib.request

env = dict(os.environ, API_URL="http://127.0.0.1:8013")         # 화면 서버에 줄 설정
api = subprocess.Popen([sys.executable, "-m", "uvicorn", "out_delay_api:app", "--port", "8013", "--log-level", "warning"])
# 터미널에서 "streamlit run out_app_api.py --server.port 8501" 을 실행한 것과 같다
ui = subprocess.Popen([sys.executable, "-m", "streamlit", "run", "out_app_api.py", "--server.port", "8501",
                       "--server.headless", "true", "--browser.gatherUsageStats", "false"],
                      env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)


def wait(url, seconds=60):
    """주소가 답할 때까지 기다리고 걸린 시간을 돌려준다"""
    t = time.perf_counter()
    while time.perf_counter() - t < seconds:
        try:
            urllib.request.urlopen(url, timeout=2)
            return time.perf_counter() - t
        except OSError:
            time.sleep(0.5)
    raise TimeoutError(url)


print(f"API: {wait('http://127.0.0.1:8013/health'):.1f}초 · 화면: {wait('http://127.0.0.1:8501/_stcore/health'):.1f}초 만에 준비")
출력
API: 4.1초 · 화면: 0.0초 만에 준비

2단계. 화면 서버가 주는 것 보기

Python
html = urllib.request.urlopen("http://127.0.0.1:8501").read().decode()
print("상태 확인:", urllib.request.urlopen("http://127.0.0.1:8501/_stcore/health").read().decode())
print("첫 페이지:", len(html), "글자 ·", html[html.find("<title>"):html.find("</title>") + 8])
print("브라우저로 열 주소: http://127.0.0.1:8501")
출력
상태 확인: ok
첫 페이지: 6467 글자 · <title>Streamlit</title>
브라우저로 열 주소: http://127.0.0.1:8501

3단계. 두 서버 끄기

Python
for name, proc in [("화면", ui), ("API", api)]:
    proc.terminate()
    proc.wait(timeout=10)
    print(name, "꺼짐, 종료 코드", proc.returncode)
출력
화면 꺼짐, 종료 코드 1
API 꺼짐, 종료 코드 1

아래 그림은 교안을 만든 PC 에서 같은 두 서버를 띄우고 브라우저로 열어, 실습 3과 같은 주문을 넣고 "예측하기"를 누른 화면을 찍은 것입니다. 화면을 본뜬 그림이 아니라 실제 화면의 캡처입니다.

Streamlit 으로 만든 배송 지연 예측 화면에서 지방, 도서, 눈, 빠른별택배, 밤 10시 주문의 지연 확률이 나온 실제 화면

이 실습이 뜻하는 것

이 실습은 본사와 매장 두 곳의 문을 실제로 여는 날과 같습니다. 두 곳의 문을 열고 간판 불이 켜질 때까지 기다리고(1단계), 매장 입구에 무엇이 놓였는지 들여다본 뒤(2단계), 두 곳의 문을 닫습니다(3단계). 여기서 문 열기가 서버 띄우기, 간판 불이 상태 확인 주소(/health, /_stcore/health), 가게의 번지가 포트 번호(8013, 8501)입니다. 비유와 다른 점은, 매장 입구에 놓인 것이 완성된 진열대가 아니라 빈 틀이라는 것입니다. 위젯은 브라우저가 들어온 뒤에 그려집니다.

단계출력뜻
1. 두 서버API: 4.1초 · 화면: 0.0초 만에 준비API 가 답할 때까지 4.1초 걸렸고, 그사이 화면 서버도 떠서 바로 답했습니다. 이 PC 에서 잰 시간입니다
2. 상태 확인ok화면 서버가 살아 있다는 답입니다
2. 첫 페이지6467 글자, 페이지 제목 Streamlit처음 받는 페이지는 위젯이 없는 빈 틀입니다. 위젯은 브라우저가 이 페이지의 자바스크립트를 실행한 뒤 그립니다
2. 주소http://127.0.0.1:8501이 주소를 브라우저로 열면 사람이 화면을 쓸 수 있습니다
3. 끄기종료 코드 1Windows 에서 terminate() 가 프로그램을 강제로 끝냈다는 표시이고, 오류가 아닙니다
실제 화면 그림지연 확률이 나온 화면교안을 만든 PC 에서 같은 두 서버를 띄우고 브라우저로 같은 주문을 넣은 캡처입니다. 68.9% 와 "지연 예상"이 보입니다

한 줄로 줄이면, AppTest 대신 진짜 서버 둘을 띄워 사람이 브라우저로 쓸 수 있는 상태까지 가 본 실습입니다. 서버 둘을 손으로 하나씩 띄우고 끄는 일을 묶어 주는 것이 docker compose 이고, 실습 5와 실습 6에서 그 준비를 합니다. 띄운 곳은 이 PC 하나이고, 다른 PC 에서 접속하는 것이나 여러 사람이 함께 쓸 때의 속도는 아직 재지 않았습니다.

코드 설명

  • subprocess.Popen 으로 API 와 화면을 각각 다른 프로그램으로 띄웠습니다. API 는 4.1초 만에 답했고, 그동안 화면 서버도 떠서 바로(0.0초) 답했습니다. Streamlit 서버의 상태 확인 주소는 /_stcore/health 입니다.
  • 화면 서버가 처음에 주는 것은 6,467글자의 빈 웹 페이지(제목 "Streamlit")입니다. 실제 위젯은 브라우저가 이 페이지의 자바스크립트를 실행한 뒤 서버와 웹소켓으로 주고받으며 그립니다. 그래서 화면을 시험할 때는 페이지를 받아 보는 것보다 AppTest 가 편합니다. 3단계에서 끈 두 서버의 종료 코드가 1인 것은 Windows 에서 terminate() 가 프로그램을 강제로 끝내기 때문이며, 오류가 아닙니다. 아래의 실제 화면 그림도 같은 주문에 68.9% 와 "지연 예상"을 보입니다.
  • --server.headless true 는 서버를 띄울 때 브라우저를 저절로 열지 않게 하고, --browser.gatherUsageStats false 는 Streamlit 이 사용 통계를 보내지 않게 합니다. 폐쇄망에서는 통계 끄기를 꼭 켭니다(11장).
  • 실제로 쓸 때는 터미널 두 개를 열어 하나에서 uvicorn out_delay_api:app --port 8013, 다른 하나에서 streamlit run out_app_api.py 를 실행하고, 브라우저로 http://127.0.0.1:8501 을 엽니다. 끌 때는 각 터미널에서 Ctrl+C 를 누릅니다.
← 이전 절14장 · 실습 3 — 화면을 API 와 잇기다음 절 →14장 · 실습 5 — 컨테이너에 넣을 준비