From cb70226f4268f04c8830af405c93a450a5f4d355 Mon Sep 17 00:00:00 2001 From: gahusb Date: Sat, 23 May 2026 12:10:29 +0900 Subject: [PATCH] feat(image-render): main + Dockerfile + compose entry (port 18714) --- services/docker-compose.yml | 27 +++++++++++++++++++ services/image-render/Dockerfile | 16 ++++++++++++ services/image-render/env.example | 18 +++++++++++++ services/image-render/main.py | 36 ++++++++++++++++++++++++++ services/image-render/requirements.txt | 9 +++++++ 5 files changed, 106 insertions(+) create mode 100644 services/image-render/Dockerfile create mode 100644 services/image-render/env.example create mode 100644 services/image-render/main.py create mode 100644 services/image-render/requirements.txt diff --git a/services/docker-compose.yml b/services/docker-compose.yml index dd5275e..03f6fcf 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -95,3 +95,30 @@ services: interval: 60s timeout: 5s retries: 3 + + image-render: + build: + context: ./image-render + container_name: image-render + restart: unless-stopped + ports: + - "18714:8000" + environment: + - TZ=Asia/Seoul + - REDIS_URL=${REDIS_URL:-redis://192.168.45.54:6379} + - NAS_BASE_URL=${NAS_BASE_URL:-http://192.168.45.54:18802} + - INTERNAL_API_KEY=${INTERNAL_API_KEY:-} + - OPENAI_API_KEY=${OPENAI_API_KEY:-} + - GEMINI_API_KEY=${GEMINI_API_KEY:-} + - COMFYUI_URL=${COMFYUI_URL:-http://host.docker.internal:8188} + - FLUX_BLOCK_TRADING_HOURS=${FLUX_BLOCK_TRADING_HOURS:-1} + - IMAGE_MEDIA_ROOT=${IMAGE_MEDIA_ROOT:-/mnt/nas/webpage/data/image} + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - /mnt/nas/webpage/data/image:/mnt/nas/webpage/data/image + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"] + interval: 60s + timeout: 5s + retries: 3 diff --git a/services/image-render/Dockerfile b/services/image-render/Dockerfile new file mode 100644 index 0000000..ab72368 --- /dev/null +++ b/services/image-render/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.12-slim-bookworm +ENV PYTHONUNBUFFERED=1 + +WORKDIR /app + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +COPY requirements.txt . +RUN pip install --no-cache-dir --timeout 600 --retries 5 -r requirements.txt + +COPY . . + +EXPOSE 8000 +CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"] diff --git a/services/image-render/env.example b/services/image-render/env.example new file mode 100644 index 0000000..cfab515 --- /dev/null +++ b/services/image-render/env.example @@ -0,0 +1,18 @@ +# Redis (NAS) +REDIS_URL=redis://192.168.45.54:6379 + +# NAS image-lab webhook +NAS_BASE_URL=http://192.168.45.54:18802 +INTERNAL_API_KEY=replace-me + +# API provider keys (worker reports failed if missing) +OPENAI_API_KEY= +GEMINI_API_KEY= +# Seedance key not used by image-render + +# FLUX local +COMFYUI_URL=http://host.docker.internal:8188 +FLUX_BLOCK_TRADING_HOURS=1 + +# NAS SMB mount target (image-render writes to this, NAS reads via /media/image/) +IMAGE_MEDIA_ROOT=/mnt/nas/webpage/data/image diff --git a/services/image-render/main.py b/services/image-render/main.py new file mode 100644 index 0000000..25dad49 --- /dev/null +++ b/services/image-render/main.py @@ -0,0 +1,36 @@ +"""image-render FastAPI entry — health + lifespan (worker loop spawn).""" +from __future__ import annotations + +import asyncio +import logging +from contextlib import asynccontextmanager + +from fastapi import FastAPI + +import worker + +logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s") +logger = logging.getLogger(__name__) + + +@asynccontextmanager +async def lifespan(app: FastAPI): + worker_task = asyncio.create_task(worker.worker_loop()) + logger.info("image-render lifespan 시작") + try: + yield + finally: + worker_task.cancel() + try: + await worker_task + except asyncio.CancelledError: + pass + logger.info("image-render lifespan 종료") + + +app = FastAPI(lifespan=lifespan) + + +@app.get("/health") +def health(): + return {"ok": True, "service": "image-render"} diff --git a/services/image-render/requirements.txt b/services/image-render/requirements.txt new file mode 100644 index 0000000..631b3a0 --- /dev/null +++ b/services/image-render/requirements.txt @@ -0,0 +1,9 @@ +fastapi==0.115.6 +uvicorn[standard]==0.34.0 +requests==2.32.3 +redis>=5.0 +httpx>=0.27 +openai>=1.50.0 +pytest>=8.0 +pytest-asyncio>=0.24 +respx>=0.21