From 34f5a89f78491090080b52130594efcb4e297df5 Mon Sep 17 00:00:00 2001 From: Rinjae Date: Fri, 12 Jun 2026 18:23:13 +0900 Subject: [PATCH] =?UTF-8?q?readiness=20probe=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9B=8C=ED=81=AC=ED=94=8C=EB=A1=9C=20=EB=B3=B4?= =?UTF-8?q?=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ReadinessController로 DB 연결 여부 확인 엔드포인트 추가 - CI 워크플로에 readiness probe 검증 로직 추가 (120초 제한) - develop, stage 브랜치 push 이벤트 트리거 추가 --- .gitea/workflows/eapim-portal-ci.yml | 51 ++++++++------ .../apim/portal/apps/ReadinessController.java | 67 +++++++++++++++++++ 2 files changed, 97 insertions(+), 21 deletions(-) create mode 100644 src/main/java/com/eactive/apim/portal/apps/ReadinessController.java diff --git a/.gitea/workflows/eapim-portal-ci.yml b/.gitea/workflows/eapim-portal-ci.yml index 3bf6f2f..f3f34ce 100644 --- a/.gitea/workflows/eapim-portal-ci.yml +++ b/.gitea/workflows/eapim-portal-ci.yml @@ -4,6 +4,8 @@ on: push: branches: - master + - develop + - stage - feats/ci-test jobs: @@ -80,7 +82,7 @@ jobs: cp eapim-portal/build/libs/eapim-portal.war "$DEPLOY_DIR/ROOT.war" ls -la "$DEPLOY_DIR" - - name: Start tomcat and health check (timeout 90s) + - name: Start tomcat and readiness probe (timeout 120s) working-directory: ${{ github.workspace }} run: | LOG="$CATALINA_BASE/logs/catalina.$(date +%Y-%m-%d).log" @@ -90,14 +92,34 @@ jobs: mkdir -p "$(dirname $CATALINA_PID)" JAVA_HOME=$JAVA_HOME_TOMCAT "$CATALINA_HOME/bin/startup.sh" - DEADLINE=$(($(date +%s) + 90)) + DEADLINE=$(($(date +%s) + 120)) + LIVE_OK=0 STATUS=000 + BODY="" while [ $(date +%s) -lt $DEADLINE ]; do - STATUS=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 3 http://localhost:$DEPLOY_HTTP_PORT/ 2>/dev/null || echo "000") - if [ "$STATUS" = "200" ]; then - echo "HTTP 200 received from /" - break + # Stage 1: liveness — servlet 응답 가능? + if [ "$LIVE_OK" = "0" ]; then + LIVE=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 3 \ + http://localhost:$DEPLOY_HTTP_PORT/health 2>/dev/null || echo "000") + if [ "$LIVE" = "200" ]; then + echo "Liveness OK (/health)" + LIVE_OK=1 + fi fi + + # Stage 2: readiness — DB(EMS, Gateway)까지 검증 + if [ "$LIVE_OK" = "1" ]; then + BODY=$(curl -sS -o /tmp/ready-body.json -w '%{http_code}' --max-time 3 \ + http://localhost:$DEPLOY_HTTP_PORT/health/ready 2>/dev/null || echo "000") + STATUS=$BODY + if [ "$STATUS" = "200" ]; then + echo "Readiness OK (/health/ready)" + cat /tmp/ready-body.json + echo + break + fi + fi + if [ -f "$LOG" ] && tail -c +$((BEFORE+1)) "$LOG" | grep -qE "Application listener .* Exception|SEVERE.*startup.Catalina"; then echo "::error::Startup error detected in log" tail -c +$((BEFORE+1)) "$LOG" | tail -80 @@ -107,24 +129,11 @@ jobs: done if [ "$STATUS" != "200" ]; then - echo "::error::Health check failed within 90s (last HTTP status: $STATUS)" + echo "::error::Readiness probe failed within 120s (last HTTP status: $STATUS)" + [ -f /tmp/ready-body.json ] && echo "--- last readiness body ---" && cat /tmp/ready-body.json && echo [ -f "$LOG" ] && tail -c +$((BEFORE+1)) "$LOG" | tail -100 exit 1 fi echo "--- key boot log lines ---" tail -c +$((BEFORE+1)) "$LOG" | grep -E "Server startup|Deployment of web" | head -10 || true - - - name: Stop tomcat (post-clean) - if: always() - working-directory: ${{ github.workspace }} - run: | - if [ -f "$CATALINA_PID" ] && kill -0 "$(cat $CATALINA_PID)" 2>/dev/null; then - JAVA_HOME=$JAVA_HOME_TOMCAT "$CATALINA_HOME/bin/shutdown.sh" 30 -force || true - for i in $(seq 1 30); do - [ -f "$CATALINA_PID" ] && kill -0 "$(cat $CATALINA_PID)" 2>/dev/null || break - sleep 1 - done - [ -f "$CATALINA_PID" ] && kill -9 "$(cat $CATALINA_PID)" 2>/dev/null || true - fi - rm -f "$CATALINA_PID" diff --git a/src/main/java/com/eactive/apim/portal/apps/ReadinessController.java b/src/main/java/com/eactive/apim/portal/apps/ReadinessController.java new file mode 100644 index 0000000..125bb9f --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/ReadinessController.java @@ -0,0 +1,67 @@ +package com.eactive.apim.portal.apps; + +import java.sql.Connection; +import java.sql.SQLException; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Readiness probe. + * + *

Distinct from {@link HealthCheckController} (liveness — servlet alive?). + * This endpoint validates both EMS and Gateway datasources via JDBC + * {@code Connection.isValid(timeout)} to confirm the app is ready to serve + * requests that depend on the database. + * + *

HTTP 200 + JSON when all checks pass. + *

HTTP 503 + JSON when any check fails — body still includes the per-component + * status so CI/CD logs the failing dependency. + */ +@RestController +public class ReadinessController { + + private static final int VALIDATION_TIMEOUT_SECONDS = 2; + + private final DataSource portalDataSource; + private final DataSource gatewayDataSource; + + public ReadinessController( + @Qualifier("portalDataSource") DataSource portalDataSource, + @Qualifier("gatewayDataSource") DataSource gatewayDataSource) { + this.portalDataSource = portalDataSource; + this.gatewayDataSource = gatewayDataSource; + } + + @GetMapping(value = "/health/ready", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity ready() { + String ems = check(portalDataSource); + String gw = check(gatewayDataSource); + boolean ok = "UP".equals(ems) && "UP".equals(gw); + + String body = "{" + + "\"status\":\"" + (ok ? "UP" : "DOWN") + "\"," + + "\"ems\":\"" + ems + "\"," + + "\"gateway\":\""+ gw + "\"" + + "}"; + + return ResponseEntity.status(ok ? 200 : 503) + .contentType(MediaType.APPLICATION_JSON) + .body(body); + } + + private static String check(DataSource ds) { + if (ds == null) return "DOWN:NO_DATASOURCE"; + try (Connection c = ds.getConnection()) { + return c.isValid(VALIDATION_TIMEOUT_SECONDS) ? "UP" : "DOWN:INVALID"; + } catch (SQLException e) { + String msg = e.getMessage() == null ? "" : e.getMessage(); + return "DOWN:" + msg.replace('"', '\'').replace('\n', ' ').replace('\r', ' '); + } + } +}