-
Notifications
You must be signed in to change notification settings - Fork 0
[FEAT] 무중단 배포 도입 #352
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
[FEAT] 무중단 배포 도입 #352
Changes from all commits
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 |
|---|---|---|
|
|
@@ -54,6 +54,7 @@ dependencies { | |
| implementation 'org.springframework.boot:spring-boot-starter-actuator' | ||
| implementation 'ch.qos.logback.contrib:logback-json-classic:0.1.5' | ||
| implementation 'ch.qos.logback.contrib:logback-jackson:0.1.5' | ||
| implementation 'com.github.maricn:logback-slack-appender:1.6.1' | ||
|
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🌐 Web query:
💡 Result: No, com.github.maricn:logback-slack-appender:1.6.1 is likely not fully compatible with Logback 1.5.x (used by Spring Boot 3.5.x). The original repository is archived since Feb 2024 with no updates since 2021, and the maintainer recommends the maintained fork https://github.com/cyfrania/logback-slack-appender, which explicitly supports Logback 1.3.x/1.4.x but notes Java 11+ for 1.4.x (Spring Boot 3.5.x requires Java 21+). The original appender depends on Logback via ${logback.version} (unspecified concrete version), but lacks confirmation of testing with Logback 1.5.x changes (e.g., supplier patterns). Potential configuration or API incompatibilities may arise, as seen in other Logback appenders with Spring Boot 3.x. Use the cyfrania fork (v1.2, 2024) for better compatibility. Citations:
🏁 Script executed: cat build.gradleRepository: samhap-soft/kokomen-backend Length of output: 1905 Spring Boot 3.5.3과 호환되지 않는 Logback appender 라이브러리 교체 필요 Line 57의 대신 유지보수 중인 포크인 🤖 Prompt for AI Agents |
||
|
|
||
| // PDF 텍스트 추출 | ||
| implementation 'org.apache.pdfbox:pdfbox:3.0.3' | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| #!/bin/bash | ||
| set -e | ||
|
|
||
| SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" | ||
| COMPOSE_FILE="$SCRIPT_DIR/docker-compose-prod.yml" | ||
| HEALTH_TIMEOUT=120 | ||
| HEALTH_INTERVAL=5 | ||
| GRACEFUL_SHUTDOWN_WAIT=65 | ||
|
|
||
| log_info() { echo "[INFO] $1"; } | ||
| log_warn() { echo "[WARN] $1"; } | ||
| log_error() { echo "[ERROR] $1"; } | ||
|
|
||
| get_active() { | ||
| if docker ps -q -f name=kokomen-api-blue | grep -q .; then | ||
| echo "blue" | ||
| elif docker ps -q -f name=kokomen-api-green | grep -q .; then | ||
| echo "green" | ||
| else | ||
| echo "none" | ||
| fi | ||
| } | ||
|
Comment on lines
+14
to
+22
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.
🔧 제안 수정+DOCKER="sudo -E docker"
...
- if docker ps -q -f name=kokomen-api-blue | grep -q .; then
+ if $DOCKER ps -q -f name=kokomen-api-blue | grep -q .; then
...
- status=$(docker inspect --format='{{.State.Health.Status}}' "$container" 2>/dev/null || echo "starting")
+ status=$($DOCKER inspect --format='{{.State.Health.Status}}' "$container" 2>/dev/null || echo "starting")
...
- if ! docker ps -q -f name=traefik | grep -q .; then
+ if ! $DOCKER ps -q -f name=traefik | grep -q .; then
...
- sudo -E docker compose -f $COMPOSE_FILE up -d traefik
+ $DOCKER compose -f "$COMPOSE_FILE" up -d traefik
...
- sudo -E docker compose -f $COMPOSE_FILE --profile $TARGET up -d "kokomen-api-$TARGET"
+ $DOCKER compose -f "$COMPOSE_FILE" --profile "$TARGET" up -d "kokomen-api-$TARGET"
...
- docker rm -f "kokomen-api-$TARGET" 2>/dev/null || true
+ $DOCKER rm -f "kokomen-api-$TARGET" 2>/dev/null || true
...
- docker stop -t $GRACEFUL_SHUTDOWN_WAIT "$OLD" || true
- docker rm -f "$OLD" 2>/dev/null || true
+ $DOCKER stop -t "$GRACEFUL_SHUTDOWN_WAIT" "$OLD" || true
+ $DOCKER rm -f "$OLD" 2>/dev/null || trueAlso applies to: 23-44, 67-75, 79-82, 93-94 🤖 Prompt for AI Agents |
||
|
|
||
| wait_healthy() { | ||
| local container=$1 | ||
| local elapsed=0 | ||
|
|
||
| log_info "헬스체크 대기: $container (타임아웃: ${HEALTH_TIMEOUT}초)" | ||
|
|
||
| while [ $elapsed -lt $HEALTH_TIMEOUT ]; do | ||
| status=$(docker inspect --format='{{.State.Health.Status}}' "$container" 2>/dev/null || echo "starting") | ||
| if [ "$status" = "healthy" ]; then | ||
| echo "" | ||
| log_info "헬스체크 통과! (${elapsed}초 소요)" | ||
| return 0 | ||
| fi | ||
| sleep $HEALTH_INTERVAL | ||
| elapsed=$((elapsed + HEALTH_INTERVAL)) | ||
| echo -n "." | ||
| done | ||
|
|
||
| echo "" | ||
| log_error "헬스체크 실패: 타임아웃 (${HEALTH_TIMEOUT}초)" | ||
| return 1 | ||
| } | ||
|
|
||
| main() { | ||
| log_info "========== Blue-Green 배포 시작 ==========" | ||
|
|
||
| CURRENT=$(get_active) | ||
| log_info "현재 활성 환경: $CURRENT" | ||
|
|
||
| if [ "$CURRENT" = "blue" ]; then | ||
| TARGET="green" | ||
| OLD="kokomen-api-blue" | ||
| elif [ "$CURRENT" = "green" ]; then | ||
| TARGET="blue" | ||
| OLD="kokomen-api-green" | ||
| else | ||
| TARGET="blue" | ||
| OLD="" | ||
| log_info "최초 배포: blue 환경으로 시작" | ||
| fi | ||
|
|
||
| log_info "타겟 환경: $TARGET" | ||
|
|
||
| # Step 1: Traefik이 실행 중인지 확인 | ||
| if ! docker ps -q -f name=traefik | grep -q .; then | ||
| log_info "Step 0: Traefik 시작" | ||
| sudo -E docker compose -f $COMPOSE_FILE up -d traefik | ||
|
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. |
||
| sleep 3 | ||
| fi | ||
|
|
||
| # Step 2: 새 컨테이너 시작 | ||
| log_info "Step 1: $TARGET 컨테이너 시작" | ||
| sudo -E docker compose -f $COMPOSE_FILE --profile $TARGET up -d "kokomen-api-$TARGET" | ||
|
|
||
| # Step 3: 헬스체크 대기 | ||
| log_info "Step 2: 헬스체크 수행" | ||
| if ! wait_healthy "kokomen-api-$TARGET"; then | ||
| log_error "배포 실패: 새 컨테이너 헬스체크 실패" | ||
| log_warn "롤백: 새 컨테이너 제거" | ||
| docker rm -f "kokomen-api-$TARGET" 2>/dev/null || true | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Step 4: Traefik 라우팅 안정화 대기 | ||
| log_info "Step 3: Traefik 라우팅 안정화 대기" | ||
| sleep 5 | ||
|
|
||
| # Step 5: 기존 컨테이너 graceful 종료 | ||
| if [ -n "$OLD" ]; then | ||
| log_info "Step 4: 기존 컨테이너 종료 ($OLD, ${GRACEFUL_SHUTDOWN_WAIT}초 대기)" | ||
| docker stop -t $GRACEFUL_SHUTDOWN_WAIT "$OLD" || true | ||
| docker rm -f "$OLD" 2>/dev/null || true | ||
| log_info "기존 컨테이너 종료 완료" | ||
| fi | ||
|
|
||
| # Step 6: 완료 확인 | ||
| log_info "Step 5: 배포 완료 확인" | ||
| if curl -sf "http://localhost:80/actuator/health" > /dev/null 2>&1; then | ||
| log_info "========== 배포 성공! ==========" | ||
| log_info "활성 환경: $TARGET" | ||
| else | ||
| log_warn "경고: 외부 헬스체크 실패 (Traefik 라우팅 확인 필요)" | ||
| fi | ||
| } | ||
|
|
||
| main "$@" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,25 @@ | ||
| services: | ||
| kokomen-interview-api-prod: | ||
| traefik: | ||
| image: traefik:v3.0 | ||
| container_name: traefik | ||
| restart: unless-stopped | ||
| ports: | ||
| - "80:80" | ||
| volumes: | ||
| - /var/run/docker.sock:/var/run/docker.sock:ro | ||
| - ./traefik/traefik.yml:/etc/traefik/traefik.yml:ro | ||
| environment: | ||
| TZ: Asia/Seoul | ||
| networks: | ||
| - api-net | ||
|
|
||
| kokomen-api-blue: | ||
| image: samhap/kokomen-interview-api:prod | ||
| container_name: kokomen-interview-api-prod | ||
| container_name: kokomen-api-blue | ||
| restart: on-failure:3 | ||
| ports: | ||
| - "8080:8080" | ||
| - "8081:8081" | ||
| expose: | ||
| - "8080" | ||
| - "8081" | ||
| volumes: | ||
| - ./app/logs:/logs | ||
| environment: | ||
|
|
@@ -25,22 +39,73 @@ services: | |
| GOOGLE_CLIENT_ID_PROD: ${GOOGLE_CLIENT_ID_PROD} | ||
| GOOGLE_CLIENT_SECRET_PROD: ${GOOGLE_CLIENT_SECRET_PROD} | ||
| WIDGET_SECRET_KEY_PROD: ${WIDGET_SECRET_KEY_PROD} | ||
| SLACK_WEBHOOK_URL_PROD: ${SLACK_WEBHOOK_URL_PROD} | ||
| healthcheck: | ||
| test: ["CMD", "curl", "-f", "http://localhost:8081/actuator/health"] | ||
| interval: 10s | ||
| timeout: 5s | ||
| retries: 3 | ||
| start_period: 40s | ||
| labels: | ||
| - "traefik.enable=true" | ||
| - "traefik.http.routers.api.rule=Host(`api.kokomen.kr`)" | ||
| - "traefik.http.routers.api.entrypoints=web" | ||
| - "traefik.http.services.api.loadbalancer.server.port=8080" | ||
| - "traefik.http.services.api.loadbalancer.healthcheck.path=/actuator/health" | ||
| - "traefik.http.services.api.loadbalancer.healthcheck.port=8081" | ||
| - "traefik.http.services.api.loadbalancer.healthcheck.interval=5s" | ||
| - "traefik.http.services.api.loadbalancer.healthcheck.timeout=3s" | ||
|
Comment on lines
+49
to
+57
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: find . -type f -name "docker-compose-prod.yml" -o -name "deploy.sh" -o -name "rollback.sh" | head -20Repository: samhap-soft/kokomen-backend Length of output: 1905 🏁 Script executed: # Get the full structure of the docker directory
fd -t f "docker" | grep -E "(compose|deploy|rollback)" | head -30Repository: samhap-soft/kokomen-backend Length of output: 1905 🏁 Script executed: # Look for the specific file mentioned in the review
cat docker/prod/api/docker-compose-prod.yml 2>/dev/null || find . -name "docker-compose-prod.yml" -exec cat {} \;Repository: samhap-soft/kokomen-backend Length of output: 1905 🌐 Web query:
💡 Result: Traefik's behavior with multiple Docker containers having identical labels in blue-green deployments depends on how the labels are configured, particularly the router and service names. By default, Traefik creates one router and one service per container based on container-specific identifiers (like container name or ID). Identical router rules (e.g., same Host(example.com)) across multiple containers lead to configuration conflicts, as Traefik detects multiple routers with the same rule but different services, resulting in errors like "Router defined multiple times with different configurations" and failure to route properly. To enable load balancing across multiple containers with identical routing needs (useful for scaling or canary/blue-green with weights), all containers must explicitly share the same service name via labels like traefik.http.services.shared-service.loadbalancer.server.port=80, and reference it in routers with traefik.http.routers.myrouter.service=shared-service. Traefik then aggregates matching containers into a single load-balanced service with servers for each container instance. For strict blue-green deployments (zero-downtime version switching without concurrent load balancing): - Use separate containers/services for blue/green with distinct router names or priorities (e.g., traefik.http.routers.app-blue.rule=Host(app.localhost); traefik.http.routers.app-blue.priority=10 vs green with higher priority). - Enable/disable via traefik.enable=true/false or weights (e.g., traefik.http.services.app.loadbalancer.weight=100 for active, 0 for inactive). - Switch by updating labels (recreating containers), health checks, or dynamic file provider configs pointing to Citations:
🌐 Web query:
💡 Result: In Traefik, to configure an HTTP router (e.g., http.routers.http) to load balance across multiple backends using the same service labels, use the Docker provider with identical service names across multiple containers. Traefik automatically aggregates containers with the same service name into a single loadBalancer service, adding each container's instance as a server. Key steps: 1. Define the same service name (e.g., my-service) on multiple containers via labels like traefik.http.services.my-service.loadbalancer.server.port=8080. 2. Point one or more routers to that service name using traefik.http.routers.http.service=my-service. 3. Traefik detects matching service configurations and load balances traffic across all containers exposing that service (using strategies like wrr by default). Example Docker Compose for two whoami containers load balanced by router "http": version: '3' services: whoami-1: image: traefik/whoami labels: - traefik.enable=true - traefik.http.routers.http.rule=Host(example.com) - traefik.http.routers.http.service=my-service - traefik.http.services.my-service.loadbalancer.server.port=80 whoami-2: image: traefik/whoami labels: - traefik.enable=true - traefik.http.routers.http.service=my-service # Router points to shared service - traefik.http.services.my-service.loadbalancer.server.port=80 # Same service name deploy: replicas: 1 # Scale as needed Traefik creates a loadBalancer for my-service with servers from both containers. Without explicit router labels on the second container, Traefik auto-creates routers if rules are defined, but explicit shared service ensures aggregation. For static/file provider, explicitly list servers: http: services: my-service: loadBalancer: servers: - url: http://backend1:80 - url: http://backend2:80 Load balancing strategies (wrr, p2c, etc.) configurable via traefik.http.services.my-service.loadbalancer.strategy=wrr. This works in Traefik v2+ and v3 (current as of 2026), per official docs. Citations:
🌐 Web query:
💡 Result: Traefik supports blue-green deployment routing through its dynamic configuration and weighted services, enabling zero-downtime cutovers by switching traffic between blue (current) and green (new) environments. Core Strategy: Maintain two identical environments: blue (live) and green (staging new version). Deploy and test the new version in green while blue handles all production traffic. Once validated, perform cutover by updating Traefik routing to direct 100% traffic to green. Rollback by switching back to blue. Cutover Methods by Environment: 1. Docker/Docker Compose (using labels): - Enable/disable services via labels like traefik.enable=true/false. - Update docker-compose.yml with env vars: BLUE_ENABLED=true, GREEN_ENABLED=false initially. - Deploy: docker compose up -d - Test green, then cutover: BLUE_ENABLED=false GREEN_ENABLED=true; docker compose up -d - Traefik auto-detects changes and switches instantly. - Example labels: traefik.http.services.app-blue.loadbalancer.server.port=3000 traefik.enable=${BLUE_ENABLED:-true} 2. File Provider (dynamic config): - Define separate services for blue/green. - Router points to active service: service: green@file - Edit config file (watched by Traefik), change service reference, save for instant switch. 3. Kubernetes (CRDs recommended): - Deploy blue/green deployments + services (e.g., app-blue-svc, app-green-svc). - Use IngressRoute with TraefikService for weighted routing: apiVersion: traefik.io/v1alpha1 kind: TraefikService metadata: name: bg-service spec: weighted: services: - name: app-blue-svc port: 80 weight: 100 # Initially 100% blue - name: app-green-svc port: 80 weight: 0 - IngressRoute references bg-service. - Cutover: Update weights (100->0 blue, 0->100 green), apply. Supports gradual shift (90/10 -> 50/50 -> 0/100). - Health checks ensure only healthy services receive traffic. Additional Best Practices: - Use health checks (Traefik auto-removes unhealthy servers). - Weighted Round Robin (WRR) for gradual cutover or canary. - Traffic mirroring for green testing (duplicate requests to green while blue responds). - Tools like Flagger automate with metrics-based promotion. - Monitor via Traefik dashboard. This leverages Traefik's service discovery and hot-reload for zero-downtime. Citations:
현재 Traefik 설정은 동시에 두 백엔드로 트래픽을 분산합니다. 진정한 blue-green 전환이 아닙니다. 동일한
현재 Also applies to: 96-104 🤖 Prompt for AI Agents |
||
| networks: | ||
| - api-net | ||
| profiles: | ||
| - blue | ||
|
|
||
| nginx: | ||
| image: nginx:1.28.0 | ||
| container_name: nginx | ||
| ports: | ||
| - "80:80" | ||
| kokomen-api-green: | ||
|
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. |
||
| image: samhap/kokomen-interview-api:prod | ||
| container_name: kokomen-api-green | ||
| restart: on-failure:3 | ||
| expose: | ||
| - "8080" | ||
| - "8081" | ||
| volumes: | ||
| - ./nginx/nginx.conf:/etc/nginx/nginx.conf | ||
| - ./nginx/logs:/var/log/nginx | ||
| restart: unless-stopped | ||
| - ./app/logs:/logs | ||
| environment: | ||
| TZ: Asia/Seoul | ||
| JAVA_TOOL_OPTIONS: -Duser.timezone=Asia/Seoul | ||
| HOSTNAME: ${HOSTNAME} | ||
| SPRING_PROFILES_ACTIVE: prod | ||
| SPRING_DATASOURCE_URL_PROD: ${SPRING_DATASOURCE_URL_PROD} | ||
| SPRING_DATASOURCE_USERNAME_PROD: ${SPRING_DATASOURCE_USERNAME_PROD} | ||
| SPRING_DATASOURCE_PASSWORD_PROD: ${SPRING_DATASOURCE_PASSWORD_PROD} | ||
| REDIS_PRIMARY_HOST_PROD: ${REDIS_PRIMARY_HOST_PROD} | ||
| OPEN_AI_API_KEY: ${OPEN_AI_API_KEY} | ||
| KAKAO_CLIENT_ID_PROD: ${KAKAO_CLIENT_ID_PROD} | ||
| KAKAO_CLIENT_SECRET_PROD: ${KAKAO_CLIENT_SECRET_PROD} | ||
| KAKAO_ADMIN_KEY_PROD: ${KAKAO_ADMIN_KEY_PROD} | ||
| SUPERTONE_API_TOKEN: ${SUPERTONE_API_TOKEN} | ||
| GOOGLE_CLIENT_ID_PROD: ${GOOGLE_CLIENT_ID_PROD} | ||
| GOOGLE_CLIENT_SECRET_PROD: ${GOOGLE_CLIENT_SECRET_PROD} | ||
| WIDGET_SECRET_KEY_PROD: ${WIDGET_SECRET_KEY_PROD} | ||
| SLACK_WEBHOOK_URL_PROD: ${SLACK_WEBHOOK_URL_PROD} | ||
| healthcheck: | ||
| test: ["CMD", "curl", "-f", "http://localhost:8081/actuator/health"] | ||
| interval: 10s | ||
| timeout: 5s | ||
| retries: 3 | ||
| start_period: 40s | ||
| labels: | ||
| - "traefik.enable=true" | ||
| - "traefik.http.routers.api.rule=Host(`api.kokomen.kr`)" | ||
| - "traefik.http.routers.api.entrypoints=web" | ||
| - "traefik.http.services.api.loadbalancer.server.port=8080" | ||
| - "traefik.http.services.api.loadbalancer.healthcheck.path=/actuator/health" | ||
| - "traefik.http.services.api.loadbalancer.healthcheck.port=8081" | ||
| - "traefik.http.services.api.loadbalancer.healthcheck.interval=5s" | ||
| - "traefik.http.services.api.loadbalancer.healthcheck.timeout=3s" | ||
| networks: | ||
| - api-net | ||
| profiles: | ||
| - green | ||
|
|
||
| node: | ||
| image: prom/node-exporter | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| #!/bin/bash | ||
| set -e | ||
|
|
||
| SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" | ||
| COMPOSE_FILE="$SCRIPT_DIR/docker-compose-prod.yml" | ||
| HEALTH_TIMEOUT=120 | ||
| HEALTH_INTERVAL=5 | ||
|
|
||
| log_info() { echo "[INFO] $1"; } | ||
| log_warn() { echo "[WARN] $1"; } | ||
| log_error() { echo "[ERROR] $1"; } | ||
|
|
||
| get_active() { | ||
| if docker ps -q -f name=kokomen-api-blue | grep -q .; then | ||
| echo "blue" | ||
| elif docker ps -q -f name=kokomen-api-green | grep -q .; then | ||
| echo "green" | ||
| else | ||
| echo "none" | ||
| fi | ||
| } | ||
|
|
||
| check_container_exists() { | ||
| local container=$1 | ||
| if docker ps -a -q -f name="$container" | grep -q .; then | ||
| return 0 | ||
| else | ||
| return 1 | ||
| fi | ||
| } | ||
|
Comment on lines
+13
to
+30
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.
🔧 제안 수정+DOCKER="sudo -E docker"
...
- if docker ps -q -f name=kokomen-api-blue | grep -q .; then
+ if $DOCKER ps -q -f name=kokomen-api-blue | grep -q .; then
...
- status=$(docker inspect --format='{{.State.Health.Status}}' "$container" 2>/dev/null || echo "starting")
+ status=$($DOCKER inspect --format='{{.State.Health.Status}}' "$container" 2>/dev/null || echo "starting")
...
- docker start "kokomen-api-$ROLLBACK_TARGET" || true
+ $DOCKER start "kokomen-api-$ROLLBACK_TARGET" || true
...
- sudo -E docker compose -f $COMPOSE_FILE --profile $ROLLBACK_TARGET up -d "kokomen-api-$ROLLBACK_TARGET"
+ $DOCKER compose -f "$COMPOSE_FILE" --profile "$ROLLBACK_TARGET" up -d "kokomen-api-$ROLLBACK_TARGET"
...
- docker stop -t 65 "kokomen-api-$CURRENT" || true
- docker rm -f "kokomen-api-$CURRENT" 2>/dev/null || true
+ $DOCKER stop -t 65 "kokomen-api-$CURRENT" || true
+ $DOCKER rm -f "kokomen-api-$CURRENT" 2>/dev/null || trueAlso applies to: 31-52, 72-80, 91-94 🤖 Prompt for AI Agents |
||
|
|
||
| wait_healthy() { | ||
| local container=$1 | ||
| local elapsed=0 | ||
|
|
||
| log_info "헬스체크 대기: $container" | ||
|
|
||
| while [ $elapsed -lt $HEALTH_TIMEOUT ]; do | ||
| status=$(docker inspect --format='{{.State.Health.Status}}' "$container" 2>/dev/null || echo "starting") | ||
| if [ "$status" = "healthy" ]; then | ||
| echo "" | ||
| log_info "헬스체크 통과!" | ||
| return 0 | ||
| fi | ||
| sleep $HEALTH_INTERVAL | ||
| elapsed=$((elapsed + HEALTH_INTERVAL)) | ||
| echo -n "." | ||
| done | ||
|
|
||
| echo "" | ||
| log_error "헬스체크 실패" | ||
| return 1 | ||
| } | ||
|
|
||
| main() { | ||
| log_info "========== 롤백 시작 ==========" | ||
|
|
||
| CURRENT=$(get_active) | ||
| log_info "현재 활성 환경: $CURRENT" | ||
|
|
||
| if [ "$CURRENT" = "blue" ]; then | ||
| ROLLBACK_TARGET="green" | ||
| elif [ "$CURRENT" = "green" ]; then | ||
| ROLLBACK_TARGET="blue" | ||
| else | ||
| log_error "현재 활성 환경을 확인할 수 없습니다" | ||
| exit 1 | ||
| fi | ||
|
|
||
| log_info "롤백 대상: $ROLLBACK_TARGET" | ||
|
|
||
| # 롤백 대상 컨테이너 확인 | ||
| if check_container_exists "kokomen-api-$ROLLBACK_TARGET"; then | ||
| # 컨테이너가 존재하면 시작 | ||
| log_info "기존 컨테이너 시작" | ||
| docker start "kokomen-api-$ROLLBACK_TARGET" || true | ||
| else | ||
| # 컨테이너가 없으면 새로 생성 | ||
| log_info "롤백 대상 컨테이너 생성" | ||
| sudo -E docker compose -f $COMPOSE_FILE --profile $ROLLBACK_TARGET up -d "kokomen-api-$ROLLBACK_TARGET" | ||
| fi | ||
|
|
||
| # 헬스체크 | ||
| if ! wait_healthy "kokomen-api-$ROLLBACK_TARGET"; then | ||
| log_error "롤백 실패: 헬스체크 실패" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Traefik 라우팅 안정화 | ||
| sleep 5 | ||
|
|
||
| # 현재 활성 컨테이너 종료 | ||
| log_info "현재 컨테이너 종료: kokomen-api-$CURRENT" | ||
| docker stop -t 65 "kokomen-api-$CURRENT" || true | ||
|
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. |
||
| docker rm -f "kokomen-api-$CURRENT" 2>/dev/null || true | ||
|
|
||
| log_info "========== 롤백 완료 ==========" | ||
| log_info "활성 환경: $ROLLBACK_TARGET" | ||
| } | ||
|
|
||
| main "$@" | ||
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.
export와 명령어 할당을 분리하여 반환값 마스킹을 방지하세요.Shellcheck SC2155:
export HOSTNAME=$(hostname)은hostname명령의 반환값을 마스킹합니다.hostname이 실패해도export의 성공으로 인해$?가 0이 됩니다.🔧 제안 수정
📝 Committable suggestion
🧰 Tools
🪛 actionlint (1.7.12)
[error] 89-89: shellcheck reported issue in this script: SC2155:warning:1:8: Declare and assign separately to avoid masking return values
(shellcheck)
🤖 Prompt for AI Agents