-
Notifications
You must be signed in to change notification settings - Fork 0
[SOU-421] 프로덕션 서버 무중단 배포 구현 #211
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
Changes from all commits
078eb4a
a42030e
76965c6
88b5f0a
4aca853
d2205c6
48e5dd8
60ecc34
2039f38
955263b
8c9774e
53e86ab
5513f2d
8ec3d6e
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,131 @@ | ||
| #!/bin/bash | ||
| set -e | ||
|
|
||
| RED='\033[0;31m' | ||
| GREEN='\033[0;32m' | ||
| YELLOW='\033[1;33m' | ||
| NC='\033[0m' | ||
|
|
||
| REGISTRY="asia-northeast3-docker.pkg.dev/souzip-488211/souzip-prod-repo/souzip-api" | ||
|
|
||
| WORK_DIR="/home/souzip-prod/souzip" | ||
| DEPLOY_DIR="$WORK_DIR/deploy/prod" | ||
|
|
||
|
Comment on lines
+11
to
+13
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: # First, verify the hardcoded WORK_DIR in the deploy script
cat -n deploy/prod/scripts/deploy.sh | head -20Repository: souzip/souzip-api Length of output: 648 🏁 Script executed: # Search for workflow files that might use GCP_PROD_USER
find .github -name "*.yaml" -o -name "*.yml" | head -10Repository: souzip/souzip-api Length of output: 125 🏁 Script executed: # Look for references to the hardcoded path in other files
rg "souzip-prod" --type sh --type yamlRepository: souzip/souzip-api Length of output: 497 🏁 Script executed: cat -n .github/workflows/prod-cicd.yaml | grep -A 5 -B 5 "GCP_PROD_USER\|WORK_DIR\|/home"Repository: souzip/souzip-api Length of output: 1121 🏁 Script executed: # Check for any secrets usage in the workflow
cat .github/workflows/prod-cicd.yaml | head -100Repository: souzip/souzip-api Length of output: 2442 워크플로우 경로와 스크립트 경로 불일치로 배포 실패 위험이 있습니다. 워크플로우는 상대 경로 기반으로 변경하여 스크립트 위치에 따라 동적으로 경로를 결정하는 것을 권장합니다. 🔧 제안 수정안--- a/deploy/prod/scripts/deploy.sh
+++ b/deploy/prod/scripts/deploy.sh
@@
-WORK_DIR="/home/souzip-prod/souzip"
-DEPLOY_DIR="$WORK_DIR/deploy/prod"
+SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
+DEPLOY_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
+WORK_DIR="$(cd "$DEPLOY_DIR/../.." && pwd)"🤖 Prompt for AI Agents |
||
| BLUE_COMPOSE="docker-compose.blue.yaml" | ||
| GREEN_COMPOSE="docker-compose.green.yaml" | ||
|
|
||
| BLUE_PROJECT="souzip-blue" | ||
| GREEN_PROJECT="souzip-green" | ||
|
|
||
| NGINX_UPSTREAM_FILE="/etc/nginx/conf.d/upstream-souzip.conf" | ||
|
|
||
| BLUE_PORT=8081 | ||
| GREEN_PORT=8082 | ||
|
|
||
| MAX_RETRY=6 | ||
| RETRY_INTERVAL=10 | ||
|
|
||
| cd "$WORK_DIR" || exit 1 | ||
|
|
||
| echo -e "${YELLOW}[1/7] 최신 이미지 다운로드${NC}" | ||
|
|
||
| docker pull ${REGISTRY}:latest | ||
|
|
||
| NEW_IMAGE=$(docker images ${REGISTRY}:latest -q) | ||
|
Comment on lines
+9
to
+34
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
# latest 태그 의존 지점 확인
echo "=== Checking deploy.sh ==="
cat -n deploy/prod/scripts/deploy.sh | sed -n '1,50p'
echo -e "\n=== Searching for :latest tags in deployment files ==="
rg -n ':latest' deploy/prod/ .github/workflows/ 2>/dev/null | head -30Repository: souzip/souzip-api Length of output: 2102 🏁 Script executed: #!/bin/bash
# Check how docker-compose files are used and if they reference the pulled image
echo "=== Checking docker-compose usage in deploy.sh ==="
cat -n deploy/prod/scripts/deploy.sh | grep -A 20 -B 5 "docker-compose"
echo -e "\n=== Checking docker-compose.blue.yaml content ==="
head -20 deploy/prod/docker-compose.blue.yaml
echo -e "\n=== Checking how NEW_IMAGE is used after being set ==="
cat -n deploy/prod/scripts/deploy.sh | grep -A 15 "NEW_IMAGE="Repository: souzip/souzip-api Length of output: 1788 🏁 Script executed: #!/bin/bash
# Check how docker-compose files are invoked and if NEW_IMAGE is used
echo "=== Searching for docker-compose invocations in deploy.sh ==="
rg -n "docker-compose" deploy/prod/scripts/deploy.sh
echo -e "\n=== Full deploy.sh to see complete flow ==="
wc -l deploy/prod/scripts/deploy.shRepository: souzip/souzip-api Length of output: 289 🏁 Script executed: #!/bin/bash
# Get the full deploy.sh to understand the complete deployment flow
cat -n deploy/prod/scripts/deploy.shRepository: souzip/souzip-api Length of output: 3856 프로덕션 배포에서 배포 스크립트(32, 34번 줄)뿐만 아니라 docker-compose 파일들(blue.yaml, green.yaml 5번 줄)에서도 commit SHA 또는 semantic version 같은 immutable 태그로 고정하되, 다음을 함께 변경해야 합니다:
🔧 제안 수정안--- a/deploy/prod/scripts/deploy.sh
+++ b/deploy/prod/scripts/deploy.sh
@@ -1,6 +1,7 @@
#!/bin/bash
set -e
+IMAGE_TAG="${IMAGE_TAG:-latest}"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
@@ -29,11 +30,11 @@ cd "$WORK_DIR" || exit 1
echo -e "${YELLOW}[1/7] 최신 이미지 다운로드${NC}"
-docker pull ${REGISTRY}:latest
+docker pull "${REGISTRY}:${IMAGE_TAG}"
-NEW_IMAGE=$(docker images ${REGISTRY}:latest -q)
+NEW_IMAGE=$(docker images "${REGISTRY}:${IMAGE_TAG}" -q)--- a/deploy/prod/docker-compose.blue.yaml
+++ b/deploy/prod/docker-compose.blue.yaml
@@ -2,7 +2,7 @@ version: "3.8"
services:
backend:
- image: asia-northeast3-docker.pkg.dev/souzip-488211/souzip-prod-repo/souzip-api:latest
+ image: asia-northeast3-docker.pkg.dev/souzip-488211/souzip-prod-repo/souzip-api:${IMAGE_TAG:-latest}
container_name: souzip-api-blue--- a/deploy/prod/docker-compose.green.yaml
+++ b/deploy/prod/docker-compose.green.yaml
@@ -2,7 +2,7 @@ version: "3.8"
services:
backend:
- image: asia-northeast3-docker.pkg.dev/souzip-488211/souzip-prod-repo/souzip-api:latest
+ image: asia-northeast3-docker.pkg.dev/souzip-488211/souzip-prod-repo/souzip-api:${IMAGE_TAG:-latest}
container_name: souzip-api-green🤖 Prompt for AI Agents |
||
|
|
||
| if [ -z "${NEW_IMAGE:-}" ]; then | ||
| echo -e "${RED}[ERROR] 이미지 다운로드 실패${NC}" | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo -e "${GREEN}[SUCCESS] 새 이미지: ${NEW_IMAGE}${NC}" | ||
|
|
||
| cd "$DEPLOY_DIR" || exit 1 | ||
|
|
||
| [ -f "$NGINX_UPSTREAM_FILE" ] || { | ||
| echo -e "${RED}[ERROR] upstream 파일 없음${NC}" | ||
| exit 1 | ||
|
Comment on lines
+45
to
+47
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. |
||
| } | ||
|
|
||
| echo -e "${YELLOW}[2/7] 현재 active 포트 확인${NC}" | ||
|
|
||
| CURRENT_PORT=$(grep -oE '127\.0\.0\.1:[0-9]+' "$NGINX_UPSTREAM_FILE" | cut -d: -f2 | head -n 1 || true) | ||
|
|
||
| if [ -z "$CURRENT_PORT" ]; then | ||
| echo -e "${YELLOW}[WARN] upstream 없음 → 최초 배포 (blue=8081)${NC}" | ||
| CURRENT_PORT="none" | ||
| fi | ||
|
|
||
| if [ "$CURRENT_PORT" == "$BLUE_PORT" ]; then | ||
| TARGET="green" | ||
| TARGET_PORT=$GREEN_PORT | ||
| TARGET_PROJECT=$GREEN_PROJECT | ||
| TARGET_COMPOSE=$GREEN_COMPOSE | ||
|
|
||
| STOP_PROJECT=$BLUE_PROJECT | ||
| STOP_COMPOSE=$BLUE_COMPOSE | ||
|
|
||
| else | ||
| TARGET="blue" | ||
| TARGET_PORT=$BLUE_PORT | ||
| TARGET_PROJECT=$BLUE_PROJECT | ||
| TARGET_COMPOSE=$BLUE_COMPOSE | ||
|
|
||
| STOP_PROJECT=$GREEN_PROJECT | ||
| STOP_COMPOSE=$GREEN_COMPOSE | ||
| fi | ||
|
|
||
| echo -e "${GREEN}[INFO] 현재:$CURRENT_PORT → 배포:$TARGET($TARGET_PORT)${NC}" | ||
|
|
||
| echo -e "${YELLOW}[3/7] $TARGET 컨테이너 실행${NC}" | ||
|
|
||
| docker compose -p "$TARGET_PROJECT" -f "$TARGET_COMPOSE" pull | ||
| docker compose -p "$TARGET_PROJECT" -f "$TARGET_COMPOSE" up -d | ||
|
|
||
| echo -e "${YELLOW}[4/7] 헬스체크 시작${NC}" | ||
|
|
||
| RETRY_COUNT=0 | ||
| HEALTH_OK=false | ||
|
|
||
| while [ $RETRY_COUNT -lt $MAX_RETRY ]; do | ||
|
|
||
| if curl -f -s --max-time 5 "http://localhost:${TARGET_PORT}/actuator/health" > /dev/null; then | ||
| echo -e "${GREEN}[SUCCESS] 헬스체크 성공${NC}" | ||
| HEALTH_OK=true | ||
| break | ||
| else | ||
| RETRY_COUNT=$((RETRY_COUNT + 1)) | ||
| echo -e "${YELLOW}[RETRY] ${RETRY_COUNT}/${MAX_RETRY}${NC}" | ||
| sleep $RETRY_INTERVAL | ||
| fi | ||
|
|
||
| done | ||
|
|
||
| if [ "$HEALTH_OK" = false ]; then | ||
| echo -e "${RED}[ERROR] 헬스체크 실패${NC}" | ||
| docker compose -p "$TARGET_PROJECT" -f "$TARGET_COMPOSE" down | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo -e "${YELLOW}[5/7] nginx upstream 전환${NC}" | ||
|
|
||
| sudo tee "$NGINX_UPSTREAM_FILE" > /dev/null <<EOF | ||
| upstream souzip { | ||
| server 127.0.0.1:${TARGET_PORT}; | ||
| } | ||
| EOF | ||
|
|
||
| sudo nginx -t | ||
| sudo nginx -s reload | ||
|
|
||
| echo -e "${GREEN}[SUCCESS] nginx 전환 완료${NC}" | ||
|
|
||
| echo -e "${YELLOW}[6/7] 이전 컨테이너 종료${NC}" | ||
|
|
||
| docker compose -p "$STOP_PROJECT" -f "$STOP_COMPOSE" down || true | ||
|
|
||
| echo -e "${YELLOW}[7/7] 이미지 정리${NC}" | ||
|
|
||
| docker image prune -f || true | ||
|
|
||
| echo -e "${GREEN}[DEPLOY SUCCESS] 완료${NC}" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,8 @@ | ||
| spring: | ||
| docker: | ||
| compose: | ||
| enabled: false | ||
|
|
||
| datasource: | ||
| url: ${PROD_DB_URL} | ||
| username: ${PROD_POSTGRES_USER} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,10 +11,10 @@ | |
| -- Name: cdb_admin; Type: SCHEMA; Schema: -; Owner: postgres | ||
| -- | ||
|
|
||
| CREATE SCHEMA cdb_admin; | ||
| CREATE SCHEMA IF NOT EXISTS cdb_admin; | ||
|
|
||
|
|
||
| ALTER SCHEMA cdb_admin OWNER TO postgres; | ||
| -- ALTER SCHEMA cdb_admin OWNER TO postgres; | ||
|
|
||
| -- | ||
| -- Name: public; Type: SCHEMA; Schema: -; Owner: postgres | ||
|
|
@@ -23,20 +23,20 @@ ALTER SCHEMA cdb_admin OWNER TO postgres; | |
| -- *not* creating schema, since initdb creates it | ||
|
|
||
|
|
||
| ALTER SCHEMA public OWNER TO postgres; | ||
| -- ALTER SCHEMA public OWNER TO postgres; | ||
|
|
||
| -- | ||
| -- Name: postgis; Type: EXTENSION; Schema: -; Owner: - | ||
| -- | ||
|
|
||
| CREATE EXTENSION IF NOT EXISTS postgis WITH SCHEMA cdb_admin; | ||
| -- CREATE EXTENSION IF NOT EXISTS postgis WITH SCHEMA cdb_admin; | ||
|
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.
이 변경으로 V1 마이그레이션이 Useful? React with 👍 / 👎. |
||
|
|
||
|
|
||
| -- | ||
| -- Name: EXTENSION postgis; Type: COMMENT; Schema: -; Owner: | ||
| -- | ||
|
|
||
| COMMENT ON EXTENSION postgis IS 'PostGIS geometry and geography spatial types and functions'; | ||
| -- COMMENT ON EXTENSION postgis IS 'PostGIS geometry and geography spatial types and functions'; | ||
|
Comment on lines
+32
to
+39
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
# Check the migration file content around lines 32-39
echo "=== Migration file content (lines 25-45) ==="
sed -n '25,45p' src/main/resources/db/migration/V1__init_schema.sql
echo -e "\n=== Check SouvenirRepository for ST_DWithin/ST_Distance usage ==="
rg -n 'ST_DWithin|ST_Distance|ST_SetSRID|ST_MakePoint' src/main/java/com/souzip/domain/souvenir/repository/SouvenirRepository.java
echo -e "\n=== Check all PostGIS extension references in migration files ==="
find src/main/resources/db/migration -name "*.sql" -exec grep -n 'postgis\|PostGIS' {} +
echo -e "\n=== Full SouvenirRepository.java file (checking lines around 21-46) ==="
wc -l src/main/java/com/souzip/domain/souvenir/repository/SouvenirRepository.javaRepository: souzip/souzip-api Length of output: 1686 🏁 Script executed: #!/bin/bash
# View the full SouvenirRepository.java to understand the query context
echo "=== Full SouvenirRepository.java content ==="
cat -n src/main/java/com/souzip/domain/souvenir/repository/SouvenirRepository.java
echo -e "\n=== Check if other Java files use PostGIS functions ==="
rg -l 'ST_DWithin|ST_Distance|ST_SetSRID|ST_MakePoint' src/main/java/
echo -e "\n=== Check for any PostGIS setup in other migration files ==="
find src/main/resources/db/migration -name "*.sql" | sortRepository: souzip/souzip-api Length of output: 3282 PostGIS 확장 생성이 주석 처리되어 있어 런타임 쿼리 실패를 초래합니다.
확장 생성 구문을 활성화하거나, 외부 프로비저닝을 사용한다면 마이그레이션에서 존재 여부를 검증해야 합니다. 제안 수정안--- a/src/main/resources/db/migration/V1__init_schema.sql
+++ b/src/main/resources/db/migration/V1__init_schema.sql
@@ -29,6 +29,6 @@
-- Name: postgis; Type: EXTENSION; Schema: -; Owner: -
--
--- CREATE EXTENSION IF NOT EXISTS postgis WITH SCHEMA cdb_admin;
+CREATE EXTENSION IF NOT EXISTS postgis;
@@ -36,4 +36,4 @@
-- Name: EXTENSION postgis; Type: COMMENT; Schema: -; Owner:
--
--- COMMENT ON EXTENSION postgis IS 'PostGIS geometry and geography spatial types and functions';
+COMMENT ON EXTENSION postgis IS 'PostGIS geometry and geography spatial types and functions';🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
|
|
||
|
|
||
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.
🧩 Analysis chain
🏁 Script executed:
Repository: souzip/souzip-api
Length of output: 5432
동시 배포 실행 제어가 누락되었습니다.
push트리거로 연속 발생 시deploy작업이 동시에 실행되어 blue/green 배포와 nginx 설정에서 경합이 발생할 수 있습니다. 배포를 직렬화하기 위해 워크플로우 레벨에concurrency설정이 필요합니다.🔧 제안 수정안
on: pull_request: branches: - main push: branches: - main +concurrency: + group: prod-deploy + cancel-in-progress: false🤖 Prompt for AI Agents