@@ -252,7 +252,7 @@ jobs:
252252 IMAGE : ${{ needs.build-docker.outputs.image-tag }}
253253 run : |
254254 ssh -i ~/.ssh/deploy_key "$DEPLOY_USER@$DEPLOY_HOST" \
255- "docker pull $IMAGE && (docker stop carecode-staging || true) && (docker rm carecode-staging || true) && docker run -d --name carecode-staging -p 8082:8082 --env-file /opt/carecode/.env $IMAGE"
255+ "docker pull $IMAGE && (docker stop carecode-staging || true) && (docker rm carecode-staging || true) && docker run -d --name carecode-staging --memory=640m --memory-swap=640m - p 8082:8082 --env-file /opt/carecode/.env $IMAGE"
256256
257257 - name : Run health check
258258 env :
@@ -369,66 +369,79 @@ jobs:
369369 set -euo pipefail
370370
371371 APP=carecode
372- PROBE=carecode-probe
373372 PORT=8082
374- # 예전 blue/green 이 8083 을 쓰므로 검증 포트는 겹치지 않는 곳으로 잡는다.
375- PROBE_PORT=18082
376373 ENV_FILE=/opt/carecode/.env
377374
375+ # 컨테이너 메모리 한도. 없으면 JVM 이 호스트 전체를 기준으로 힙을 잡아(측정: 858MB)
376+ # 같은 머신의 MariaDB·Redis 가 쓸 메모리를 먹는다. 1GB 인스턴스 기준 기본값이며,
377+ # 서버 .env 에 APP_MEMORY 를 두면 그 값을 쓴다(여유 있는 인스턴스에서 올리면 된다).
378+ APP_MEMORY="$(grep -E '^APP_MEMORY=' "$ENV_FILE" 2>/dev/null | tail -1 | cut -d= -f2)"
379+ APP_MEMORY="${APP_MEMORY:-640m}"
380+
378381 if [ ! -f "$ENV_FILE" ]; then
379382 echo "$ENV_FILE 이 없습니다."
380383 exit 1
381384 fi
382385
386+ # 되돌릴 대상. 지금 돌고 있는 컨테이너가 쓰는 이미지 ID 를 먼저 붙잡아 둔다.
387+ # 태그가 아니라 ID 를 쓰는 이유: 같은 태그가 새 이미지로 덮여도 예전 것을 가리키게 하기 위해서다.
388+ PREV_IMAGE="$(docker inspect -f '{{.Image}}' "$APP" 2>/dev/null || true)"
389+
383390 echo "===== pull $IMAGE ====="
384391 docker pull "$IMAGE"
385392
386- # 이전 실행이 남긴 검증 컨테이너 정리
387- docker rm -f "$PROBE" >/dev/null 2>&1 || true
388-
389- # 1) 예비 포트에서 먼저 띄워 본다. 살아 있는 컨테이너는 아직 그대로다.
390- echo "===== 새 이미지 검증 (:$PROBE_PORT) ====="
391- docker run -d --name "$PROBE" -p "127.0.0.1:$PROBE_PORT:8082" --env-file "$ENV_FILE" "$IMAGE"
392-
393- ok=0
394- for _ in $(seq 1 40); do
395- if curl -fsS "http://127.0.0.1:$PROBE_PORT/actuator/health" 2>/dev/null | grep -q '"status":"UP"'; then
396- ok=1; break
397- fi
398- sleep 5
393+ run_app() {
394+ docker run -d --name "$APP" --restart unless-stopped --memory="$APP_MEMORY" --memory-swap="$APP_MEMORY" -p "$PORT:8082" --env-file "$ENV_FILE" "$1" >/dev/null
395+ }
396+
397+ wait_healthy() {
398+ for _ in $(seq 1 48); do
399+ if curl -fsS "http://127.0.0.1:$PORT/actuator/health" 2>/dev/null | grep -q '"status":"UP"'; then
400+ return 0
401+ fi
402+ # 컨테이너가 이미 죽었으면 더 기다릴 이유가 없다 (OOM 이면 여기서 잡힌다).
403+ if [ -z "$(docker ps -q -f name="^${APP}$")" ]; then
404+ return 1
405+ fi
406+ sleep 5
407+ done
408+ return 1
409+ }
410+
411+ # 교체. 1GB 인스턴스에서는 새 컨테이너를 미리 띄워 검증할 메모리가 없다
412+ # (JVM 두 개 = 측정 기준 1.3GB). 그래서 짧은 순단을 받아들이고, 실패하면 되돌린다.
413+ echo "===== 교체 ($APP_MEMORY) ====="
414+ for name in "$APP" carecode-probe carecode-blue carecode-green; do
415+ docker rm -f "$name" >/dev/null 2>&1 || true
399416 done
400417
401- if [ "$ok" -ne 1 ]; then
402- echo "새 이미지가 기동하지 못했습니다. 운영 컨테이너는 건드리지 않습니다."
403- echo "----- 컨테이너 로그 (마지막 100줄) -----"
404- docker logs --tail 100 "$PROBE" 2>&1 || true
405- docker rm -f "$PROBE" >/dev/null 2>&1 || true
406- exit 1
418+ run_app "$IMAGE"
419+
420+ if wait_healthy; then
421+ echo "===== 교체 완료 ====="
422+ docker image prune -f >/dev/null 2>&1 || true
423+ exit 0
407424 fi
408425
409- echo "검증 통과. 교체합니다."
410- docker rm -f "$PROBE" >/dev/null 2>&1 || true
426+ echo "새 이미지가 기동하지 못했습니다."
427+ echo "----- 컨테이너 로그 (마지막 100줄) -----"
428+ docker logs --tail 100 "$APP" 2>&1 || true
411429
412- # 2) 교체. 여기서부터 짧은 순단이 있다.
413- # 예전 워크플로가 만들던 blue/green 이름도 함께 정리한다. 남아 있으면 포트를 잡고 있다.
414- for name in "$APP" carecode-blue carecode-green; do
415- docker rm -f "$name" >/dev/null 2>&1 || true
416- done
430+ if [ -z "$PREV_IMAGE" ]; then
431+ echo "::error::되돌릴 이전 이미지가 없습니다(첫 배포로 보입니다). 서비스가 내려간 상태입니다."
432+ exit 1
433+ fi
417434
418- docker run -d --name "$APP" --restart unless-stopped \
419- -p "$PORT:8082" --env-file "$ENV_FILE" "$IMAGE"
435+ echo "===== 이전 이미지로 되돌립니다 ($PREV_IMAGE) ====="
436+ docker rm -f "$APP" >/dev/null 2>&1 || true
437+ run_app "$PREV_IMAGE"
420438
421- for _ in $(seq 1 40); do
422- if curl -fsS "http://127.0.0.1:$PORT/actuator/health" 2>/dev/null | grep -q '"status":"UP"'; then
423- echo "===== 교체 완료 ====="
424- docker image prune -f >/dev/null 2>&1 || true
425- exit 0
426- fi
427- sleep 5
428- done
439+ if wait_healthy; then
440+ echo "::error::새 이미지 기동 실패. 이전 이미지로 되돌렸고 서비스는 살아 있습니다."
441+ exit 1
442+ fi
429443
430- echo "교체 후 기동에 실패했습니다."
431- echo "----- 컨테이너 로그 (마지막 100줄) -----"
444+ echo "::error::되돌린 이미지도 기동하지 못했습니다. 서비스가 내려간 상태입니다 — 서버를 직접 확인하세요."
432445 docker logs --tail 100 "$APP" 2>&1 || true
433446 exit 1
434447 REMOTE
0 commit comments