# Jenkins CI/CD 셋업 가이드

> **작성일**: 2026-02-22
> **상태**: 설계 확정
> **대상**: SAM 프로젝트 개발팀장

---

## 1. Jenkins 이해하기

### 1.1 Jenkins란

Jenkins는 오픈소스 자동화 서버다. 코드를 Push하면 자동으로 빌드, 테스트, 배포를 수행한다.

### 1.2 현재 수동 vs 자동화 비교

```
현재: 개발자 → git push → SSH 접속 → git pull → composer install → 재시작 (수동, 5~10분)
목표: 개발자 → git push → Jenkins 자동 감지 → 빌드/테스트/배포 (자동, Slack 알림)
```

### 1.3 핵심 용어

| 용어 | 설명 |
|------|------|
| **Job** | 하나의 작업 단위 (예: `sam-api-deploy`) |
| **Pipeline** | Stage를 순서대로 실행하는 흐름 |
| **Stage / Step** | Pipeline의 단계 / 단계 내 개별 명령 |
| **Credential** | Jenkins에 저장하는 비밀 정보 (SSH 키, 토큰) |
| **Webhook** | Gitea가 Push 이벤트를 Jenkins에 알려주는 HTTP 호출 |

---

## 2. 사전 준비

| 항목 | 값 |
|------|------|
| IP | `114.203.209.83` |
| CPU/RAM | 2코어 / 3.8GB |
| Gitea | `http://114.203.209.83:3000` |

> **경고: RAM이 부족하므로 Swap 추가가 필수다.**

### 2.1 Swap 4GB 추가

```bash
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
free -h  # 확인
```

### 2.2 Java 17 + 방화벽

```bash
sudo apt update && sudo apt install -y openjdk-17-jdk
java -version  # 17.x.x 확인

sudo ufw allow 8080/tcp  # Jenkins 웹 UI 포트
```

---

## 3. Jenkins 설치

### 3.1 패키지 설치

```bash
curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key | sudo tee \
  /usr/share/keyrings/jenkins-keyring.asc > /dev/null

echo "deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] \
  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
sudo systemctl start jenkins && sudo systemctl enable jenkins
```

### 3.2 초기 설정

```bash
sudo cat /var/lib/jenkins/secrets/initialAdminPassword  # 초기 비밀번호
```

1. `http://114.203.209.83:8080` 접속 → 비밀번호 입력
2. **Install suggested plugins** 선택 → 설치 대기 (3~5분)
3. 관리자 계정 생성 (Username: `admin`)
4. Jenkins URL: `http://114.203.209.83:8080/` → **Save and Finish**

---

## 4. 필수 플러그인 설치

**Jenkins 관리 → Plugins → Available plugins** 에서 설치한다.

| 플러그인 | 역할 | 필수 |
|---------|------|------|
| **Git plugin** | 소스 코드 체크아웃 | 🔴 |
| **Pipeline** | Jenkinsfile 지원 | 🔴 |
| **SSH Agent** | SSH 키로 운영 서버 배포 | 🔴 |
| **Generic Webhook Trigger** | Gitea Push 이벤트 수신 | 🔴 |
| **Slack Notification** | 배포 결과 Slack 알림 | 🟡 |
| **NodeJS** | React 빌드용 Node.js | 🟡 |

> **참고**: Git plugin, Pipeline은 suggested plugins에 포함되어 이미 설치되었을 수 있다.

NodeJS 설정: **Jenkins 관리 → Tools → NodeJS installations → Add NodeJS** → Name: `NodeJS-20`, Version: `20.x`

---

## 5. Credential 설정

### 5.1 SSH 키 생성 (Jenkins → 운영 서버)

```bash
# Jenkins 서버에서 실행
sudo su - jenkins
ssh-keygen -t ed25519 -C "jenkins@sam" -f ~/.ssh/id_ed25519 -N ""
cat ~/.ssh/id_ed25519.pub  # 이 값을 운영 서버에 등록
exit

# 운영 서버에서 실행 (공개키 등록)
echo "ssh-ed25519 AAAA... jenkins@sam" >> /home/deploy/.ssh/authorized_keys
```

### 5.2 Jenkins Credential 등록

**Jenkins 관리 → Credentials → (global) → Add Credentials**

| Credential | Kind | ID | 내용 |
|-----------|------|-----|------|
| SSH 키 | SSH Username with private key | `prod-server-ssh` | `~jenkins/.ssh/id_ed25519` 비밀키 |
| Gitea 토큰 | Username with password | `gitea-token` | Gitea 사용자명 + API 토큰 |
| Slack URL | Secret text | `slack-webhook` | Slack Incoming Webhook URL |

```bash
# SSH 비밀키 확인 (Jenkins에 붙여넣기)
sudo cat /var/lib/jenkins/.ssh/id_ed25519
```

```
❌ Jenkinsfile에 비밀번호/토큰/키를 하드코딩 금지
✅ 모든 비밀 정보는 Jenkins Credential에 등록 후 credentials('ID')로 참조
```

---

## 6. Gitea Webhook 연동

### 6.1 Jenkins Pipeline Job 생성

1. **New Item** → 이름: `sam-api-deploy` → **Pipeline** 선택
2. **Build Triggers**: Generic Webhook Trigger 체크, Token: `sam-api`
3. **Pipeline**: Pipeline script from SCM → Git
   - URL: `http://114.203.209.83:3000/SamProject/sam-api.git`
   - Credentials: `gitea-token`
   - Branch: `*/main`
   - Script Path: `Jenkinsfile`

### 6.2 전체 Job 목록

| Job 이름 | 저장소 | 브랜치 | Token |
|---------|--------|--------|-------|
| `sam-api-deploy` | `sam-api.git` | `*/main` | `sam-api` |
| `sam-mng-deploy` | `sam-manage.git` | `*/master` | `sam-mng` |
| `sam-react-deploy` | `sam-react-prod.git` | `*/master` | `sam-react` |
| `sam-sales-deploy` | `sam-sales.git` | `*/main` | `sam-sales` |

### 6.3 Gitea Webhook 설정

각 저장소: **Settings → Webhooks → Add Webhook → Gitea**

| 항목 | 값 |
|------|------|
| Target URL | `http://114.203.209.83:8080/generic-webhook-trigger/invoke?token=sam-api` |
| Content Type | `application/json` |
| Trigger On | **Push Events** |
| Branch filter | `main` |

**Test Delivery** → 응답 200이면 성공

---

## 7. Jenkinsfile 작성 가이드

### 7.1 기본 구조

```groovy
pipeline {
    agent any
    environment { KEY = 'value' }
    stages {
        stage('단계명') {
            steps { sh 'command' }
        }
    }
    post {
        success { slackSend channel: '#sam-deploy', message: "성공" }
        failure { slackSend channel: '#sam-alerts', message: "실패" }
    }
}
```

### 7.2 SAM 저장소별 Jenkinsfile

> 상세 코드는 `plans/production-deployment-plan.md` 4.4절 참조

| 저장소 | Stage 흐름 | 특이사항 |
|--------|-----------|---------|
| **sam-api** | Checkout → Lint → Test → Deploy | `migrate --force` 포함 |
| **sam-manage** | Checkout → Lint → Build Assets → Deploy | 마이그레이션 없음 |
| **sam-react-prod** | Checkout → Install → Lint → Build → Package → Deploy | `tar.gz`로 전송 |
| **sam-sales** | Deploy | 간소화 (git pull + composer) |

### 7.3 배치 방법

각 저장소 **루트**에 `Jenkinsfile` 생성 → `git add Jenkinsfile && git commit -m "chore: Jenkinsfile 추가"` → push

---

## 8. 트러블슈팅

### 8.1 빌드 실패

Jenkins 대시보드 → Job → 빌드 번호 → **Console Output** 에서 에러 로그 확인

### 8.2 SSH 권한 오류 (`Permission denied`)

```bash
sudo su - jenkins && ssh deploy@운영서버IP  # 수동 테스트
# 운영 서버에서 authorized_keys 등록 확인
chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys
```

### 8.3 메모리 부족

```bash
# Jenkins 힙 메모리 제한: /etc/default/jenkins에 JAVA_ARGS="-Xmx512m" 추가
sudo systemctl restart jenkins
# Job 설정 → Discard old builds → 최대 빌드 수: 10
```

### 8.4 Webhook 미동작

```bash
# 수동 트리거 테스트
curl -X POST "http://114.203.209.83:8080/generic-webhook-trigger/invoke?token=sam-api"
# Gitea: Webhooks → Recent Deliveries → 응답 코드 확인 (200=정상, 403=Token 불일치)
```

### 8.5 React 빌드 OOM

```bash
# Jenkinsfile에서 메모리 증가
sh 'export NODE_OPTIONS="--max-old-space-size=2048" && npm run build'
# 실패 시 로컬(WSL)에서 react/deploy.sh 사용
```

> **경고: 개발 서버에서 React 빌드 실패 시 로컬에서 `deploy.sh`를 사용한다.**

---

## 관련 문서

- [운영 환경 배포 계획서](../plans/production-deployment-plan.md) - Jenkinsfile 상세, 브랜치 전략
- [.env 동기화 절차](production-env-sync.md) - 환경 변수 분리
- [Docker 환경 스펙](../system/docker-setup.md) - 현재 개발 환경

---

**최종 업데이트**: 2026-02-22
