fix(devops): 백엔드 미배포 근본수정 — deploy_kintex.sh 단일 빌드 진실원 + SIGPIPE 검증 버그
증상: push 후 프론트만 배포, 백엔드 jar 스테일(V6 고정, V7/V8 미적용). 근본원인 3종: 1) deploy_kintex.sh가 백엔드 빌드 안 함(복사만) → 이제 프론트+백엔드 직접 빌드(단일 진실원) 2) gradlew mode 100644(비실행) → git +x(100755) + sh ./gradlew(실행권한 불요) 3) jar 검증 'unzip -l | grep -q'가 set -o pipefail 하에서 grep 조기종료→unzip SIGPIPE(141) →메인클래스 발견에도 오탐 실패→die→jar 미복사. → grep -q 제거(전량 소비) + 선택 tail(파이프 조기종료 회피) - deploy_server kintex 블록: npm/gradle/verify 개별단계 제거 → git pull → deploy_kintex.sh(빌드+배포) → health - Jenkinsfile: 동일 스크립트 위임(경로 함정 회피, WORKSPACE 일치) - 서버 /opt/zioinfo/deploy_server.py 반영·재시작 완료(백업) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ca971c1cae
commit
63386b62aa
36
Jenkinsfile
vendored
36
Jenkinsfile
vendored
@ -18,37 +18,13 @@ pipeline {
|
|||||||
stage('Checkout') {
|
stage('Checkout') {
|
||||||
steps { checkout scm }
|
steps { checkout scm }
|
||||||
}
|
}
|
||||||
stage('Frontend Build') {
|
stage('Build & Deploy') {
|
||||||
steps {
|
steps {
|
||||||
dir('src/frontend') {
|
// 빌드+배포 단일 진실원 — deploy_kintex.sh 가 프론트(npm)·백엔드(sh ./gradlew bootJar) 빌드→검증→배포.
|
||||||
sh 'npm ci --silent || npm install --silent'
|
// webhook 경로(deploy_server.py)와 동일 스크립트 사용(경로 함정·SIGPIPE 검증 버그 회피).
|
||||||
sh 'NODE_OPTIONS=--max-old-space-size=4096 npm run build'
|
// 권한 필요한 배포는 sudo NOPASSWD 권장. 체크아웃 사본을 실행(설치본 스테일 회피).
|
||||||
}
|
sh 'chmod +x "$WORKSPACE/deploy/deploy_kintex.sh"'
|
||||||
}
|
sh 'sudo bash "$WORKSPACE/deploy/deploy_kintex.sh" "$WORKSPACE"'
|
||||||
}
|
|
||||||
stage('Backend Build') {
|
|
||||||
steps {
|
|
||||||
dir('src/backend') {
|
|
||||||
sh 'chmod +x gradlew'
|
|
||||||
sh './gradlew clean bootJar -x test -q --no-daemon'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Verify Jar') {
|
|
||||||
steps {
|
|
||||||
sh '''
|
|
||||||
JAR=$(ls -1 src/backend/build/libs/kintex-backend-*.jar | grep -v plain | head -1)
|
|
||||||
[ -n "$JAR" ] || { echo "jar not found"; exit 1; }
|
|
||||||
unzip -l "$JAR" | grep -q 'BOOT-INF/classes/com/zioinfo/kintex/KintexApplication.class' \
|
|
||||||
|| { echo "invalid jar (no main class)"; exit 1; }
|
|
||||||
echo "jar OK: $JAR"
|
|
||||||
'''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Deploy') {
|
|
||||||
steps {
|
|
||||||
// 권한 필요한 배포는 deploy_kintex.sh 로 위임(sudo NOPASSWD 권장).
|
|
||||||
sh 'sudo /opt/kintex/deploy_kintex.sh "$WORKSPACE"'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Health Check') {
|
stage('Health Check') {
|
||||||
|
|||||||
95
deploy/deploy_kintex.sh
Normal file → Executable file
95
deploy/deploy_kintex.sh
Normal file → Executable file
@ -1,67 +1,82 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# KINTEX 배포 스크립트 — deploy_server.py(webhook) 또는 Jenkins가 실행.
|
# KINTEX 배포 스크립트 — deploy_server.py(webhook) 또는 Jenkins가 실행.
|
||||||
# 인자: $1 = 소스 체크아웃 루트(빌드 산출물 위치, 기본 /opt/kintex/src)
|
# 인자: $1 = 소스 체크아웃 루트(기본 /opt/kintex/src). git pull 이후의 실제 경로와 반드시 일치.
|
||||||
# 동작(fail-safe): 프론트 dist 검증→원자적 교체(/var/www/kintex)→백엔드 jar 교체→서비스 재기동→헬스체크.
|
# 동작(단일 진실원): 프론트 빌드 + 백엔드 빌드 → 검증 → 원자적 교체 → 서비스 재기동 → 헬스체크 → 실패 시 롤백.
|
||||||
# 깨진 빌드가 라이브를 무너뜨리지 않도록 "검증 후 교체" + 실패 시 롤백(WISE deploy_uiws.sh 패턴).
|
#
|
||||||
|
# 재발방지(2026-07-11):
|
||||||
|
# ① 백엔드 미빌드 버그 — 과거엔 build/libs jar를 "복사만" 했다. 이제 이 스크립트가 직접 bootJar 빌드(단일 진실원).
|
||||||
|
# ② gradlew 실행권한 — 리포가 mode 100644로 커밋될 수 있어 `sh ./gradlew`로 실행(+x 불요) + chmod 병행.
|
||||||
|
# ③ jar 검증 SIGPIPE — `unzip -l | grep -q` 는 pipefail 하에서 grep 조기 종료→unzip SIGPIPE(141)→오탐 실패.
|
||||||
|
# → grep 을 -q 없이 전량 소비(>/dev/null)해 SIGPIPE 회피. jar 선택도 head 대신 tail(파이프 조기종료 방지).
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SRC="${1:-/opt/kintex/src}"
|
SRC="${1:-/opt/kintex/src}"
|
||||||
WEB_ROOT=/var/www/kintex
|
WEB_ROOT=/var/www/kintex
|
||||||
APP_JAR=/opt/kintex/app/app.jar
|
APP_JAR=/opt/kintex/app/app.jar
|
||||||
BACKEND_PORT=8021
|
BACKEND_PORT=8021
|
||||||
|
MAIN_CLASS_PATH='BOOT-INF/classes/com/zioinfo/kintex/KintexApplication.class'
|
||||||
|
|
||||||
log(){ echo "[deploy-kintex] $*"; }
|
log(){ echo "[deploy-kintex] $*"; }
|
||||||
die(){ echo "[deploy-kintex] ERROR: $*" >&2; exit 1; }
|
die(){ echo "[deploy-kintex] ERROR: $*" >&2; exit 1; }
|
||||||
|
|
||||||
# ── 1. 프론트: 빌드 완전성 검증(라이브 건드리기 전) → 스테이징 → 원자적 rename ──
|
[ -d "$SRC/src/backend" ] || die "소스 경로 불일치: $SRC/src/backend 없음(인자 \$1=$SRC 확인)"
|
||||||
|
|
||||||
|
# ── 1. 프론트 빌드(Vite) ──────────────────────────────────────────────────────
|
||||||
|
log "프론트 빌드 시작"
|
||||||
|
( cd "$SRC/src/frontend" && (npm ci 2>/dev/null || npm install) && \
|
||||||
|
NODE_OPTIONS=--max-old-space-size=4096 npm run build ) || die "프론트 빌드 실패"
|
||||||
|
|
||||||
|
# ── 2. 백엔드 빌드(bootJar) — `sh ./gradlew`(+x 불요), clean 으로 스테일 jar 제거 ──
|
||||||
|
log "백엔드 빌드 시작(bootJar)"
|
||||||
|
sed -i 's/\r$//' "$SRC/src/backend/gradlew" 2>/dev/null || true
|
||||||
|
chmod +x "$SRC/src/backend/gradlew" 2>/dev/null || true
|
||||||
|
( cd "$SRC/src/backend" && sh ./gradlew clean bootJar -x test --no-daemon ) \
|
||||||
|
|| die "백엔드 bootJar 빌드 실패"
|
||||||
|
|
||||||
|
# ── 3. 프론트: 완전성 검증 → 스테이징 → 원자적 rename 교체(빈 사이트 구간 제거) ──
|
||||||
DIST="$SRC/src/frontend/dist"
|
DIST="$SRC/src/frontend/dist"
|
||||||
if [ -d "$DIST" ]; then
|
[ -f "$DIST/index.html" ] || die "dist/index.html 없음 — 불완전 빌드, 중단(라이브 무변경)"
|
||||||
[ -f "$DIST/index.html" ] || die "dist/index.html 없음 — 불완전 빌드, 배포 중단(라이브 무변경)"
|
[ -d "$DIST/assets" ] || die "dist/assets 없음 — 불완전 빌드, 중단"
|
||||||
[ -d "$DIST/assets" ] || die "dist/assets 없음 — 불완전 빌드, 배포 중단"
|
mapfile -t REFS < <(grep -oE '/assets/[A-Za-z0-9._-]+\.(js|css)' "$DIST/index.html" | sort -u)
|
||||||
mapfile -t REFS < <(grep -oE '/assets/[A-Za-z0-9._-]+\.(js|css)' "$DIST/index.html" | sort -u)
|
[ "${#REFS[@]}" -gt 0 ] || die "index.html 참조 assets 없음 — 비정상 빌드, 중단"
|
||||||
[ "${#REFS[@]}" -gt 0 ] || die "index.html 참조 assets 없음 — 비정상 빌드, 중단"
|
for ref in "${REFS[@]}"; do
|
||||||
for ref in "${REFS[@]}"; do
|
[ -f "${DIST}${ref}" ] || die "참조 자산 누락: ${ref} — 불완전 빌드, 중단"
|
||||||
[ -f "${DIST}${ref}" ] || die "참조 자산 누락: ${ref} — 불완전 빌드, 중단"
|
done
|
||||||
done
|
log "프론트 빌드 검증 통과(${#REFS[@]} assets)"
|
||||||
log "프론트 빌드 검증 통과(${#REFS[@]} assets)"
|
|
||||||
|
|
||||||
STAGE="${WEB_ROOT}.new.$$"
|
STAGE="${WEB_ROOT}.new.$$"
|
||||||
rm -rf "$STAGE"; mkdir -p "$STAGE"
|
rm -rf "$STAGE"; mkdir -p "$STAGE"
|
||||||
cp -r "$DIST/." "$STAGE/"
|
cp -r "$DIST/." "$STAGE/"
|
||||||
if [ -d "$WEB_ROOT" ] && [ -n "$(ls -A "$WEB_ROOT" 2>/dev/null)" ]; then
|
if [ -d "$WEB_ROOT" ] && [ -n "$(ls -A "$WEB_ROOT" 2>/dev/null)" ]; then
|
||||||
tar czf "/var/www/kintex.bak_$(date +%Y%m%d_%H%M%S).tgz" -C "$WEB_ROOT" . || log "WARN: 프론트 백업 실패(계속)"
|
tar czf "/var/www/kintex.bak_$(date +%Y%m%d_%H%M%S).tgz" -C "$WEB_ROOT" . || log "WARN: 프론트 백업 실패(계속)"
|
||||||
fi
|
|
||||||
OLD="${WEB_ROOT}.old.$$"
|
|
||||||
[ -d "$WEB_ROOT" ] && mv "$WEB_ROOT" "$OLD" || true
|
|
||||||
mv "$STAGE" "$WEB_ROOT"
|
|
||||||
rm -rf "$OLD"
|
|
||||||
log "프론트 배포 완료 → $WEB_ROOT"
|
|
||||||
else
|
|
||||||
log "WARN: $DIST 없음 — 프론트 배포 건너뜀(백엔드만)"
|
|
||||||
fi
|
fi
|
||||||
|
OLD="${WEB_ROOT}.old.$$"
|
||||||
|
[ -d "$WEB_ROOT" ] && mv "$WEB_ROOT" "$OLD" || true
|
||||||
|
mv "$STAGE" "$WEB_ROOT"
|
||||||
|
rm -rf "$OLD"
|
||||||
|
log "프론트 배포 완료 → $WEB_ROOT"
|
||||||
|
|
||||||
# ── 2. 백엔드 jar: 검증(BOOT-INF 메인 클래스) 후 교체 ──
|
# ── 4. 백엔드 jar: 선택(tail — SIGPIPE 회피) → 검증(grep 전량소비 — SIGPIPE 회피) → 교체 ──
|
||||||
JAR=$(ls -1 "$SRC"/src/backend/build/libs/kintex-backend-*.jar 2>/dev/null | grep -v plain | head -1 || true)
|
JAR=$(ls -1 "$SRC"/src/backend/build/libs/kintex-backend-*.jar 2>/dev/null | grep -v plain | tail -1 || true)
|
||||||
if [ -n "$JAR" ]; then
|
[ -n "$JAR" ] || die "bootJar 없음($SRC/src/backend/build/libs) — 빌드 산출물 확인"
|
||||||
unzip -l "$JAR" | grep -q 'BOOT-INF/classes/com/zioinfo/kintex/KintexApplication.class' \
|
# grep 을 -q 없이 실행해 unzip 출력을 끝까지 소비(pipefail+SIGPIPE 오탐 방지). 발견 시 rc=0.
|
||||||
|| die "유효하지 않은 jar(메인 클래스 없음) — 배포 중단"
|
if ! unzip -l "$JAR" | grep -F "$MAIN_CLASS_PATH" >/dev/null; then
|
||||||
[ -f "$APP_JAR" ] && cp "$APP_JAR" "${APP_JAR}.bak" || true
|
die "유효하지 않은 jar(메인 클래스 없음: $JAR) — 배포 중단"
|
||||||
cp "$JAR" "$APP_JAR"
|
|
||||||
log "백엔드 jar 교체 → $APP_JAR ($(basename "$JAR"))"
|
|
||||||
else
|
|
||||||
log "WARN: bootJar 없음 — 백엔드 교체 건너뜀"
|
|
||||||
fi
|
fi
|
||||||
|
[ -f "$APP_JAR" ] && cp "$APP_JAR" "${APP_JAR}.bak" || true
|
||||||
|
cp "$JAR" "$APP_JAR"
|
||||||
|
log "백엔드 jar 교체 → $APP_JAR ($(basename "$JAR"), $(stat -c%s "$JAR") bytes)"
|
||||||
|
|
||||||
# ── 3. 워커 의존성 갱신(venv) — requirements 변경 대비(멱등) ──
|
# ── 5. 워커 의존성 갱신(멱등) ──
|
||||||
if [ -x /opt/kintex/worker/venv/bin/pip ]; then
|
if [ -x /opt/kintex/worker/venv/bin/pip ]; then
|
||||||
/opt/kintex/worker/venv/bin/pip install -q redis Pillow google-genai 2>/dev/null || log "WARN: 워커 의존성 갱신 스킵"
|
/opt/kintex/worker/venv/bin/pip install -q redis Pillow google-genai 2>/dev/null || log "WARN: 워커 의존성 갱신 스킵"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── 4. 서비스 재기동 ──
|
# ── 6. 서비스 재기동 ──
|
||||||
systemctl restart kintex.service
|
systemctl restart kintex.service
|
||||||
systemctl restart kintex-nanobanana.service 2>/dev/null || log "WARN: 워커 재기동 스킵"
|
systemctl restart kintex-nanobanana.service 2>/dev/null || log "WARN: 워커 재기동 스킵"
|
||||||
|
|
||||||
# ── 5. 헬스체크(백엔드 /health 200 = success:true) — 실패 시 jar 롤백 ──
|
# ── 7. 헬스체크(/health 200) — 실패 시 jar 롤백 ──
|
||||||
ok=0
|
ok=0
|
||||||
for i in $(seq 1 20); do
|
for i in $(seq 1 20); do
|
||||||
sleep 3
|
sleep 3
|
||||||
@ -74,4 +89,4 @@ if [ "$ok" != "1" ]; then
|
|||||||
systemctl is-active kintex.service || true
|
systemctl is-active kintex.service || true
|
||||||
die "배포 헬스체크 실패(롤백 수행)"
|
die "배포 헬스체크 실패(롤백 수행)"
|
||||||
fi
|
fi
|
||||||
log "배포 완료 — 백엔드 8021 UP, 워커 재기동"
|
log "배포 완료 — 백엔드 8021 UP(jar 갱신), 프론트 배포, 워커 재기동"
|
||||||
|
|||||||
@ -2,12 +2,15 @@
|
|||||||
# (deploy_server.py 는 루트 인프라 파일이므로 kintex 리포엔 이 참조 사본만 둔다. 서버 사본이 실행 권위.)
|
# (deploy_server.py 는 루트 인프라 파일이므로 kintex 리포엔 이 참조 사본만 둔다. 서버 사본이 실행 권위.)
|
||||||
# 삽입 위치: 마지막 `elif repo == "uiws":` 블록 다음, `logging.info(f"=== {repo} 배포 완료 ===")` 앞.
|
# 삽입 위치: 마지막 `elif repo == "uiws":` 블록 다음, `logging.info(f"=== {repo} 배포 완료 ===")` 앞.
|
||||||
# Gitea owner=zio, 기본 브랜치=main, 백엔드 포트 8021.
|
# Gitea owner=zio, 기본 브랜치=main, 백엔드 포트 8021.
|
||||||
|
# ★빌드는 deploy_kintex.sh 단일 진실원(프론트 npm + 백엔드 sh ./gradlew bootJar). 블록은 git pull → deploy_kintex.sh → health.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
elif repo == "kintex":
|
elif repo == "kintex":
|
||||||
# 분리형 배포: 프론트 dist(Vite) → /var/www/kintex, 백엔드 bootJar(Gradle) → /opt/kintex/app/app.jar,
|
# 분리형 배포: 프론트 dist(Vite) → /var/www/kintex, 백엔드 bootJar(Gradle) → /opt/kintex/app/app.jar,
|
||||||
# kintex.service + kintex-nanobanana.service(나노바나나 워커, GEMINI 키는 워커 env 전용) 재기동.
|
# kintex.service + kintex-nanobanana.service(나노바나나 워커, GEMINI 키는 워커 env 전용) 재기동.
|
||||||
# Flyway baseline-on-migrate=true → 기동 시 스키마 자동 적용. deploy_kintex.sh 가 검증→원자교체→헬스체크→롤백.
|
# Flyway baseline-on-migrate=true → 기동 시 스키마 자동 적용.
|
||||||
|
# ★빌드는 deploy_kintex.sh 가 단일 진실원으로 수행(프론트 npm + 백엔드 sh ./gradlew bootJar)
|
||||||
|
# → 검증(SIGPIPE-safe)→원자교체→jar 교체→재기동→헬스체크→실패 시 롤백. (2026-07-11 백엔드 미배포 버그 근본수정)
|
||||||
SRC = "/opt/kintex/src"
|
SRC = "/opt/kintex/src"
|
||||||
ok = run_steps(repo, [
|
ok = run_steps(repo, [
|
||||||
("git pull", ["bash", "-c",
|
("git pull", ["bash", "-c",
|
||||||
@ -17,19 +20,7 @@
|
|||||||
f"[ -e {SRC} ] && mv {SRC} {SRC}.bak_$(date +%Y%m%d%H%M%S); "
|
f"[ -e {SRC} ] && mv {SRC} {SRC}.bak_$(date +%Y%m%d%H%M%S); "
|
||||||
f"git clone 'http://zio:1q2w3e%21Q@127.0.0.1:9003/zio/kintex.git' {SRC}; "
|
f"git clone 'http://zio:1q2w3e%21Q@127.0.0.1:9003/zio/kintex.git' {SRC}; "
|
||||||
f"fi"]),
|
f"fi"]),
|
||||||
("npm build", ["bash", "-c",
|
("build+deploy (deploy_kintex.sh)", ["bash", "-c",
|
||||||
f"cd {SRC}/src/frontend && (npm ci 2>/dev/null || npm install) && "
|
|
||||||
f"NODE_OPTIONS=--max-old-space-size=4096 npm run build"]),
|
|
||||||
("gradle build", ["bash", "-c",
|
|
||||||
f"cd {SRC}/src/backend && chmod +x gradlew 2>/dev/null; sed -i 's/\\r$//' gradlew 2>/dev/null; "
|
|
||||||
f"./gradlew clean bootJar -x test -q --no-daemon > /tmp/kintex_gradle.log 2>&1; "
|
|
||||||
f"rc=$?; tail -8 /tmp/kintex_gradle.log; exit $rc"]),
|
|
||||||
("verify jar", ["bash", "-c",
|
|
||||||
f"JAR=$(ls -1 {SRC}/src/backend/build/libs/kintex-backend-*.jar 2>/dev/null | grep -v plain | head -1); "
|
|
||||||
f"[ -n \"$JAR\" ] || {{ echo 'jar not found'; exit 1; }}; "
|
|
||||||
f"unzip -l \"$JAR\" | grep -q 'BOOT-INF/classes/com/zioinfo/kintex/KintexApplication.class' "
|
|
||||||
f"|| {{ echo 'invalid jar'; exit 1; }}; echo \"jar OK: $JAR\""]),
|
|
||||||
("deploy (deploy_kintex.sh)", ["bash", "-c",
|
|
||||||
f"sed -i 's/\\r$//' {SRC}/deploy/*.sh 2>/dev/null; "
|
f"sed -i 's/\\r$//' {SRC}/deploy/*.sh 2>/dev/null; "
|
||||||
f"bash {SRC}/deploy/deploy_kintex.sh {SRC}"]),
|
f"bash {SRC}/deploy/deploy_kintex.sh {SRC}"]),
|
||||||
("health check", ["bash", "-c",
|
("health check", ["bash", "-c",
|
||||||
|
|||||||
0
src/backend/gradlew
vendored
Normal file → Executable file
0
src/backend/gradlew
vendored
Normal file → Executable file
Loading…
Reference in New Issue
Block a user