Jenkins 파이프라인

Jenkins를 사용한 CI/CD 파이프라인 구축과 관리


Jenkins를 사용한 CI/CD 파이프라인 구축과 관리

목표: 프로덕션급 Jenkins 파이프라인을 구축하고 유지보수할 수 있는 수준


🎯 Jenkins 개요

아키텍처

graph TB
    subgraph "Jenkins"
        MASTER["🎛️ Jenkins Master"] -->|"스케줄링"| AGENT1["💻 Agent 1"]
        MASTER -->|"스케줄링"| AGENT2["💻 Agent 2"]
        
        subgraph "Master"
            UI["🌐 Web UI"]
            CONFIG["⚙️ 설정"]
            QUEUE["📋 Job Queue"]
        end
    end
    
    GIT["📁 Git"] -->|"Webhook"| MASTER
    AGENT1 -->|"빌드"| ARTIFACT["📦 Artifacts"]

🛠️ 설치

Docker로 설치

# Jenkins 실행
docker run -d \
  --name jenkins \
  -p 8080:8080 \
  -p 50000:50000 \
  -v jenkins_home:/var/jenkins_home \
  -v /var/run/docker.sock:/var/run/docker.sock \
  jenkins/jenkins:lts

# 초기 비밀번호
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword

Kubernetes로 설치

# Helm 설치
helm repo add jenkins https://charts.jenkins.io
helm install jenkins jenkins/jenkins \
  --set controller.serviceType=LoadBalancer

# 비밀번호
kubectl exec --namespace default -it svc/jenkins -c jenkins \
  -- /bin/cat /run/secrets/additional/chart-admin-password

📝 Pipeline 작성

Declarative Pipeline

// Jenkinsfile
pipeline {
    agent any
    
    environment {
        DOCKER_REGISTRY = 'myregistry.com'
        IMAGE_NAME = 'myapp'
        VERSION = sh(script: 'git describe --tags --always', returnStdout: true).trim()
    }
    
    options {
        buildDiscarder(logRotator(numToKeepStr: '10'))
        disableConcurrentBuilds()
        timeout(time: 30, unit: 'MINUTES')
    }
    
    triggers {
        pollSCM('H/5 * * * *')
        // 또는 webhook
    }
    
    stages {
        stage('Checkout') {
            steps {
                checkout scm
                sh 'git log -1'
            }
        }
        
        stage('Build') {
            steps {
                sh 'npm ci'
                sh 'npm run build'
            }
        }
        
        stage('Test') {
            parallel {
                stage('Unit Tests') {
                    steps {
                        sh 'npm run test:unit'
                    }
                    post {
                        always {
                            publishTestResults testResultsPattern: 'coverage/*.xml'
                        }
                    }
                }
                stage('Integration Tests') {
                    steps {
                        sh 'npm run test:integration'
                    }
                }
            }
        }
        
        stage('Code Quality') {
            steps {
                withSonarQubeEnv('SonarQube') {
                    sh 'sonar-scanner'
                }
            }
        }
        
        stage('Security Scan') {
            steps {
                sh 'trivy fs --exit-code 0 --no-progress .'
            }
        }
        
        stage('Build Docker Image') {
            steps {
                script {
                    docker.build("${DOCKER_REGISTRY}/${IMAGE_NAME}:${VERSION}")
                }
            }
        }
        
        stage('Push Docker Image') {
            when {
                branch 'main'
            }
            steps {
                script {
                    docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry-credentials') {
                        docker.image("${DOCKER_REGISTRY}/${IMAGE_NAME}:${VERSION}").push()
                        docker.image("${DOCKER_REGISTRY}/${IMAGE_NAME}:${VERSION}").push('latest')
                    }
                }
            }
        }
        
        stage('Deploy to Staging') {
            when {
                branch 'develop'
            }
            steps {
                sh '''
                    kubectl config use-context staging
                    kubectl set image deployment/myapp myapp=${DOCKER_REGISTRY}/${IMAGE_NAME}:${VERSION}
                    kubectl rollout status deployment/myapp
                '''
            }
        }
        
        stage('Deploy to Production') {
            when {
                branch 'main'
            }
            steps {
                input message: 'Deploy to Production?', ok: 'Deploy'
                sh '''
                    kubectl config use-context production
                    kubectl set image deployment/myapp myapp=${DOCKER_REGISTRY}/${IMAGE_NAME}:${VERSION}
                    kubectl rollout status deployment/myapp
                '''
            }
        }
    }
    
    post {
        always {
            cleanWs()
        }
        success {
            slackSend(color: 'good', message: "Build succeeded: ${env.JOB_NAME} #${env.BUILD_NUMBER}")
        }
        failure {
            slackSend(color: 'danger', message: "Build failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}")
        }
    }
}

Scripted Pipeline

// 더 유연한 Scripted Pipeline
node('docker-agent') {
    try {
        stage('Checkout') {
            checkout scm
        }
        
        stage('Build') {
            docker.image('node:18').inside {
                sh 'npm ci'
                sh 'npm run build'
            }
        }
        
        stage('Test') {
            docker.image('node:18').inside {
                sh 'npm test'
            }
        }
        
        if (env.BRANCH_NAME == 'main') {
            stage('Deploy') {
                sh 'kubectl apply -f k8s/'
            }
        }
    } catch (e) {
        currentBuild.result = 'FAILURE'
        throw e
    } finally {
        cleanWs()
    }
}

🔧 고급 기능

Shared Library

// vars/buildDocker.groovy
def call(Map config) {
    def image = docker.build("${config.registry}/${config.image}:${config.tag}")
    
    if (config.push) {
        docker.withRegistry("https://${config.registry}", config.credentials) {
            image.push()
        }
    }
    
    return image
}
// Jenkinsfile
@Library('my-shared-library') _

pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                buildDocker(
                    registry: 'myregistry.com',
                    image: 'myapp',
                    tag: 'v1.0.0',
                    push: true,
                    credentials: 'docker-registry-credentials'
                )
            }
        }
    }
}

멀티브랜치 파이프라인

// Jenkinsfile (각 브랜치에)
pipeline {
    agent any
    
    stages {
        stage('Build') {
            steps {
                sh 'make build'
            }
        }
    }
    
    when {
        anyOf {
            branch 'main'
            branch 'develop'
            branch pattern: 'release/.*', comparator: 'REGEXP'
        }
    }
}

📋 체크리스트

  • Jenkins 설치
  • Pipeline 작성
  • Declarative vs Scripted
  • 환경변수 설정
  • 병렬 실행
  • 조건적 실행
  • Docker 빌드/푸시
  • Kubernetes 배포
  • Shared Library
  • 알림 설정
  • 보안 설정

🔗 관련 노트

  • 01-01-CI-CD-개념 — CI/CD 개념
  • 02-02-GitHub-Actions — GitHub Actions
  • 01-03-도커-기본 — Docker

마지막 업데이트: 2026-06-01