-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathzero-downtime-deploy.sh
More file actions
231 lines (183 loc) · 6.07 KB
/
zero-downtime-deploy.sh
File metadata and controls
231 lines (183 loc) · 6.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
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
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 "$@"