์ํฐํ๋ผ์ด์ฆ CI/CD ์๋ํ ์๋ฒ ์๋ฒฝ ๊ฐ์ด๋: Docker ๊ธฐ๋ฐ ์ค์น ๋ฐ ์ด๊ธฐ ์ค์ , Declarative Pipeline ๋ฌธ๋ฒ ๊ตฌ์กฐ, Controller-Agent ๋ถ์ฐ ์ํคํ
์ฒ, ํ๊ฒฝ๋ณ์ ๋ฐ Credentials ๊ด๋ฆฌ, Docker ์ปจํ
์ด๋ ์์ด์ ํธ ๋น๋, ํ๋ผ๋ฏธํฐ ๋น๋, ์๋ ์น์ธ(Input) ๋ฐฐํฌ, ์ฌ๋ ์๋ฆผ ๋ฐ ํ๋ก๋์
์ด์ ์ฒดํฌ๋ฆฌ์คํธ
## 1. ํ์ ์ฌ์ ์ค์น ์๊ฑด & ๊ฐ๋ฐ ํ๊ฒฝ ๊ตฌ์ฑ (Prerequisites)
Jenkins๋ ์ ์ธ๊ณ ์ํฐํ๋ผ์ด์ฆ ํ๊ฒฝ์์ ๊ฐ์ฅ ๋๋ฆฌ ์ฌ์ฉ๋๋ ์คํ์์ค ์ง์์ ํตํฉ/์ง์์ ๋ฐฐํฌ(CI/CD) ์๋ํ ์๋ฒ์
๋๋ค. Java 17 ๋๋ 21 LTS ๋ฐํ์ ํ๊ฒฝ์์ ๋์ํ๋ฉฐ, Docker ์ปจํ
์ด๋๋ก ์ค์นํ๋ ๋ฐฉ์์ด ๊ฐ์ฅ ์์ ํ๊ณ ๋น ๋ฆ
๋๋ค.
### ๐ณ 1. Docker๋ก 1๋ถ ๋ง์ Jenkins ์ค์น ๋ฐ ์คํ (๊ฐ๋ ฅ ๊ถ์ฅ)
ํธ์คํธ OS์ Java๋ฅผ ์ง์ ์ค์นํ์ง ์๊ณ ๊ฒฉ๋ฆฌ๋ ์ปจํ
์ด๋ ํ๊ฒฝ์ผ๋ก ์คํํฉ๋๋ค:
```bash
# 1. ์๊ตฌ ๋ฐ์ดํฐ ์ ์ฅ์ ์ํ Docker ๋ณผ๋ฅจ ์์ฑ
docker volume create jenkins_home
# 2. Jenkins LTS ์ปจํ
์ด๋ ์คํ
# 8080: ์น ๋์๋ณด๋ ํฌํธ
# 50000: ์ธ๋ฐ์ด๋ ์์ด์ ํธ ์ฐ๊ฒฐ ํฌํธ
# /var/run/docker.sock: ์ปจํ
์ด๋ ๋ด๋ถ์์ ํธ์คํธ Docker๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํ ๋ง์ดํธ
docker run -d \
--name jenkins \
--restart unless-stopped \
-p 8080:8080 \
-p 50000:50000 \
-v jenkins_home:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
jenkins/jenkins:lts-jdk17
# 3. ์ปจํ
์ด๋ ์ ์ ๊ตฌ๋ ํ์ธ
docker ps | grep jenkins
```
### โ 2. OS ๋ค์ดํฐ๋ธ ์ค์น (Linux/Ubuntu ํ๊ฒฝ)
์๋ฒ์ ์ง์ ์ค์นํด์ผ ํ๋ ๊ฒฝ์ฐ Java 17 LTS๋ฅผ ๋จผ์ ์ค์นํฉ๋๋ค:
```bash
# 1. OpenJDK 17 LTS ์ค์น
sudo apt update
sudo apt install -y openjdk-17-jdk
# 2. Jenkins ๊ณต์ ์ ์ฅ์ ํค ๋ฐ ํจํค์ง ๋ชฉ๋ก ์ถ๊ฐ
sudo wget -O /usr/share/keyrings/jenkins-keyring.asc https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key
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
# 3. Jenkins ์ค์น ๋ฐ ์๋น์ค ์์
sudo apt update
sudo apt install -y jenkins
sudo systemctl enable --now jenkins
sudo systemctl status jenkins
```
### ๐ 3. ์ด๊ธฐ ๊ด๋ฆฌ์ ๋น๋ฐ๋ฒํธ ํ์ธ ๋ฐ ์น UI ์ ์
์น ๋ธ๋ผ์ฐ์ ์์ `http://localhost:8080` (๋๋ ์๋ฒ IP)๋ก ์ ์ํ ๋ค ์ด๊ธฐ ๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํฉ๋๋ค:
```bash
# Docker ์ปจํ
์ด๋์ธ ๊ฒฝ์ฐ:
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
# Linux ๋ค์ดํฐ๋ธ ์ค์น์ธ ๊ฒฝ์ฐ:
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
# ์ถ๋ ฅ ์์: 4a2b9f8e1c3d4e5a6b7c8d9e0f1a2b3c
```
### ๐งฉ 4. ํ๋ฌ๊ทธ์ธ ์ค์น & ๊ด๋ฆฌ์ ๊ณ์ ์์ฑ
1. ์น UI์์ **"Install suggested plugins"** (๊ถ์ฅ ํ๋ฌ๊ทธ์ธ ์ค์น)๋ฅผ ํด๋ฆญํ์ฌ Git, Pipeline, Workspace Cleanup, SSH Build Agents ๋ฑ์ ์๋ ์ค์นํฉ๋๋ค.
2. ์ถ๊ฐ ํ์ ํ๋ฌ๊ทธ์ธ ๊ฒ์ ์ค์น (`Jenkins ๊ด๋ฆฌ` โ `Plugins` โ `Available plugins`):
- **Docker Pipeline**: ํ์ดํ๋ผ์ธ ๋ด๋ถ์์ Docker ์ปจํ
์ด๋๋ฅผ ๋น๋ ์์ด์ ํธ๋ก ํ์ฉ
- **Credentials Binding**: API ํค, ํ ํฐ, ๋น๋ฐ๋ฒํธ๋ฅผ ์์ ํ๊ฒ ํ์ดํ๋ผ์ธ์ ๋ฐ์ธ๋ฉ
- **Slack Notification**: ๋น๋ ๊ฒฐ๊ณผ ๋ฐ ์น์ธ ์์ฒญ ์ฌ๋ ์ฑ๋ ์๋ฆผ
---
## 2. Jenkins ํต์ฌ ์ํคํ
์ฒ & ๋ฉํ ๋ชจ๋ธ (Mental Model)
Jenkins๋ ๋๊ท๋ชจ ์ํฐํ๋ผ์ด์ฆ ๋น๋๋ฅผ ๊ฐ๋นํ ์ ์๋๋ก **Controller-Agent ๋ถ์ฐ ์ํคํ
์ฒ**์ **์ ์ธ์ ํ์ดํ๋ผ์ธ(Declarative Pipeline)** ๊ตฌ์กฐ๋ฅผ ์ฑํํ๊ณ ์์ต๋๋ค.
### ๐ข Controller-Agent ๋ถ์ฐ ์ํคํ
์ฒ
```text
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Jenkins Controller (Master) โ
โ - ์น ๋์๋ณด๋ UI ์ ๊ณต, ํ์ดํ๋ผ์ธ ์ค์ผ์ค๋ง ๋ฐ ๋น๋ ํธ๋ฆฌ๊ฑฐ ๋ชจ๋ํฐ๋ง โ
โ - Credentials ์ ์ฅ์ ๊ด๋ฆฌ, ํ๋ฌ๊ทธ์ธ ๊ด๋ฆฌ, ๋น๋ ๊ธฐ๋ก/๋ก๊ทธ ์์นด์ด๋น โ
โ โป [์์น] Controller ๋
ธ๋ ์์ฒด์์๋ ๋ฌด๊ฑฐ์ด ๋น๋/์ปดํ์ผ ์์
์ ๊ธ์งํจ โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโ
โ (SSH / Inbound JNLP ํฌํธ 50000)
โผ โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Build Agent #1 (Linux) โ โ Build Agent #2 (Docker) โ
โ - Gradle / Maven ๋น๋ ์คํ โ โ - Node.js ํ๋ก ํธ์๋ ๋น๋ โ
โ - ๋จ์ ํ
์คํธ & ์ ์ ๋ถ์ โ โ - ์ปจํ
์ด๋ ์ด๋ฏธ์ง ๋น๋/ํธ์ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
```
### ๐ Declarative Pipeline ์๋ช
์ฃผ๊ธฐ ํ๋ฆ
```text
[๋น๋ ํธ๋ฆฌ๊ฑฐ: Git Webhook / Poll SCM / ์๋ ์คํ]
โ
โผ
1. pipeline
โ
โผ
2. agent (์ด๋ ๋
ธ๋ ๋๋ ๋์ปค ์ปจํ
์ด๋์์ ์คํํ ๊ฒ์ธ๊ฐ?)
โ
โผ
3. stages (์์ฐจ์ ์ผ๋ก ์คํ๋ ๋จ๊ณ ๋ฌถ์)
โโ Stage 1: Checkout (Git ์ ์ฅ์ ์์ค์ฝ๋ ๋ณต์ )
โโ Stage 2: Build & Test (์ปดํ์ผ, ๋จ์ ํ
์คํธ)
โโ Stage 3: SonarQube Scan (์ฝ๋ ํ์ง ์ ์ ๋ถ์)
โโ Stage 4: Docker Image Build & Push (๋ ์ง์คํธ๋ฆฌ ๋ฑ๋ก)
โโ Stage 5: Deploy (์๋ฒ ๋ฐฐํฌ / K8s ๋งค๋ํ์คํธ ๊ฐฑ์ )
โ
โผ
4. post (๊ฒฐ๊ณผ ํ์ ์กฐ์น)
โโ always: ์ํฌ์คํ์ด์ค ์ ๋ฆฌ(cleanWs)
โโ success: ์ฑ๊ณต ๋ก๊ทธ ๋ฐ ์๋ฆผ ์ ์ก
โโ failure: ์ค๋ฅ ๋ก๊ทธ ๋คํ ๋ฐ ๊ฐ๋ฐํ ์ฌ๋ ๊ธด๊ธ ์๋ฆผ
```
---
## 3. [์ด๋ณด์ 1๋จ๊ณ] ํฌ๋ก์๋: ์ต์ ๋์ ์ ์ธ์ ํ์ดํ๋ผ์ธ
๋ณต์กํ ์ค์ ์์ด Jenkins ์น ๋์๋ณด๋์์ 1๋ถ ๋ง์ ์์ฑํ๊ณ ์คํํ ์ ์๋ ์ต์ Declarative Pipeline ์์ ์
๋๋ค.
### ๐ ํ์ดํ๋ผ์ธ ์์ฑ ์์
1. Jenkins ๋์๋ณด๋ ์ข์ธก ์๋จ **"์๋ก์ด Item" (New Item)** ํด๋ฆญ
2. Item ์ด๋ฆ ์
๋ ฅ: `hello-world-pipeline` โ **"Pipeline"** ์ ํ โ **"OK"** ํด๋ฆญ
3. ํ์ด์ง ํ๋จ **Pipeline** ์น์
โ Definition: `Pipeline script` ์ ํ
4. ์๋ ์คํฌ๋ฆฝํธ๋ฅผ ์
๋ ฅ๋์ ๋ณต์ฌํ์ฌ ๋ถ์ฌ๋ฃ๊ณ **"์ ์ฅ" (Save)** ํด๋ฆญ:
```groovy
// ์ต์ ๋์ Declarative Pipeline
pipeline {
// 1. ๋น๋๋ฅผ ์คํํ ์์ด์ ํธ ์ง์ (any: ์ฌ์ฉ ๊ฐ๋ฅํ ์๋ฌด ์์ด์ ํธ์์๋ ์คํ)
agent any
// 2. ์คํ ์ต์
์ง์
options {
timeout(time: 10, unit: 'MINUTES') // 10๋ถ ์ด๊ณผ ์ ์๋ ์ค๋จ
timestamps() // ๋ก๊ทธ์ ํ์์คํฌํ ๊ธฐ๋ก
}
// 3. ์์ฐจ์ ์ผ๋ก ์คํ๋ ๋จ๊ณ ์ ์
stages {
stage('์ค๋น & ํ๊ฒฝ ํ์ธ') {
steps {
echo '=== [Stage 1] ๋น๋ ํ๊ฒฝ ์ ๋ณด ์ถ๋ ฅ ==='
sh 'node -v || true'
sh 'java -version || true'
sh 'echo "ํ์ฌ ์์
๋๋ ํ ๋ฆฌ: $WORKSPACE"'
}
}
stage('์ ํ๋ฆฌ์ผ์ด์
ํ
์คํธ') {
steps {
echo '=== [Stage 2] ๋ชจ์ ํ
์คํธ ์ํ ์ค ==='
sh 'echo "ํ
์คํธ ์ค์ํธ ์คํ ์๋ฃ: ์ฑ๊ณต (0 ์คํจ)"'
}
}
stage('๋ฐฐํฌ ์ค๋น') {
steps {
echo '=== [Stage 3] ํฌ๋ก์๋ ๋ฐฐํฌ ์๋ฃ ==='
echo "๋น๋ ๋ฒํธ: ${BUILD_NUMBER}"
echo "์ก ์ด๋ฆ: ${JOB_NAME}"
}
}
}
// 4. ๋ชจ๋ ๋จ๊ณ ์ข
๋ฃ ํ ์คํ๋ ํ์ ์กฐ์น
post {
always {
echo 'ํ์ดํ๋ผ์ธ ์คํ์ด ์๋ฃ๋์์ต๋๋ค (์ฑ๊ณต/์คํจ ์ฌ๋ถ ๋ฌด๊ด).'
}
success {
echo '๐ ์ถํํฉ๋๋ค! ๋ชจ๋ ๋จ๊ณ๊ฐ ์ ์ ํต๊ณผ๋์์ต๋๋ค.'
}
failure {
echo '๐จ ํ์ดํ๋ผ์ธ ์คํ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.'
}
}
}
```
### ๐ป ์คํ ๋ฐ ์ฝ์ ์ถ๋ ฅ ๊ฒฐ๊ณผ ํ์ธ
์ข์ธก ๋ฉ๋ด์ **"์ง๊ธ ๋น๋" (Build Now)** ๋ฅผ ํด๋ฆญํ๋ฉด ๋น๋๊ฐ ์์๋ฉ๋๋ค. ์์ฑ๋ `#1` ๋น๋๋ฅผ ํด๋ฆญํ๊ณ **"Console Output"** ์ ํ์ธํฉ๋๋ค:
```text
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /var/jenkins_home/workspace/hello-world-pipeline
[Pipeline] {
[Pipeline] stage
[Pipeline] { (์ค๋น & ํ๊ฒฝ ํ์ธ)
[Pipeline] echo
=== [Stage 1] ๋น๋ ํ๊ฒฝ ์ ๋ณด ์ถ๋ ฅ ===
[Pipeline] sh
+ echo "ํ์ฌ ์์
๋๋ ํ ๋ฆฌ: /var/jenkins_home/workspace/hello-world-pipeline"
ํ์ฌ ์์
๋๋ ํ ๋ฆฌ: /var/jenkins_home/workspace/hello-world-pipeline
[Pipeline] }
[Pipeline] stage
[Pipeline] { (์ ํ๋ฆฌ์ผ์ด์
ํ
์คํธ)
[Pipeline] echo
=== [Stage 2] ๋ชจ์ ํ
์คํธ ์ํ ์ค ===
[Pipeline] sh
+ echo "ํ
์คํธ ์ค์ํธ ์คํ ์๋ฃ: ์ฑ๊ณต (0 ์คํจ)"
ํ
์คํธ ์ค์ํธ ์คํ ์๋ฃ: ์ฑ๊ณต (0 ์คํจ)
[Pipeline] }
[Pipeline] stage
[Pipeline] { (๋ฐฐํฌ ์ค๋น)
[Pipeline] echo
=== [Stage 3] ํฌ๋ก์๋ ๋ฐฐํฌ ์๋ฃ ===
[Pipeline] echo
๋น๋ ๋ฒํธ: 1
[Pipeline] echo
์ก ์ด๋ฆ: hello-world-pipeline
[Pipeline] }
[Pipeline] { (Declarative: Post Actions)
[Pipeline] echo
ํ์ดํ๋ผ์ธ ์คํ์ด ์๋ฃ๋์์ต๋๋ค (์ฑ๊ณต/์คํจ ์ฌ๋ถ ๋ฌด๊ด).
[Pipeline] echo
๐ ์ถํํฉ๋๋ค! ๋ชจ๋ ๋จ๊ณ๊ฐ ์ ์ ํต๊ณผ๋์์ต๋๋ค.
[Pipeline] }
Finished: SUCCESS
```
---
## 4. [์ด๋ณด์ 2๋จ๊ณ] ํต์ฌ ๊ธฐ๋ฅ & ํ์ดํ๋ผ์ธ ๋ฌธ๋ฒ (Jenkinsfile)
์ค๋ฌด ํ์ค ๋ฐฉ์์ ์ ์ฅ์ ๋ฃจํธ์ `Jenkinsfile`์ ์์ฑํ์ฌ Git์ผ๋ก ๋ฒ์ ๊ด๋ฆฌํ๋ ๋ฐฉ์(Pipeline-as-Code)์
๋๋ค.
### ๐งฑ 1. Declarative Pipeline 5๋ ํต์ฌ ๋ธ๋ก
| ์ง์์ด (Directive) | ์ญํ ๋ฐ ์ค๋ช
|
| :--- | :--- |
| **`pipeline { ... }`** | ์ ์ธ์ ํ์ดํ๋ผ์ธ์ ์์๊ณผ ๋์ ๊ฐ์ธ๋ ํ์ ์ต์์ ๋ธ๋ก |
| **`agent`** | ํ์ดํ๋ผ์ธ ๋๋ ๊ฐ๋ณ Stage๋ฅผ ์คํํ ๋
ธ๋ ์ง์ (`any`, `none`, `label`, `docker`) |
| **`stages`** | ์ค์ ๋น๋ ๋ก์ง์ ๋ด๋ ํ๋ ์ด์์ `stage`๋ค์ ํฌํจํ๋ ์ปจํ
์ด๋ |
| **`steps`** | ๊ฐ stage ๋ด๋ถ์์ ์ค์ ๋ก ์ํ๋๋ ๋ช
๋ น ๋จ์ (`sh`, `bat`, `echo`, `checkout` ๋ฑ) |
| **`post`** | ํ์ดํ๋ผ์ธ ๋๋ Stage ์ข
๋ฃ ์์ ์ ์กฐ๊ฑด๋ถ ํ์ ์กฐ์น (`always`, `success`, `failure`, `changed`) |
### ๐ 2. ํ๊ฒฝ๋ณ์(Environment) & Credentials ์์ ๋ฐ์ธ๋ฉ
๋น๋ฐ๋ฒํธ, API ํ ํฐ, SSH Key๋ ์ ๋ ์ฝ๋์ ํ๋์ฝ๋ฉํ์ง ์๊ณ Jenkins Credentials Store์ ๋ฑ๋ก ํ ๋ฐ์ธ๋ฉํฉ๋๋ค.
```groovy
pipeline {
agent any
environment {
// ์ผ๋ฐ ํ๊ฒฝ๋ณ์ ์ ์
APP_NAME = 'lucky-backend'
PROFILE = 'production'
// Jenkins Credentials Store์ ๋น๋ฐ๊ฐ์ ํ๊ฒฝ๋ณ์๋ก ์ฃผ์
// (ID๊ฐ 'dockerhub-creds'์ธ Username/Password ์๊ฒฉ์ฆ๋ช
)
DOCKER_CREDS = credentials('dockerhub-creds')
// DOCKER_CREDS_USR (์์ด๋), DOCKER_CREDS_PSW (๋น๋ฐ๋ฒํธ) ์๋ ์์ฑ ๋ฐ ์ฝ์ ๋ง์คํน(****)
// Secret Text(๋น๋ฐ ํ
์คํธ) ์ฃผ์
SLACK_TOKEN = credentials('slack-webhook-token')
}
stages {
stage('์๊ฒฉ์ฆ๋ช
ํ์ฉ') {
steps {
sh '''
echo "์ ํ๋ฆฌ์ผ์ด์
: ${APP_NAME} (${PROFILE})"
# Jenkins ์ฝ์ ์ถ๋ ฅ ์ ๋น๋ฐ๋ฒํธ๋ ์๋์ผ๋ก **** ๋ก ๋ง์คํน๋ฉ๋๋ค.
echo "Docker Hub ์ฌ์ฉ์: ${DOCKER_CREDS_USR}"
'''
}
}
}
}
```
### ๐๏ธ 3. ํ๋ผ๋ฏธํฐ ๋น๋ (Parameters)
๋น๋ ์คํ ์ ์ฌ์ฉ์๋ก๋ถํฐ ๋ธ๋์น๋ช
, ๋ฐฐํฌ ํ๊ฒฝ, ๋กค๋ฐฑ ์ฌ๋ถ ๋ฑ์ ์
๋ ฅ๋ฐ์ต๋๋ค.
```groovy
pipeline {
agent any
parameters {
choice(name: 'ENVIRONMENT', choices: ['dev', 'staging', 'prod'], description: '๋ฐฐํฌํ ๋์ ํ๊ฒฝ')
string(name: 'GIT_BRANCH', defaultValue: 'main', description: '์ฒดํฌ์์ํ Git ๋ธ๋์น ๋๋ ํ๊ทธ')
booleanParam(name: 'RUN_MIGRATION', defaultValue: false, description: 'DB ๋ง์ด๊ทธ๋ ์ด์
์คํ ์ฌ๋ถ')
}
stages {
stage('ํ๋ผ๋ฏธํฐ ํ์ธ') {
steps {
echo "์ ํํ ๋์ ํ๊ฒฝ: ${params.ENVIRONMENT}"
echo "๋น๋ ๋์ ๋ธ๋์น: ${params.GIT_BRANCH}"
echo "DB ๋ง์ด๊ทธ๋ ์ด์
์ ์ฉ: ${params.RUN_MIGRATION}"
}
}
}
}
```
### ๐ฆ 4. ์คํ ์กฐ๊ฑด๋ฌธ (When)
ํน์ ๋ธ๋์น์ผ ๋๋ง ๋ฐฐํฌํ๊ฑฐ๋, ํ๋ผ๋ฏธํฐ ์กฐ๊ฑด์ ๋ฐ๋ผ Stage๋ฅผ ๊ฑด๋๋๋๋ค.
```groovy
stage('์ด์ ์๋ฒ ๋ฐฐํฌ') {
// main ๋ธ๋์น์ด๊ณ ENVIRONMENT ํ๋ผ๋ฏธํฐ๊ฐ 'prod'์ผ ๋๋ง ์ด ๋จ๊ณ๋ฅผ ์คํ
when {
allOf {
branch 'main'
environment name: 'ENVIRONMENT', value: 'prod'
}
}
steps {
echo '์ด์ ์๋ฒ(Production) ๋ฐฐํฌ ์คํฌ๋ฆฝํธ๋ฅผ ์คํํฉ๋๋ค.'
sh './scripts/deploy-prod.sh'
}
}
```
### โก 5. ๋ณ๋ ฌ ์คํ (Parallel)
๋
๋ฆฝ์ ์ธ ์์
(๋จ์ ํ
์คํธ, ์ ์ ์ฝ๋ ๋ถ์, E2E ํ
์คํธ)์ ๋์์ ๋ณ๋ ฌ๋ก ์คํํ์ฌ ๋น๋ ์๊ฐ์ ํ๊ธฐ์ ์ผ๋ก ๋จ์ถํฉ๋๋ค.
```groovy
stage('ํ์ง ๊ฒ์ฆ (๋ณ๋ ฌ ์คํ)') {
parallel {
stage('๋จ์ ํ
์คํธ') {
steps {
echo 'JUnit ๋จ์ ํ
์คํธ ์คํ...'
sh './gradlew test'
}
}
stage('์ ์ ์ฝ๋ ๋ถ์') {
steps {
echo 'SonarQube ํ์ง ๊ฒ์ดํธ ๊ฒ์ฆ...'
sh './gradlew sonar'
}
}
stage('๋ณด์ ์ทจ์ฝ์ ์ ๊ฒ') {
steps {
echo 'Trivy ์ปจํ
์ด๋ ์ทจ์ฝ์ ์ค์บ...'
sh 'trivy fs --severity HIGH,CRITICAL .'
}
}
}
}
```
---
## 5. [์ด๋ณด์ 3๋จ๊ณ] ์ค๋ฌด ์์ฉ ํจํด & ํ๋ก๋์
ํ์ดํ๋ผ์ธ
์ค๋ฌด ์๋น์ค์์ ์ฌ์ฉํ๋ Docker ์ปจํ
์ด๋ ์์ด์ ํธ ๊ธฐ๋ฐ ๋น๋, ์น์ธ ํ ๋ฐฐํฌ, ์ฌ๋ ์๋ฆผ์ ํฌํจํ ์์ ํ `Jenkinsfile` ์์ ์
๋๋ค.
### ๐ ์ค์ ํ๋ก๋์
`Jenkinsfile` (๋ณต์ฌํด์ ๋ฐ๋ก ์ฌ์ฉ ๊ฐ๋ฅ)
```groovy
pipeline {
// ๋ชจ๋ Stage๋ฅผ Docker ์ปจํ
์ด๋ ๊ฒฉ๋ฆฌ ํ๊ฒฝ์์ ๋น๋
agent {
docker {
image 'gradle:8.5-jdk17-alpine'
args '-v /root/.gradle:/root/.gradle' // Gradle ์ข
์์ฑ ์บ์ฑ ๋ง์ดํธ
}
}
options {
// ์ต๊ทผ 10๊ฐ ๋น๋๋ง ๋ณด๊ดํ์ฌ ๋์คํฌ ์ฉ๋ ๊ณ ๊ฐ ๋ฐฉ์ง (๋งค์ฐ ์ค์)
buildDiscarder(logRotator(numToKeepStr: '10', artifactNumToKeepStr: '5'))
disableConcurrentBuilds() // ๋์ผ ์ก ๋์ ์คํ ๋ฐฉ์ง
timeout(time: 30, unit: 'MINUTES')
timestamps()
}
environment {
DOCKER_REGISTRY = 'registry.luckytech.world'
IMAGE_NAME = 'lucky-service'
IMAGE_TAG = "${env.BUILD_NUMBER}-${env.GIT_COMMIT.take(7)}"
DOCKER_CRED_ID = 'harbor-registry-creds'
}
stages {
stage('์ํํธ์จ์ด ๋น๋') {
steps {
echo "=== 1. Gradle ๋น๋ ๋ฐ ์ํฐํฉํธ ์ปดํ์ผ ==="
sh 'gradle clean build -x test --no-daemon'
}
}
stage('๋จ์ ํ
์คํธ & ๋ณด๊ณ ์ ์์ง') {
steps {
echo "=== 2. ๋จ์ ํ
์คํธ ์คํ ๋ฐ ๊ฒฐ๊ณผ ๋ฆฌํฌํ
==="
sh 'gradle test --no-daemon'
}
post {
always {
// JUnit ํ
์คํธ ๊ฒฐ๊ณผ XML์ ์์งํ์ฌ Jenkins ๋์๋ณด๋ ๊ทธ๋ํ๋ก ํํ
junit allowEmptyResults: true, testResults: '**/build/test-results/**/*.xml'
}
}
}
stage('Docker ์ด๋ฏธ์ง ๋น๋ & ๋ ์ง์คํธ๋ฆฌ ํธ์') {
// Docker ๋ฐ๋ชฌ ๋ช
๋ น์ด๋ฅผ ์คํํ๊ธฐ ์ํด ํธ์คํธ ๋
ธ๋ ์์ด์ ํธ ์ ํ
agent any
steps {
echo "=== 3. ๋์ปค ์ด๋ฏธ์ง ๋น๋: ${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} ==="
script {
docker.withRegistry("https://${DOCKER_REGISTRY}", DOCKER_CRED_ID) {
def customImage = docker.build("${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}", ".")
customImage.push()
customImage.push("latest")
}
}
}
}
stage('๊ฐ๋ฐ ํ๊ฒฝ(Dev) ์๋ ๋ฐฐํฌ') {
agent any
steps {
echo "=== 4. ๊ฐ๋ฐ ์ฟ ๋ฒ๋คํฐ์ค ํด๋ฌ์คํฐ ๋กค์์ ==="
sh """
kubectl set image deployment/lucky-service lucky-service=${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} -n dev
kubectl rollout status deployment/lucky-service -n dev --timeout=2m
"""
}
}
stage('์ด์ ๋ฐฐํฌ ๊ด๋ฆฌ์ ์น์ธ') {
// main ๋ธ๋์น์ผ ๋๋ง ์น์ธ ๋จ๊ณ ํ์ฑํ
when {
branch 'main'
}
agent none
steps {
// ๊ด๋ฆฌ์๊ฐ ์น UI์์ [์น์ธ(Proceed)] ๋ฒํผ์ ํด๋ฆญํ ๋๊น์ง ํ์ดํ๋ผ์ธ ๋๊ธฐ
input message: "์ด์ ์๋ฒ(Prod)๋ก [${IMAGE_TAG}] ๋ฒ์ ์ ๋ฐฐํฌํ์๊ฒ ์ต๋๊น?", ok: '์น์ธ ๋ฐ ๋ฐฐํฌ'
}
}
stage('์ด์ ํ๊ฒฝ(Prod) ๋ฐฐํฌ') {
when {
branch 'main'
}
agent any
steps {
echo "=== 5. ์ด์ ์ฟ ๋ฒ๋คํฐ์ค ํด๋ฌ์คํฐ ๋ฌด์ค๋จ ๋ฐฐํฌ ==="
sh """
kubectl set image deployment/lucky-service lucky-service=${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} -n prod
kubectl rollout status deployment/lucky-service -n prod --timeout=5m
"""
}
}
}
post {
always {
// ์ํฌ์คํ์ด์ค ์ ๋ฆฌ๋ก ๋์คํฌ ์ฉ๋ ํ๋ณด
cleanWs()
}
success {
echo "๐ [${JOB_NAME} #${BUILD_NUMBER}] ํ์ดํ๋ผ์ธ์ด ์ฑ๊ณต์ ์ผ๋ก ์๋ฃ๋์์ต๋๋ค."
// ์ฌ๋ ์๋ฆผ ์ฐ๋ ์์:
// slackSend channel: '#deploy-alerts', color: 'good', message: "SUCCESS: ${JOB_NAME} #${BUILD_NUMBER} ๋ฐฐํฌ ์๋ฃ (<${BUILD_URL}|๋ก๊ทธ ํ์ธ>)"
}
failure {
echo "๐จ [${JOB_NAME} #${BUILD_NUMBER}] ๋น๋ ๋๋ ๋ฐฐํฌ์ ์คํจํ์์ต๋๋ค!"
// slackSend channel: '#deploy-alerts', color: 'danger', message: "FAILED: ${JOB_NAME} #${BUILD_NUMBER} ์ค๋ฅ ๋ฐ์ (<${BUILD_URL}|์์ธ ํ์ธ>)"
}
}
}
```
---
## 6. ์ค๋ฌด ์ฃผ์์ฌํญ & ํ๋ก๋์
์ฒดํฌ๋ฆฌ์คํธ
### โ ๏ธ 1. ์ด๋ณด์๊ฐ ๊ฐ์ฅ ํํ ๊ฒช๋ ์ค์ & ํด๊ฒฐ๋ฒ
| ๋ฒํธ | ํํ ์๋ฌ ๋ฐ ์ฆ์ | ๊ทผ๋ณธ ์์ธ | ํด๊ฒฐ ๋ฐฉ๋ฒ |
| :---: | :--- | :--- | :--- |
| **1** | `No space left on device` (๋์คํฌ ๊ฐ๋ ์ฐธ) | ์ด์ ๋น๋ ๊ธฐ๋ก๊ณผ ์์
๊ณต๊ฐ(workspace) ํ์ผ์ด ๋ฌดํ์ ๋์ ๋จ | ํ์ดํ๋ผ์ธ ์๋จ์ `options { buildDiscarder(logRotator(numToKeepStr: '10')) }` ์ค์ ๋ฐ `post { always { cleanWs() } }` ํ์ ์ ์ฉ |
| **2** | `Got permission denied while trying to connect to Docker daemon socket` | Jenkins ์คํ ์ฌ์ฉ์๊ฐ `/var/run/docker.sock` ์ ๊ทผ ๊ถํ์ด ์์ | `sudo chmod 666 /var/run/docker.sock` ์์ ํด๊ฒฐ ๋๋ `sudo usermod -aG docker jenkins` ํ Jenkins ์ฌ์์ |
| **3** | Controller ๋
ธ๋์์ ์ง์ ๋น๋ ์คํ์ผ๋ก Jenkins ์น UI ๋ฉ์ถค | ๋ถ์ฐ ์์ด์ ํธ ๋ฏธ๊ตฌ์ฑ ์ํ์์ Controller ๋
ธ๋์ CPU/๋ฉ๋ชจ๋ฆฌ ๊ณ ๊ฐ | `Jenkins ๊ด๋ฆฌ` โ `Nodes`์์ `Built-In Node`์ ์คํ์(Executors) ์๋ฅผ `0`์ผ๋ก ์ง์ ํ์ฌ ๋น๋ ์ ์ฉ ์์ด์ ํธ๋ก๋ง ์คํ ์ ๋ |
| **4** | Credentials ํ ํฐ์ด ์ฝ์ ๋ก๊ทธ์ ๋
ธ์ถ๋จ | ํ๋ฌธ ํ๊ฒฝ๋ณ์๋ก ์ฃผ์
ํ๊ฑฐ๋ `sh` ๋ช
๋ น์์ echo๋ก ์ถ๋ ฅ | `withCredentials` ๋๋ `credentials('id')`๋ฅผ ์ฌ์ฉํ๋ฉด Jenkins๊ฐ ์ฝ์ ์ถ๋ ฅ ์ `****`๋ก ์๋ ๋ง์คํนํจ |
| **5** | ํ๋ฌ๊ทธ์ธ ์
๋ฐ์ดํธ ํ Jenkins ๋จนํต ํ์ | ํธํ์ฑ ๊ฒ์ฆ๋์ง ์์ ๋ฉ์ด์ ํ๋ฌ๊ทธ์ธ ์ผ๊ด ์
๋ฐ์ดํธ | Jenkins LTS ๋ฒ์ ์
๊ทธ๋ ์ด๋ ์ `/var/jenkins_home` ์ ์ฒด ๋ณผ๋ฅจ ์ค๋
์ท ๋ฐฑ์
ํ์ |
### ๐ก๏ธ 2. ํ๋ก๋์
๋ณด์ & ์ด์ ๊ถ์ฅ ์ฒดํฌ๋ฆฌ์คํธ
1. **CSRF Protection & ์ฌ์ฉ์ ์ธ์ฆ ํ์ฑํ**: ์ต๋ช
์ฌ์ฉ์(Anonymous)์ ์ฝ๊ธฐ ๊ถํ์ ๊ธฐ๋ณธ ๋นํ์ฑํํ๊ณ , Role-based Authorization Strategy ํ๋ฌ๊ทธ์ธ์ผ๋ก RBAC ๊ถํ์ ๊ตฌ์ฑํฉ๋๋ค.
2. **๋ค์ค ๋ธ๋์น ํ์ดํ๋ผ์ธ(Multibranch Pipeline)**: ๊ฐ๋ณ ์ก ๋์ Multibranch Pipeline์ ์ฌ์ฉํ๋ฉด, ์ ๊ท PR์ด๋ ๋ธ๋์น๊ฐ ์์ฑ๋ ๋ ์๋์ผ๋ก Jenkinsfile์ ๊ฐ์งํ์ฌ ๋น๋/ํ
์คํธ๋ฅผ ์ํํฉ๋๋ค.
3. **Jenkinsfile์ ๋ฒ์ ๊ด๋ฆฌ**: UI ๋ด๋ถ ์คํฌ๋ฆฝํธ(Pipeline script) ์์ฑ ๋ฐฉ์์ ์ง์ํ๊ณ , ๋ฐ๋์ Git ์ ์ฅ์ ์ต์๋จ `Jenkinsfile`๋ก ํ์ ๊ด๋ฆฌ(SCM)๋ฅผ ์ํํฉ๋๋ค.
4. **ํ์์์(Timeout) ํ์ ์ง์ **: ๋คํธ์ํฌ ์ง์ฐ์ด๋ ๋๊ธฐ ์ํ๋ก ์ธํด ๋น๋๊ฐ ์๊ตฌ ์ ์ฒด๋๋ ํ์์ ๋ฐฉ์งํ๊ธฐ ์ํด ์ ์ฒด ํ์ดํ๋ผ์ธ ๋ฐ ๋จ๊ณ๋ณ `timeout`์ ๋ช
์ํฉ๋๋ค.
### ๐ 3. Declarative Pipeline ํต์ฌ ์ง์์ด ๋น ๋ฅธ ์์ฝ
```groovy
pipeline {
agent { label 'linux-node' } // ๋น๋ ์คํ ๋
ธ๋ ๋ผ๋ฒจ ์ง์
environment { KEY = 'VALUE' } // ์ ์ญ ํ๊ฒฝ๋ณ์ ์ง์
options { timeout(30); timestamps() } // ํ์์์ ๋ฐ ํ์์คํฌํ
triggers { cron('H 2 * * *') } // ๋งค์ผ ์๋ฒฝ 2์ ์ ๊ธฐ ์๋ ๋น๋
parameters { string(name: 'TAG') } // ์๋ ๋น๋ ํ๋ผ๋ฏธํฐ ์์
stages {
stage('Step 1') {
when { branch 'release/*' } // ๋ฆด๋ฆฌ์ฆ ๋ธ๋์น ์กฐ๊ฑด๋ถ ์คํ
steps { sh './deploy.sh' } // ์
ธ ์คํฌ๋ฆฝํธ ์คํ
}
}
post {
failure { echo '์๋ฆผ ์ ์ก' } // ์คํจ ์ ์คํ ๋ธ๋ก
}
}
```
์๊ฒฌ ๋ฐ ์ง๋ฌธ
0์์ง ๋ฑ๋ก๋ ์๊ฒฌ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋๊ธ์ ๋จ๊ฒจ๋ณด์ธ์!
๋๊ธ ์์
๋๊ธ ์ญ์