-
Notifications
You must be signed in to change notification settings - Fork 1
[REFACTOR] 인프라 구조를 Docker로 전환 - 2 #240
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
e83a389
cd6fb00
9ad4ad0
e05bfdc
5da021b
f5220bb
36039a7
43c0efe
741cf67
0fbed91
d86844c
aa75b10
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| services: | ||
| application: | ||
| image: debatetimer/debate_timer:${ENV:-dev} | ||
| container_name: application | ||
| environment: | ||
| - SERVER_FORWARD_HEADERS_STRATEGY=framework | ||
| - SPRING_PROFILES_ACTIVE=${ENV:-dev},monitor | ||
| - TZ=Asia/Seoul | ||
|
|
||
| - DD_AGENT_HOST=datadog-agent | ||
| - DD_SERVICE=debate-timer | ||
| - DD_ENV=${ENV:-dev} | ||
| - DD_VERSION=${APP_VERSION:-1.0.0} | ||
| - DD_LOGS_INJECTION=true | ||
| - DD_PROFILING_ENABLED=true | ||
| - DD_PROFILING_ALLOCATION_ENABLED=true | ||
| - DD_PROFILING_HEAP_ENABLED=true | ||
|
|
||
| - MANAGEMENT_STATSD_METRICS_EXPORT_ENABLED=true | ||
| - MANAGEMENT_STATSD_METRICS_EXPORT_FLAVOR=datadog | ||
| - MANAGEMENT_STATSD_METRICS_EXPORT_HOST=datadog-agent | ||
| - MANAGEMENT_STATSD_METRICS_EXPORT_PORT=8125 | ||
| - MANAGEMENT_STATSD_METRICS_EXPORT_PROTOCOL=UDP | ||
|
|
||
| networks: | ||
| - debate-timer-net | ||
|
|
||
| depends_on: | ||
| traefik: | ||
| condition: service_healthy | ||
|
|
||
| healthcheck: | ||
| test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8083/monitoring/health" ] | ||
| interval: 60s | ||
| retries: 10 | ||
| start_period: 450s | ||
|
|
||
| labels: | ||
| - "traefik.enable=true" | ||
| - "traefik.http.routers.application.rule=Host(`api.${ENV:-dev}.debate-timer.com`)" | ||
| - "traefik.http.routers.application.entrypoints=websecure" | ||
| - "traefik.http.routers.application.tls=true" | ||
| - "traefik.http.routers.application.tls.certresolver=myresolver" | ||
| - "traefik.http.routers.application.service=application" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이거 뭔가 했는데 Nginx에서 편집해줬던 443 라우팅 정책이네요 ㄷㄷ traefik이 미래다.. |
||
| - "traefik.http.services.application.loadbalancer.server.port=8080" | ||
|
|
||
| - "traefik.http.routers.application-monitor.rule=PathPrefix(`/`)" | ||
| - "traefik.http.routers.application-monitor.entrypoints=monitoring" | ||
| - "traefik.http.routers.application-monitor.service=application-monitor-svc" | ||
| - "traefik.http.services.application-monitor-svc.loadbalancer.server.port=8083" | ||
|
|
||
| - "com.datadoghq.ad.logs=[{\"source\": \"java\", \"service\": \"debate-timer\"}]" | ||
|
|
||
| restart: unless-stopped | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| services: | ||
| webhook: | ||
| build: | ||
| context: .. | ||
| dockerfile: ./webhook/webhook.Dockerfile | ||
| container_name: webhook | ||
| user: root | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 후순위긴 하지만 이거 나중에 시도해봐도 좋을 듯 👍 |
||
| command: ["-verbose", "-hooks=/etc/webhook/hooks.json", "-hotreload"] | ||
| environment: | ||
| - ENV=${ENV:-dev} | ||
| - SECRET_TOKEN=${WEBHOOK_KEY} | ||
| labels: | ||
| - "traefik.enable=true" | ||
| - "traefik.http.routers.webhook.rule=Host(`webhook.${ENV:-dev}.debate-timer.com`)" | ||
| - "traefik.http.routers.webhook.entrypoints=websecure" | ||
| - "traefik.http.routers.webhook.tls=true" | ||
| - "traefik.http.routers.webhook.tls.certresolver=myresolver" | ||
| - "traefik.http.services.webhook.loadbalancer.server.port=9000" | ||
| volumes: | ||
| - /var/run/docker.sock:/var/run/docker.sock | ||
| - /home/ubuntu/.docker:/root/.docker:ro | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| - ./hooks.json:/etc/webhook/hooks.json | ||
| - ../..:/home/ubuntu/debate-timer | ||
| networks: | ||
| - debate-timer-net | ||
| depends_on: | ||
| traefik: | ||
| condition: service_healthy | ||
| healthcheck: | ||
| test: ["CMD-SHELL", "nc -z localhost 9000 || exit 1"] | ||
| interval: 120s | ||
| timeout: 30s | ||
| retries: 5 | ||
| start_period: 30s | ||
| restart: always | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| [ | ||
| { | ||
| "id": "deploy", | ||
| "http-methods": ["POST"], | ||
| "execute-command": "/home/ubuntu/debate-timer/scripts/deploy/deploy-docker.sh", | ||
| "command-working-directory": "/home/ubuntu/debate-timer", | ||
| "pass-environment-to-command": [ | ||
| { | ||
| "source": "header", | ||
| "name": "X-Deploy-Token", | ||
| "envname": "REQUEST_TOKEN" | ||
| } | ||
| ], | ||
| "pass-arguments-to-command": [ | ||
| { "source": "entire-payload" } | ||
| ] | ||
| }, | ||
| { | ||
| "id": "status", | ||
| "http-methods": ["GET"], | ||
| "execute-command": "/home/ubuntu/debate-timer/scripts/deploy/check-deploy-status.sh", | ||
| "command-working-directory": "/home/ubuntu/debate-timer", | ||
| "include-command-output-in-response": true, | ||
| "response-headers": [ | ||
| { | ||
| "name": "Content-Type", | ||
| "value": "application/json" | ||
| } | ||
| ], | ||
| "pass-environment-to-command": [ | ||
| { | ||
| "source": "header", | ||
| "name": "X-Deploy-Token", | ||
| "envname": "REQUEST_TOKEN" | ||
| } | ||
| ] | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ] | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,7 @@ | ||||||||||||||||||||
| FROM almir/webhook:2.8.3 | ||||||||||||||||||||
| USER root | ||||||||||||||||||||
| RUN apk add --no-cache docker-cli-compose curl bash util-linux git | ||||||||||||||||||||
|
|
||||||||||||||||||||
| WORKDIR /etc/webhook | ||||||||||||||||||||
|
Comment on lines
+2
to
+5
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify USER declarations, docker.sock mounts, and socket access configuration
rg -n "^\s*USER\s+|^\s*user:\s*|/var/run/docker.sock|group_add" docker/webhook/webhook.Dockerfile docker/webhook/docker-compose.webhook.ymlRepository: debate-timer/debate-timer-be Length of output: 271 🏁 Script executed: #!/bin/bash
# Read the full Dockerfile to understand the complete context
cat -n docker/webhook/webhook.DockerfileRepository: debate-timer/debate-timer-be Length of output: 229 🏁 Script executed: #!/bin/bash
# Check if docker-compose file exists and review its contents
if [ -f docker/webhook/docker-compose.webhook.yml ]; then
cat -n docker/webhook/docker-compose.webhook.yml
else
# Try other common naming patterns
find docker/webhook -name "*compose*" -type f 2>/dev/null | head -10
fiRepository: debate-timer/debate-timer-be Length of output: 1516 🏁 Script executed: #!/bin/bash
# Check for any entrypoint or command specifications
rg -n "ENTRYPOINT|CMD|entrypoint|command:" docker/webhook/ -A 2Repository: debate-timer/debate-timer-be Length of output: 871 웹훅 프로세스가 루트로 실행되는 문제를 모두 해결해야 합니다. Dockerfile의 웹훅이 docker.sock에 접근해야 하므로, 비루트 사용자를 docker 그룹에 추가하는 방식으로 권한을 분리하세요: 권장 수정docker/webhook/webhook.Dockerfile: FROM almir/webhook:2.8.3
USER root
RUN apk add --no-cache docker-cli docker-cli-compose curl bash util-linux
+RUN addgroup -S webhook && adduser -S -G webhook webhook && addgroup webhook docker
WORKDIR /etc/webhook
+USER webhookdocker/webhook/docker-compose.webhook.yml: webhook:
build:
context: ..
dockerfile: ./webhook/webhook.Dockerfile
container_name: webhook
- user: root
command: ["-verbose", "-hooks=/etc/webhook/hooks.json", "-hotreload"]📝 Committable suggestion
Suggested change
🧰 Tools🪛 Trivy (0.69.1)[error] 2-2: Image user should not be 'root' Last USER command in Dockerfile should not be 'root' Rule: DS-0002 (IaC/Dockerfile) 🤖 Prompt for AI Agents |
||||||||||||||||||||
|
|
||||||||||||||||||||
| RUN git config --global --add safe.directory /home/ubuntu/debate-timer | ||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# 목적: safe.directory 설정 경로와 실제 마운트 경로 정합성 점검(읽기 전용)
set -e
echo "[1] safe.directory 관련 설정 검색"
rg -n -C2 'safe\.directory|/home/ubuntu/debate-timer|GIT_SAFE_DIRECTORY' docker
echo
echo "[2] webhook compose의 볼륨/경로 설정 검색"
rg -n -C3 'webhook|volumes:|/home/ubuntu/debate-timer|hooks\.json' dockerRepository: debate-timer/debate-timer-be Length of output: 6613 🏁 Script executed: #!/bin/bash
# Check full Dockerfile content
echo "[1] Full webhook.Dockerfile"
cat -n docker/webhook/webhook.Dockerfile
echo
echo "[2] Full hooks.json content"
cat -n docker/webhook/hooks.json
echo
echo "[3] Full docker-compose.webhook.yml"
cat -n docker/webhook/docker-compose.webhook.ymlRepository: debate-timer/debate-timer-be Length of output: 3040 배포 이식성을 깨뜨리는 하드코딩된 경로는 Line 7의 권장 수정안 FROM almir/webhook:2.8.3
USER root
RUN apk add --no-cache docker-cli-compose curl bash util-linux git
+ARG GIT_SAFE_DIRECTORY=/home/ubuntu/debate-timer
WORKDIR /etc/webhook
-RUN git config --global --add safe.directory /home/ubuntu/debate-timer
+RUN git config --system --add safe.directory "${GIT_SAFE_DIRECTORY}"(추가로 🤖 Prompt for AI Agents |
||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| #!/bin/bash | ||
|
|
||
| if [ -z "$SECRET_TOKEN" ] || [ "$REQUEST_TOKEN" != "$SECRET_TOKEN" ]; then | ||
| echo '{"status": "error", "message": "Unauthorized request. Invalid Token."}' | ||
| exit 0 | ||
| fi | ||
leegwichan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| LOCK_FILE="/tmp/deploy-application.lock" | ||
|
|
||
| exec 200>"$LOCK_FILE" | ||
|
|
||
| if ! flock -n 200; then | ||
| echo '{"status": "running", "message": "배포 스크립트가 현재 실행 중입니다."}' | ||
| else | ||
| flock -u 200 | ||
| echo '{"status": "idle", "message": "현재 진행 중인 배포가 없습니다. 대기 중입니다."}' | ||
| fi | ||
|
|
||
| exec 200>&- | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| #!/bin/bash | ||
|
|
||
| if [ -z "$SECRET_TOKEN" ] || [ "$REQUEST_TOKEN" != "$SECRET_TOKEN" ]; then | ||
| echo "[$(date)] 🚨 보안 경고: 유효하지 않은 배포 요청입니다. (Unauthorized)" | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "스크립트 실행 여부를 확인합니다." | ||
| LOCK_FILE="/tmp/deploy-application.lock" | ||
| exec 200>"$LOCK_FILE" | ||
| flock -n 200 || { echo "⚠️ 이미 배포 스크립트가 실행 중입니다. 중복 실행을 차단합니다."; exit 1; } | ||
| echo "스크립트를 실행하고 있지 않습니다. 배포를 시작합니다." | ||
|
|
||
| TARGET_ENV=${ENV:-dev} | ||
| PROJECT_DIR="/home/ubuntu/debate-timer" | ||
| DOCKER_DIR="/home/ubuntu/debate-timer/docker" | ||
| TARGET_SERVICE="application" | ||
|
|
||
| echo "--- Git 저장소 최신화 시작 ---" | ||
| cd $PROJECT_DIR || { echo "프로젝트 디렉토리 이동 실패"; exit 1; } | ||
|
|
||
leegwichan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if [ "$TARGET_ENV" = "prod" ]; then | ||
| echo "▶ [ENV: prod] main 브랜치로 이동하여 최신 코드를 가져옵니다." | ||
| git fetch origin main || { echo "❌ git fetch(main) 실패"; exit 1; } | ||
| git switch main || { echo "❌ git switch(main) 실패"; exit 1; } | ||
| git reset --hard origin/main || { echo "❌ git reset(main) 실패"; exit 1; } | ||
| elif [ "$TARGET_ENV" = "dev" ]; then | ||
| echo "▶ [ENV: dev] develop 브랜치로 이동하여 최신 코드를 가져옵니다." | ||
| git fetch origin develop || { echo "❌ git fetch(develop) 실패"; exit 1; } | ||
| git switch develop || { echo "❌ git switch(develop) 실패"; exit 1; } | ||
| git reset --hard origin/develop || { echo "❌ git reset(develop) 실패"; exit 1; } | ||
| else | ||
| CURRENT_BRANCH=$(git branch --show-current) | ||
| if [ -z "$CURRENT_BRANCH" ]; then | ||
| echo "❌ 현재 브랜치를 확인할 수 없습니다 (detached HEAD 상태일 수 있음)" | ||
| exit 1 | ||
| fi | ||
| echo "▶ [ENV: $TARGET_ENV] 현재 브랜치($CURRENT_BRANCH)에서 최신 코드를 가져옵니다." | ||
| git fetch origin "$CURRENT_BRANCH" || { echo "❌ git fetch($CURRENT_BRANCH) 실패"; exit 1; } | ||
| git reset --hard origin/"$CURRENT_BRANCH" || { echo "❌ git reset($CURRENT_BRANCH) 실패"; exit 1; } | ||
| fi | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| cd $DOCKER_DIR || { echo "디렉토리 이동 실패"; exit 1; } | ||
| echo "--- 중단 배포 시작 ENV: $TARGET_ENV at $(date) ---" | ||
| export ENV=$TARGET_ENV | ||
|
|
||
| echo "최근 이미지 가져오는 중 (ENV: $TARGET_ENV)" | ||
| docker compose pull $TARGET_SERVICE || { echo "❌ 이미지 풀 실패"; exit 1; } | ||
| if docker ps --format '{{.Names}}' | grep -q "^${TARGET_SERVICE}$"; then | ||
| echo "기존 $TARGET_SERVICE 중지 및 제거 중..." | ||
| docker compose stop $TARGET_SERVICE | ||
| fi | ||
|
|
||
| echo "새 컨테이너 실행 중 - $TARGET_SERVICE..." | ||
| docker compose up -d --no-deps $TARGET_SERVICE || { echo "❌ 컨테이너 실행 실패"; exit 1; } | ||
|
|
||
| echo "헬스 체크 진행 중 - $TARGET_SERVICE" | ||
| MAX_RETRIES=50 | ||
| SLEEP_SECOND=10 | ||
| COUNT=0 | ||
|
|
||
| while [ $COUNT -lt $MAX_RETRIES ]; do | ||
| HEALTH_STATUS=$(docker inspect --format='{{.State.Health.Status}}' "$TARGET_SERVICE" 2>/dev/null) | ||
| if [ "$HEALTH_STATUS" = "healthy" ]; then | ||
| echo "헬스 체크 완료 - $TARGET_SERVICE" | ||
| break | ||
| fi | ||
|
|
||
| echo "헬스체크 진행 중 ($COUNT/$MAX_RETRIES) - 현재 상태: $HEALTH_STATUS" | ||
| sleep $SLEEP_SECOND | ||
| COUNT=$((COUNT + 1)) | ||
| done | ||
|
|
||
| if [ $COUNT -eq $MAX_RETRIES ]; then | ||
| echo "배포 실패: $TARGET_SERVICE가 healthy 상태가 되지 않았습니다." | ||
| docker logs --tail 50 $TARGET_SERVICE | ||
| exit 1 | ||
| fi | ||
|
|
||
| docker image prune -f | ||
|
|
||
| echo "--- $TARGET_ENV 배포 완료 at $(date) ---" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[사소한 의견]
오... 거의 7분 30초동안은 실패 카운트 안하는 것 같은데 혹시 기준점이 따로 있엇나요?
10분 동안 애플리케이션 healthcheck 시도할 수도 있을 것 같아서 period와 retries 횟수 조금 줄이는 건 어떨까... 하는 사견입니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
메모리 1GB, 스왑 메모리 2GB 기준 평균 Spring Boot 로딩 시간이 5분입니다. (H2 내장 DB 사용 기준)
그래서 일단 7.5분으로 진행하고, 나중에 DEV, PROD 부팅 평균 시간을 측정해서 개선해 나가는게 좋을 것 같아요!