CHORE(merge): merge from develop
Some checks failed
Build Docker Image / build-and-push (push) Has been cancelled
CI / lint-and-test (push) Has been cancelled

- Initial FastAPI setup and OMR service
- K3S deployment configuration
- Memory limits for image processing
This commit is contained in:
2026-01-06 17:34:49 +09:00
parent 615fe6e574
commit b27daaa00c
19 changed files with 20 additions and 660 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -2,7 +2,7 @@ name: Build Docker Image
on:
push:
branches: [main]
branches: [main, develop]
tags:
- 'v*'
workflow_dispatch:
@@ -13,15 +13,11 @@ env:
jobs:
build-and-push:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
permissions:
contents: write
contents: read
packages: write
outputs:
image-tag: ${{ steps.meta.outputs.tags }}
image-digest: ${{ steps.build.outputs.digest }}
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -34,7 +30,7 @@ jobs:
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
password: ${{ secrets.CR_PAT }}
- name: Lowercase repository name
id: lowercase
@@ -48,111 +44,24 @@ jobs:
images: ${{ env.REGISTRY }}/${{ steps.lowercase.outputs.repo }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix={{branch}}-sha-,format=long
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
id: build
uses: docker/build-push-action@v5
with:
context: ./services/fastapi
file: ./deploy/docker/Dockerfile.prod
context: ./fastapi
file: ./Dockerfile
push: true
platforms: linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Extract SHA tag
id: extract-tag
run: |
# Extract the SHA-based tag from the tags list
TAGS="${{ steps.meta.outputs.tags }}"
echo "All tags:"
echo "$TAGS"
echo "---"
# Get commit SHA (full 40 characters)
COMMIT_SHA="${{ github.sha }}"
# Method 1: Extract the full SHA tag from docker/metadata-action output
# docker/metadata-action creates: main-sha-<full-40-char-sha>
SHA_TAG=$(echo "$TAGS" | grep -oE 'main-sha-[a-f0-9]{40}' | head -n 1)
# Method 2: If not found, try to extract any main-sha- tag (fallback)
if [ -z "$SHA_TAG" ]; then
SHA_TAG=$(echo "$TAGS" | grep -oE 'main-sha-[a-f0-9]+' | head -n 1)
if [ -n "$SHA_TAG" ]; then
echo "⚠️ Found SHA tag (may not be full 40 chars): $SHA_TAG"
fi
fi
# Method 3: Fallback to commit SHA directly (construct the tag)
if [ -z "$SHA_TAG" ]; then
SHA_TAG="main-sha-$COMMIT_SHA"
echo "⚠️ Could not extract from tags, using commit SHA: $SHA_TAG"
fi
if [ -z "$SHA_TAG" ]; then
echo "❌ ERROR: Failed to extract SHA tag"
exit 1
fi
echo "sha-tag=$SHA_TAG" >> $GITHUB_OUTPUT
echo "✅ Extracted SHA tag: $SHA_TAG"
- name: Update kustomization with new image tag
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
# Validate that SHA_TAG is not empty
SHA_TAG="${{ steps.extract-tag.outputs.sha-tag }}"
if [ -z "$SHA_TAG" ]; then
echo "❌ ERROR: SHA_TAG is empty, cannot update kustomization"
exit 1
fi
echo "📝 Updating kustomization.yaml with tag: $SHA_TAG"
# Update kustomization.yaml with new image tag
# Handle both cases: newTag: (with value) and newTag: (empty)
sed -i.bak "s|newTag:.*|newTag: $SHA_TAG|" deploy/k8s/overlays/prod/kustomization.yaml
# Verify the update was successful
if grep -q "newTag: $SHA_TAG" deploy/k8s/overlays/prod/kustomization.yaml; then
echo "✅ Successfully updated kustomization.yaml"
rm -f deploy/k8s/overlays/prod/kustomization.yaml.bak
else
echo "❌ ERROR: Failed to update kustomization.yaml"
cat deploy/k8s/overlays/prod/kustomization.yaml
exit 1
fi
# Commit and push if there are changes
if git diff --quiet; then
echo "No changes to commit"
else
git add deploy/k8s/overlays/prod/kustomization.yaml
git commit -m "Update image to $SHA_TAG"
git push
echo "✅ Kustomization updated with new image tag: $SHA_TAG"
fi
- name: Display image information
run: |
echo "Image built and pushed successfully!"
echo "📦 Image tags:"
echo "Image built and pushed successfully!"
echo "Image tags:"
echo "${{ steps.meta.outputs.tags }}"
echo "🔖 SHA tag: ${{ steps.extract-tag.outputs.sha-tag }}"
echo "🔖 Digest: ${{ steps.build.outputs.digest }}"
echo ""
echo "🚀 Kustomization updated with new image tag"
echo " ArgoCD will automatically detect and deploy this new image"
echo " Monitor deployment at your ArgoCD dashboard"

View File

@@ -19,30 +19,29 @@ jobs:
with:
python-version: '3.11'
cache: 'pip'
cache-dependency-path: services/fastapi/requirements.txt
cache-dependency-path: fastapi/requirements.txt
- name: Install dependencies
working-directory: services/fastapi
working-directory: fastapi
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install ruff pytest httpx
- name: Run Ruff linter
working-directory: services/fastapi
working-directory: fastapi
run: ruff check . --ignore E501
- name: Run Ruff formatter check
working-directory: services/fastapi
working-directory: fastapi
run: ruff format --check . || true
- name: Test FastAPI application import
working-directory: services/fastapi
working-directory: fastapi
run: |
python -c "from main import app; print('FastAPI app imported successfully')"
python -c "from main import app; print('FastAPI app imported successfully')"
- name: Check application health endpoint
working-directory: services/fastapi
working-directory: fastapi
run: |
echo "CI completed successfully"
echo "CI completed successfully"

View File

@@ -3,11 +3,11 @@ FROM python:3.11-slim AS base
# 시스템 의존성 설치 (OpenCV용)
RUN apt-get update && apt-get install -y --no-install-recommends \
libgl1-mesa-glx \
libgl1 \
libglib2.0-0 \
libsm6 \
libxext6 \
libxrender-dev \
libxrender1 \
curl \
&& rm -rf /var/lib/apt/lists/*

View File

@@ -1,42 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: joossam
namespace: argocd
annotations:
argocd-image-updater.argoproj.io/image-list: joossam=ghcr.io/mayne0213/joossam
argocd-image-updater.argoproj.io/joossam.update-strategy: latest
argocd-image-updater.argoproj.io/write-back-method: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/Mayne0213/joossam.git
targetRevision: main
path: deploy/k8s/overlays/prod
destination:
server: https://kubernetes.default.svc
namespace: joossam
syncPolicy:
automated:
prune: true # 매니페스트에서 제거된 리소스 자동 삭제
selfHeal: true # 클러스터에서 수동 변경 시 자동 복구
allowEmpty: false
syncOptions:
- CreateNamespace=true # namespace가 없으면 자동 생성
- PrunePropagationPolicy=foreground
- PruneLast=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
revisionHistoryLimit: 10

View File

@@ -1,34 +0,0 @@
# Development Dockerfile for Joossam FastAPI OMR Grading Service
FROM python:3.11-slim
# 시스템 의존성 설치 (OpenCV용)
RUN apt-get update && apt-get install -y --no-install-recommends \
libgl1-mesa-glx \
libglib2.0-0 \
libsm6 \
libxext6 \
libxrender-dev \
curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Python 의존성 설치
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 개발용 의존성 추가
RUN pip install --no-cache-dir watchfiles
# 애플리케이션 코드 복사
COPY . .
EXPOSE 8000
# 헬스체크
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# 개발 서버 실행 (hot-reload 활성화)
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]

View File

@@ -1,25 +0,0 @@
services:
# Development Joossam FastAPI Application
app:
build:
context: ../../services/fastapi
dockerfile: ../../deploy/docker/Dockerfile.dev
container_name: joossam-app-dev
restart: unless-stopped
labels:
kompose.namespace: joossam
ports:
- 8001:8000
environment:
- ENV=development
networks:
- joossam-network
volumes:
- ../../services/fastapi:/app
command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
networks:
joossam-network:
driver: bridge
name: joossam-network-dev

View File

@@ -1,23 +0,0 @@
services:
# Production Joossam FastAPI Application
app:
image: joossam-app
build:
context: ../../services/fastapi
dockerfile: ../../deploy/docker/Dockerfile.prod
container_name: joossam-app-prod
restart: unless-stopped
labels:
kompose.namespace: joossam
ports:
- 8001:8000
environment:
- ENV=production
networks:
- joossam-network
networks:
joossam-network:
driver: bridge
name: joossam-network-prod

View File

@@ -1,51 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: joossam-app
labels:
app: joossam-app
spec:
replicas: 1
selector:
matchLabels:
app: joossam-app
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
metadata:
labels:
app: joossam-app
spec:
containers:
- name: joossam-app
image: ghcr.io/mayne0213/joossam:latest
imagePullPolicy: Always
ports:
- containerPort: 8000
protocol: TCP
env:
- name: ENV
value: production
resources:
requests:
memory: "150Mi"
cpu: "100m"
limits:
memory: "300Mi"
cpu: "300m"
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 5
restartPolicy: Always

View File

@@ -1,14 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
commonLabels:
app.kubernetes.io/name: joossam
app.kubernetes.io/component: api
images:
- name: ghcr.io/mayne0213/joossam
newTag: latest

View File

@@ -1,15 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: joossam-service
labels:
app: joossam-app
spec:
type: ClusterIP
ports:
- name: http
port: 80
targetPort: 8000
protocol: TCP
selector:
app: joossam-app

View File

@@ -1,19 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: joossam-app
labels:
environment: production
spec:
replicas: 1
template:
spec:
containers:
- name: joossam-app
resources:
requests:
memory: "100Mi"
cpu: "50m"
limits:
memory: "200Mi"
cpu: "200m"

View File

@@ -1,19 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: joossam
resources:
- ../../base
- resourcequota.yaml
commonLabels:
environment: production
# 이미지 태그 설정
images:
- name: ghcr.io/mayne0213/joossam
newTag: latest
patchesStrategicMerge:
- deployment-patch.yaml

View File

@@ -1,12 +0,0 @@
apiVersion: v1
kind: ResourceQuota
metadata:
name: joossam-quota
namespace: joossam
spec:
hard:
requests.memory: "300Mi"
requests.cpu: "200m"
limits.memory: "400Mi"
limits.cpu: "400m"
pods: "3"

View File

@@ -1,11 +1,8 @@
from fastapi import FastAPI, HTTPException, UploadFile, File, Form
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Dict, List, Optional
from typing import Dict
import json
import tempfile
import os
import sys
import cv2
import numpy as np

View File

@@ -1,172 +0,0 @@
#!/bin/bash
# Joossam 스크립트 공통 유틸리티 함수들
# 모든 Joossam 스크립트에서 사용할 수 있는 공통 기능들을 정의
set -e
# 공통 스크립트의 절대 경로 기반 디렉토리 상수
JOOSSAM_SCRIPTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
JOOSSAM_ROOT="$(dirname "$JOOSSAM_SCRIPTS_DIR")"
# 색상 정의
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 로깅 함수들
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_debug() {
echo -e "${BLUE}[DEBUG]${NC} $1"
}
# 경로 계산 함수
get_joossam_root() {
echo "$JOOSSAM_ROOT"
}
get_mayne_root() {
local joossam_root="$(get_joossam_root)"
echo "$(dirname "$(dirname "$joossam_root")")"
}
# 확인 함수
confirm_action() {
local message="$1"
local default="${2:-N}"
if [[ "$default" == "Y" ]]; then
read -p "$message (Y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Nn]$ ]]; then
return 1
fi
return 0
else
read -p "$message (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
return 0
fi
return 1
fi
}
# 필수 디렉토리 확인
check_required_dirs() {
local joossam_root="$(get_joossam_root)"
local dirs=("$@")
for dir in "${dirs[@]}"; do
if [ ! -d "$joossam_root/$dir" ]; then
log_error "필수 디렉토리가 없습니다: $dir"
exit 1
fi
done
}
# 필수 파일 확인
check_required_files() {
local joossam_root="$(get_joossam_root)"
local files=("$@")
for file in "${files[@]}"; do
if [ ! -f "$joossam_root/$file" ]; then
log_error "필수 파일이 없습니다: $file"
exit 1
fi
done
}
# Docker 관련 유틸리티
docker_cleanup_joossam() {
log_info "Joossam 관련 Docker 리소스 정리 중..."
# 컨테이너 중지 및 삭제
docker-compose -p joossam -f deploy/docker/docker-compose.yml down --remove-orphans 2>/dev/null || true
docker-compose -p joossam -f deploy/docker/docker-compose.dev.yml down --remove-orphans 2>/dev/null || true
local containers=$(docker ps -aq --filter "name=joossam" 2>/dev/null)
if [[ -n "$containers" ]]; then
echo "$containers" | xargs docker rm -f 2>/dev/null || true
fi
# 이미지 삭제
local images=$(docker images --filter "reference=joossam*" -q 2>/dev/null)
if [[ -n "$images" ]]; then
echo "$images" | xargs docker rmi -f 2>/dev/null || true
fi
images=$(docker images --filter "reference=*joossam*" -q 2>/dev/null)
if [[ -n "$images" ]]; then
echo "$images" | xargs docker rmi -f 2>/dev/null || true
fi
# 볼륨 삭제
local volumes=$(docker volume ls -q --filter "name=joossam" 2>/dev/null)
if [[ -n "$volumes" ]]; then
echo "$volumes" | xargs docker volume rm 2>/dev/null || true
fi
# 네트워크 삭제
docker network rm joossam-network-dev joossam-network-prod 2>/dev/null || true
# 시스템 정리
docker system prune -f
log_info "Docker 리소스 정리 완료"
}
# 환경 변수 로드
load_env_file() {
local joossam_root="$(get_joossam_root)"
local env_file="$joossam_root/.env"
if [ -f "$env_file" ]; then
log_info "환경 변수 파일 로드: $env_file"
source "$env_file"
return 0
else
log_warn "환경 변수 파일이 없습니다: $env_file"
return 1
fi
}
# 에러 처리
handle_error() {
local exit_code=$?
log_error "스크립트 실행 중 오류가 발생했습니다 (종료 코드: $exit_code)"
exit $exit_code
}
# 스크립트 시작 시 공통 설정
setup_script() {
trap handle_error ERR
cd "$(get_joossam_root)"
log_info "스크립트 시작: $(basename "${BASH_SOURCE[1]}")"
}
# 스크립트 종료 시 정리
cleanup_script() {
local exit_code=$?
if [ $exit_code -eq 0 ]; then
log_info "스크립트 완료: $(basename "${BASH_SOURCE[1]}")"
else
log_error "스크립트 실패: $(basename "${BASH_SOURCE[1]}") (종료 코드: $exit_code)"
fi
exit $exit_code
}
# 스크립트 종료 시 정리 함수 등록
trap cleanup_script EXIT

View File

@@ -1,98 +0,0 @@
#!/bin/bash
# Joossam Docker 빌드 및 실행 스크립트
# 공통 유틸리티 함수 로드
source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
# 스크립트 설정
setup_script
log_info "🚀 Joossam Docker 빌드 및 실행 시작..."
# 필수 디렉토리 및 파일 확인
log_info "📁 폴더 구조 확인 중..."
check_required_dirs "services" "deploy/docker"
log_info "📄 필수 파일 확인 중..."
check_required_files "deploy/docker/docker-compose.yml" "deploy/docker/docker-compose.dev.yml" "deploy/docker/Dockerfile.prod" "deploy/docker/Dockerfile.dev"
log_info "✅ 폴더 구조 및 필수 파일 확인 완료!"
# 환경 선택
echo ""
log_info "🎯 실행할 환경을 선택하세요:"
echo "1) 개발 환경 (Development)"
echo "2) 프로덕션 환경 (Production)"
echo "3) 빌드만 (Build Only)"
read -p "선택 (1-3): " -n 1 -r
echo
case ${REPLY} in
1)
log_info "🔧 개발 환경 빌드 및 실행 중..."
cd deploy/docker
docker-compose -p joossam -f docker-compose.dev.yml build --no-cache
docker-compose -p joossam -f docker-compose.dev.yml up -d
JOOSSAM_ROOT=$(get_joossam_root)
cd "${JOOSSAM_ROOT}"
ENV_TYPE="development"
COMPOSE_FILE_PATH="deploy/docker/docker-compose.dev.yml"
;;
2)
log_info "🏭 프로덕션 환경 빌드 및 실행 중..."
cd deploy/docker
docker-compose -p joossam -f docker-compose.yml build --no-cache
docker-compose -p joossam -f docker-compose.yml up -d
JOOSSAM_ROOT=$(get_joossam_root)
cd "${JOOSSAM_ROOT}"
ENV_TYPE="production"
COMPOSE_FILE_PATH="deploy/docker/docker-compose.yml"
;;
3)
log_info "🔨 이미지 빌드만 실행 중..."
cd deploy/docker
log_info " - 개발 이미지 빌드 중..."
docker-compose -p joossam -f docker-compose.dev.yml build --no-cache
log_info " - 프로덕션 이미지 빌드 중..."
docker-compose -p joossam -f docker-compose.yml build --no-cache
JOOSSAM_ROOT=$(get_joossam_root)
cd "${JOOSSAM_ROOT}"
log_info "✅ 빌드 완료! 실행하려면 다시 이 스크립트를 실행하고 환경을 선택하세요."
exit 0
;;
*)
log_error "잘못된 선택입니다."
exit 1
;;
esac
# 서비스 상태 확인
echo ""
log_info "⏳ 서비스 시작 대기 중..."
sleep 10
echo ""
log_info "📊 서비스 상태 확인:"
docker-compose -p joossam -f "$COMPOSE_FILE_PATH" ps
echo ""
log_info "🔍 컨테이너 로그 확인:"
echo " - 애플리케이션 로그: docker-compose -p joossam -f $COMPOSE_FILE_PATH logs -f app"
echo ""
log_info "🌐 접속 URL:"
if [ "$ENV_TYPE" = "development" ]; then
echo " - API: http://localhost:8001"
echo " - API 문서: http://localhost:8001/docs"
else
echo " - API: http://localhost:8001"
echo " - API 문서: http://localhost:8001/docs"
fi
echo ""
log_info "✅ Docker 빌드 및 실행 완료!"
echo ""
log_info "📋 유용한 명령어:"
echo " - 서비스 중지: docker-compose -p joossam -f $COMPOSE_FILE_PATH down"
echo " - 로그 확인: docker-compose -p joossam -f $COMPOSE_FILE_PATH logs -f"
echo " - 서비스 재시작: docker-compose -p joossam -f $COMPOSE_FILE_PATH restart"

View File

@@ -1,21 +0,0 @@
#!/bin/bash
# Joossam Docker Cleanup Script
# 공통 유틸리티 함수 로드
source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
# 스크립트 설정
setup_script
log_info "🧹 Joossam Docker 리소스 정리 시작..."
# 확인 메시지
if ! confirm_action "모든 Joossam 관련 Docker 리소스를 정리하시겠습니까?"; then
log_info "정리를 취소했습니다."
exit 0
fi
# Docker 리소스 정리 실행
docker_cleanup_joossam
log_info "✅ Joossam Docker 리소스 정리 완료!"