- 운영서버(211.117.60.189) 전체 설치 완료 문서화 - OS, MySQL 8.4.8, Redis 7.0.15, Nginx 1.24.0, PHP 8.4.18 - 7개 도메인 SSL (develop@codebridge-x.com), PM2 cluster - Supervisor queue worker, node_exporter, 보안 설정 - CI/CD 서버(110.10.147.46) 셋팅 가이드 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1172 lines
42 KiB
Markdown
1172 lines
42 KiB
Markdown
# CI/CD 서버 셋팅 가이드
|
|
|
|
> 작성일: 2026-02-23 | 최종 수정: 2026-02-24
|
|
> 상태: 설치 완료 (Jenkinsfile 작성 + 실제 배포 테스트 남음)
|
|
|
|
---
|
|
|
|
## 1. 서버 구성 개요
|
|
|
|
### 인프라 구조
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────────────┐
|
|
│ CI/CD서버 (2 vCPU / 8GB) │
|
|
│ Ubuntu 24.04 / IDC 클라우드 │
|
|
│ IP: 110.10.147.46 │
|
|
│ │
|
|
│ ┌──────────┐ ┌───────────┐ ┌───────────────────────────┐ │
|
|
│ │ Nginx │ │ Certbot │ │ UFW (22,80,443) │ │
|
|
│ │ (Proxy) │ │ (SSL) │ │ │ │
|
|
│ └────┬─────┘ └───────────┘ └───────────────────────────┘ │
|
|
│ │ │
|
|
│ ┌────┴───────────────────────────────────────────────────┐ │
|
|
│ │ Virtual Hosts │ │
|
|
│ │ │ │
|
|
│ │ git.sam.it.kr ──────────→ Gitea (:3000) │ │
|
|
│ │ ci.sam.it.kr ───────────→ Jenkins (:8080) │ │
|
|
│ │ monitor.sam.it.kr ──────→ Grafana (:3100) │ │
|
|
│ └─────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌────────────┐ ┌────────────┐ ┌────────────────────────┐ │
|
|
│ │ Gitea │ │ Jenkins │ │ MySQL 8.4 │ │
|
|
│ │ (운영 Git) │ │ (CI/CD) │ │ (Gitea DB + 백업) │ │
|
|
│ └────────────┘ └────────────┘ └────────────────────────┘ │
|
|
│ │
|
|
│ ┌──────────────┐ ┌──────────────┐ │
|
|
│ │ Prometheus │ │ Grafana │ │
|
|
│ │ (:9090) │ │ (:3100) │ │
|
|
│ └──────────────┘ └──────────────┘ │
|
|
└───────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 도메인 매핑
|
|
|
|
| 도메인 | 서비스 | 포트 | SSL |
|
|
|--------|--------|------|-----|
|
|
| git.sam.it.kr | Gitea | 3000 | Let's Encrypt |
|
|
| ci.sam.it.kr | Jenkins | 8080 | Let's Encrypt |
|
|
| monitor.sam.it.kr | Grafana | 3100 | Let's Encrypt |
|
|
|
|
### Git 동기화 전략
|
|
|
|
**방침**: 개발서버 Gitea(origin) 유지 + CI/CD Gitea에 **선택적 브랜치 push** (post-receive hook)
|
|
|
|
> Gitea Push Mirror는 전체 브랜치를 미러링하므로 사용하지 않음.
|
|
> 대신 개발서버 Gitea의 **post-receive hook**으로 필요한 브랜치만 CI/CD Gitea에 push.
|
|
|
|
```
|
|
개발자 로컬
|
|
│ git push origin (develop/stage/main)
|
|
▼
|
|
개발서버 Gitea (114.203.209.83:3000) ← 모든 개발자의 origin
|
|
│
|
|
├─ develop push 시
|
|
│ ├─ api/mng/sales: 기존 post-update hook (개발서버 pull) ← 현행 유지
|
|
│ └─ react: hook → CI/CD Gitea push → Jenkins 빌드 → 개발서버 배포
|
|
│
|
|
├─ stage push 시
|
|
│ ├─ react: hook → CI/CD Gitea push → Jenkins 빌드 → 운영서버 Stage 배포
|
|
│ └─ api: hook → CI/CD Gitea push → Jenkins → 운영서버 Stage pull
|
|
│
|
|
└─ main push 시 (react/mng/api)
|
|
└─ ❌ CI/CD Gitea에 자동 push 안함
|
|
→ 배포관리자가 수동으로 CI/CD Gitea에 push
|
|
→ Jenkins 자동 배포
|
|
|
|
별도 처리:
|
|
sales/www(landing): Push Mirror 또는 hook → CI/CD Gitea → Jenkins → 운영서버 pull
|
|
```
|
|
|
|
### 브랜치별 배포 정책 상세
|
|
|
|
| 브랜치 | 저장소 | CI/CD Gitea 동기화 | Jenkins 배포 | 배포 대상 |
|
|
|--------|--------|-------------------|-------------|----------|
|
|
| **stage** | react | 자동 (hook) | 빌드 + rsync | 운영서버 Stage |
|
|
| **stage** | api | 자동 (hook) | SSH pull | 운영서버 Stage |
|
|
| **main** | react | 수동 (배포관리자) | 빌드 + rsync | 운영서버 Production |
|
|
| **main** | mng | 수동 (배포관리자) | SSH deploy | 운영서버 Production |
|
|
| **main** | api | 수동 (배포관리자) | SSH deploy | 운영서버 Production |
|
|
| **main** | sales | 자동 (hook/mirror) | SSH pull | 운영서버 Production |
|
|
| **main** | www | 자동 (hook/mirror) | SSH pull | 운영서버 Production |
|
|
| **develop** | react | 자동 (hook) | 빌드 → 개발서버 배포 | 개발서버 |
|
|
| **develop** | api | ❌ (현행 유지) | ❌ | 개발서버 (post-update hook) |
|
|
| **develop** | mng | ❌ (현행 유지) | ❌ | 개발서버 (post-update hook) |
|
|
| **develop** | sales | ❌ (현행 유지) | ❌ | 개발서버 (post-update hook) |
|
|
|
|
### 배포관리자 운영 배포 워크플로우
|
|
|
|
```bash
|
|
# 배포관리자 로컬에서 (react/mng/api 저장소)
|
|
# CI/CD Gitea를 별도 remote로 등록 (1회)
|
|
git remote add production https://git.sam.it.kr/SamProject/sam-api.git
|
|
|
|
# 운영 배포 시: main 브랜치를 CI/CD Gitea에 push
|
|
git push production main
|
|
# → CI/CD Gitea webhook → Jenkins → 운영서버 자동 배포
|
|
```
|
|
|
|
현재 Git remote 현황 (개발서버):
|
|
```
|
|
sam-api: http://114.203.209.83:3000/SamProject/sam-api.git
|
|
sam-manage: http://114.203.209.83:3000/SamProject/sam-manage.git
|
|
sam-react-prod: http://114.203.209.83:3000/SamProject/sam-react-prod.git
|
|
sam-sales: http://114.203.209.83:3000/SamProject/sam-sales.git
|
|
sam-docs: http://114.203.209.83:3000/SamProject/sam-docs.git
|
|
sam-design: http://114.203.209.83:3000/SamProject/sam-design.git
|
|
sam-planning: http://114.203.209.83:3000/SamProject/sam-planning.git
|
|
```
|
|
|
|
---
|
|
|
|
## 2. 메모리 배분 계획 (8GB)
|
|
|
|
| 서비스 | 할당 | 설정 | 비고 |
|
|
|--------|------|------|------|
|
|
| Jenkins | ~2.0GB | -Xmx2048m | Java 기반, 빌드 시 메모리 소모 큼 |
|
|
| MySQL 8.4 | ~1.5GB | innodb_buffer_pool_size=1536M | Gitea DB + 운영 백업 |
|
|
| Gitea | ~0.5GB | - | Go 기반, 가벼움 |
|
|
| Prometheus | ~0.5GB | --storage.tsdb.retention.time=30d | 메트릭 저장 30일 |
|
|
| Grafana | ~0.3GB | - | 대시보드 시각화 |
|
|
| Nginx | ~0.1GB | - | 리버스 프록시 |
|
|
| node_exporter | ~0.01GB | - | 자체 모니터링 |
|
|
| OS + 여유 | ~3.1GB | 스왑 4GB | 안전 마진 |
|
|
| **합계** | **~8GB** | | **스왑 4GB 백업** |
|
|
|
|
---
|
|
|
|
## 3. 설치 순서
|
|
|
|
### ① OS 기본 셋팅 ✅
|
|
|
|
```bash
|
|
# 시스템 업데이트
|
|
sudo apt update && sudo apt upgrade -y
|
|
|
|
# 기본 패키지
|
|
sudo apt install -y curl wget git unzip vim htop net-tools software-properties-common
|
|
|
|
# 타임존
|
|
sudo timedatectl set-timezone Asia/Seoul
|
|
|
|
# 스왑 4GB 설정
|
|
sudo fallocate -l 4G /swapfile
|
|
sudo chmod 600 /swapfile
|
|
sudo mkswap /swapfile
|
|
sudo swapon /swapfile
|
|
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
|
|
|
|
# 스왑 최적화
|
|
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
|
|
sudo sysctl -p
|
|
|
|
# UFW 방화벽
|
|
sudo ufw default deny incoming
|
|
sudo ufw default allow outgoing
|
|
sudo ufw allow 22/tcp # SSH
|
|
sudo ufw allow 80/tcp # HTTP
|
|
sudo ufw allow 443/tcp # HTTPS
|
|
sudo ufw enable
|
|
|
|
# webservice 사용자 그룹 (배포 스크립트용)
|
|
sudo groupadd -f webservice
|
|
sudo usermod -aG webservice hskwon
|
|
```
|
|
|
|
### ② MySQL 8.4 ✅
|
|
|
|
> **설치 완료**: MySQL 8.4.8 (GPG 키 만료 이슈로 Ubuntu keyserver 경유 설치)
|
|
> **주의**: `validate_password.policy` 설정은 플러그인 미로딩 시 MySQL 시작 실패 — 사용하지 않음
|
|
|
|
```bash
|
|
# 1. mysql-apt-config deb로 repo 등록
|
|
sudo wget https://dev.mysql.com/get/mysql-apt-config_0.8.33-1_all.deb
|
|
sudo DEBIAN_FRONTEND=noninteractive dpkg -i mysql-apt-config_0.8.33-1_all.deb
|
|
|
|
# 2. GPG 키 만료 시 — Ubuntu keyserver에서 갱신
|
|
sudo gpg --keyserver keyserver.ubuntu.com --recv-keys B7B3B788A8D3785C
|
|
sudo gpg --export B7B3B788A8D3785C | sudo tee /usr/share/keyrings/mysql-apt-config.gpg > /dev/null
|
|
|
|
# 3. 설치
|
|
sudo apt update
|
|
sudo apt install -y mysql-server
|
|
```
|
|
|
|
**성능 튜닝** (`/etc/mysql/mysql.conf.d/sam-tuning.cnf`):
|
|
|
|
```ini
|
|
[mysqld]
|
|
innodb_buffer_pool_size = 1536M
|
|
innodb_redo_log_capacity = 536870912
|
|
innodb_flush_log_at_trx_commit = 2
|
|
max_connections = 50
|
|
character-set-server = utf8mb4
|
|
collation-server = utf8mb4_unicode_ci
|
|
slow_query_log = 1
|
|
slow_query_log_file = /var/log/mysql/slow.log
|
|
long_query_time = 2
|
|
|
|
# Replication (Slave) — 운영서버 Master 설정 완료 후 활성화
|
|
# server-id = 2
|
|
# relay-log = /var/log/mysql/mysql-relay-bin
|
|
# read-only = 1 ← Gitea 로컬 DB 쓰기를 차단하므로 반드시 주석 유지
|
|
```
|
|
|
|
```bash
|
|
sudo systemctl restart mysql
|
|
```
|
|
|
|
**DB 및 사용자:**
|
|
|
|
```sql
|
|
-- Gitea DB
|
|
CREATE DATABASE gitea CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
CREATE USER 'gitea'@'localhost' IDENTIFIED BY '<gitea_비밀번호>';
|
|
GRANT ALL PRIVILEGES ON gitea.* TO 'gitea'@'localhost';
|
|
|
|
-- 관리자 (hskwon) - auth_socket 인증
|
|
CREATE USER 'hskwon'@'localhost' IDENTIFIED WITH auth_socket;
|
|
GRANT ALL PRIVILEGES ON *.* TO 'hskwon'@'localhost' WITH GRANT OPTION;
|
|
|
|
FLUSH PRIVILEGES;
|
|
```
|
|
|
|
> **참고**: root는 auth_socket 인증 (비밀번호 없이 `sudo mysql`로 접근)
|
|
|
|
### ②-slave MySQL Replication 설정 (운영 DB 백업)
|
|
|
|
> 운영서버 MySQL Master → CI/CD 서버 MySQL Slave
|
|
|
|
**운영서버(Master)에서 실행:**
|
|
|
|
```sql
|
|
CREATE USER 'repl_user'@'110.10.147.46' IDENTIFIED BY '<복제_비밀번호>';
|
|
GRANT REPLICATION SLAVE ON *.* TO 'repl_user'@'110.10.147.46';
|
|
FLUSH PRIVILEGES;
|
|
SHOW MASTER STATUS;
|
|
-- → File: mysql-bin.XXXXXX, Position: XXXX 기록
|
|
```
|
|
|
|
**운영서버 my.cnf 추가:**
|
|
```ini
|
|
[mysqld]
|
|
server-id = 1
|
|
log-bin = /var/log/mysql/mysql-bin
|
|
binlog-do-db = sam_production
|
|
binlog-do-db = chandj
|
|
```
|
|
|
|
**CI/CD서버(Slave)에서 실행:**
|
|
|
|
```sql
|
|
CHANGE REPLICATION SOURCE TO
|
|
SOURCE_HOST='211.117.60.189',
|
|
SOURCE_USER='repl_user',
|
|
SOURCE_PASSWORD='<복제_비밀번호>',
|
|
SOURCE_LOG_FILE='mysql-bin.XXXXXX',
|
|
SOURCE_LOG_POS=XXXX;
|
|
|
|
START REPLICA;
|
|
SHOW REPLICA STATUS\G
|
|
-- Slave_IO_Running: Yes, Slave_SQL_Running: Yes 확인
|
|
```
|
|
|
|
### ③ Redis → 운영서버에 설치
|
|
|
|
> **Redis는 운영서버(211.117.60.189)에 설치.**
|
|
> 상세 설정: [production-server-setup.md](./production-server-setup.md) Redis 섹션 참조
|
|
|
|
### ④ Java (Jenkins 의존) ✅
|
|
|
|
> **설치 완료**: OpenJDK 17.0.18
|
|
|
|
```bash
|
|
sudo apt install -y openjdk-17-jre-headless
|
|
java -version
|
|
```
|
|
|
|
### ⑤ Gitea ✅
|
|
|
|
> **설치 완료**: Gitea 1.22.6
|
|
> - 관리자: hskwon / kent@codebridge-x.com
|
|
> - Organization: SamProject
|
|
> - 저장소: sam-api, sam-manage, sam-react-prod, sam-sales, sam-landing
|
|
> - 회원가입 비활성화, API 토큰 생성 완료
|
|
|
|
```bash
|
|
GITEA_VERSION="1.22.6"
|
|
wget -O /tmp/gitea https://dl.gitea.com/gitea/${GITEA_VERSION}/gitea-${GITEA_VERSION}-linux-amd64
|
|
sudo mv /tmp/gitea /usr/local/bin/gitea
|
|
sudo chmod +x /usr/local/bin/gitea
|
|
|
|
sudo adduser --system --group --disabled-password --shell /bin/bash --home /home/git git
|
|
|
|
sudo mkdir -p /var/lib/gitea/{custom,data,log}
|
|
sudo mkdir -p /etc/gitea
|
|
sudo chown -R git:git /var/lib/gitea
|
|
sudo chown git:git /etc/gitea
|
|
sudo chmod 750 /etc/gitea
|
|
```
|
|
|
|
**systemd 서비스 등록:**
|
|
|
|
```bash
|
|
sudo tee /etc/systemd/system/gitea.service > /dev/null << 'EOF'
|
|
[Unit]
|
|
Description=Gitea (Git with a cup of tea)
|
|
After=syslog.target network.target mysql.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=git
|
|
Group=git
|
|
WorkingDirectory=/var/lib/gitea/
|
|
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
|
|
Restart=always
|
|
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea
|
|
RestartSec=10
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable gitea
|
|
```
|
|
|
|
**Gitea 설정** (`/etc/gitea/app.ini`):
|
|
|
|
```ini
|
|
[server]
|
|
DOMAIN = git.sam.it.kr
|
|
HTTP_PORT = 3000
|
|
ROOT_URL = https://git.sam.it.kr/
|
|
DISABLE_SSH = false
|
|
SSH_PORT = 22
|
|
LFS_START_SERVER = true
|
|
|
|
[database]
|
|
DB_TYPE = mysql
|
|
HOST = 127.0.0.1:3306
|
|
NAME = gitea
|
|
USER = gitea
|
|
PASSWD = <gitea_비밀번호>
|
|
CHARSET = utf8mb4
|
|
|
|
[repository]
|
|
ROOT = /var/lib/gitea/data/repositories
|
|
|
|
[log]
|
|
ROOT_PATH = /var/lib/gitea/log
|
|
MODE = file
|
|
LEVEL = info
|
|
|
|
[service]
|
|
DISABLE_REGISTRATION = true
|
|
REQUIRE_SIGNIN_VIEW = true
|
|
```
|
|
|
|
```bash
|
|
sudo systemctl start gitea
|
|
# 확인: curl http://localhost:3000
|
|
```
|
|
|
|
**CI/CD Gitea 초기 설정:**
|
|
|
|
```
|
|
1. https://git.sam.it.kr 웹 설치 마법사 완료
|
|
2. 관리자 계정 생성
|
|
3. Organization "SamProject" 생성
|
|
4. 빈 저장소 생성:
|
|
- sam-api, sam-manage, sam-react-prod
|
|
- sam-sales, sam-landing (www)
|
|
5. Jenkins Webhook용 API 토큰 생성
|
|
```
|
|
|
|
### ⑤-hook 개발서버 post-receive hook 설정 (선택적 브랜치 동기화) ✅
|
|
|
|
> **설치 완료**: 개발서버 Gitea bare repository에 post-receive hook 추가
|
|
> stage/develop(react만) push 시 CI/CD Gitea에 해당 브랜치만 push.
|
|
> 기존 post-update hook (개발서버 pull)은 **그대로 유지**.
|
|
|
|
**개발서버 Gitea bare repo 경로:**
|
|
|
|
```
|
|
/data/GIT/samproject/<repo>.git/hooks/post-receive.d/push-to-cicd
|
|
```
|
|
|
|
**토큰 보안 (환경변수 파일):**
|
|
|
|
```bash
|
|
# /data/GIT/.cicd-env (chmod 600, owner: git)
|
|
CICD_GITEA_TOKEN=<토큰>
|
|
CICD_GITEA_USER=hskwon
|
|
CICD_GITEA_HOST=git.sam.it.kr
|
|
```
|
|
|
|
**hook 스크립트 구조 (공통 패턴):**
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
source /data/GIT/.cicd-env
|
|
LOGFILE=/home/webservice/logs/cicd_push_<repo>.log
|
|
CICD_REMOTE="https://${CICD_GITEA_USER}:${CICD_GITEA_TOKEN}@${CICD_GITEA_HOST}/SamProject/<repo>.git"
|
|
|
|
mkdir -p /home/webservice/logs
|
|
|
|
while read oldrev newrev refname; do
|
|
BRANCH=$(echo "$refname" | sed 's|refs/heads/||')
|
|
if [ "$BRANCH" = "<target_branch>" ]; then
|
|
echo "$(date '+%Y-%m-%d %H:%M:%S'): Pushing ${BRANCH} to CI/CD Gitea" >> $LOGFILE
|
|
git push $CICD_REMOTE ${BRANCH}:${BRANCH} >> $LOGFILE 2>&1
|
|
echo "$(date '+%Y-%m-%d %H:%M:%S'): Done (exit: $?)" >> $LOGFILE
|
|
fi
|
|
done
|
|
```
|
|
|
|
> **참고**: Gitea는 `hooks/post-receive.d/` 디렉토리의 스크립트를 자동 실행.
|
|
> 기존 post-update hook (pull 스크립트)과 별도로 동작.
|
|
|
|
**동기화 요약:**
|
|
|
|
| 저장소 | hook 대상 브랜치 | 동작 |
|
|
|--------|-----------------|------|
|
|
| sam-react-prod | stage, develop | CI/CD Gitea에 push |
|
|
| sam-api | stage | CI/CD Gitea에 push |
|
|
| sam-sales | main | CI/CD Gitea에 push |
|
|
| sam-landing | main | CI/CD Gitea에 push |
|
|
| sam-manage | ❌ 없음 | main만 사용, 배포관리자 수동 push |
|
|
|
|
### ⑥ Jenkins ✅
|
|
|
|
> **설치 완료**: Jenkins 2.541.2
|
|
> - 관리자: hskwon
|
|
> - 플러그인: Gitea, SSH Agent, Pipeline Stage View, Workflow Aggregator, Blue Ocean, NodeJS
|
|
> - Gitea 서버 연동 완료 (https://git.sam.it.kr)
|
|
> - SSH Credential 등록: `deploy-ssh-key` (운영/개발 서버 모두 접속 확인)
|
|
> - Gitea API Token Credential 등록: `gitea-api-token`
|
|
> - Node.js 22.22.0 설치 완료
|
|
|
|
```bash
|
|
# Jenkins GPG 키 + APT Repository
|
|
# 주의: 공식 2023 키가 유효하지 않을 수 있음 → Ubuntu keyserver에서 획득
|
|
sudo gpg --keyserver keyserver.ubuntu.com --recv-keys 7198F4B714ABFC68
|
|
sudo gpg --export 7198F4B714ABFC68 | sudo tee /usr/share/keyrings/jenkins-keyring.gpg > /dev/null
|
|
echo "deb [signed-by=/usr/share/keyrings/jenkins-keyring.gpg]" \
|
|
https://pkg.jenkins.io/debian-stable binary/ | sudo tee \
|
|
/etc/apt/sources.list.d/jenkins.list > /dev/null
|
|
|
|
sudo apt update
|
|
sudo apt install -y jenkins
|
|
|
|
# Jenkins JVM 메모리 제한
|
|
sudo mkdir -p /etc/systemd/system/jenkins.service.d
|
|
sudo tee /etc/systemd/system/jenkins.service.d/override.conf > /dev/null << 'EOF'
|
|
[Service]
|
|
Environment="JAVA_OPTS=-Xmx2048m -Xms512m -Djava.awt.headless=true"
|
|
EOF
|
|
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable jenkins
|
|
sudo systemctl start jenkins
|
|
|
|
# 초기 관리자 비밀번호
|
|
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
|
|
```
|
|
|
|
**Jenkins에 필요한 도구 설치:**
|
|
|
|
```bash
|
|
# Node.js 22 (react 빌드용)
|
|
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
|
|
sudo apt install -y nodejs
|
|
|
|
# PHP 8.4 + Composer (Laravel 테스트/린트용 — 선택사항)
|
|
sudo add-apt-repository ppa:ondrej/php -y
|
|
sudo apt update
|
|
sudo apt install -y php8.4-cli php8.4-mbstring php8.4-xml php8.4-curl php8.4-zip php8.4-mysql php8.4-bcmath
|
|
curl -sS https://getcomposer.org/installer | php
|
|
sudo mv composer.phar /usr/local/bin/composer
|
|
```
|
|
|
|
**Jenkins 초기 설정 (웹 UI):**
|
|
|
|
1. `https://ci.sam.it.kr` 접속
|
|
2. 초기 비밀번호 입력
|
|
3. 추천 플러그인 설치 + 추가 플러그인:
|
|
- **Gitea Plugin** (Gitea webhook 연동)
|
|
- **SSH Agent Plugin** (운영/개발서버 SSH 배포)
|
|
- **Pipeline** (Jenkinsfile 지원)
|
|
- **Blue Ocean** (모던 UI, 선택)
|
|
|
|
4. Jenkins SSH 키 설정:
|
|
```bash
|
|
# Jenkins 사용자로 SSH 키 생성
|
|
sudo -u jenkins ssh-keygen -t ed25519 -C "jenkins@sam-cicd" -f /var/lib/jenkins/.ssh/id_ed25519 -N ""
|
|
|
|
# 운영서버에 공개키 등록
|
|
ssh sam-prod "echo '$(cat /var/lib/jenkins/.ssh/id_ed25519.pub)' >> /home/hskwon/.ssh/authorized_keys"
|
|
|
|
# 개발서버에도 공개키 등록 (develop react 빌드 배포용)
|
|
ssh sam-dev "echo '$(cat /var/lib/jenkins/.ssh/id_ed25519.pub)' >> /home/hskwon/.ssh/authorized_keys"
|
|
|
|
# SSH 호스트 키 등록 (known_hosts)
|
|
sudo -u jenkins ssh-keyscan -H 211.117.60.189 >> /var/lib/jenkins/.ssh/known_hosts
|
|
sudo -u jenkins ssh-keyscan -H 114.203.209.83 >> /var/lib/jenkins/.ssh/known_hosts
|
|
```
|
|
|
|
5. Jenkins Credentials 설정 (CLI로 완료):
|
|
- `deploy-ssh-key`: SSH 키 (hskwon@운영/개발 서버 공용)
|
|
- `gitea-api-token`: Gitea API 토큰
|
|
|
|
### ⑦ Nginx + Certbot ✅
|
|
|
|
> **설치 완료**: Nginx + Let's Encrypt SSL (git/ci/monitor.sam.it.kr)
|
|
|
|
```bash
|
|
sudo apt install -y nginx certbot python3-certbot-nginx
|
|
```
|
|
|
|
**Gitea 리버스 프록시** (`/etc/nginx/sites-available/git.sam.it.kr`):
|
|
|
|
```nginx
|
|
server {
|
|
listen 80;
|
|
server_name git.sam.it.kr;
|
|
client_max_body_size 100M;
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:3000;
|
|
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;
|
|
}
|
|
}
|
|
```
|
|
|
|
**Jenkins 리버스 프록시** (`/etc/nginx/sites-available/ci.sam.it.kr`):
|
|
|
|
```nginx
|
|
server {
|
|
listen 80;
|
|
server_name ci.sam.it.kr;
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:8080;
|
|
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-Port $server_port;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_read_timeout 90;
|
|
proxy_buffering off;
|
|
}
|
|
}
|
|
```
|
|
|
|
**Grafana 리버스 프록시** (`/etc/nginx/sites-available/monitor.sam.it.kr`):
|
|
|
|
```nginx
|
|
server {
|
|
listen 80;
|
|
server_name monitor.sam.it.kr;
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:3100;
|
|
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_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
}
|
|
}
|
|
```
|
|
|
|
```bash
|
|
# 사이트 활성화 + SSL
|
|
sudo ln -s /etc/nginx/sites-available/git.sam.it.kr /etc/nginx/sites-enabled/
|
|
sudo ln -s /etc/nginx/sites-available/ci.sam.it.kr /etc/nginx/sites-enabled/
|
|
sudo ln -s /etc/nginx/sites-available/monitor.sam.it.kr /etc/nginx/sites-enabled/
|
|
sudo nginx -t && sudo systemctl reload nginx
|
|
|
|
sudo certbot --nginx -d git.sam.it.kr
|
|
sudo certbot --nginx -d ci.sam.it.kr
|
|
sudo certbot --nginx -d monitor.sam.it.kr
|
|
sudo certbot renew --dry-run
|
|
```
|
|
|
|
### ⑧ Prometheus + node_exporter ✅
|
|
|
|
> **설치 완료**: Prometheus 2.51.0, node_exporter 1.8.2
|
|
|
|
```bash
|
|
# Prometheus
|
|
PROM_VERSION="2.51.0"
|
|
cd /tmp
|
|
wget https://github.com/prometheus/prometheus/releases/download/v${PROM_VERSION}/prometheus-${PROM_VERSION}.linux-amd64.tar.gz
|
|
tar xzf prometheus-${PROM_VERSION}.linux-amd64.tar.gz
|
|
sudo mv prometheus-${PROM_VERSION}.linux-amd64/prometheus /usr/local/bin/
|
|
sudo mv prometheus-${PROM_VERSION}.linux-amd64/promtool /usr/local/bin/
|
|
sudo mkdir -p /etc/prometheus /var/lib/prometheus
|
|
sudo mv prometheus-${PROM_VERSION}.linux-amd64/consoles /etc/prometheus/
|
|
sudo mv prometheus-${PROM_VERSION}.linux-amd64/console_libraries /etc/prometheus/
|
|
rm -rf prometheus-${PROM_VERSION}*
|
|
sudo useradd --no-create-home --shell /bin/false prometheus
|
|
sudo chown -R prometheus:prometheus /etc/prometheus /var/lib/prometheus
|
|
```
|
|
|
|
**Prometheus 설정** (`/etc/prometheus/prometheus.yml`):
|
|
|
|
```yaml
|
|
global:
|
|
scrape_interval: 15s
|
|
evaluation_interval: 15s
|
|
|
|
scrape_configs:
|
|
- job_name: 'prometheus'
|
|
static_configs:
|
|
- targets: ['localhost:9090']
|
|
|
|
- job_name: 'sam-prod'
|
|
static_configs:
|
|
- targets: ['211.117.60.189:9100']
|
|
labels:
|
|
server: 'production'
|
|
|
|
- job_name: 'sam-cicd'
|
|
static_configs:
|
|
- targets: ['localhost:9100']
|
|
labels:
|
|
server: 'cicd'
|
|
```
|
|
|
|
```bash
|
|
# Prometheus systemd
|
|
sudo tee /etc/systemd/system/prometheus.service > /dev/null << 'EOF'
|
|
[Unit]
|
|
Description=Prometheus
|
|
After=network-online.target
|
|
|
|
[Service]
|
|
User=prometheus
|
|
Group=prometheus
|
|
Type=simple
|
|
ExecStart=/usr/local/bin/prometheus \
|
|
--config.file=/etc/prometheus/prometheus.yml \
|
|
--storage.tsdb.path=/var/lib/prometheus/ \
|
|
--storage.tsdb.retention.time=30d \
|
|
--web.listen-address=:9090
|
|
Restart=always
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
# node_exporter
|
|
cd /tmp
|
|
wget https://github.com/prometheus/node_exporter/releases/download/v1.8.2/node_exporter-1.8.2.linux-amd64.tar.gz
|
|
tar xzf node_exporter-1.8.2.linux-amd64.tar.gz
|
|
sudo mv node_exporter-1.8.2.linux-amd64/node_exporter /usr/local/bin/
|
|
rm -rf node_exporter-1.8.2*
|
|
|
|
sudo tee /etc/systemd/system/node_exporter.service > /dev/null << 'EOF'
|
|
[Unit]
|
|
Description=Node Exporter
|
|
After=network.target
|
|
|
|
[Service]
|
|
User=nobody
|
|
ExecStart=/usr/local/bin/node_exporter
|
|
Restart=always
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable prometheus node_exporter
|
|
sudo systemctl start prometheus node_exporter
|
|
```
|
|
|
|
### ⑨ Grafana ✅
|
|
|
|
> **설치 완료**: Grafana (포트 3100)
|
|
|
|
```bash
|
|
sudo mkdir -p /etc/apt/keyrings/
|
|
wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/grafana.gpg > /dev/null
|
|
echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" | sudo tee /etc/apt/sources.list.d/grafana.list
|
|
sudo apt update
|
|
sudo apt install -y grafana
|
|
```
|
|
|
|
**Grafana 설정** (`/etc/grafana/grafana.ini`):
|
|
|
|
```ini
|
|
[server]
|
|
http_port = 3100
|
|
domain = monitor.sam.it.kr
|
|
root_url = https://monitor.sam.it.kr/
|
|
|
|
[security]
|
|
admin_password = <grafana_비밀번호>
|
|
|
|
[users]
|
|
allow_sign_up = false
|
|
```
|
|
|
|
```bash
|
|
sudo systemctl enable grafana-server
|
|
sudo systemctl start grafana-server
|
|
```
|
|
|
|
**Grafana 초기 설정 (웹 UI):**
|
|
|
|
1. Data Source: Prometheus → `http://localhost:9090`
|
|
2. 대시보드 임포트: **Node Exporter Full** (ID: 1860)
|
|
|
|
---
|
|
|
|
## 4. 배포 파이프라인 설계
|
|
|
|
### Webhook 설정 (CI/CD Gitea → Jenkins)
|
|
|
|
각 저장소에 Webhook 추가 (CI/CD Gitea 웹 UI):
|
|
|
|
```
|
|
Repository Settings → Webhooks → Add Webhook (Gitea)
|
|
- URL: https://ci.sam.it.kr/gitea-webhook/post
|
|
- Content Type: application/json
|
|
- Secret: <webhook_secret>
|
|
- Events: Push events
|
|
```
|
|
|
|
### 파이프라인: Laravel API (api/)
|
|
|
|
**Jenkinsfile** (`api/Jenkinsfile`):
|
|
|
|
```groovy
|
|
pipeline {
|
|
agent any
|
|
|
|
environment {
|
|
DEPLOY_USER = 'hskwon'
|
|
APP_NAME = 'api'
|
|
RELEASE_ID = new Date().format('yyyyMMdd_HHmmss')
|
|
}
|
|
|
|
stages {
|
|
stage('Checkout') {
|
|
steps { checkout scm }
|
|
}
|
|
|
|
// ── main → 운영서버 (배포관리자 수동 push 후 트리거) ──
|
|
stage('Deploy Production') {
|
|
when { branch 'main' }
|
|
steps {
|
|
sshagent(credentials: ['deploy-ssh-key']) {
|
|
sh """
|
|
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} &&
|
|
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/releases/${RELEASE_ID} /home/webservice/api/current &&
|
|
sudo systemctl reload php8.4-fpm &&
|
|
sudo supervisorctl restart sam-queue-worker:* &&
|
|
cd /home/webservice/api/releases &&
|
|
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
|
|
'
|
|
"""
|
|
}
|
|
}
|
|
}
|
|
|
|
// develop → Jenkins 관여 안함 (기존 post-update hook 유지)
|
|
}
|
|
|
|
post {
|
|
success { echo "✅ api 배포 완료 (${env.BRANCH_NAME})" }
|
|
failure {
|
|
echo "❌ api 배포 실패 (${env.BRANCH_NAME})"
|
|
// 운영/Stage 실패 시 이전 릴리즈로 롤백
|
|
script {
|
|
if (env.BRANCH_NAME in ['main', 'stage']) {
|
|
def baseDir = env.BRANCH_NAME == 'main'
|
|
? '/home/webservice/api'
|
|
: '/home/webservice/api-stage'
|
|
sshagent(credentials: ['deploy-ssh-key']) {
|
|
sh """
|
|
ssh ${DEPLOY_USER}@211.117.60.189 '
|
|
PREV=\$(ls -1dt ${baseDir}/releases/*/ | sed -n "2p" | xargs basename) &&
|
|
[ -n "\$PREV" ] && ln -sfn ${baseDir}/releases/\$PREV ${baseDir}/current &&
|
|
sudo systemctl reload php8.4-fpm
|
|
'
|
|
"""
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 파이프라인: Laravel Admin (mng/)
|
|
|
|
> API와 동일 구조. 차이점:
|
|
> - main만 (stage/develop 없음 — develop은 기존 post-update hook)
|
|
> - Queue Worker 재시작 불필요
|
|
> - npm run build 추가 (Blade + Vite)
|
|
|
|
### 파이프라인: Next.js React (react/)
|
|
|
|
**Jenkinsfile** (`react/Jenkinsfile`):
|
|
|
|
```groovy
|
|
pipeline {
|
|
agent any
|
|
|
|
environment {
|
|
DEPLOY_USER = 'hskwon'
|
|
RELEASE_ID = new Date().format('yyyyMMdd_HHmmss')
|
|
}
|
|
|
|
stages {
|
|
stage('Checkout') {
|
|
steps { checkout scm }
|
|
}
|
|
|
|
stage('Build') {
|
|
steps {
|
|
sh 'npm ci && npm run build'
|
|
}
|
|
}
|
|
|
|
// ── main → 운영서버 (배포관리자 수동 push 후 트리거) ──
|
|
stage('Deploy Production') {
|
|
when { branch 'main' }
|
|
steps {
|
|
sshagent(credentials: ['deploy-ssh-key']) {
|
|
sh """
|
|
ssh ${DEPLOY_USER}@211.117.60.189 'mkdir -p /home/webservice/react/releases/${RELEASE_ID}'
|
|
|
|
rsync -az --delete \
|
|
.next/ package.json package-lock.json next.config.* public/ node_modules/ \
|
|
${DEPLOY_USER}@211.117.60.189:/home/webservice/react/releases/${RELEASE_ID}/
|
|
|
|
ssh ${DEPLOY_USER}@211.117.60.189 '
|
|
ln -sfn /home/webservice/react/shared/.env.local /home/webservice/react/releases/${RELEASE_ID}/.env.local &&
|
|
ln -sfn /home/webservice/react/releases/${RELEASE_ID} /home/webservice/react/current &&
|
|
cd /home/webservice && pm2 reload sam-front &&
|
|
cd /home/webservice/react/releases && ls -1dt */ | tail -n +4 | 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 'mkdir -p /home/webservice/react-stage/releases/${RELEASE_ID}'
|
|
|
|
rsync -az --delete \
|
|
.next/ package.json package-lock.json next.config.* public/ node_modules/ \
|
|
${DEPLOY_USER}@211.117.60.189:/home/webservice/react-stage/releases/${RELEASE_ID}/
|
|
|
|
ssh ${DEPLOY_USER}@211.117.60.189 '
|
|
ln -sfn /home/webservice/react-stage/shared/.env.local /home/webservice/react-stage/releases/${RELEASE_ID}/.env.local &&
|
|
ln -sfn /home/webservice/react-stage/releases/${RELEASE_ID} /home/webservice/react-stage/current &&
|
|
cd /home/webservice && pm2 reload sam-front-stage &&
|
|
cd /home/webservice/react-stage/releases && ls -1dt */ | tail -n +3 | xargs rm -rf 2>/dev/null || true
|
|
'
|
|
"""
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── develop → 개발서버 (CI/CD에서 빌드 후 배포) ──
|
|
stage('Deploy Development') {
|
|
when { branch 'develop' }
|
|
steps {
|
|
sshagent(credentials: ['deploy-ssh-key']) {
|
|
sh """
|
|
# 빌드 결과물을 개발서버로 전송
|
|
rsync -az --delete \
|
|
.next/ package.json package-lock.json next.config.* public/ node_modules/ \
|
|
${DEPLOY_USER}@114.203.209.83:/home/webservice/react/
|
|
|
|
ssh ${DEPLOY_USER}@114.203.209.83 '
|
|
cd /home/webservice/react &&
|
|
pm2 restart sam-front
|
|
'
|
|
"""
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
post {
|
|
success { echo "✅ react 배포 완료 (${env.BRANCH_NAME})" }
|
|
failure { echo "❌ react 배포 실패 (${env.BRANCH_NAME})" }
|
|
}
|
|
}
|
|
```
|
|
|
|
### 파이프라인: Sales (레거시 PHP)
|
|
|
|
```groovy
|
|
pipeline {
|
|
agent any
|
|
environment { DEPLOY_USER = 'hskwon' }
|
|
|
|
stages {
|
|
// main → 운영서버 (hook 자동)
|
|
stage('Deploy Production') {
|
|
when { branch 'main' }
|
|
steps {
|
|
sshagent(credentials: ['deploy-ssh-key']) {
|
|
sh "ssh ${DEPLOY_USER}@211.117.60.189 'cd /home/webservice/sales && git pull origin main'"
|
|
}
|
|
}
|
|
}
|
|
// develop → 개발서버는 기존 post-update hook 유지
|
|
}
|
|
}
|
|
```
|
|
|
|
### 파이프라인: Landing (www)
|
|
|
|
```groovy
|
|
pipeline {
|
|
agent any
|
|
environment { DEPLOY_USER = 'hskwon' }
|
|
|
|
stages {
|
|
stage('Deploy Production') {
|
|
when { branch 'main' }
|
|
steps {
|
|
sshagent(credentials: ['deploy-ssh-key']) {
|
|
sh "ssh ${DEPLOY_USER}@211.117.60.189 'cd /home/webservice/landing && git pull origin main'"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 5. 배포 흐름도
|
|
|
|
```
|
|
개발자 로컬
|
|
│ git push origin (develop / stage / main)
|
|
▼
|
|
┌──────────────────────────────────────────────────────────────┐
|
|
│ 개발서버 Gitea (114.203.209.83:3000) ← 모든 개발자 origin │
|
|
│ │
|
|
│ post-receive hooks: │
|
|
│ │
|
|
│ ┌─ develop push ────────────────────────────────────────┐ │
|
|
│ │ react → hook: CI/CD Gitea push ──→ Jenkins 빌드 │ │
|
|
│ │ → 빌드 결과 rsync → 개발서버 배포 │ │
|
|
│ │ api → 기존 post-update hook (pull + migrate) │ │
|
|
│ │ mng → 기존 post-update hook (pull + build) │ │
|
|
│ │ sales → 기존 post-update hook (pull) │ │
|
|
│ └───────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌─ stage push ──────────────────────────────────────────┐ │
|
|
│ │ react → hook: CI/CD Gitea push ──→ Jenkins 빌드 │ │
|
|
│ │ → rsync → 운영서버 Stage + PM2 reload │ │
|
|
│ │ api → hook: CI/CD Gitea push ──→ Jenkins │ │
|
|
│ │ → 운영서버 Stage Release + 심링크 │ │
|
|
│ └───────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌─ main push (sales/www만 자동) ────────────────────────┐ │
|
|
│ │ sales → hook: CI/CD Gitea push ──→ Jenkins │ │
|
|
│ │ → 운영서버 pull │ │
|
|
│ │ www → hook: CI/CD Gitea push ──→ Jenkins │ │
|
|
│ │ → 운영서버 pull │ │
|
|
│ │ react/mng/api → ❌ 자동 push 안함 │ │
|
|
│ └───────────────────────────────────────────────────────┘ │
|
|
└───────────────────────────────────────────────────────────────┘
|
|
|
|
┌─ 운영 배포 (main - react/mng/api) ──────────────────────────┐
|
|
│ │
|
|
│ 배포관리자 로컬 │
|
|
│ │ git push production main (CI/CD Gitea remote) │
|
|
│ ▼ │
|
|
│ CI/CD Gitea (git.sam.it.kr) │
|
|
│ │ Webhook │
|
|
│ ▼ │
|
|
│ Jenkins → 운영서버 배포 │
|
|
│ react: CI/CD 빌드 → rsync → PM2 reload │
|
|
│ api: Release + 심링크 → PHP-FPM reload │
|
|
│ mng: Release + 심링크 → PHP-FPM reload │
|
|
│ │
|
|
└───────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 환경별 배포 비교
|
|
|
|
| 항목 | 운영 (main) | Stage (stage) | 개발 (develop) |
|
|
|------|------------|---------------|----------------|
|
|
| **트리거** | 배포관리자 수동 push | 자동 (hook) | react만 자동 (hook), 나머지 기존 hook |
|
|
| **react 전략** | CI/CD 빌드 → rsync | CI/CD 빌드 → rsync | CI/CD 빌드 → rsync |
|
|
| **api 전략** | Release + 심링크 | Release + 심링크 | 기존 post-update (pull) |
|
|
| **mng 전략** | Release + 심링크 | - | 기존 post-update (pull + build) |
|
|
| **롤백** | 이전 릴리즈 심링크 | 이전 릴리즈 심링크 | git revert |
|
|
| **릴리즈 보관** | 최근 5개 | 최근 3개 | - |
|
|
|
|
---
|
|
|
|
## 6. 백업 자동화
|
|
|
|
### DB 일일 백업 (CI/CD 서버 crontab)
|
|
|
|
```bash
|
|
# /home/hskwon/scripts/backup-db.sh
|
|
#!/bin/bash
|
|
set -e
|
|
|
|
BACKUP_DIR="/home/hskwon/backups/mysql"
|
|
DATE=$(date +%Y%m%d_%H%M%S)
|
|
RETENTION_DAYS=14
|
|
|
|
mkdir -p $BACKUP_DIR
|
|
|
|
mysqldump --single-transaction --routines --triggers \
|
|
sam_production > $BACKUP_DIR/sam_production_$DATE.sql
|
|
mysqldump --single-transaction --routines --triggers \
|
|
chandj > $BACKUP_DIR/chandj_$DATE.sql
|
|
|
|
gzip $BACKUP_DIR/sam_production_$DATE.sql
|
|
gzip $BACKUP_DIR/chandj_$DATE.sql
|
|
find $BACKUP_DIR -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete
|
|
|
|
echo "$(date): Backup completed" >> $BACKUP_DIR/backup.log
|
|
```
|
|
|
|
```bash
|
|
chmod +x /home/hskwon/scripts/backup-db.sh
|
|
# crontab (매일 새벽 3시)
|
|
(crontab -l 2>/dev/null; echo "0 3 * * * /home/hskwon/scripts/backup-db.sh") | crontab -
|
|
```
|
|
|
|
---
|
|
|
|
## 7. 최종 점검
|
|
|
|
```bash
|
|
# 서비스 상태
|
|
sudo systemctl status nginx jenkins gitea mysql prometheus grafana-server node_exporter
|
|
|
|
# 방화벽 / 메모리 / 디스크 / 포트
|
|
sudo ufw status verbose
|
|
free -h
|
|
df -h
|
|
sudo ss -tlnp
|
|
|
|
# 웹 접속 테스트
|
|
curl -sI https://ci.sam.it.kr
|
|
curl -sI https://git.sam.it.kr
|
|
curl -sI https://monitor.sam.it.kr
|
|
|
|
# SSL
|
|
sudo certbot certificates
|
|
```
|
|
|
|
---
|
|
|
|
## 8. 보안 체크리스트
|
|
|
|
- [x] SSH 키 인증만 허용 (PasswordAuthentication no)
|
|
- [x] root SSH 로그인 비활성화 (PermitRootLogin no)
|
|
- [x] UFW 방화벽 활성화 (22, 80, 443만 허용)
|
|
- [x] Jenkins 관리자 계정 변경 (hskwon)
|
|
- [ ] Jenkins CSRF 보호 활성화
|
|
- [x] Gitea 회원가입 비활성화 (DISABLE_REGISTRATION = true)
|
|
- [x] Grafana 익명 접근 비활성화 (allow_sign_up = false)
|
|
- [x] Prometheus 외부 접근 차단 (127.0.0.1:9090 바인딩)
|
|
- [x] MySQL root 원격 접근 차단 (auth_socket 인증)
|
|
- [x] fail2ban 설치 (sshd jail 활성)
|
|
- [x] Certbot 자동 갱신 (certbot.timer 활성)
|
|
- [x] Jenkins SSH 키 ed25519 생성 + Credential 등록
|
|
- [x] Webhook Secret 설정 (Gitea → Jenkins)
|
|
- [x] post-receive hook 토큰 보안 (/data/GIT/.cicd-env 파일 참조, 600 권한)
|
|
|
|
---
|
|
|
|
## 9. 설치 순서 요약
|
|
|
|
| 순서 | 항목 | 예상 시간 | 의존성 |
|
|
|------|------|----------|--------|
|
|
| ① | OS 기본 셋팅 | 15분 | - |
|
|
| ② | MySQL 8.4 | 20분 | ① |
|
|
| ②-s | MySQL Replication | 30분 | ② + 운영서버 MySQL |
|
|
| ④ | Java 17 | 5분 | ① |
|
|
| ⑤ | Gitea 설치 + 초기 설정 | 30분 | ② |
|
|
| ⑤-h | 개발서버 post-receive hook 설정 | 30분 | ⑤ |
|
|
| ⑥ | Jenkins | 20분 | ④ |
|
|
| ⑦ | Nginx + SSL | 20분 | ⑤⑥ |
|
|
| ⑧ | Prometheus + node_exporter | 15분 | ① |
|
|
| ⑨ | Grafana | 15분 | ⑧ |
|
|
| ⑩ | Jenkins 파이프라인 + Webhook 설정 | 1시간 | ⑥⑦ |
|
|
| ⑪ | 백업 자동화 | 15분 | ②-s |
|
|
| ⑫ | 최종 점검 + 보안 | 30분 | 전체 |
|
|
|
|
**총 예상 시간: 5~6시간**
|
|
|
|
---
|
|
|
|
## 10. 결정 필요 사항
|
|
|
|
- [x] ~~Gitea 이전 여부~~ → 선택적 브랜치 동기화 (post-receive hook)
|
|
- [x] ~~브랜치 전략~~ → main(수동/운영), stage(자동/Stage), develop(react만 자동)
|
|
- [x] ~~도메인 확정~~ → git.sam.it.kr, ci.sam.it.kr, monitor.sam.it.kr (SSL 발급 완료)
|
|
- [ ] **Jenkins 테스트 실행 여부**: CI에서 phpunit/lint 실행 vs 배포만
|
|
- [ ] **알림 채널**: Slack, 이메일, 카카오톡 등
|
|
- [ ] **개발서버 Gitea bare repo 경로 확인** (hook 설정을 위해) |