From 08e634c3a50d73b7064ee7cb8d693b9f8d4adf45 Mon Sep 17 00:00:00 2001 From: itms-merge-dev Date: Fri, 17 Jul 2026 21:08:59 +0900 Subject: [PATCH] =?UTF-8?q?ci(itms):=20CI/CD=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=95=84=ED=8B=B0=ED=8C=A9=ED=8A=B8=20=EC=B6=94=EA=B0=80=20(?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20=EB=8C=80=EA=B8=B0=C2=B7=EC=82=B0=EC=B6=9C?= =?UTF-8?q?=EB=AC=BC=20=EC=A4=80=EB=B9=84=ED=98=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit zioinfo.co.kr 피저빌리티 게이트 결과 ITMS 실기동(Tier2)은 이 환경 불가 (런타임 DB MariaDB/Oracle 도달 불가 + JDK17 부재 + Boot2.6.2 Java21 미지원) → 서버 미변경, 배포 대상 환경에서 적용할 준비된 산출물만 생성. - deploy/deploy_server_itms_block.py: deploy_server.py 에 붙일 itms 블록 (Tier1 빌드+커밋별 아카이브; Tier2 재기동은 주석 처리, 기존 블록 불변, 자격증명 env 주입) - deploy/build.sh: common 선행 → 5모듈 bootWar (JDK17) - deploy/systemd/itms-{auth,api,front,batch,datatrans}.service: 4포트 WAR+데몬 유닛 템플릿 - deploy/env/itms.env.example: SPRING_PROFILES_ACTIVE 등 (실 시크릿 미포함) - deploy/gitea-webhook.md: webhook 3대 함정(url·secret·ALLOWED_HOST_LIST) 반영 생성 가이드(미실행) - deploy/README.md: 특수성·적용 절차·피저빌리티 근거 보안: AES 암호문·DB 접속정보·NCP 키·Gitea 비번 하드코딩 0(env 주입). 기존 단일-jar 배포 무영향. Co-Authored-By: Claude Fable 5 --- deploy/README.md | 48 +++++++++++++++++++++++++ deploy/build.sh | 21 +++++++++++ deploy/deploy_server_itms_block.py | 50 +++++++++++++++++++++++++++ deploy/env/itms.env.example | 7 ++++ deploy/gitea-webhook.md | 31 +++++++++++++++++ deploy/systemd/itms-api.service | 14 ++++++++ deploy/systemd/itms-auth.service | 14 ++++++++ deploy/systemd/itms-batch.service | 14 ++++++++ deploy/systemd/itms-datatrans.service | 14 ++++++++ deploy/systemd/itms-front.service | 14 ++++++++ 10 files changed, 227 insertions(+) create mode 100644 deploy/README.md create mode 100644 deploy/build.sh create mode 100644 deploy/deploy_server_itms_block.py create mode 100644 deploy/env/itms.env.example create mode 100644 deploy/gitea-webhook.md create mode 100644 deploy/systemd/itms-api.service create mode 100644 deploy/systemd/itms-auth.service create mode 100644 deploy/systemd/itms-batch.service create mode 100644 deploy/systemd/itms-datatrans.service create mode 100644 deploy/systemd/itms-front.service diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 00000000..686f08ca --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,48 @@ +# ITMS CI/CD 배포 아티팩트 (적용 대기) + +> 생성: 2026-07-17 (itms-cicd 하네스, 산출물 준비형) | 대상 저장소: Gitea `ythong/itms` +> **상태: 적용 대기(NOT applied).** 이 디렉터리는 배포 대상 환경에서 적용할 준비된 산출물이다. 서버는 변경되지 않았다. + +## 왜 "적용 대기"인가 (피저빌리티 게이트) + +GUARDiA 인프라 서버(zioinfo.co.kr)에서 실측한 결과, **ITMS 앱의 실기동(Tier2)은 이 환경에서 불가**하다: +- api/batch/datatrans 런타임 DB(MariaDB `ur_itms_db`·Oracle XE)가 GUARDiA 인프라에서 **도달 불가**(고객사 전용망 DB). +- 서버 런타임 JDK가 **21뿐**(JDK17 부재). ITMS는 Boot 2.6.2로 **Java 21 미지원**. + +따라서 실배포는 **고객사 배포 환경**(대상 기관 DB망 + 기관 프로파일 시크릿 + JDK17)에서 아래 아티팩트를 적용해 수행한다. Tier1(빌드 자동화)만 별도로 쓰려면 빌드 서버에 JDK17을 두고 `deploy_server_itms_block.py`의 빌드 단계만 사용한다. + +## 구성 특수성 + +ITMS는 단일-jar가 아니라 **4포트 독립 WAR 스택 + 데몬**: + +| 모듈 | 포트 | 산출물 | 런타임 의존 | systemd | +|---|---|---|---|---| +| auth | 11000 | bootWar | (RSA jwtkey) | itms-auth.service | +| api | 11010 | bootWar | MariaDB ur_itms_db | itms-api.service | +| front | 11020 | bootWar | DB 무접속(REST만) | itms-front.service | +| batch | 11030 | bootWar | MariaDB + Oracle XE + 메일/알림톡 | itms-batch.service | +| datatrans | (데몬) | bootWar | kccfnova/uac_cltr_db + NAS | itms-datatrans.service | +| common | — | 라이브러리(선행 빌드) | — | — | +| legacy/nlib | — | Maven·Java8, **CI 제외** | — | — | + +## 파일 + +| 파일 | 용도 | +|---|---| +| `deploy_server_itms_block.py` | `scripts/deploy/deploy_server.py`의 `_deploy()`에 붙여넣을 `elif repo == "itms":` 블록(Tier1 빌드+아카이브; 기존 블록 불변) | +| `build.sh` | 모듈별 gradle 빌드(common 선행 → 5모듈 bootWar) — JDK17 필요 | +| `systemd/itms-*.service` | 모듈별 systemd 유닛 템플릿(Tier2, 활성화 조건 충족 시) | +| `env/itms-*.env.example` | 모듈별 env 템플릿(`SPRING_PROFILES_ACTIVE`=기관) — **실 시크릿은 여기 넣지 말고 서버 로컬에서 관리** | +| `gitea-webhook.md` | Gitea `ythong/itms` push webhook 생성 가이드 + 스크립트(미실행) | + +## 적용 절차 (고객사/배포 대상 환경) + +1. **선결:** JDK17 설치, 런타임 DB(MariaDB/Oracle) 연결, 기관 프로파일 시크릿(keystore·yml) 배치. +2. **CI:** `deploy_server_itms_block.py`를 배포 서버 deploy_server.py에 병합 → deploy 서비스 재시작. Gitea webhook 생성(`gitea-webhook.md`). +3. **빌드:** `build.sh`(또는 deploy 블록)로 `common` 선행 → 5모듈 bootWar. 산출물 `*/build/libs/*.war`. +4. **CD(Tier2):** `env/itms-.env`에 `SPRING_PROFILES_ACTIVE=<기관>` 설정 → `systemd/itms-*.service` 배치 → `systemctl daemon-reload && enable --now`. +5. **헬스체크:** 11000/11010/11020 도달, batch/datatrans 로그 라이브니스. + +## 보안 불변 +- 이 디렉터리 어떤 파일에도 AES 암호문·DB 접속정보·NCP/카카오 키·Gitea 비번·SSH 계정을 **하드코딩 금지**. 자격증명은 env/서버 로컬에서만. +- force push 금지. 기존 단일-jar 솔루션 배포에 무영향(deploy_server는 itms 블록만 추가). diff --git a/deploy/build.sh b/deploy/build.sh new file mode 100644 index 00000000..b9afbd77 --- /dev/null +++ b/deploy/build.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# ITMS 모듈별 빌드 (JDK17 필요). common 선행 → 5모듈 bootWar. legacy/nlib 제외. +# 사용: JAVA_HOME=/path/to/jdk17 deploy/build.sh (repo 루트에서 실행) +set -euo pipefail +cd "$(dirname "$0")/.." + +if [ -z "${JAVA_HOME:-}" ]; then + echo "WARN: JAVA_HOME 미설정 — JDK17 경로를 지정하세요 (ITMS 는 Boot 2.6.2·Java17)." >&2 +fi + +echo "== gradle: common build + 5모듈 bootWar ==" +./gradlew --no-daemon \ + :common:build \ + :auth:bootWar :api:bootWar :front:bootWar :batch:bootWar :datatrans:bootWar \ + -x test + +echo "== 산출물 ==" +for m in auth api front batch datatrans; do + ls -1 "$m"/build/libs/*.war 2>/dev/null || echo " ($m: war 없음)" +done +echo "완료. WAR 를 systemd(itms-*.service)로 기동하려면 런타임 DB·기관 프로파일 시크릿 확보 필요." diff --git a/deploy/deploy_server_itms_block.py b/deploy/deploy_server_itms_block.py new file mode 100644 index 00000000..5b58fc81 --- /dev/null +++ b/deploy/deploy_server_itms_block.py @@ -0,0 +1,50 @@ +# ===================================================================== +# ITMS CI/CD 블록 — scripts/deploy/deploy_server.py 의 _deploy(self, repo) 에 +# 기존 elif 체인 말미에 그대로 붙여넣는다. (기존 블록은 변경하지 않는다.) +# +# 특수성: ITMS 는 Gradle 멀티모듈(Boot 2.6.2·Java17) 4포트 WAR 스택. +# - Tier1(빌드 자동화): common 선행 → 5모듈 bootWar → 커밋별 아카이브. DB 불필요. +# - Tier2(기동): 런타임 DB(MariaDB/Oracle)·기관 시크릿·JDK17 확보 환경에서만 +# 아래 (restart ...) 단계 주석을 해제하여 systemd 재기동. 기본은 빌드+아카이브까지. +# +# 선결: 배포 서버에 JDK17 설치(예: /usr/lib/jvm/java-17-openjdk-amd64) 후 JAVA17 경로 지정. +# 자격증명: Gitea 클론 URL 의 사용자/토큰은 서버 환경변수로 주입한다(하드코딩 금지). +# 예) 서버 deploy_server 환경에 GITEA_CLONE_CRED='user:token' 를 두고 아래 f-string 에서 사용. +# ===================================================================== + + elif repo == "itms": + SRC = "/opt/itms/repo" + ARTIF = "/opt/itms/artifacts" + JAVA17 = "/usr/lib/jvm/java-17-openjdk-amd64" # 배포 대상 환경의 JDK17 경로로 수정 + # GITEA_CLONE_CRED 는 서버 env 에서 주입(예: 'ythong:'). 코드/커밋에 비번 금지. + CRED = os.environ.get("GITEA_CLONE_CRED", "") # 미설정 시 기존 리모트/캐시 사용 + CLONE = (f"git clone 'http://{CRED}@127.0.0.1:9003/ythong/itms.git' {SRC}" + if CRED else f"echo 'GITEA_CLONE_CRED 미설정 — 기존 {SRC} 사용' ") + ok = run_steps(repo, [ + ("git pull", ["bash", "-c", + f"[ -d {SRC}/.git ] && git -C {SRC} fetch origin main && git -C {SRC} reset --hard origin/main" + f" || ( mkdir -p /opt/itms && {CLONE} )"]), + # common 선행 + 5모듈 bootWar (JDK17 지정). legacy/nlib 제외. + ("gradle build", ["bash", "-c", + f"cd {SRC} && JAVA_HOME={JAVA17} ./gradlew --no-daemon " + f":common:build :auth:bootWar :api:bootWar :front:bootWar :batch:bootWar :datatrans:bootWar " + f"-x test"]), + # 커밋 해시별 산출물 아카이브(롤백 대비) + ("archive war", ["bash", "-c", + f"H=$(git -C {SRC} rev-parse --short HEAD); D={ARTIF}/$H; mkdir -p $D; " + f"for m in auth api front batch datatrans; do " + f"cp {SRC}/$m/build/libs/*.war $D/itms-$m.war 2>/dev/null || true; done; " + f"ln -sfn $D {ARTIF}/current; ls -l {ARTIF}/current"]), + # --- Tier2 (기동): DB/시크릿/JDK17 확보 환경에서만 주석 해제 --- + # ("restart auth", ["bash","-c","systemctl restart itms-auth 2>/dev/null || true"]), + # ("restart api", ["bash","-c","systemctl restart itms-api 2>/dev/null || true"]), + # ("restart front", ["bash","-c","systemctl restart itms-front 2>/dev/null || true"]), + # ("restart batch", ["bash","-c","systemctl restart itms-batch 2>/dev/null || true"]), + # ("restart datatrans", ["bash","-c","systemctl restart itms-datatrans 2>/dev/null || true"]), + # ("health check", ["bash","-c","sleep 6 && for p in 11000 11010 11020; do " + # "curl -sf -o /dev/null http://127.0.0.1:$p/ && echo \"$p UP\" || echo \"$p DOWN\"; done"]), + ]) + if ok: + notify_itsm(True, "✅ itms 빌드/아카이브 완료 (Tier1)") + else: + notify_itsm(False, "❌ itms 빌드 실패") diff --git a/deploy/env/itms.env.example b/deploy/env/itms.env.example new file mode 100644 index 00000000..c9c9c210 --- /dev/null +++ b/deploy/env/itms.env.example @@ -0,0 +1,7 @@ +# ITMS 모듈 env 템플릿 — /etc/itms/itms-.env 로 복사해 사용. +# 실 시크릿(DB 접속정보·keystore 비번·NCP 키)은 여기 넣지 말고 프로파일 yml/keystore + 서버 로컬로 관리. +# 기관 프로파일 선택(파일·키 불변): 배포 대상 기관명으로 지정. +SPRING_PROFILES_ACTIVE=local +# JVM 옵션(선택) +JAVA_OPTS=-Xms256m -Xmx512m +# 필요 시 추가 환경변수(예: 외부 keystore 경로)를 여기에. 비밀번호 평문 금지. diff --git a/deploy/gitea-webhook.md b/deploy/gitea-webhook.md new file mode 100644 index 00000000..344ea73c --- /dev/null +++ b/deploy/gitea-webhook.md @@ -0,0 +1,31 @@ +# Gitea webhook — ythong/itms (적용 대기) + +push 시 배포 서버 deploy_server(port 9999)로 알리는 webhook. **아직 생성하지 않음.** + +## 3대 함정 (GUARDiA 기지 교훈 — 반드시 준수) +1. **url**: NAT 헤어핀 회피 위해 `http://127.0.0.1:9999/`(localhost) 로 지정. +2. **secret**: 배포 서버 deploy_server 의 검증 secret 과 **일치**시킬 것(불일치 시 403). 값은 서버 운영 시크릿 — 여기에 적지 말 것. +3. **ALLOWED_HOST_LIST**: Gitea app.ini `[webhook] ALLOWED_HOST_LIST` 에 loopback 포함(없으면 localhost 웹훅 차단). + +## 생성 (배포 대상 환경에서 실행; 자격증명·secret 은 env 로 주입) +```bash +# GITEA_TOKEN, DEPLOY_SECRET 를 환경변수로 주입(하드코딩 금지) +curl -s -X POST "https://git.zioinfo.co.kr/api/v1/repos/ythong/itms/hooks" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "gitea", + "active": true, + "events": ["push"], + "config": { + "url": "http://127.0.0.1:9999/", + "content_type": "json", + "secret": "'"${DEPLOY_SECRET}"'" + } + }' +``` +- branch 필터가 필요하면 deploy_server 측에서 `ref == refs/heads/main` 확인(현행 deploy_server 는 repo 이름으로 분기). +- 생성 후 Gitea UI 의 "Test Delivery" 로 발화 → deploy_server 로그에서 `repo=itms` 수신 확인. + +## deploy_server 반영 +`deploy_server_itms_block.py` 를 배포 서버 `deploy_server.py` 의 `_deploy()` elif 체인에 병합 → 서버 사본 반영 → deploy 서비스 재시작. (교훈: 서버 사본 미반영/미재시작 시 웹훅 로그 '완료'여도 1ms no-op = 블록 부재.) diff --git a/deploy/systemd/itms-api.service b/deploy/systemd/itms-api.service new file mode 100644 index 00000000..c70b71da --- /dev/null +++ b/deploy/systemd/itms-api.service @@ -0,0 +1,14 @@ +[Unit] +Description=ITMS Api (OAuth2 resource server, :11010) +After=network-online.target +Wants=network-online.target +[Service] +Type=simple +EnvironmentFile=/etc/itms/itms-api.env +ExecStart=/usr/lib/jvm/java-17-openjdk-amd64/bin/java -jar /opt/itms/artifacts/current/itms-api.war +Restart=on-failure +RestartSec=5 +User=itms +Group=itms +[Install] +WantedBy=multi-user.target diff --git a/deploy/systemd/itms-auth.service b/deploy/systemd/itms-auth.service new file mode 100644 index 00000000..52007ebf --- /dev/null +++ b/deploy/systemd/itms-auth.service @@ -0,0 +1,14 @@ +[Unit] +Description=ITMS Auth (OAuth2 authorization server, :11000) +After=network-online.target +Wants=network-online.target +[Service] +Type=simple +EnvironmentFile=/etc/itms/itms-auth.env +ExecStart=/usr/lib/jvm/java-17-openjdk-amd64/bin/java -jar /opt/itms/artifacts/current/itms-auth.war +Restart=on-failure +RestartSec=5 +User=itms +Group=itms +[Install] +WantedBy=multi-user.target diff --git a/deploy/systemd/itms-batch.service b/deploy/systemd/itms-batch.service new file mode 100644 index 00000000..ce0f5641 --- /dev/null +++ b/deploy/systemd/itms-batch.service @@ -0,0 +1,14 @@ +[Unit] +Description=ITMS Batch (Quartz clustered, :11030) +After=network-online.target +Wants=network-online.target +[Service] +Type=simple +EnvironmentFile=/etc/itms/itms-batch.env +ExecStart=/usr/lib/jvm/java-17-openjdk-amd64/bin/java -jar /opt/itms/artifacts/current/itms-batch.war +Restart=on-failure +RestartSec=8 +User=itms +Group=itms +[Install] +WantedBy=multi-user.target diff --git a/deploy/systemd/itms-datatrans.service b/deploy/systemd/itms-datatrans.service new file mode 100644 index 00000000..a3ab3c3b --- /dev/null +++ b/deploy/systemd/itms-datatrans.service @@ -0,0 +1,14 @@ +[Unit] +Description=ITMS DataTrans (NCMS->IAMS ETL @Scheduled daemon) +After=network-online.target +Wants=network-online.target +[Service] +Type=simple +EnvironmentFile=/etc/itms/itms-datatrans.env +ExecStart=/usr/lib/jvm/java-17-openjdk-amd64/bin/java -jar /opt/itms/artifacts/current/itms-datatrans.war +Restart=on-failure +RestartSec=10 +User=itms +Group=itms +[Install] +WantedBy=multi-user.target diff --git a/deploy/systemd/itms-front.service b/deploy/systemd/itms-front.service new file mode 100644 index 00000000..c23246b7 --- /dev/null +++ b/deploy/systemd/itms-front.service @@ -0,0 +1,14 @@ +[Unit] +Description=ITMS Front (JSP/Tiles web tier, :11020) +After=network-online.target itms-auth.service itms-api.service +Wants=network-online.target +[Service] +Type=simple +EnvironmentFile=/etc/itms/itms-front.env +ExecStart=/usr/lib/jvm/java-17-openjdk-amd64/bin/java -jar /opt/itms/artifacts/current/itms-front.war +Restart=on-failure +RestartSec=5 +User=itms +Group=itms +[Install] +WantedBy=multi-user.target