Gitea 1.22 호환: 워크플로에서 workflow_dispatch 제거
All checks were successful
deploy-dev / deploy (push) Successful in 31s

Gitea 1.22.3은 workflow_dispatch(수동 실행)와 inputs 컨텍스트를 지원하지 않아
워크플로가 Actions 목록에 표시되지 않았다. 트리거를 push 하나로 단순화한다.
롤백은 서버에서 직접 수행하도록 README에 절차를 남긴다.

Tomcat 힙도 768m -> 448m로 낮춘다. 가용 메모리 1G 안팎인 서버에서
Tomcat 재기동 직후 서버 전체가 응답 불능이 된 사례가 두 번 있었다.
This commit is contained in:
URP2026 2026-07-29 17:03:55 +09:00
parent 7c574922d8
commit 62ef19c78f
6 changed files with 238 additions and 41 deletions

View File

@ -2,7 +2,7 @@
#
# ┌ 설계 배경 ─────────────────────────────────────────────────────────────┐
# │ 이 서버(zio-server)는 2코어 / 메모리 여유 1G에 40개 이상의 서비스가 │
# │ 동거한다. 실제로 구축 중 39분간 응답 불능 후 재부팅된 이력이 있다.
# │ 동거한다. 구축 중 서버 전체가 응답 불능이 되어 재부팅된 일이 두 번 있다.
# │ 그래서 Maven 빌드(힙 1G)는 서버에서 돌리지 않는다. │
# └────────────────────────────────────────────────────────────────────────┘
#
@ -10,7 +10,11 @@
# [개발자 PC] git push origin master → 이 워크플로 자동 실행
# [서버] ROOT 재전개 + 환경설정 주입 + 재기동 + 헬스체크(+실패 시 롤백)
#
# 수동 실행(workflow_dispatch)으로 재배포·롤백도 가능하다.
# ※ Gitea 1.22.3 기준으로 작성했다. 이 버전은 workflow_dispatch(수동 실행 버튼)와
# inputs 컨텍스트를 지원하지 않는다 — 넣으면 워크플로 자체가 목록에 뜨지 않는다.
# 그래서 트리거는 push 하나만 둔다.
# 재배포는 커밋 없이도 `git commit --allow-empty` 후 push 하면 되고,
# 롤백은 서버에서 직접 수행한다(deploy/dev/README.md 참고).
name: deploy-dev
on:
@ -22,16 +26,6 @@ on:
- '**.pptx'
- 'docs/**'
- '_workspace_privacy/**'
workflow_dispatch:
inputs:
action:
description: '수행할 작업'
required: true
default: 'deploy'
type: choice
options:
- deploy
- rollback
concurrency:
group: arko-dev-deploy
@ -51,13 +45,11 @@ jobs:
- name: 사전 점검
run: |
set -e
echo "트리거: ${{ gitea.event_name }} / 작업: ${{ inputs.action || 'deploy' }}"
df -h "$APP_HOME" | tail -1
free -h | head -2
uptime
- name: WAR 확인
if: ${{ inputs.action != 'rollback' }}
run: |
set -e
if [ ! -f "$INCOMING_WAR" ]; then
@ -68,36 +60,17 @@ jobs:
ls -lh "$INCOMING_WAR"
WAR_EPOCH=$(stat -c %Y "$INCOMING_WAR")
echo "WAR 빌드 시각: $(date -d @$WAR_EPOCH '+%F %T')"
# WAR이 이번 커밋보다 오래되었으면 옛 산출물을 배포하는 것이다.
# 문서/CI 파일만 바뀐 커밋도 있으므로 실패시키지 않고 경고만 남긴다.
COMMIT_EPOCH=$(git log -1 --format=%ct 2>/dev/null || echo 0)
if [ "$COMMIT_EPOCH" -gt "$WAR_EPOCH" ]; then
AGE_MIN=$(( (COMMIT_EPOCH - WAR_EPOCH) / 60 ))
echo "::warning::업로드된 WAR이 이번 커밋보다 ${AGE_MIN}분 오래됐다. 소스 변경이 포함된 커밋이라면 build-and-upload.sh 로 다시 올릴 것."
NOW=$(date +%s)
AGE_MIN=$(( (NOW - WAR_EPOCH) / 60 ))
if [ "$AGE_MIN" -gt 120 ]; then
echo "::warning::업로드된 WAR이 ${AGE_MIN}분 전 것이다. 소스 변경이 포함된 커밋이라면 build-and-upload.sh 로 다시 올릴 것."
fi
- name: 배포
if: ${{ inputs.action != 'rollback' }}
run: |
set -e
bash "$APP_HOME/bin/arko-dev-deploy.sh" "$INCOMING_WAR"
- name: 롤백
if: ${{ inputs.action == 'rollback' }}
run: |
set -e
LATEST=$(ls -1t "$APP_HOME"/backup/ROOT_*.tar.gz 2>/dev/null | head -1 || true)
if [ -z "$LATEST" ]; then
echo "::error::롤백할 백업이 없다"
exit 1
fi
echo "롤백 대상: $LATEST"
systemctl stop arko-dev
rm -rf "$APP_HOME/tomcat/webapps/ROOT"
tar -xzf "$LATEST" -C "$APP_HOME/tomcat/webapps"
systemctl start arko-dev
- name: 결과 확인
run: |
set -e

View File

@ -89,7 +89,19 @@ git push origin master
bash deploy/dev/build-and-upload.sh --deploy
```
**재배포·롤백:** Gitea 웹 > Actions > deploy-dev > Run workflow > `deploy` 또는 `rollback`
**소스 변경 없이 재배포 (WAR만 다시 올린 경우):**
```bash
git commit --allow-empty -m "재배포" && git push origin master
```
**롤백:** Gitea 1.22는 수동 실행(workflow_dispatch) 버튼이 없어 서버에서 직접 수행한다.
```bash
ssh root@101.79.17.164
systemctl stop arko-dev
rm -rf /opt/arko-dev/tomcat/webapps/ROOT
tar -xzf /opt/arko-dev/backup/ROOT_<타임스탬프>.tar.gz -C /opt/arko-dev/tomcat/webapps
systemctl start arko-dev
```
## 운영 명령

View File

@ -2,7 +2,12 @@
# 설치 위치: /etc/systemd/system/arko-dev.service
#
# 이 서버(zio-server)의 기존 서비스들과 같은 방식(systemd 직접 관리)을 따른다.
# 메모리 여유가 1.3G 수준이라 힙을 768m로 제한한다 — 부족하면 서버 전체가 스왑/OOM으로 흔들린다.
#
# 힙 448m — 처음엔 768m로 잡았다가 낮췄다. 이 서버는 40여 개 서비스가 동거해
# 평상시 가용 메모리가 1G 안팎이고, 2026-07-29 Tomcat 재기동 직후 서버 전체가
# 응답 불능이 되어 재부팅된 일이 두 번 있었다. arko는 기동 시 JSP 컴파일로
# 메모리를 크게 쓰므로 상한을 낮춰 다른 서비스를 밀어내지 않도록 한다.
# 늘리려면 반드시 free -h 로 여유를 먼저 확인할 것.
[Unit]
Description=arko_renual DEV (Tomcat 9, csv.zioinfo.co.kr, :8088)
After=network.target mariadb.service
@ -15,10 +20,10 @@ Environment="JAVA_HOME=/opt/arko-dev/jdk8"
Environment="CATALINA_HOME=/opt/arko-dev/tomcat"
Environment="CATALINA_BASE=/opt/arko-dev/tomcat"
Environment="CATALINA_PID=/opt/arko-dev/tomcat/temp/tomcat.pid"
# -Xmx768m: 서버 가용 메모리 기준 상한. 늘리려면 free -h 먼저 확인할 것
# headless: 이미지 처리(썸네일) 시 X11 없는 환경에서 죽지 않도록
# Asia/Seoul: 로그·배치 시각을 서버 다른 서비스와 맞춘다
Environment="CATALINA_OPTS=-Xms256m -Xmx768m -XX:MaxMetaspaceSize=256m -Djava.awt.headless=true -Duser.timezone=Asia/Seoul -Dfile.encoding=UTF-8"
# ExitOnOutOfMemoryError: OOM으로 반쯤 죽은 채 살아있느니 죽고 재기동하는 편이 낫다
Environment="CATALINA_OPTS=-Xms192m -Xmx448m -XX:MaxMetaspaceSize=192m -XX:+ExitOnOutOfMemoryError -Djava.awt.headless=true -Duser.timezone=Asia/Seoul -Dfile.encoding=UTF-8"
ExecStart=/opt/arko-dev/tomcat/bin/startup.sh
ExecStop=/opt/arko-dev/tomcat/bin/shutdown.sh 30 -force

View File

@ -0,0 +1,84 @@
#!/usr/bin/env bash
# csv.wise.ai.kr 웹 티어 구축 — 웹서버에서 실행
#
# 사용법: bash 01-web-server-setup.sh [WAS주소]
# WAS주소 기본값 101.79.17.164:8088 (같은 사설망이면 10.0.1.6:8088 로 주면 된다)
#
# 이 서버는 nginx만 올린다. WAS(Tomcat)와 DB(MariaDB)는 개발서버(101.79.17.164)를 그대로 쓴다.
# 멱등: 다시 실행해도 안전하다.
set -euo pipefail
WAS_ADDR=${1:-101.79.17.164:8088}
DOMAIN=csv.wise.ai.kr
SRC_DIR=$(cd "$(dirname "$0")" && pwd)
log() { echo -e "\n[$(date +'%F %T')] === $* ==="; }
# ----------------------------------------------------------- 1. nginx 설치
log "1. nginx 확인"
if command -v nginx > /dev/null; then
nginx -v 2>&1
else
echo "nginx 설치"
apt-get update -qq
apt-get install -y nginx
fi
# sites-enabled 구조가 없는 배포판(예: RHEL 계열)이면 만들어 준다
mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled
if ! grep -q "sites-enabled" /etc/nginx/nginx.conf; then
echo "경고: nginx.conf 가 sites-enabled 를 include 하지 않는다. 수동 확인 필요."
fi
# ------------------------------------------------- 2. WAS 도달 여부 확인
log "2. WAS 도달 확인 ($WAS_ADDR)"
CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 15 -H "Host: $DOMAIN" "http://$WAS_ADDR/" || echo 000)
echo "http://$WAS_ADDR/ → HTTP $CODE"
if [ "$CODE" = "000" ]; then
cat <<EOF
중단: WAS에 도달할 수 없다.
- WAS서버(101.79.17.164)에서 deploy/web/02-was-expose.sh <이 웹서버IP> 를 먼저 실행할 것
(Tomcat 바인딩 확대 + 방화벽에서 이 서버 IP만 8088 허용)
- 같은 사설망이면 WAS주소를 10.0.1.6:8088 로 주고 다시 실행할 것
EOF
exit 1
fi
# --------------------------------------------------------- 3. vhost 배치
log "3. nginx 가상호스트 등록"
install -m 644 "$SRC_DIR/$DOMAIN.conf" "/etc/nginx/sites-available/$DOMAIN.conf"
# upstream 주소를 인자 값으로 교체
sed -i "s|server 101.79.17.164:8088;|server $WAS_ADDR;|" "/etc/nginx/sites-available/$DOMAIN.conf"
grep -n "server .*:8088;" "/etc/nginx/sites-available/$DOMAIN.conf"
ln -sfn "/etc/nginx/sites-available/$DOMAIN.conf" "/etc/nginx/sites-enabled/$DOMAIN.conf"
if nginx -t; then
systemctl reload nginx
echo "nginx 반영 완료"
else
rm -f "/etc/nginx/sites-enabled/$DOMAIN.conf"
echo "nginx 설정 오류 → 링크 제거하고 중단"
exit 1
fi
# ------------------------------------------------------------- 4. 확인
log "4. 프록시 확인"
CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 30 -H "Host: $DOMAIN" http://127.0.0.1/ || echo 000)
echo "nginx 경유 → HTTP $CODE"
curl -s --max-time 30 -H "Host: $DOMAIN" http://127.0.0.1/ | grep -oiE "<title>[^<]*</title>" | head -1
# --------------------------------------------------------------- 5. SSL
log "5. SSL"
if [ -d "/etc/letsencrypt/live/$DOMAIN" ]; then
echo "인증서 이미 존재"
else
cat <<EOF
DNS A 레코드($DOMAIN → 이 서버 IP)가 등록된 뒤에 실행할 것:
certbot --nginx -d $DOMAIN --non-interactive --agree-tos --redirect
certbot 미설치 시: apt-get install -y certbot python3-certbot-nginx
EOF
fi
log "완료"

View File

@ -0,0 +1,57 @@
#!/usr/bin/env bash
# WAS서버(zio-server / 101.79.17.164)에서 실행 — 웹서버가 Tomcat에 붙을 수 있게 연다.
#
# 사용법: bash 02-was-expose.sh <웹서버IP>
#
# 현재 Tomcat은 127.0.0.1:8088 에만 바인딩되어 있어 같은 서버의 nginx만 접근 가능하다.
# 별도 웹서버를 두려면 바인딩을 넓히고, 대신 방화벽으로 그 웹서버 IP만 허용한다.
# (8088을 전체 공개하면 nginx를 우회해 WAS에 직접 접근할 수 있게 되므로 반드시 IP를 한정한다)
set -euo pipefail
WEB_IP=${1:-}
[ -n "$WEB_IP" ] || { echo "사용법: $0 <웹서버IP>"; exit 1; }
echo "$WEB_IP" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' || { echo "IP 형식이 아니다: $WEB_IP"; exit 1; }
XML=/opt/arko-dev/tomcat/conf/server.xml
TS=$(date +%Y%m%d_%H%M%S)
echo "=== 1. server.xml 백업 ==="
cp -f "$XML" "$XML.bak_$TS"
echo "$XML.bak_$TS"
echo
echo "=== 2. Tomcat 바인딩 확대 (127.0.0.1 → 0.0.0.0) ==="
# address 속성을 제거하면 전체 인터페이스에 바인딩된다.
# 노출 통제는 아래 ufw 규칙이 담당한다.
sed -i 's|port="8088" protocol="HTTP/1.1" address="127.0.0.1"|port="8088" protocol="HTTP/1.1"|' "$XML"
python3 -c "import xml.dom.minidom; xml.dom.minidom.parse('$XML'); print('server.xml XML 검증 OK')"
grep -n 'port="8088"' "$XML" | head -2
echo
echo "=== 3. 방화벽: $WEB_IP 만 8088 허용 ==="
ufw allow from "$WEB_IP" to any port 8088 proto tcp comment "arko-dev WAS <- csv.wise.ai.kr 웹서버"
ufw status | grep 8088
echo
echo "=== 4. Tomcat 재기동 ==="
systemctl restart arko-dev
for i in $(seq 1 60); do
CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 http://127.0.0.1:8088/ || echo 000)
if [ "$CODE" = "200" ] || [ "$CODE" = "302" ]; then break; fi
sleep 3
done
echo "로컬 확인 → HTTP $CODE"
echo
echo "=== 5. 리슨 상태 ==="
ss -lntp | grep 8088
cat <<EOF
다음: 웹서버에서 도달 확인
curl -s -o /dev/null -w "%{http_code}\\n" -H "Host: csv.wise.ai.kr" http://101.79.17.164:8088/
되돌리려면:
cp -f $XML.bak_$TS $XML && systemctl restart arko-dev
ufw delete allow from $WEB_IP to any port 8088 proto tcp
EOF

View File

@ -0,0 +1,66 @@
# csv.wise.ai.kr — arko_renual 웹 티어 (별도 웹서버에 설치)
#
# 구성: 브라우저 → [웹서버] nginx 443 → [WAS서버 101.79.17.164] Tomcat 8088
# DB는 WAS서버의 MariaDB(csv)를 그대로 사용한다. 웹서버는 DB에 접근하지 않는다.
#
# 설치 위치: /etc/nginx/sites-available/csv.wise.ai.kr.conf
# → /etc/nginx/sites-enabled/ 에 심볼릭 링크
#
# 선행 조건:
# 1) csv.wise.ai.kr A 레코드 → 이 웹서버 IP (현재 미등록)
# 2) WAS서버에서 8088을 이 웹서버 IP에만 개방 (deploy/web/02-was-expose.sh)
# 3) HTTP로 먼저 올린 뒤 certbot --nginx 가 SSL 블록을 주입
# WAS 백엔드 — 설치 시 실제 도달 가능한 주소로 바꿀 것
# 같은 사설망이면 10.0.1.6:8088, 공인망 경유면 101.79.17.164:8088
upstream arko_was {
server 101.79.17.164:8088;
keepalive 16;
}
server {
server_name csv.wise.ai.kr;
# 자원봉사 활동사진·첨부파일 업로드가 있어 기본 1m로는 부족하다
client_max_body_size 100m;
# 개발서버이므로 검색엔진 색인 차단
add_header X-Robots-Tag "noindex, nofollow" always;
access_log /var/log/nginx/csv.wise.ai.kr.access.log;
error_log /var/log/nginx/csv.wise.ai.kr.error.log;
location / {
proxy_pass http://arko_was;
proxy_http_version 1.1;
proxy_set_header Connection "";
# Host를 그대로 넘겨야 WAS가 csv.wise.ai.kr 기준으로 링크를 만든다
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
# arko는 첫 요청에서 JSP를 컴파일하고, 통계·엑셀 다운로드가 오래 걸린다
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_buffering on;
proxy_buffer_size 16k;
proxy_buffers 8 16k;
}
# 정적 리소스 — 웹서버에서 짧게 캐시 (WAS 왕복을 줄인다)
location ~* ^/(images|css|js|fonts|new)/ {
proxy_pass http://arko_was;
proxy_set_header Host $host;
proxy_cache_valid 200 10m;
expires 10m;
add_header Cache-Control "public, max-age=600";
}
listen 80;
}