-
Notifications
You must be signed in to change notification settings - Fork 0
fix: deploy.sh를 2프로세스 롤링 재시작 방식으로 변경 #201
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
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 |
|---|---|---|
|
|
@@ -6,77 +6,104 @@ cd "$APP_DIR" | |
| JAR_PATH=$(ls $APP_DIR/*.jar | grep -v plain | head -1) | ||
| echo "> JAR 파일: $JAR_PATH" | ||
|
|
||
| echo "> 실행 중인 애플리케이션 종료" | ||
| CURRENT_PID=$(pgrep -f '\.jar' || true) | ||
| if [ -n "$CURRENT_PID" ]; then | ||
| echo "> 종료: $CURRENT_PID" | ||
| kill -15 $CURRENT_PID | ||
| sleep 5 | ||
| else | ||
| echo "> 실행 중인 애플리케이션 없음" | ||
| fi | ||
|
|
||
| echo "> 애플리케이션 시작" | ||
| OTEL_AGENT_JAR=$APP_DIR/grafana-opentelemetry-java.jar | ||
| OTEL_ENV_FILE=$APP_DIR/otel.env | ||
| JAVA_AGENT_OPTS="" | ||
| if [ -f "$OTEL_AGENT_JAR" ] && [ -f "$OTEL_ENV_FILE" ]; then | ||
| echo "> Grafana OTel agent 감지, 모니터링 활성화" | ||
| set -a | ||
| source "$OTEL_ENV_FILE" | ||
| set +a | ||
| JAVA_AGENT_OPTS="-javaagent:$OTEL_AGENT_JAR" | ||
| else | ||
| echo "> Grafana OTel agent 미설정, 모니터링 없이 실행" | ||
| fi | ||
|
|
||
| LOG_FILE=/home/ec2-user/app/nohup.out | ||
| START_LINE=$(wc -l < "$LOG_FILE" 2>/dev/null || echo 0) | ||
|
|
||
| nohup java $JAVA_AGENT_OPTS -jar \ | ||
| -Duser.timezone=Asia/Seoul \ | ||
| $JAR_PATH \ | ||
| >> "$LOG_FILE" 2>&1 & | ||
| APP_PID=$! | ||
| echo "> 프로세스 시작됨 (PID: $APP_PID)" | ||
|
|
||
| echo "> 앱 준비 신호 대기 중 (최대 240초, 프로세스 생존 여부로 실패 판단)" | ||
| MAX_WAIT=240 | ||
| ELAPSED=0 | ||
| READY=false | ||
|
|
||
| while [ $ELAPSED -lt $MAX_WAIT ]; do | ||
| if ! kill -0 $APP_PID 2>/dev/null; then | ||
| echo "> 프로세스가 예기치 않게 종료됨 (크래시)" | ||
| echo "> 최근 로그:" | ||
| # 포트 하나씩 순서대로 종료→재기동한다 (롤링 재시작). | ||
| # nginx가 두 포트를 upstream으로 물고 있어서, 한쪽이 재시작되는 동안에도 | ||
| # 다른 한쪽이 계속 트래픽을 받아 무중단으로 배포된다. | ||
| deploy_process() { | ||
| local PORT=$1 | ||
| local USE_OTEL=$2 | ||
| local LOG_FILE=$APP_DIR/app-$PORT.log | ||
|
|
||
| echo "=== [$PORT] 배포 시작 ===" | ||
|
|
||
| echo "> [$PORT] 실행 중인 프로세스 종료" | ||
| OLD_PID=$(pgrep -f "server.port=$PORT" || true) | ||
| if [ -n "$OLD_PID" ]; then | ||
| echo "> [$PORT] 종료: $OLD_PID" | ||
| kill -15 $OLD_PID | ||
| sleep 5 | ||
| else | ||
| echo "> [$PORT] 실행 중인 프로세스 없음" | ||
| fi | ||
|
|
||
| JAVA_AGENT_OPTS="" | ||
| if [ "$USE_OTEL" = "true" ] && [ -f "$OTEL_AGENT_JAR" ] && [ -f "$OTEL_ENV_FILE" ]; then | ||
| echo "> [$PORT] Grafana OTel agent 활성화" | ||
| set -a | ||
| source "$OTEL_ENV_FILE" | ||
| set +a | ||
| JAVA_AGENT_OPTS="-javaagent:$OTEL_AGENT_JAR" | ||
| else | ||
| echo "> [$PORT] Grafana OTel agent 미적용" | ||
| fi | ||
|
|
||
| START_LINE=$(wc -l < "$LOG_FILE" 2>/dev/null || echo 0) | ||
|
|
||
| nohup java $JAVA_AGENT_OPTS -jar \ | ||
| -Duser.timezone=Asia/Seoul \ | ||
| $JAR_PATH \ | ||
| --server.port=$PORT \ | ||
| >> "$LOG_FILE" 2>&1 & | ||
| APP_PID=$! | ||
| echo "> [$PORT] 프로세스 시작됨 (PID: $APP_PID)" | ||
|
Comment on lines
+45
to
+51
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. 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== deploy.sh excerpt =="
cat -n scripts/deploy.sh | sed -n '1,130p'
echo
echo "== available java version =="
if command -v java >/dev/null 2>&1; then
java -version 2>&1 || true
else
echo "java not installed"
fi
echo
echo "== Java -jar option semantics probe =="
if command -v java >/dev/null 2>&1; then
mkdir -p /tmp/java-jar-probe
echo 'public class App { public static void main(String... args) { for(String a: args) System.out.println(a); } }' > /tmp/java-jar-probe/App.java
javac /tmp/java-jar-probe/App.java
java -jar -Duser.timezone=Asia/Seoul /tmp/java-jar-probe/App.jar 2>&1 || true
java -cp /tmp/java-jar-probe -Duser.timezone=Asia/Seoul App 2>&1 || true
rm -f /tmp/java-jar-probe/App.java /tmp/java-jar-probe/App.class
fiRepository: Runnect/Runnect-Spring-Boot-Server Length of output: 1957 🌐 Web query:
💡 Result: When launching a Java application using the java command, options intended for the Java Virtual Machine (JVM)—such as -Xmx or -Dproperty=value—must precede the name of the class or the JAR file [1]. The general syntax for the java command is: java [options] classname [args] or java [options] -jar jarfile [args] Everything provided before the class name or JAR file is treated as an option for the JVM, while everything provided after is treated as an argument for the application's main method [1][2]. Regarding specific tools or legacy commands: 1. javaws (Java Web Start): Documentation has explicitly stated that all run-options must precede the JNLP file argument [3]. 2. javadoc: Certain options, such as -locale, must be placed before other options to function correctly [4]. 3. jcmd: Options and arguments for this diagnostic tool have specific syntax requirements, often requiring key or key=value syntax [5]. For most standard Java invocations, the requirement that JVM options precede the application entry point (class or JAR) is a fundamental rule of the Java launcher's command-line parsing [1]. Citations:
🌐 Web query:
💡 Result: Yes, when using the -jar option with the java command, all command-line options intended for the Java Virtual Machine (JVM)—such as system properties defined with -D—must precede the -jar flag [1][2]. The standard syntax for the java command is [3][4][5]: java [options] -jar jarfile [args...] In this structure: 1. [options]: These are JVM options (e.g., -Dproperty=value, -Xmx, etc.) that must be placed before the -jar argument to be recognized as configuration for the JVM [1][2]. 2. -jar jarfile: This specifies the JAR file to execute. When this flag is used, the JAR file becomes the source of all user classes, and other classpath settings are ignored [3][4][5]. 3. [args...]: Any arguments placed after the JAR file name are treated as application arguments and are passed directly to the main method of the application's entry point class, not to the JVM [3][4][5]. If JVM options (like -D) are placed after the -jar flag, the JVM will typically interpret them as application arguments rather than system settings, which often causes the application to fail to receive the expected configuration [1][2]. Citations:
Critical: Move JVM options must precede Proposed fix nohup java $JAVA_AGENT_OPTS -jar \
- -Duser.timezone=Asia/Seoul \
- $JAR_PATH \
+ -Duser.timezone=Asia/Seoul \
+ -jar $JAR_PATH \
--server.port=$PORT \
>> "$LOG_FILE" 2>&1 &🧰 Tools🪛 Shellcheck (0.11.0)[info] 47-47: Double quote to prevent globbing and word splitting. (SC2086) [info] 48-48: Double quote to prevent globbing and word splitting. (SC2086) 🤖 Prompt for AI Agents |
||
|
|
||
| local MAX_WAIT=240 | ||
| local ELAPSED=0 | ||
| local READY=false | ||
|
|
||
| while [ $ELAPSED -lt $MAX_WAIT ]; do | ||
| if ! kill -0 $APP_PID 2>/dev/null; then | ||
| echo "> [$PORT] 프로세스가 예기치 않게 종료됨 (크래시)" | ||
| echo "> [$PORT] 최근 로그:" | ||
| tail -n 40 "$LOG_FILE" | ||
| return 1 | ||
| fi | ||
|
|
||
| if tail -n +"$((START_LINE + 1))" "$LOG_FILE" | grep -q "Started ServerApplication"; then | ||
| echo "> [$PORT] 준비 완료 신호 감지 (${ELAPSED}초 소요)" | ||
| READY=true | ||
| break | ||
| fi | ||
|
|
||
| sleep 2 | ||
| ELAPSED=$((ELAPSED + 2)) | ||
| done | ||
|
|
||
| if [ "$READY" != "true" ]; then | ||
| echo "> [$PORT] ${MAX_WAIT}초 내에 준비 신호 없음 — 안전장치 발동" | ||
| echo "> [$PORT] 최근 로그:" | ||
| tail -n 40 "$LOG_FILE" | ||
| echo "> 배포 실패" | ||
| exit 1 | ||
| return 1 | ||
| fi | ||
|
|
||
| if tail -n +"$((START_LINE + 1))" "$LOG_FILE" | grep -q "Started ServerApplication"; then | ||
| echo "> 앱 준비 완료 신호 감지 (${ELAPSED}초 소요)" | ||
| READY=true | ||
| break | ||
| RESPONSE=$(curl -s --max-time 10 http://localhost:$PORT/actuator/health || true) | ||
| echo "> [$PORT] 헬스체크 확인: $RESPONSE" | ||
|
|
||
| if ! echo "$RESPONSE" | grep -q '"status":"UP"'; then | ||
| echo "> [$PORT] 헬스체크 실패" | ||
| return 1 | ||
| fi | ||
|
|
||
| sleep 2 | ||
| ELAPSED=$((ELAPSED + 2)) | ||
| done | ||
| return 0 | ||
| } | ||
|
|
||
| if [ "$READY" != "true" ]; then | ||
| echo "> ${MAX_WAIT}초 내에 준비 신호 없음 — 안전장치 발동" | ||
| echo "> 최근 로그:" | ||
| tail -n 40 "$LOG_FILE" | ||
| if ! deploy_process 8080 true; then | ||
| echo "> 8080 배포 실패 — 8081은 계속 서비스 중이므로 전체 다운은 아니지만 배포는 실패 처리" | ||
| echo "> 배포 실패" | ||
| exit 1 | ||
| fi | ||
|
|
||
| RESPONSE=$(curl -s --max-time 10 http://localhost:8080/actuator/health || true) | ||
| echo "> 헬스체크 확인: $RESPONSE" | ||
| if ! deploy_process 8081 false; then | ||
| echo "> 8081 배포 실패 — 8080은 정상 서비스 중이므로 전체 다운은 아니지만 배포는 실패 처리" | ||
| echo "> 배포 실패" | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "> Nginx 시작" | ||
| sudo systemctl start nginx || true | ||
| sudo systemctl enable nginx || true | ||
|
|
||
| echo "> 배포 완료" | ||
| echo "> 배포 완료 (8080, 8081 모두 정상)" | ||
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.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Fixed 5s sleep after SIGTERM doesn't guarantee the port is free.
After
kill -15, the script blindly sleeps 5 seconds with no check that the old process actually exited before starting a new process bound to the same port. If graceful shutdown (e.g., in-flight connection draining) takes longer than 5s, the newjavaprocess will fail to bind the port; this is only caught indirectly via the crash-detection loop, causing an avoidable deploy failure for that port.♻️ Proposed fix — poll for exit with an escalation fallback
if [ -n "$OLD_PID" ]; then echo "> [$PORT] 종료: $OLD_PID" kill -15 $OLD_PID - sleep 5 + for i in $(seq 1 10); do + kill -0 $OLD_PID 2>/dev/null || break + sleep 1 + done + kill -0 $OLD_PID 2>/dev/null && kill -9 $OLD_PID else📝 Committable suggestion
🧰 Tools
🪛 Shellcheck (0.11.0)
[info] 26-26: Double quote to prevent globbing and word splitting.
(SC2086)
🤖 Prompt for AI Agents