docs(DOC): ops-manual 2-Branch 전략 반영 — stage 브랜치 제거, Jenkins 승인 기반 배포

- 04-service-cicd: credential 타입 수정, Nginx 설정 추가, 동기화 브랜치 테이블 업데이트
- 05-deployment: 파이프라인 설정/흐름도/Jenkinsfile 전면 개편 (develop+main 2-Branch)
- React/API: main push → Stage 자동배포 → Jenkins 승인 → Production 배포
- MNG/Sales: main push → Production 직접 배포

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-24 15:56:50 +09:00
parent 9610feafc0
commit 5a6859ce23
2 changed files with 259 additions and 134 deletions

View File

@@ -48,7 +48,7 @@ sudo journalctl -u jenkins --since "2 hours ago" --no-pager
| Credential ID | 유형 | 용도 | | Credential ID | 유형 | 용도 |
|--------------|------|------| |--------------|------|------|
| deploy-ssh-key | SSH Username with private key | 운영/개발서버 SSH 배포 | | deploy-ssh-key | SSH Username with private key | 운영/개발서버 SSH 배포 |
| gitea-api-token | Secret text | Gitea API 연동 | | gitea-api-token | Username with password | Gitea API 연동 (token을 username, 비밀번호 빈값) |
**Credential 위치:** Jenkins 관리 > Credentials > System > Global credentials **Credential 위치:** Jenkins 관리 > Credentials > System > Global credentials
@@ -181,18 +181,24 @@ sudo -u git /usr/local/bin/gitea admin user create \
**토큰 파일 (개발서버):** `/data/GIT/.cicd-env` (chmod 600, owner: git) **토큰 파일 (개발서버):** `/data/GIT/.cicd-env` (chmod 600, owner: git)
| 저장소 | 동기화 브랜치 | | 저장소 | 동기화 브랜치 | 비고 |
|--------|-------------| |--------|-------------|------|
| sam-react-prod | stage, develop | | sam-react-prod | main, develop | post-update hook 비활성화 (CI/CD가 개발서버 배포 담당) |
| sam-api | stage | | sam-api | main | develop은 기존 post-update hook 유지 |
| sam-sales | main | | sam-sales | main | |
| sam-manage | 없음 (배포관리자 수동 push) | | sam-manage | main | 2026-02-24 hook 추가 |
> **참고:** react의 개발서버 배포는 Jenkins CI/CD 파이프라인이 처리한다.
> 기존 post-update hook의 git pull 방식(`pull_react.sh`)은 비활성화됨 (2026-02-24).
> 스크립트 위치: `/home/webservice/script/pull_react.sh`
**동기화 로그 확인:** **동기화 로그 확인:**
```bash ```bash
ssh sam-dev "tail -20 /home/webservice/logs/cicd_push_sam-react-prod.log" ssh sam-dev "tail -20 /home/webservice/logs/cicd_push_react-prod.log"
ssh sam-dev "tail -20 /home/webservice/logs/cicd_push_sam-api.log" ssh sam-dev "tail -20 /home/webservice/logs/cicd_push_api.log"
ssh sam-dev "tail -20 /home/webservice/logs/cicd_push_sales.log"
ssh sam-dev "tail -20 /home/webservice/logs/cicd_push_manage.log"
``` ```
--- ---
@@ -303,6 +309,13 @@ sudo systemctl status nginx
| /etc/nginx/sites-available/ci.sam.it.kr | Jenkins 리버스 프록시 | | /etc/nginx/sites-available/ci.sam.it.kr | Jenkins 리버스 프록시 |
| /etc/nginx/sites-available/monitor.sam.it.kr | Grafana 리버스 프록시 | | /etc/nginx/sites-available/monitor.sam.it.kr | Grafana 리버스 프록시 |
**git.sam.it.kr 주요 설정:**
```nginx
client_max_body_size 500M; # 대용량 Git push 허용
proxy_request_buffering off; # 스트리밍 전송 (413 방지)
```
--- ---
## node_exporter / Certbot / fail2ban / UFW ## node_exporter / Certbot / fail2ban / UFW

View File

@@ -17,28 +17,28 @@
| 저장소 | 파이프라인 | 트리거 브랜치 | 배포 대상 | | 저장소 | 파이프라인 | 트리거 브랜치 | 배포 대상 |
|--------|-----------|-------------|----------| |--------|-----------|-------------|----------|
| sam-react-prod | React 빌드+배포 | develop, stage, main | 개발/Stage/운영 | | sam-react-prod | React 빌드+배포 | develop, main | 개발 / Stage→승인→운영 |
| sam-api | Laravel API 배포 | stage, main | Stage/운영 | | sam-api | Laravel API 배포 | main | Stage→승인→운영 |
| sam-manage | Laravel Admin 배포 | main | 운영 | | sam-manage | Laravel Admin 배포 | main | 운영 (직접) |
| sam-sales | 레거시 PHP 배포 | main | 운영 | | sam-sales | 레거시 PHP 배포 | main | 운영 (직접) |
### 브랜치별 동작 ### 2-Branch 전략 (develop + main)
> **stage 브랜치 없음.** main 브랜치 push 시 Stage 자동 배포 → Jenkins 승인 → Production 배포.
| 브랜치 | react | api | mng | sales | | 브랜치 | react | api | mng | sales |
|--------|-------|-----|-----|-------| |--------|-------|-----|-----|-------|
| develop | Jenkins 빌드 -> 개발서버 | 기존 hook | 기존 hook | 기존 hook | | develop | Jenkins 빌드 개발서버 | 기존 post-update hook | 기존 post-update hook | 기존 post-update hook |
| stage | Jenkins 빌드 -> 운영 Stage | Jenkins -> 운영 Stage | - | - | | main | Stage 배포 → **승인** → Production 배포 | Stage 배포 → **승인** → Production 배포 | Production 직접 배포 | Production 직접 배포 |
| main | Jenkins 빌드 -> 운영 Production | Jenkins -> 운영 Production | Jenkins -> 운영 Production | Jenkins -> 운영 pull |
**main 브랜치 배포:** 배포관리자가 CI/CD Gitea에 수동 push 후 자동 실행 **main 브랜치 배포 흐름 (react/api):**
1. 개발자가 develop → main 머지 후 push
2. post-receive hook → CI/CD Gitea 자동 push
3. Jenkins 빌드 → Stage 자동 배포
4. Jenkins UI에서 **승인 클릭** → Production 배포 (24시간 타임아웃)
```bash **main 브랜치 배포 흐름 (mng/sales):**
# 1회 remote 등록 1. 개발자가 main push → hook → CI/CD Gitea → Jenkins → Production 직접 배포
git remote add production https://git.sam.it.kr/SamProject/sam-react-prod.git
# 운영 배포 시
git push production main
```
--- ---
@@ -51,7 +51,7 @@ git push production main
``` ```
개발자 로컬 개발자 로컬
│ git push origin (develop/stage/main) │ git push origin (develop / main)
개발서버 Gitea (114.203.209.83:3000) ← 모든 개발자의 origin 개발서버 Gitea (114.203.209.83:3000) ← 모든 개발자의 origin
@@ -59,30 +59,21 @@ git push production main
│ ├─ api/mng/sales: 기존 post-update hook (개발서버 pull) ← 현행 유지 │ ├─ api/mng/sales: 기존 post-update hook (개발서버 pull) ← 현행 유지
│ └─ react: hook → CI/CD Gitea push → Jenkins 빌드 → 개발서버 배포 │ └─ react: hook → CI/CD Gitea push → Jenkins 빌드 → 개발서버 배포
stage push 시 main push 시
├─ react: hook → CI/CD Gitea push → Jenkins 빌드 → 운영서버 Stage 배포 ├─ react: hook → CI/CD Gitea → Jenkins 빌드 → Stage 배포 → 승인 → Production 배포
─ api: hook → CI/CD Gitea push → Jenkins → 운영서버 Stage pull ─ api: hook → CI/CD Gitea → Jenkins → Stage 배포 → 승인 → Production 배포
├─ mng: hook → CI/CD Gitea → Jenkins → Production 직접 배포
└─ main push 시 (react/mng/api) └─ sales: hook → CI/CD Gitea → Jenkins → Production 직접 배포
└─ ❌ CI/CD Gitea에 자동 push 안함
→ 배포관리자가 수동으로 CI/CD Gitea에 push
→ Jenkins 자동 배포
별도 처리:
sales/www(landing): hook → CI/CD Gitea → Jenkins → 운영서버 pull
``` ```
### 브랜치별 배포 정책 상세 ### 브랜치별 배포 정책 상세
| 브랜치 | 저장소 | CI/CD Gitea 동기화 | Jenkins 배포 | 배포 대상 | | 브랜치 | 저장소 | CI/CD Gitea 동기화 | Jenkins 배포 | 배포 대상 |
|--------|--------|-------------------|-------------|----------| |--------|--------|-------------------|-------------|----------|
| **stage** | react | 자동 (hook) | 빌드 + rsync | 운영서버 Stage | | **main** | react | 자동 (hook) | 빌드 → Stage → **승인** → 재빌드 → Production | Stage + Production |
| **stage** | api | 자동 (hook) | SSH pull | 운영서버 Stage | | **main** | api | 자동 (hook) | rsync → Stage → **승인** → rsync → Production | Stage + Production |
| **main** | react | 동 (배포관리자) | 빌드 + rsync | 운영서버 Production | | **main** | mng | 동 (hook) | rsync + npm build → Production | Production |
| **main** | mng | 동 (배포관리자) | SSH deploy | 운영서버 Production | | **main** | sales | 동 (hook) | rsync → Production | Production |
| **main** | api | 수동 (배포관리자) | SSH deploy | 운영서버 Production |
| **main** | sales | 자동 (hook) | SSH pull | 운영서버 Production |
| **main** | www | 자동 (hook) | SSH pull | 운영서버 Production |
| **develop** | react | 자동 (hook) | 빌드 → 개발서버 배포 | 개발서버 | | **develop** | react | 자동 (hook) | 빌드 → 개발서버 배포 | 개발서버 |
| **develop** | api/mng/sales | ❌ (현행 유지) | ❌ | 개발서버 (post-update hook) | | **develop** | api/mng/sales | ❌ (현행 유지) | ❌ | 개발서버 (post-update hook) |
@@ -90,11 +81,11 @@ git push production main
| 저장소 | hook 대상 브랜치 | 동작 | | 저장소 | hook 대상 브랜치 | 동작 |
|--------|-----------------|------| |--------|-----------------|------|
| sam-react-prod | stage, develop | CI/CD Gitea에 push | | sam-react-prod | main, develop | CI/CD Gitea에 push |
| sam-api | stage | CI/CD Gitea에 push | | sam-api | main | CI/CD Gitea에 push |
| sam-manage | main | CI/CD Gitea에 push |
| sam-sales | main | CI/CD Gitea에 push | | sam-sales | main | CI/CD Gitea에 push |
| sam-landing | main | CI/CD Gitea에 push | | sam-landing | main | CI/CD Gitea에 push |
| sam-manage | ❌ 없음 | main만 사용, 배포관리자 수동 push |
hook 스크립트 경로: `/data/GIT/samproject/<repo>.git/hooks/post-receive.d/push-to-cicd` hook 스크립트 경로: `/data/GIT/samproject/<repo>.git/hooks/post-receive.d/push-to-cicd`
토큰 환경변수: `/data/GIT/.cicd-env` (chmod 600, owner: git) 토큰 환경변수: `/data/GIT/.cicd-env` (chmod 600, owner: git)
@@ -117,7 +108,7 @@ Repository Settings → Webhooks → Add Webhook (Gitea)
``` ```
개발자 로컬 개발자 로컬
│ git push origin (develop / stage / main) │ git push origin (develop / main)
┌──────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────┐
│ 개발서버 Gitea (114.203.209.83:3000) ← 모든 개발자 origin │ │ 개발서버 Gitea (114.203.209.83:3000) ← 모든 개발자 origin │
@@ -132,46 +123,42 @@ Repository Settings → Webhooks → Add Webhook (Gitea)
│ │ sales → 기존 post-update hook (pull) │ │ │ │ sales → 기존 post-update hook (pull) │ │
│ └───────────────────────────────────────────────────────┘ │ │ └───────────────────────────────────────────────────────┘ │
│ │ │ │
│ ┌─ stage push ──────────────────────────────────────────┐ │ │ ┌─ main push (모든 저장소 자동) ────────────────────────┐ │
│ │ react → hook: CI/CD Gitea push ──→ Jenkins 빌드 │ │ │ │ react → hook: CI/CD Gitea push ──→ Jenkins │ │
│ │ → rsync → 운영서버 Stage + PM2 reload │ │ │ │ → Stage 빌드+배포 → 승인 → Production 재빌드 │ │
│ │ api → hook: CI/CD Gitea push ──→ Jenkins │ │ │ │ api → hook: CI/CD Gitea push ──→ Jenkins │ │
│ │ → 운영서버 Stage Release + 심링크 │ │ │ │ → Stage rsync+배포 → 승인 → Production 배포 │ │
└───────────────────────────────────────────────────────┘ │ mng → hook: CI/CD Gitea push ──→ Jenkins │
→ Production rsync + build │
│ ┌─ main push (sales/www만 자동) ────────────────────────┐ │
│ │ sales → hook: CI/CD Gitea push ──→ Jenkins │ │ │ │ sales → hook: CI/CD Gitea push ──→ Jenkins │ │
│ │ → 운영서버 rsync │ │ │ │ → Production rsync │ │
│ │ www → hook: CI/CD Gitea push ──→ Jenkins │ │
│ │ → 운영서버 pull │ │
│ │ react/mng/api → ❌ 자동 push 안함 │ │
│ └───────────────────────────────────────────────────────┘ │ │ └───────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────┘ └───────────────────────────────────────────────────────────────┘
┌─ 운영 배포 (main - react/mng/api) ─────────────────────────┐ ┌─ Jenkins 승인 흐름 (react/api main) ─────────────────────────┐
│ │ │ │
배포관리자 로컬 Jenkins 빌드 시작
│ │ git push production main (CI/CD Gitea remote) │ │
├─ Stage 자동 배포 (react: .env.stage 빌드)
CI/CD Gitea (git.sam.it.kr)
│ Webhook ├─ ⏸️ 승인 대기 (24시간 타임아웃)
│ https://ci.sam.it.kr 에서 "운영 배포 진행" 클릭
Jenkins → 운영서버 배포
react: CI/CD 빌드 → rsync → PM2 reload ├─ Production 배포 (react: .env.main 재빌드)
api: Release + 심링크 → PHP-FPM reload
mng: Release + 심링크 → PHP-FPM reload └─ 완료
│ │ │ │
└───────────────────────────────────────────────────────────────┘ └───────────────────────────────────────────────────────────────┘
``` ```
### 환경별 배포 비교 ### 환경별 배포 비교
| 항목 | 운영 (main) | Stage (stage) | 개발 (develop) | | 항목 | Production (main→승인) | Stage (main→자동) | 개발 (develop) |
|------|------------|---------------|----------------| |------|----------------------|------------------|----------------|
| **트리거** | 배포관리자 수동 push | 자동 (hook) | react만 자동 (hook), 나머지 기존 hook | | **트리거** | main push → Jenkins 승인 | main push → 자동 | react만 자동 (hook), 나머지 기존 hook |
| **react 전략** | CI/CD 빌드 → rsync | CI/CD 빌드 → rsync | CI/CD 빌드 → rsync | | **react 전략** | CI/CD 빌드(.env.main) → rsync | CI/CD 빌드(.env.stage) → rsync | CI/CD 빌드(.env.develop) → rsync |
| **api 전략** | Release + 심링크 | Release + 심링크 | 기존 post-update (pull) | | **api 전략** | rsync + Release 심링크 | rsync + Release 심링크 | 기존 post-update (pull) |
| **mng 전략** | Release + 심링크 | - | 기존 post-update (pull + build) | | **mng 전략** | rsync + npm build + Release 심링크 | - | 기존 post-update (pull + build) |
| **롤백** | 이전 릴리즈 심링크 | 이전 릴리즈 심링크 | git revert | | **롤백** | 이전 릴리즈 심링크 | 이전 릴리즈 심링크 | git revert |
| **릴리즈 보관** | 최근 5개 | 최근 3개 | - | | **릴리즈 보관** | 최근 5개 | 최근 3개 | - |
@@ -188,11 +175,11 @@ CI/CD Gitea push -> Webhook -> Jenkins
**브랜치별 배포 대상:** **브랜치별 배포 대상:**
| 브랜치 | 대상 서버 | 대상 경로 | PM2 이름 | 트리거 | | 브랜치 | 배포 단계 | 대상 서버 | 대상 경로 | PM2 이름 |
|--------|----------|----------|----------|--------| |--------|----------|----------|----------|----------|
| develop | 개발서버 (114.203.209.83) | /home/webservice/react/ | sam-react | 자동 (hook) | | develop | 개발서버 | 114.203.209.83 | /home/webservice/react/ | sam-react |
| stage | 운영서버 (211.117.60.189) | /home/webservice/react-stage/releases/ | sam-front-stage | 자동 (hook) | | main | Stage (자동) | 211.117.60.189 | /home/webservice/react-stage/releases/ | sam-front-stage |
| main | 운영서버 (211.117.60.189) | /home/webservice/react/releases/ | sam-front | 수동 push | | main | Production (승인 후) | 211.117.60.189 | /home/webservice/react/releases/ | sam-front |
**환경변수 파일 (CI/CD 서버):** /var/lib/jenkins/env-files/react/ **환경변수 파일 (CI/CD 서버):** /var/lib/jenkins/env-files/react/
@@ -225,8 +212,13 @@ pipeline {
stage('Prepare Env') { stage('Prepare Env') {
steps { steps {
script { script {
def envFile = "/var/lib/jenkins/env-files/react/.env.${env.BRANCH_NAME}" if (env.BRANCH_NAME == 'main') {
sh "cp ${envFile} .env.local" // main: Stage 빌드 먼저 (승인 후 Production 재빌드)
sh "cp /var/lib/jenkins/env-files/react/.env.stage .env.local"
} else {
def envFile = "/var/lib/jenkins/env-files/react/.env.${env.BRANCH_NAME}"
sh "cp ${envFile} .env.local"
}
} }
} }
} }
@@ -256,9 +248,9 @@ pipeline {
} }
} }
// ── stage → 운영서버 Stage 배포 ── // ── main → 운영서버 Stage 배포 ──
stage('Deploy Stage') { stage('Deploy Stage') {
when { branch 'stage' } when { branch 'main' }
steps { steps {
sshagent(credentials: ['deploy-ssh-key']) { sshagent(credentials: ['deploy-ssh-key']) {
sh """ sh """
@@ -277,6 +269,26 @@ pipeline {
} }
} }
// ── 운영 배포 승인 ──
stage('Production Approval') {
when { branch 'main' }
steps {
timeout(time: 24, unit: 'HOURS') {
input message: 'Stage 확인 후 운영 배포를 진행하시겠습니까?\nStage: https://stage.sam.it.kr',
ok: '운영 배포 진행'
}
}
}
// ── main → Production 재빌드 (운영 환경변수) ──
stage('Rebuild for Production') {
when { branch 'main' }
steps {
sh "cp /var/lib/jenkins/env-files/react/.env.main .env.local"
sh 'npm run build'
}
}
// ── main → 운영서버 Production 배포 ── // ── main → 운영서버 Production 배포 ──
stage('Deploy Production') { stage('Deploy Production') {
when { branch 'main' } when { branch 'main' }
@@ -306,6 +318,10 @@ pipeline {
} }
``` ```
> **참고:** Next.js는 `NEXT_PUBLIC_*` 환경변수가 빌드 시 바인딩되므로,
> Stage(.env.stage)와 Production(.env.main)에서 별도 빌드가 필요하다.
> main 빌드 시 Stage용으로 먼저 빌드 → 승인 후 Production용으로 재빌드.
### PM2 수동 재시작 ### PM2 수동 재시작
```bash ```bash
@@ -329,16 +345,16 @@ pm2 save
``` ```
CI/CD Gitea push -> Webhook -> Jenkins CI/CD Gitea push -> Webhook -> Jenkins
-> SSH: git clone -> composer install -> artisan cache -> migrate -> 심링크 전환 -> PHP-FPM reload -> checkout -> rsync → Stage 배포 → 승인 → rsync → Production 배포
``` ```
**브랜치별 배포 대상:** **브랜치별 배포 대상:**
| 브랜치 | 대상 서버 | 대상 경로 | 트리거 | | 브랜치 | 배포 단계 | 대상 서버 | 대상 경로 |
|--------|----------|----------|--------| |--------|----------|----------|----------|
| stage | 운영서버 | /home/webservice/api-stage/releases/ | 자동 (hook) | | main | Stage (자동) | 운영서버 | /home/webservice/api-stage/releases/ |
| main | 운영서버 | /home/webservice/api/releases/ | 수동 push | | main | Production (승인 후) | 운영서버 | /home/webservice/api/releases/ |
| develop | 개발서버 | - (기존 post-update hook) | 기존 hook | | develop | 개발서버 | 개발서버 | 기존 post-update hook |
### Jenkinsfile (api/Jenkinsfile) ### Jenkinsfile (api/Jenkinsfile)
@@ -348,8 +364,7 @@ pipeline {
environment { environment {
DEPLOY_USER = 'hskwon' DEPLOY_USER = 'hskwon'
APP_NAME = 'api' RELEASE_ID = new Date().format('yyyyMMdd_HHmmss')
RELEASE_ID = new Date().format('yyyyMMdd_HHmmss')
} }
stages { stages {
@@ -357,18 +372,63 @@ pipeline {
steps { checkout scm } steps { checkout scm }
} }
// ── main → 운영서버 (배포관리자 수동 push 후 트리거) ── // ── main → 운영서버 Stage 배포 ──
stage('Deploy Stage') {
when { branch 'main' }
steps {
sshagent(credentials: ['deploy-ssh-key']) {
sh """
ssh ${DEPLOY_USER}@211.117.60.189 'mkdir -p /home/webservice/api-stage/releases/${RELEASE_ID}'
rsync -az --delete \
--exclude='.git' --exclude='.env' \
--exclude='storage/app' --exclude='storage/logs' \
--exclude='storage/framework/sessions' --exclude='storage/framework/cache' \
. ${DEPLOY_USER}@211.117.60.189:/home/webservice/api-stage/releases/${RELEASE_ID}/
ssh ${DEPLOY_USER}@211.117.60.189 '
cd /home/webservice/api-stage/releases/${RELEASE_ID} &&
ln -sfn /home/webservice/api-stage/shared/.env .env &&
ln -sfn /home/webservice/api-stage/shared/storage/app storage/app &&
composer install --no-dev --optimize-autoloader --no-interaction &&
php artisan config:cache &&
php artisan route:cache &&
php artisan view:cache &&
php artisan migrate --force &&
ln -sfn /home/webservice/api-stage/releases/${RELEASE_ID} /home/webservice/api-stage/current &&
sudo systemctl reload php8.4-fpm &&
cd /home/webservice/api-stage/releases && ls -1dt */ | tail -n +4 | xargs rm -rf 2>/dev/null || true
'
"""
}
}
}
// ── 운영 배포 승인 ──
stage('Production Approval') {
when { branch 'main' }
steps {
timeout(time: 24, unit: 'HOURS') {
input message: 'Stage 확인 후 운영 배포를 진행하시겠습니까?\nStage API: https://stage-api.sam.it.kr',
ok: '운영 배포 진행'
}
}
}
// ── main → 운영서버 Production 배포 ──
stage('Deploy Production') { stage('Deploy Production') {
when { branch 'main' } when { branch 'main' }
steps { steps {
sshagent(credentials: ['deploy-ssh-key']) { sshagent(credentials: ['deploy-ssh-key']) {
sh """ sh """
ssh ${DEPLOY_USER}@211.117.60.189 'mkdir -p /home/webservice/api/releases/${RELEASE_ID}'
rsync -az --delete \
--exclude='.git' --exclude='.env' \
--exclude='storage/app' --exclude='storage/logs' \
--exclude='storage/framework/sessions' --exclude='storage/framework/cache' \
. ${DEPLOY_USER}@211.117.60.189:/home/webservice/api/releases/${RELEASE_ID}/
ssh ${DEPLOY_USER}@211.117.60.189 ' ssh ${DEPLOY_USER}@211.117.60.189 '
cd /home/webservice/api/releases &&
git clone --depth 1 --branch main https://git.sam.it.kr/SamProject/sam-api.git ${RELEASE_ID} &&
ln -sfn /home/webservice/api/shared/storage /home/webservice/api/releases/${RELEASE_ID}/storage &&
ln -sfn /home/webservice/api/shared/.env /home/webservice/api/releases/${RELEASE_ID}/.env &&
cd /home/webservice/api/releases/${RELEASE_ID} && cd /home/webservice/api/releases/${RELEASE_ID} &&
ln -sfn /home/webservice/api/shared/.env .env &&
ln -sfn /home/webservice/api/shared/storage/app storage/app &&
composer install --no-dev --optimize-autoloader --no-interaction && composer install --no-dev --optimize-autoloader --no-interaction &&
php artisan config:cache && php artisan config:cache &&
php artisan route:cache && php artisan route:cache &&
@@ -377,35 +437,7 @@ pipeline {
ln -sfn /home/webservice/api/releases/${RELEASE_ID} /home/webservice/api/current && ln -sfn /home/webservice/api/releases/${RELEASE_ID} /home/webservice/api/current &&
sudo systemctl reload php8.4-fpm && sudo systemctl reload php8.4-fpm &&
sudo supervisorctl restart sam-queue-worker:* && sudo supervisorctl restart sam-queue-worker:* &&
cd /home/webservice/api/releases && cd /home/webservice/api/releases && ls -1dt */ | tail -n +6 | xargs rm -rf 2>/dev/null || true
ls -1dt */ | tail -n +6 | xargs rm -rf 2>/dev/null || true
'
"""
}
}
}
// ── stage → 운영서버 Stage ──
stage('Deploy Stage') {
when { branch 'stage' }
steps {
sshagent(credentials: ['deploy-ssh-key']) {
sh """
ssh ${DEPLOY_USER}@211.117.60.189 '
cd /home/webservice/api-stage/releases &&
git clone --depth 1 --branch stage https://git.sam.it.kr/SamProject/sam-api.git ${RELEASE_ID} &&
ln -sfn /home/webservice/api-stage/shared/storage /home/webservice/api-stage/releases/${RELEASE_ID}/storage &&
ln -sfn /home/webservice/api-stage/shared/.env /home/webservice/api-stage/releases/${RELEASE_ID}/.env &&
cd /home/webservice/api-stage/releases/${RELEASE_ID} &&
composer install --no-dev --optimize-autoloader --no-interaction &&
php artisan config:cache &&
php artisan route:cache &&
php artisan view:cache &&
php artisan migrate --force &&
ln -sfn /home/webservice/api-stage/releases/${RELEASE_ID} /home/webservice/api-stage/current &&
sudo systemctl reload php8.4-fpm &&
cd /home/webservice/api-stage/releases &&
ls -1dt */ | tail -n +4 | xargs rm -rf 2>/dev/null || true
' '
""" """
} }
@@ -420,17 +452,14 @@ pipeline {
failure { failure {
echo "❌ api 배포 실패 (${env.BRANCH_NAME})" echo "❌ api 배포 실패 (${env.BRANCH_NAME})"
script { script {
if (env.BRANCH_NAME in ['main', 'stage']) { if (env.BRANCH_NAME == 'main') {
def baseDir = env.BRANCH_NAME == 'main'
? '/home/webservice/api'
: '/home/webservice/api-stage'
sshagent(credentials: ['deploy-ssh-key']) { sshagent(credentials: ['deploy-ssh-key']) {
sh """ sh """
ssh ${DEPLOY_USER}@211.117.60.189 ' ssh ${DEPLOY_USER}@211.117.60.189 '
PREV=\$(ls -1dt ${baseDir}/releases/*/ | sed -n "2p" | xargs basename) && PREV=\$(ls -1dt /home/webservice/api/releases/*/ | sed -n "2p" | xargs basename 2>/dev/null) &&
[ -n "\$PREV" ] && ln -sfn ${baseDir}/releases/\$PREV ${baseDir}/current && [ -n "\$PREV" ] && ln -sfn /home/webservice/api/releases/\$PREV /home/webservice/api/current &&
sudo systemctl reload php8.4-fpm sudo systemctl reload php8.4-fpm
' ' || true
""" """
} }
} }
@@ -440,8 +469,15 @@ pipeline {
} }
``` ```
> **참고:** Laravel은 런타임 .env를 사용하므로 Stage/Production 별도 빌드가 필요 없다.
> 각 환경의 shared/.env가 심링크로 연결된다.
### 수동 배포 절차 (API Production) ### 수동 배포 절차 (API Production)
> **참고:** CI/CD Gitea는 `REQUIRE_SIGNIN_VIEW = true` 설정이므로,
> 수동 git clone 시 `https://사용자:비밀번호@git.sam.it.kr/...` 형식 또는
> CI/CD 서버에서 rsync로 전송하는 방식을 사용한다.
```bash ```bash
ssh sam-prod ssh sam-prod
@@ -511,6 +547,82 @@ ls -1dt */ | tail -n +4 | xargs rm -rf 2>/dev/null || true
API와 동일한 releases/shared 구조. 차이점: npm build 추가, Queue Worker 재시작 불필요. API와 동일한 releases/shared 구조. 차이점: npm build 추가, Queue Worker 재시작 불필요.
### Jenkinsfile (mng/Jenkinsfile)
```groovy
pipeline {
agent any
environment {
DEPLOY_USER = 'hskwon'
RELEASE_ID = new Date().format('yyyyMMdd_HHmmss')
}
stages {
stage('Checkout') {
steps { checkout scm }
}
// ── main → 운영서버 Production ──
stage('Deploy Production') {
when { branch 'main' }
steps {
sshagent(credentials: ['deploy-ssh-key']) {
sh """
ssh ${DEPLOY_USER}@211.117.60.189 'mkdir -p /home/webservice/mng/releases/${RELEASE_ID}'
rsync -az --delete \
--exclude='.git' --exclude='.env' \
--exclude='storage/app' --exclude='storage/logs' \
--exclude='storage/framework/sessions' --exclude='storage/framework/cache' \
--exclude='node_modules' \
. ${DEPLOY_USER}@211.117.60.189:/home/webservice/mng/releases/${RELEASE_ID}/
ssh ${DEPLOY_USER}@211.117.60.189 '
cd /home/webservice/mng/releases/${RELEASE_ID} &&
ln -sfn /home/webservice/mng/shared/.env .env &&
ln -sfn /home/webservice/mng/shared/storage/app storage/app &&
composer install --no-dev --optimize-autoloader --no-interaction &&
npm install --production=false &&
npm run build &&
php artisan config:cache &&
php artisan route:cache &&
php artisan view:cache &&
php artisan migrate --force &&
ln -sfn /home/webservice/mng/releases/${RELEASE_ID} /home/webservice/mng/current &&
sudo systemctl reload php8.4-fpm &&
cd /home/webservice/mng/releases && ls -1dt */ | tail -n +6 | xargs rm -rf 2>/dev/null || true
'
"""
}
}
}
// develop → Jenkins 관여 안함 (기존 post-update hook 유지)
}
post {
success { echo "✅ mng 배포 완료 (${env.BRANCH_NAME})" }
failure {
echo "❌ mng 배포 실패 (${env.BRANCH_NAME})"
script {
if (env.BRANCH_NAME == 'main') {
sshagent(credentials: ['deploy-ssh-key']) {
sh """
ssh ${DEPLOY_USER}@211.117.60.189 '
PREV=\$(ls -1dt /home/webservice/mng/releases/*/ | sed -n "2p" | xargs basename) &&
[ -n "\$PREV" ] && ln -sfn /home/webservice/mng/releases/\$PREV /home/webservice/mng/current &&
sudo systemctl reload php8.4-fpm
'
"""
}
}
}
}
}
}
```
### 수동 배포
```bash ```bash
ssh sam-prod ssh sam-prod