Some checks failed
deploy-wise / deploy (push) Failing after 11s
웹서버(175.118.124.176)의 Gitea 러너가 JDK8 로 WAR 을 빌드해 WAS(27.255.76.2)의 Tomcat·MariaDB 컨테이너로 배포한다. WAS 는 웹서버를 통해서만 닿고, Tomcat 은 127.0.0.1 바인딩이라 웹서버가 SSH 터널로 잇는다. - WAS 는 가용 메모리가 3G 안팎(llama-server 200G+ 점유)이라 빌드를 웹서버에서 한다 - claudeops 가 sudo 를 쓸 수 없어 Tomcat·MariaDB 를 컨테이너로 올렸다 - 배포 스크립트를 매 실행마다 저장소본으로 덮어써 서버 사본 드리프트를 막는다 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
116 lines
5.3 KiB
Bash
116 lines
5.3 KiB
Bash
#!/usr/bin/env bash
|
|
# arko(문화품앗이) WAS 배포 스크립트 — 27.255.76.2
|
|
#
|
|
# 설치 위치: /opt/arko-app/deploy/arko-deploy.sh
|
|
# 호출 주체: Gitea Actions(웹서버 러너)가 ssh 로, 또는 수동 실행
|
|
# 사용법 : bash arko-deploy.sh /opt/arko-app/incoming/arko.war
|
|
#
|
|
# ┌ 이 서버의 제약 ────────────────────────────────────────────────────────┐
|
|
# │ claudeops 는 sudo 를 쓸 수 없다. 대신 docker 그룹에 속한다. │
|
|
# │ 그래서 Tomcat·MariaDB 는 패키지 설치가 아니라 컨테이너로 돌린다. │
|
|
# │ 또한 llama-server 가 200G+ 를 점유해 가용 메모리가 3G 안팎이다. │
|
|
# │ 컨테이너에 메모리 상한을 걸어 두었으니 함부로 올리지 말 것. │
|
|
# └────────────────────────────────────────────────────────────────────────┘
|
|
#
|
|
# 동작: WAR 검증 → 현재 ROOT 백업(3개 유지) → 컨테이너 정지 → 재전개
|
|
# → 환경설정 주입 → 기동 → 헬스체크 (실패 시 직전 백업으로 자동 롤백)
|
|
set -euo pipefail
|
|
|
|
APP_HOME=/opt/arko-app
|
|
WEBAPPS=$APP_HOME/webapps
|
|
ROOT_DIR=$WEBAPPS/ROOT
|
|
CONFIG_DIR=$APP_HOME/config
|
|
BACKUP_DIR=$APP_HOME/backup
|
|
LOG_DIR=$APP_HOME/logs
|
|
CONTAINER=arko-tomcat
|
|
HEALTH_URL=http://127.0.0.1:11060/
|
|
HEALTH_TIMEOUT=240 # 초. arko 는 첫 기동에서 JSP 컴파일로 60~120초 걸린다
|
|
KEEP_BACKUPS=3
|
|
|
|
WAR_SRC=${1:-$APP_HOME/incoming/arko.war}
|
|
TS=$(date +%Y%m%d_%H%M%S)
|
|
|
|
mkdir -p "$BACKUP_DIR" "$LOG_DIR" "$WEBAPPS"
|
|
log() { echo "[$(date +'%F %T')] $*" | tee -a "$LOG_DIR/deploy.log"; }
|
|
die() { log "ERROR: $*"; exit 1; }
|
|
|
|
# ------------------------------------------------------------- 1. WAR 검증
|
|
[ -f "$WAR_SRC" ] || die "WAR 파일 없음: $WAR_SRC"
|
|
# unzip -l 을 파이프로 grep 하면 set -o pipefail 과 맞물려 정상 WAR 을 오판한다
|
|
# (2026-07-30 개발 파이프라인에서 실제로 배포가 멈춘 적이 있다). 한 번만 읽어 변수에 담는다.
|
|
WAR_LIST=$(unzip -l "$WAR_SRC") || die "WAR 손상: $WAR_SRC"
|
|
grep -q "WEB-INF/web.xml" <<< "$WAR_LIST" || die "WEB-INF/web.xml 없음 — 정상 WAR 아님"
|
|
log "배포 시작: $WAR_SRC ($(du -h "$WAR_SRC" | cut -f1))"
|
|
|
|
# 전개본 + 백업까지 WAR 3배의 여유가 필요하다
|
|
NEED_KB=$(( $(du -k "$WAR_SRC" | cut -f1) * 3 ))
|
|
FREE_KB=$(df -k "$APP_HOME" | awk 'NR==2 {print $4}')
|
|
[ "$FREE_KB" -gt "$NEED_KB" ] || die "디스크 부족: 여유 $((FREE_KB/1024))MB, 필요 $((NEED_KB/1024))MB"
|
|
|
|
# --------------------------------------------------------- 2. 현재본 백업
|
|
BACKUP_FILE=""
|
|
if [ -d "$ROOT_DIR" ]; then
|
|
BACKUP_FILE="$BACKUP_DIR/ROOT_$TS.tar.gz"
|
|
log "현재 ROOT 백업 → $BACKUP_FILE"
|
|
tar -czf "$BACKUP_FILE" -C "$WEBAPPS" ROOT
|
|
ls -1t "$BACKUP_DIR"/ROOT_*.tar.gz 2>/dev/null | tail -n +$((KEEP_BACKUPS+1)) | xargs -r rm -f
|
|
fi
|
|
|
|
# --------------------------------------------------------- 3. 컨테이너 정지
|
|
if docker ps -a --format '{{.Names}}' | grep -qx "$CONTAINER"; then
|
|
log "Tomcat 정지"
|
|
docker stop "$CONTAINER" >/dev/null 2>&1 || true
|
|
fi
|
|
|
|
# ------------------------------------------------------------- 4. 재전개
|
|
log "ROOT 재전개"
|
|
rm -rf "$ROOT_DIR" "$WEBAPPS/ROOT.tmp"
|
|
mkdir -p "$WEBAPPS/ROOT.tmp"
|
|
unzip -q "$WAR_SRC" -d "$WEBAPPS/ROOT.tmp"
|
|
mv "$WEBAPPS/ROOT.tmp" "$ROOT_DIR"
|
|
|
|
# ------------------------------------------------- 5. 환경설정 주입 (필수)
|
|
# globals-{dev,live}.properties 는 .gitignore 대상이라 저장소에도 WAR 에도 없다.
|
|
# context-datasource.xml 이 globals-dev.properties 를 읽으므로 이 주입이 없으면 기동 실패한다.
|
|
PROPS_DEST="$ROOT_DIR/WEB-INF/classes/egovframework/egovProps"
|
|
[ -d "$PROPS_DEST" ] || die "egovProps 경로 없음: $PROPS_DEST"
|
|
for f in globals-dev.properties globals-live.properties; do
|
|
[ -f "$CONFIG_DIR/$f" ] || die "서버 보관 환경설정 없음: $CONFIG_DIR/$f"
|
|
cp -f "$CONFIG_DIR/$f" "$PROPS_DEST/$f"
|
|
log "환경설정 주입: $f"
|
|
done
|
|
chmod 640 "$PROPS_DEST"/globals-*.properties
|
|
|
|
# --------------------------------------------------------- 6. 컨테이너 기동
|
|
log "Tomcat 기동"
|
|
docker start "$CONTAINER" >/dev/null || die "컨테이너 기동 실패 — 'docker logs $CONTAINER' 확인"
|
|
|
|
# ------------------------------------------------------------- 7. 헬스체크
|
|
log "헬스체크 (최대 ${HEALTH_TIMEOUT}초)"
|
|
CODE=000
|
|
for _ in $(seq 1 $((HEALTH_TIMEOUT/5))); do
|
|
CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$HEALTH_URL" || echo 000)
|
|
case "$CODE" in 200|302) break ;; esac
|
|
sleep 5
|
|
done
|
|
|
|
case "$CODE" in
|
|
200|302)
|
|
log "배포 완료 — HTTP $CODE"
|
|
;;
|
|
*)
|
|
log "헬스체크 실패 (HTTP $CODE)"
|
|
if [ -n "$BACKUP_FILE" ] && [ -f "$BACKUP_FILE" ]; then
|
|
log "롤백: $BACKUP_FILE 복구"
|
|
docker stop "$CONTAINER" >/dev/null 2>&1 || true
|
|
rm -rf "$ROOT_DIR"
|
|
tar -xzf "$BACKUP_FILE" -C "$WEBAPPS"
|
|
docker start "$CONTAINER" >/dev/null 2>&1 || true
|
|
log "롤백 완료 — 기동 로그: docker logs --tail 100 $CONTAINER"
|
|
else
|
|
log "복구할 백업이 없다(최초 배포). 컨테이너 로그를 확인할 것."
|
|
fi
|
|
exit 1
|
|
;;
|
|
esac
|