서비스 학습
SOURCE · be/app/core/config.py · 2026-09-06

실행 위치가 달라도
코드 대신 설정만 바꾼다

macOS에서 직접 실행하면 127.0.0.1, Docker 안에서는 host.docker.internal을 사용한다. 주소·model ID·timeout을 source 밖으로 빼 같은 image를 환경별로 재사용한다.

왜 URL을 Adapter에 고정하지 않는가?

http://127.0.0.1:8080을 코드에 쓰면 host 실행에서는 맞지만 container 안에서는 자기 자신을 가리킨다. 반대로 Docker 전용 주소를 고정하면 local Python 실행이 불편해진다. Environment variable은 “무엇을 할지”라는 code와 “어디에 연결할지”라는 배포 값을 분리한다.

현재 전체 코드

from functools import lru_cache
from typing import Literal

from pydantic import Field, HttpUrl, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    """Configuration loaded from APP_-prefixed environment variables."""

    service_name: str = "local-moe-backend"
    service_version: str = "0.1.0"
    environment: Literal["development", "test", "production"] = "development"
    log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO"
    inference_base_url: HttpUrl = HttpUrl("http://127.0.0.1:8080")
    inference_model: str = Field(default="gemma-4-26b-a4b-it", min_length=1)
    inference_connect_timeout: float = Field(default=3.0, gt=0, allow_inf_nan=False)
    inference_probe_timeout: float = Field(default=5.0, gt=0, allow_inf_nan=False)
    inference_generation_timeout: float = Field(default=300.0, gt=0, allow_inf_nan=False)

    @field_validator("inference_base_url")
    @classmethod
    def require_server_root(cls, value: HttpUrl) -> HttpUrl:
        """Paths live in the adapter, not in deployment configuration."""
        if value.path not in (None, "/") or any(
            part is not None
            for part in (value.username, value.password, value.query, value.fragment)
        ):
            raise ValueError("Use a server root URL without /v1, credentials, query or fragment")
        return value

    model_config = SettingsConfigDict(
        env_prefix="APP_",
        env_file=".env",
        env_file_encoding="utf-8",
        extra="ignore",
    )


@lru_cache
def get_settings() -> Settings:
    """Create one immutable-by-convention settings object per process."""
    return Settings()
소스 동기화: 실제 파일 전체와 동일한 코드를 표시한다.

추론 설정 다섯 개

환경변수기본값검증과 의미
APP_INFERENCE_BASE_URLhttp://127.0.0.1:8080유효한 HTTP(S) URL이며 server root만 허용
APP_INFERENCE_MODELgemma-4-26b-a4b-it빈 문자열 불가, model 목록과 응답에서 재확인
APP_INFERENCE_CONNECT_TIMEOUT3초연결·pool 대기 상한, 0·음수·무한·NaN 불가
APP_INFERENCE_PROBE_TIMEOUT5초health와 models 각각의 HTTP 전송·본문 수신 deadline
APP_INFERENCE_GENERATION_TIMEOUT300초생성 HTTP 요청의 풀 대기·전송·본문 수신 deadline

연결 실패는 빠르게 확인해야 하지만 8GB Mac에서 큰 MoE 생성은 수십 초 이상 걸릴 수 있다. 하나의 짧은 timeout을 모든 작업에 쓰면 정상적인 생성도 실패하므로 목적별로 나눈다. 두 probe 요청에 각각 5초가 적용되므로 readiness 전체의 5초 보장은 아니며, HTTP 수신 뒤의 동기 JSON 검증도 이 deadline 밖이다.

왜 base URL에 /v1을 넣으면 안 되는가?

올바름 base_url = http://127.0.0.1:8080 path = /v1/models result = http://127.0.0.1:8080/v1/models 잘못된 설정 base_url = http://127.0.0.1:8080/v1 path = /health → endpoint 규칙이 설정과 Adapter 두 곳에 분산

require_server_root()는 path, 사용자명·비밀번호, query, fragment가 붙은 URL을 거절한다. Endpoint 경로는 Adapter 하나가 소유해야 변경 지점이 명확하다.

HttpUrl을 쓰면 무엇이 좋아지는가?

단순 strnot-a-url도 받아들인다. HttpUrl은 scheme과 host를 파싱한 object를 만들어 Settings 구성 시점에 잘못된 설정을 발견한다. 기본 실행에서는 app 모듈 import 중 create_app → get_settings에서 발생하므로 lifespan startup에 들어가기 전 실패할 수도 있다. 실제 연결 가능성까지 검사하는 것은 아니며 그것은 /readyz의 역할이다.

Host와 Docker에서 실제 값이 달라지는 과정

macOS Python
127.0.0.1:8080

Python과 TurboFieldfare가 같은 macOS network namespace에 있으므로 loopback으로 연결한다.

Docker Backend
host.docker.internal:8080

Container의 127.0.0.1은 container 자신이다. Docker Desktop의 특별 DNS 이름으로 macOS host를 가리킨다.

docker run ... \ -e APP_INFERENCE_BASE_URL=http://host.docker.internal:8080 \ local-moe-be:step2

실제 container에서 이 주소로 /health, /v1/models, completion이 모두 HTTP 200임을 확인했다. TurboFieldfare 자체의 bind는 계속 127.0.0.1이며 LAN에 직접 공개하지 않았다.

lru_cache와 “immutable-by-convention”

get_settings()를 처음 호출하면 Settings를 만들고, 이후 같은 process에서는 cache된 object를 반환한다. 요청마다 .env를 다시 파싱하지 않는다. 다만 Settings class 자체에 frozen=True를 설정한 것은 아니므로 언어 차원에서 변경 불가능한 object는 아니다. “수정하지 않기로 한 관례”이며 test는 명시적 Settings를 create_app()에 전달해 서로 격리한다.

자주 생기는 오류

증상가능한 원인확인
unavailableserver 미실행, Docker에서 127 사용실행 위치와 base URL 확인
설정 구성 중 validation error/v1 포함, timeout 0, URL 오타.env와 environment 검사
model_mismatch--model-id와 설정이 다름/v1/models 응답 확인
환경변수를 바꿨는데 유지실행 중 process의 cached settingsBackend를 정상 재시작
← 이전main.py다음 →Route 모으기