diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml new file mode 100644 index 0000000..3ee7fae --- /dev/null +++ b/.github/workflows/workflow.yml @@ -0,0 +1,73 @@ +name: workflow.yml +on: + push: + branches: [ main ] + +jobs: + + build: + name: Build Image + runs-on: ubuntu-24.04 + + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} + aws-region: ap-northeast-2 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + run: | + docker build -t dearbelly-cv . + docker tag dearbelly-cv:latest ${{ secrets.ECR_URI }}/dearbelly-cv + docker push ${{ secrets.ECR_URI }}/dearbelly-cv:latest + + transfer: + runs-on: ubuntu-24.04 + needs: build + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + + deploy: + name: Deploy + runs-on: ubuntu-24.04 + needs: transfer + steps: + - name: SSH into EC2 server and Deploy + uses: appleboy/ssh-action@v1 + with: + host: ${{ secrets.REMOTE_HOST }} + username: ${{ secrets.REMOTE_USER }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + port: 22 + script: | + # 1. cd + mkdir -p ~/dearbelly + cd ~/dearbelly + + # 2. .env file + echo "${{ secrets.ENV }}" > .env + sudo chmod 600 .env + + # 3. aws login + aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin ${{ secrets.ECR_URI }} + + # 4. export env + export ECR_URI=${{ secrets.ECR_URI }} + + # 5. pull Image + docker pull ${{ secrets.ECR_URI }}/dearbelly-cv:latest + + # 6. docker start + # TODO : 스크립트 작성 + docker run -d --name app-blue -p 8000:8000 ${{ secrets.ECR_URI }}/dearbelly-cv:latest \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a8e773f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM nvidia/cuda:12.6.0-runtime-ubuntu24.04 + +WORKDIR /app + +RUN apt-get update && \ + apt-get install -y python3 python3-pip git && \ + ln -s /usr/bin/python3 /usr/bin/python && \ + pip install --upgrade pip && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +COPY . . + +RUN pip install --no-cache-dir -r app/requirements.txt + +EXPOSE 8000 + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/app/core/config.py b/app/core/config.py index e6db7c6..5e336f3 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -1,15 +1,22 @@ from pydantic_settings import BaseSettings import os +from dotenv import load_dotenv + +load_dotenv() + +redis_url = os.getenv("REDIS_SERVER_URL") +bucket_name = os.getenv("BUCKET_NAME") +s3_region = os.getenv("S3_REGION") + class Settings(BaseSettings): - REDIS_URL: str = os.environ.get("REDIS_SERVER_URL", "redis://localhost:6379") - S3_REGION: str = os.environ.get("S3_REGION", "ap-northeast-2") - S3_ENDPOINT_URL: str | None = os.environ.get("S3_ENDPOINT_URL") - BUCKET_NAME: str = os.environ.get("BUCKET_NAME") + REDIS_URL: str = redis_url + S3_REGION: str = bucket_name + BUCKET_NAME: str = s3_region - STREAM_JOB: str = "image.jobs" - STREAM_RESULT: str = "image.results" - GROUP_NAME: str = "fastapi-workers" + STREAM_JOB: str = "image.jobs" # SpringBoot에서 job 발행 (FastAPI에서 listen) + STREAM_RESULT: str = "image.results" # FastAPI에서 결과 발행 (SpringBoot에서 listen) + GROUP_NAME: str = "fastapi-workers" # FastAPI Consumer group name CONSUMER_NAME: str = "consumer-1" diff --git a/app/core/lifespan.py b/app/core/lifespan.py index 73635a5..9bdc15f 100644 --- a/app/core/lifespan.py +++ b/app/core/lifespan.py @@ -25,9 +25,6 @@ async def lifespan(app: FastAPI): worker = JobWorker(redis_client) worker_task = asyncio.create_task(worker.run()) - app.state.redis_client = redis_client - app.state.worker_task = worker_task - try: yield finally: diff --git a/app/main.py b/app/main.py index 1c8c224..fa3dff5 100644 --- a/app/main.py +++ b/app/main.py @@ -3,9 +3,9 @@ from app.api.endpoints import predictions app = FastAPI( - title="DearBelly AI-Powered Pill Identification Service", - description="An AI service to identify pills from images, using a job queue for async processing.", - version="2.0.0", + title="DearBelly CV API", + description="DearBelly CV를 위한 Swagger 입니다.", + version="1.0.0", lifespan=lifespan ) diff --git a/app/schemas/job.py b/app/schemas/job.py index 6c71872..4313bc5 100644 --- a/app/schemas/job.py +++ b/app/schemas/job.py @@ -12,10 +12,10 @@ class JobResult(BaseModel): finished_at: str class ImageJob(BaseModel): - correlationId: str - presignedUrl: str - replyQueue: str - callbackUrl: str | None = None - contentType: str - createdAt: str - ttlSec: int + correlation_id: str = Field(alias="correlationId") + presigned_url: str = Field(alias="presignedUrl") + reply_queue: str = Field(alias="replyQueue") + callback_url: str | None = Field(alias="callbackUrl") + content_type: str = Field(alias="contentType") + created_at: str = Field(alias="createdAt") + ttl_sec: int = Field(alias="ttlSec") diff --git a/app/services/s3_service.py b/app/services/s3_service.py index 44bb85d..b85d4fc 100644 --- a/app/services/s3_service.py +++ b/app/services/s3_service.py @@ -11,7 +11,6 @@ def __init__(self): self.client = boto3.client( "s3", region_name=settings.S3_REGION, - endpoint_url=settings.S3_ENDPOINT_URL, config=BotoConfig( retries={"max_attempts": 5, "mode": "standard"}, read_timeout=30, diff --git a/app/worker/redis_client.py b/app/worker/redis_client.py index cf9e793..8b7339b 100644 --- a/app/worker/redis_client.py +++ b/app/worker/redis_client.py @@ -1,17 +1,9 @@ import redis -from dotenv import load_dotenv -import os from pydantic import BaseModel, Field from typing import Any, Dict from app.core.config import settings -load_dotenv() - -redis_host = os.environ.get("REDIS_SERVER_HOST") - -redis_client = redis.Redis( - host=redis_host, port=6379, encoding="UTF-8", decode_responses=True -) +redis_client = redis.asyncio.from_url(settings.REDIS_URL, decode_responses=True) class PublishRequest(BaseModel): stream: str = Field(default=settings.JOB_STREAM, description="Redis Stream Job name") diff --git a/requirements.txt b/requirements.txt index 90373b8..5cf02b2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,5 @@ boto3 requests torch==2.8.0 torchvision==0.23.0 -Pillow==11.3.0 \ No newline at end of file +Pillow==11.3.0 +dotenv \ No newline at end of file