-
Notifications
You must be signed in to change notification settings - Fork 1
[CHORE] 무중단 배포 #225
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
Closed
Closed
[CHORE] 무중단 배포 #225
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
20688b5
chore: 무중단 배포
unifolio0 7737547
chore: 무중단 배포 확인 용 임시 트리거 수정
unifolio0 93bc662
chore: nginx 프록시 설정 추가
unifolio0 79bbfa0
chore: jar 파일 다운로드 경로 수정
unifolio0 3a0bd2b
chore: 배포 트리거 수정
unifolio0 37bbfa1
chore: 배포 수정
unifolio0 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,3 +41,4 @@ out/ | |
|
|
||
| ### application-local.yml | ||
| /src/main/resources/application-local.yml | ||
| .serena | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| upstream debate_timer_backend { | ||
| server 127.0.0.1:8080; | ||
| keepalive 32; | ||
| } | ||
|
|
||
| server { | ||
| server_name api.dev.debate-timer.com; | ||
|
|
||
| location / { | ||
| proxy_pass http://debate_timer_backend; | ||
| proxy_set_header Host $host; | ||
| proxy_set_header X-Real-IP $remote_addr; | ||
| proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | ||
| proxy_set_header X-Forwarded-Proto $scheme; | ||
| } | ||
|
|
||
| listen [::]:443 ssl ipv6only=on; # managed by Certbot | ||
| listen 443 ssl; # managed by Certbot | ||
| ssl_certificate /etc/letsencrypt/live/api.dev.debate-timer.com/fullchain.pem; # managed by Certbot | ||
| ssl_certificate_key /etc/letsencrypt/live/api.dev.debate-timer.com/privkey.pem; # managed by Certbot | ||
| include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot | ||
| ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot | ||
| } | ||
|
|
||
| server { | ||
| if ($host = api.dev.debate-timer.com) { | ||
| return 308 https://$host$request_uri; | ||
| } # managed by Certbot | ||
|
|
||
| listen 80; | ||
| listen [::]:80; | ||
| server_name api.dev.debate-timer.com; | ||
| return 404; # managed by Certbot | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| upstream debate_timer_backend { | ||
| server 127.0.0.1:8080; | ||
| keepalive 32; | ||
| } | ||
|
|
||
| server { | ||
| server_name api.prod.debate-timer.com; | ||
|
|
||
| location / { | ||
| proxy_pass http://debate_timer_backend; | ||
| proxy_set_header Host $host; | ||
| proxy_set_header X-Real-IP $remote_addr; | ||
| proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | ||
| proxy_set_header X-Forwarded-Proto $scheme; | ||
| } | ||
|
|
||
| listen [::]:443 ssl ipv6only=on; # managed by Certbot | ||
| listen 443 ssl; # managed by Certbot | ||
| ssl_certificate /etc/letsencrypt/live/api.prod.debate-timer.com/fullchain.pem; # managed by Certbot | ||
| ssl_certificate_key /etc/letsencrypt/live/api.prod.debate-timer.com/privkey.pem; # managed by Certbot | ||
| include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot | ||
| ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot | ||
| } | ||
|
|
||
| server { | ||
| if ($host = api.prod.debate-timer.com) { | ||
| return 308 https://$host$request_uri; | ||
| } # managed by Certbot | ||
|
|
||
| listen 80; | ||
| listen [::]:80; | ||
| server_name api.prod.debate-timer.com; | ||
| return 404; # managed by Certbot | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,231 @@ | ||
| #!/bin/bash | ||
|
|
||
| set -e | ||
|
|
||
| APP_DIR="/home/ubuntu/app" | ||
| PORT_FILE="$APP_DIR/current_port.txt" | ||
| LOG_FILE="$APP_DIR/deploy.log" | ||
| BLUE_PORT=8080 | ||
| GREEN_PORT=8081 | ||
| BLUE_MONITOR_PORT=8083 | ||
| GREEN_MONITOR_PORT=8084 | ||
| MAX_HEALTH_CHECK_RETRIES=60 | ||
| HEALTH_CHECK_INTERVAL=2 | ||
| PROFILE="dev" | ||
| TIMEZONE="Asia/Seoul" | ||
|
|
||
| log() { | ||
| local timestamp=$(date '+%Y-%m-%d %H:%M:%S') | ||
| echo "${timestamp} $@" | tee -a "$LOG_FILE" | ||
| } | ||
|
|
||
| error_exit() { | ||
| log "$1" | ||
| exit 1 | ||
| } | ||
|
|
||
| get_current_port() { | ||
| if [ ! -f "$PORT_FILE" ]; then | ||
| log "Port file not found. Initializing with default port $BLUE_PORT" | ||
| echo "$BLUE_PORT" > "$PORT_FILE" | ||
| echo "$BLUE_PORT" | ||
| else | ||
| cat "$PORT_FILE" | ||
| fi | ||
| } | ||
|
|
||
| get_inactive_port() { | ||
| local current_port=$1 | ||
| if [ "$current_port" -eq "$BLUE_PORT" ]; then | ||
| echo "$GREEN_PORT" | ||
| else | ||
| echo "$BLUE_PORT" | ||
| fi | ||
| } | ||
|
|
||
| get_monitor_port() { | ||
| local app_port=$1 | ||
| if [ "$app_port" -eq "$BLUE_PORT" ]; then | ||
| echo "$BLUE_MONITOR_PORT" | ||
| else | ||
| echo "$GREEN_MONITOR_PORT" | ||
| fi | ||
| } | ||
|
|
||
| is_port_in_use() { | ||
| local port=$1 | ||
| lsof -t -i:$port > /dev/null 2>&1 | ||
| return $? | ||
| } | ||
|
|
||
| kill_process_on_port() { | ||
| local port=$1 | ||
| local pid=$(lsof -t -i:$port 2>/dev/null) | ||
|
|
||
| if [ -z "$pid" ]; then | ||
| log "No process running on port $port" | ||
| return 0 | ||
| fi | ||
|
|
||
| log "Sending graceful shutdown signal to process $pid on port $port" | ||
| kill -15 "$pid" | ||
|
|
||
| local wait_count=0 | ||
| while [ $wait_count -lt 35 ] && is_port_in_use "$port"; do | ||
| sleep 1 | ||
| wait_count=$((wait_count + 1)) | ||
| done | ||
|
|
||
| if is_port_in_use "$port"; then | ||
| log "Process didn't stop gracefully, forcing shutdown" | ||
| kill -9 "$pid" 2>/dev/null || true | ||
| sleep 2 | ||
| fi | ||
|
|
||
| log "Process on port $port stopped successfully" | ||
| } | ||
|
|
||
| health_check() { | ||
| local port=$1 | ||
| local monitor_port=$2 | ||
| local health_url="http://localhost:$monitor_port/monitoring/health" | ||
|
|
||
| log "Starting health check for port $port (monitor: $monitor_port)" | ||
|
|
||
| local retry=1 | ||
| while [ $retry -le $MAX_HEALTH_CHECK_RETRIES ]; do | ||
| local status=$(curl -s -o /dev/null -w "%{http_code}" "$health_url" 2>/dev/null || echo "000") | ||
|
|
||
| log "Health check attempt $retry/$MAX_HEALTH_CHECK_RETRIES - Status: $status" | ||
|
|
||
| if [ "$status" = "200" ]; then | ||
| log "Health check passed!" | ||
| return 0 | ||
| fi | ||
|
|
||
| sleep $HEALTH_CHECK_INTERVAL | ||
| retry=$((retry + 1)) | ||
| done | ||
|
|
||
| log "Health check failed after $MAX_HEALTH_CHECK_RETRIES attempts" | ||
| return 1 | ||
| } | ||
|
|
||
| start_application() { | ||
| local port=$1 | ||
| local monitor_port=$2 | ||
| local staging_jar="$APP_DIR/staging/app.jar" | ||
| local jar_file="$APP_DIR/app-$port.jar" | ||
|
|
||
| if [ ! -f "$staging_jar" ]; then | ||
| error_exit "No JAR file found in staging directory: $staging_jar" | ||
| fi | ||
|
|
||
| log "Copying JAR from staging to $jar_file" | ||
| cp "$staging_jar" "$jar_file" | ||
|
|
||
| log "Starting application on port $port with JAR: $jar_file" | ||
|
|
||
| if is_port_in_use "$port"; then | ||
| log "Port $port is in use, cleaning up..." | ||
| kill_process_on_port "$port" | ||
| fi | ||
|
|
||
| nohup java \ | ||
| -Dspring.profiles.active=$PROFILE,monitor \ | ||
| -Duser.timezone=$TIMEZONE \ | ||
| -Dserver.port=$port \ | ||
| -Dmanagement.server.port=$monitor_port \ | ||
| -Ddd.service=debate-timer \ | ||
| -Ddd.env=$PROFILE \ | ||
| -jar "$jar_file" > "$APP_DIR/app-$port.log" 2>&1 & | ||
|
|
||
| local pid=$! | ||
| log "Application started with PID: $pid" | ||
|
|
||
| sleep 3 | ||
|
|
||
| if ! kill -0 $pid 2>/dev/null; then | ||
| error_exit "Application process died immediately after start. Check logs at $APP_DIR/app-$port.log" | ||
| fi | ||
| } | ||
|
|
||
| switch_nginx_upstream() { | ||
| local new_port=$1 | ||
| local nginx_conf="/etc/nginx/sites-available/api.dev.debate-timer.com" | ||
| local temp_conf="/tmp/api.dev.debate-timer.com.tmp" | ||
|
|
||
| if [ ! -f "$nginx_conf" ]; then | ||
| error_exit "nginx configuration not found at $nginx_conf" | ||
| fi | ||
|
|
||
| log "Switching nginx upstream to port $new_port" | ||
|
|
||
| sed "s/server 127\.0\.0\.1:[0-9]\+;/server 127.0.0.1:$new_port;/" "$nginx_conf" > "$temp_conf" | ||
|
|
||
| sudo cp "$temp_conf" "$nginx_conf" | ||
| if ! sudo nginx -t 2>/dev/null; then | ||
| log "nginx configuration test failed" | ||
| git checkout "$nginx_conf" 2>/dev/null || 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. git checkout을 롤백 메커니즘으로 사용하지 마세요. nginx 설정 롤백에
백업 파일을 사용하도록 수정하세요: if ! sudo nginx -t 2>/dev/null; then
log "nginx configuration test failed"
- git checkout "$nginx_conf" 2>/dev/null || true
+ sudo cp "$BACKUP_CONF" "$nginx_conf" 2>/dev/null || true
return 1
fi그리고 스크립트 상단에 BACKUP_CONF 변수를 추가하세요: switch_nginx_upstream() {
local new_port=$1
local nginx_conf="/etc/nginx/sites-available/api.dev.debate-timer.com"
+ local backup_conf="/etc/nginx/sites-available/api.dev.debate-timer.com.backup"
local temp_conf="/tmp/api.dev.debate-timer.com.tmp"그리고 백업 생성 단계를 추가하세요: log "Switching nginx upstream to port $new_port"
+ sudo cp "$nginx_conf" "$backup_conf"
sed "s/server 127\.0\.0\.1:[0-9]\+;/server 127.0.0.1:$new_port;/" "$nginx_conf" > "$temp_conf"
🤖 Prompt for AI Agents |
||
| return 1 | ||
| fi | ||
|
|
||
| sudo nginx -s reload | ||
| log "nginx reloaded successfully" | ||
|
|
||
| sleep 2 | ||
| local response=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost/" 2>/dev/null || echo "000") | ||
| if [ "$response" = "000" ] || [ "$response" = "502" ] || [ "$response" = "503" ]; then | ||
| log "nginx health check failed after reload (status: $response)" | ||
| return 1 | ||
| fi | ||
|
|
||
| log "nginx is now routing traffic to port $new_port" | ||
| return 0 | ||
| } | ||
|
|
||
| main() { | ||
| local current_port=$(get_current_port) | ||
| local new_port=$(get_inactive_port "$current_port") | ||
| local new_monitor_port=$(get_monitor_port "$new_port") | ||
|
|
||
| log "Current active port: $current_port" | ||
| log "Deploying to port: $new_port" | ||
| log "Monitor port: $new_monitor_port" | ||
|
|
||
| log "Step 1/4: Starting new version on port $new_port" | ||
| start_application "$new_port" "$new_monitor_port" | ||
|
|
||
| log "Step 2/4: Performing health check" | ||
| if ! health_check "$new_port" "$new_monitor_port"; then | ||
| log "Deployment failed: Health check did not pass" | ||
| log "Rolling back: Stopping new version on port $new_port" | ||
| kill_process_on_port "$new_port" | ||
| error_exit "Deployment aborted due to health check failure" | ||
| fi | ||
|
|
||
| log "Step 3/4: Switching nginx to new version" | ||
| if ! switch_nginx_upstream "$new_port"; then | ||
| log "nginx switch failed, rolling back" | ||
| kill_process_on_port "$new_port" | ||
| error_exit "Deployment aborted due to nginx switch failure" | ||
| fi | ||
|
|
||
| log "Step 4/4: Stopping old version on port $current_port" | ||
| kill_process_on_port "$current_port" | ||
|
|
||
| local old_jar="$APP_DIR/app-$current_port.jar" | ||
| if [ -f "$old_jar" ]; then | ||
| log "Removing old JAR file: $old_jar" | ||
| rm -f "$old_jar" | ||
| fi | ||
|
|
||
| echo "$new_port" > "$PORT_FILE" | ||
| log "Updated active port file to $new_port" | ||
|
|
||
| log "Deployment completed successfully!" | ||
| log "Active port: $new_port" | ||
| log "Inactive port: $current_port" | ||
| } | ||
|
|
||
| main "$@" | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Shellcheck 경고를 수정해주세요.
로그 함수에 SC2155와 SC2145 경고가 있습니다.
다음과 같이 수정하세요:
log() { - local timestamp=$(date '+%Y-%m-%d %H:%M:%S') - echo "${timestamp} $@" | tee -a "$LOG_FILE" + local timestamp + timestamp=$(date '+%Y-%m-%d %H:%M:%S') + echo "${timestamp} $*" | tee -a "$LOG_FILE" }📝 Committable suggestion
🧰 Tools
🪛 Shellcheck (0.11.0)
[warning] 18-18: Declare and assign separately to avoid masking return values.
(SC2155)
[error] 19-19: Argument mixes string and array. Use * or separate argument.
(SC2145)
🤖 Prompt for AI Agents