20250824 TIL - [DevOps & Tools] Jenkins Pipeline 구조와 주요 기능 정리

myo·2025년 8월 24일

DevOps & Tools

목록 보기
2/2
post-thumbnail

TIL

Jenkins Pipeline은 Jenkinsfile로 빌드·테스트·배포 단계를 코드화하여 자동화하며, UI는 실행과 모니터링 용도로 사용된다. Parameters와 Stage 조건 제어로 환경별 유연한 빌드가 가능하며, SCM 기반 파이프라인은 Git으로 버전 관리와 협업을 지원한다. Stage별 흐름은 Build Start → Workspace Cleanup → Git Clone → Build → Docker 배포 → Post Actions 순으로 진행된다.


Jenkins 파이프라인 구성과 주요 기능 상세 정리

1. Jenkins 대시보드 주요 기능

Jenkins 대시보드에서 특정 Job을 클릭하면 다양한 메뉴와 상태 정보를 확인할 수 있습니다.

1.1 Status Changes

  • 정의: 최근 빌드 실행에서 변경된 Job/Stage 상태를 표시.

  • 용도: 최근 빌드 결과의 변화(성공 → 실패, 실패 → 성공, 불안정 상태 등)를 빠르게 확인 가능.

  • 중요성:

    	- 빌드 상태 변화를 시간 순으로 나열해 줌  
    
    	- 배포 파이프라인의 안정성을 모니터링.
    
    	- 문제 발생 시 언제부터 실패했는지 추적 가능.

1.2 Parameters (빌드 파라미터)

  • 정의: 빌드 실행 시 사용자 입력 (동적으로 변수를 입력)에 따라 빌드 동작을 제어하는 값.

  • 종류:

    	1. Boolean Parameter (체크박스)
    
    		- 예: USE_GIT, USE_BUILD, USE_DEPLOY_CORE
    
    		- 체크 여부에 따라 해당 Stage 실행 여부 결정.
    
    	2. Choice Parameter (드롭다운 선택)
    
    		- 예: GIT_BRANCH, SERVICE_NAME, SERVICE_ENV
    
    		- 특정 환경, 브랜치, 서비스 선택 시 사용.
    
    	3. String Parameter (문자열 입력)
    
    		- 예: ROOT_PATH, PROFILE
    
    		- 빌드나 배포 시 필요한 경로, 프로파일명 입력.
  • 중요성:

    	- 개발/운영 서버 분기 배포 시 환경 변수로 사용 
    
    	- 파이프라인의 실행 흐름을 유연하게 제어.
    
    	- 불필요한 Stage 실행을 건너뛰어 빌드 시간 단축.

1.3 Full Stage View

  • 정의: 각 Stage의 실행 이력, 평균 소요 시간, 성공/실패 내역을 한눈에 보여주는 뷰.

  • 주요 요소:

    	- Stage별 실행 순서와 시간.
    
    	- 빌드 히스토리별 Stage 상태.
    
    	- Average Stage Times: 평균 소요 시간 표시.
    
    	- Average Full Run Time: 전체 빌드 평균 소요 시간.
  • 중요성:

    	- 대규모 파이프라인 운영 시 단계별 성능 및 병목 현상 파악  
    
    	- 병목 Stage의 최적화 가능성 확인.
    • 실패 시점 스테이지 로그를 즉시 확인하여 빠른 대응

      - 실패 빈도가 높은 Stage 식별.

2. 커스텀 파라미터 상세

Jenkins 기본 파라미터가 아닌, 프로젝트별로 커스터마이징된 빌드 옵션입니다.

Jenkins Pipeline에서 parameters 블록은 빌드 실행 시 사용자 입력을 받을 수 있는 기능을 제공합니다.
즉, 빌드를 실행할 때 특정 값을 동적으로 입력받아 파이프라인 내에서 활용할 수 있습니다.

Boolean Parameters

파라미터명설명
USE_UPDATE_PIPELINE체크 시 파이프라인 업데이트, 다른 Stage 무시
USE_DELETE_WORKSPACE작업 전 Workspace 삭제 여부
USE_GITGit Clone 단계 실행 여부
USE_BUILDGradle 빌드 실행 여부
USE_DEPLOY_CORECORE 서비스 배포 여부
USE_BACKUPDocker 백업 실행 여부
USE_PUSHDocker 이미지 Push 여부

Choice Parameters

파라미터명선택값 예시설명
GIT_BRANCHdevGit 브랜치 선택
SERVICE_NAMEdev/billi서비스 이름 지정
SERVICE_ENVdev서비스 환경 지정

String Parameters

파라미터명기본값설명
ROOT_PATH/appDocker App 홈 경로
PROFILEbilli-dev애플리케이션 실행 프로파일

2.1 Jenkins에서 어떻게 보일까?

  • Jenkins 웹 UI에서 Build with Parameters 메뉴가 활성화됩니다.

  • 여기서 choice 파라미터는 드롭다운 리스트로 표시됩니다.

  • string, booleanParam, text 등 다른 파라미터들은 각각 입력창이나 체크박스로 표시됩니다.

즉, parameters 블록을 선언하면 Jenkins UI에 자동으로 반영되어 빌드 시점에 입력값을 받을 수 있게 됩니다.


3. Pipeline이란?

  • Jenkins Pipeline = 자동화된 빌드·테스트·배포 흐름(워크플로우)
    즉, 코드 빌드 → 테스트 → 이미지 빌드 → 배포 같은 과정을 단계별로 연결해 자동으로 수행하는 것.

  • 코드화된 정의 → Groovy DSL(Jenkinsfile)

  • 목적: CI/CD 자동화, 반복 가능하고 안정적인 배포

유사 개념: GitHub Actions, GitLab CI의 .yml 파일

3.1 Core와 FO 파이프라인 차이

Core (백엔드)

  • 서버 애플리케이션: API, DB 연동, 비즈니스 로직 포함

  • 빌드/배포 과정 복잡 → 여러 Stage 필요

    	- 예: Build Start → Delete Workspace → Git Clone → Build Gradle → Docker Push → Docker Run → Post Actions
  • 평균 빌드 시간: 상대적으로 길고 단계가 많음

FO (프론트엔드)

  • SPA 기반 프론트엔드: React, Vue 등

  • 빌드 과정 단순 → Stage도 짧음

    	- 예: Build Start → Delete Workspace → Git Clone → Docker Push → Docker Run → Post Actions
  • 평균 빌드 시간: 짧음

  • 이유: 정적 빌드 후 Docker 이미지 배포만 진행

즉, Core는 서버 애플리케이션이라 무겁고 단계가 많고, FO는 정적 빌드 후 이미지 배포라 가볍다 보면 됩니다.

4. SCM 기반 파이프라인 설정

파이프라인 스크립트를 Jenkins UI에서 직접 작성하지 않고, Git 저장소에서 가져오는 방식입니다.

4.1 설정 구성

  • SCM: Git

  • Repository URL:
    예) https://gitlab.com/pluggersoft/pet_core.git

  • Credentials:
    Private Repo 접근을 위한 계정/토큰/SSH 키.

  • Branches to build:
    */dev → dev 브랜치 사용.

  • Script Path:
    DevOps/Pipeline/billi-dev-api-container.Jenkinsfile
    (Jenkinsfile이 루트가 아닌 하위 폴더에 있는 경우 경로 지정 필요)

  • Lightweight checkout:
    Jenkinsfile만 빠르게 가져오는 옵션.


4.2 동작 방식

  1. Jenkins Job 실행 → 설정된 Git 저장소 접속.

  2. 지정된 브랜치에서 Script Path 위치의 Jenkinsfile 읽기.

  3. Jenkinsfile에 정의된 Stage 순서대로 실행.


4.3 Jenkinsfile 예제

def dockerCredentials = "****"
def imageName = "****/dev-api"

pipeline {
    agent { label "billiAgent1" }
    options {
        timestamps()
        timeout(time: 30, unit: "MINUTES")
        skipDefaultCheckout()
        buildDiscarder(logRotator(
                numToKeepStr: '5',
                daysToKeepStr: '3'
        ))
        disableConcurrentBuilds()
    }
    parameters {
        booleanParam(name: "USE_UPDATE_PIPELINE", defaultValue: false, description: "Update Pipeline (checked ignore other stage )")
        booleanParam(name: "USE_DELETE_WORKSPACE", defaultValue: true, description: "작업전 Workspace 삭제")
        booleanParam(name: "USE_GIT", defaultValue: true, description: "Git Clone")
        booleanParam(name: "USE_BUILD", defaultValue: true, description: "Build Gradle")
        booleanParam(name: "USE_DEPLOY_CORE", defaultValue: true, description: "Deploy CORE")
        booleanParam(name: "USE_BACKUP", defaultValue: false, description: "Docker Backup")

        choice(name: "GIT_BRANCH", choices: ["dev"], description: "git branch")
        choice(name: "SERVICE_NAME", choices: ["dev/billi"], description: "서비스 이름")
        choice(name: "SERVICE_ENV", choices: ["dev"], description: "서비스 환경")

        string(name: "ROOT_PATH", defaultValue: "/app", description: "Docker App Home")
        string(name: "PROFILE", defaultValue: "billi-dev", description: "")
    }

    stages {

        stage("Build Start") {
            steps {
                script {
                    slackSend(
                            channel: "****",
                            color: "#50BFA0",
                            tokenCredentialId: "****",
                            message: "[Dev 빌드/배포] \n Build : API(#${BUILD_ID}) \n Build Start ",
                    )
                }
            }
        }

        stage("Delete Workspace") {
            when {
                expression { return !params.USE_UPDATE_PIPELINE && params.USE_DELETE_WORKSPACE }
            }
            steps {
                deleteDir()
            }
        }

        stage("Check Validation") {
            steps {
                sh("printenv|sort")
            }
        }

        stage("Git Clone") {
            when {
                expression { return !params.USE_UPDATE_PIPELINE && params.USE_GIT }
            }
            steps {
                git(
                        url: "****",
                        branch: "${params.GIT_BRANCH}",
                        credentialsId: "****"
                )
            }
        }

        stage("Build Gradle") {
            when {
                expression { return !params.USE_UPDATE_PIPELINE && params.USE_BUILD }
            }
            steps {
                sh("chmod +x ./gradlew && ./gradlew clean installBootDist -x test")
            }
        }

        stage("Docker Push") {
            when {
                allOf {
                    expression { return !params.USE_UPDATE_PIPELINE && params.USE_PUSH }
                }
            }
            steps {
                sh("cp ./DevOps/Container/latest.Dockerfile ./Dockerfile")
                script {
                    withCredentials([usernamePassword(credentialsId: dockerCredentials, usernameVariable: 'ACCESS_KEY', passwordVariable: 'SECRET_KEY')]) {
                        script {
                            sh '''echo $SECRET_KEY | docker login -u $ACCESS_KEY --password-stdin'''

                            def image = docker.build("${imageName}:${BUILD_ID}",
                                    "--build-arg SERVICE_NAME=${SERVICE_NAME} --build-arg ROOT_PATH=${ROOT_PATH} --build-arg PROFILE=${PROFILE} --build-arg SERVICE_ENV=${SERVICE_ENV} .")
                            image.push()
                            image.push("latest")
                        }
                    }
                }
            }
        }

        stage("Docker Pull") {
            when {
                expression { return !params.USE_UPDATE_PIPELINE && params.USE_DEPLOY_CORE }
            }
            steps {
                withCredentials([usernamePassword(credentialsId: dockerCredentials, usernameVariable: 'ACCESS_KEY', passwordVariable: 'SECRET_KEY')]) {
                    script {
                        sh "docker pull ${imageName}:latest"
                    }
                }
            }
        }

        stage("Docker Stop") {
            when {
                expression { return !params.USE_UPDATE_PIPELINE && params.USE_DEPLOY_CORE }
            }
            steps {
                script {
                    sh "docker stop --time 120 api || true"
                    sh "docker rm api || true"
                }
            }
        }

        stage("Docker Run") {
            when {
                expression { return !params.USE_UPDATE_PIPELINE && params.USE_DEPLOY_CORE }
            }
            steps {
                script {
                    sh "docker run -d \
                      --name api \
                      --network=host \
                      -v /app/dev/files:/app/dev/billi/core/resource/files \
                      ${imageName}:latest"
                }
            }
        }

        stage("Docker Clean") {
            when {
                expression { return !params.USE_UPDATE_PIPELINE && params.USE_DEPLOY_CORE }
            }
            steps {
                script {
                    sh "docker system prune -af"
                }
            }
        }
    }

    post("Post") {
        always {
            echo "${currentBuild.result}"
        }
        success {
            echo "○ :)"
            script {
                slackSend(
                        channel: "****",
                        color: "#50BFA0",
                        tokenCredentialId: "****",
                        message: "[DEV 빌드/배포] \n Build : API(#${BUILD_ID}) \n Result : ${currentBuild.result}",
                )
            }
        }
        failure {
            echo "X :("
            script {
                slackSend(
                        channel: "****",
                        color: "#50BFA0",
                        tokenCredentialId: "****",
                        message: "[DEV 빌드/배포] \n Build : API(#${BUILD_ID}) \n Result : ${currentBuild.result}",
                )
            }
        }
        unstable {
            echo "△ :/"
            script {
                slackSend(
                        channel: "****",
                        color: "#50BFA0",
                        tokenCredentialId: "****",
                        message: "[DEV 빌드/배포] \n Build : API(#${BUILD_ID}) \n Result : ${currentBuild.result}",
                )
            }
        }
        changed {
            echo "□ :?"

        }
    }
}

SCM 기반 파이프라인을 Jenkinsfile로 한다는 건, Jenkins가 “직접 UI에서 설정한 단계”가 아니라 Git에 있는 스크립트(Jenkinsfile) 를 읽어서 그 안에 정의된 실행 순서를 그대로 따라간다는 의미입니다.


4.4 Jenkinsfile이 없으면 어떻게 되나?

SCM 기반에서 Script Path에 지정한 위치에 Jenkinsfile이 없으면 →
빌드 시작 시 "Jenkinsfile not found" 에러가 나면서 바로 실패합니다.

  • 이유:

    	- Jenkins는 해당 파일을 읽어서 Stage와 Steps를 파악해야 하는데, 파일이 없으면 실행 계획 자체를 못 만듭니다.
    
    	- SCM 기반 파이프라인은 Jenkinsfile이 설계도이자 실행 계획 역할을 합니다.

프리스타일 Job이나 UI 기반 Script 작성이라면 Jenkinsfile 없이도 가능하지만, SCM 모드에서는 필수


4.5 UI(직접 작성) vs Git(SCM 기반) 비교

구분UI 기반 (Pipeline script 직접 작성)Git(SCM 기반)
설정 위치Jenkins Job 설정 화면Git 저장소
버전 관리불가능 (변경 이력 Jenkins 내부에만 존재)Git commit 히스토리로 추적 가능
협업Jenkins 관리 권한 있는 사람만 수정 가능Git 권한 있는 사람은 누구나 수정 가능
변경 반영Jenkins UI에서 수동 수정Git push 시 자동 반영 (다음 빌드부터 적용)
안정성Jenkins 서버가 날아가면 설정 같이 소실Git에 저장되므로 Jenkins 재설치 시에도 복구 쉬움
테스트/리뷰변경 전 리뷰 불가PR/MR로 코드 리뷰 가능
복잡한 파이프라인UI에서 관리하기 불편코드로 관리 가능, 재사용 쉬움

개인/소규모 환경이라면 UI 기반도 문제 없지만 팀/운영 환경이라면 Git(SCM 기반)이 훨씬 유리 (버전 관리 + 협업 + 자동화).


5. Jenkinsfile 구조

5.1 Groovy 문법

  • Java 기반 스크립팅 언어

  • 조건문, 반복문, 변수 정의 가능

  • Jenkinsfile에서 파라미터, Stage 조건 제어 등에 활용

5.2 Global 변수

def dockerCredentials = "****"
def imageName = "****/dev-api"
  • 전체 파이프라인에서 사용되는 값 정의

5.3 Pipeline 블록

pipeline {
    agent { label "billiAgent1" }
    options { ... }
    parameters { ... }
    stages { ... }
    post { ... }
}
  • agent → Jenkins 노드 선택

  • options → 빌드 옵션 설정 (타임아웃, 동시 빌드 금지 등)

  • parameters → 빌드 시 입력값 설정

  • stages → 실제 빌드/배포 단계 정의

  • post → 빌드 종료 후 처리

5.4 Parameters

  • Boolean Parameter → Stage 실행 여부 토글
booleanParam(name: "USE_DELETE_WORKSPACE", defaultValue: true, description: "작업전 Workspace 삭제")
  • Choice Parameter → 제한된 옵션 선택
choice(name: "SERVICE_ENV", choices: ['dev','staging','prod'], description: '배포 환경 선택')
  • String Parameter → 자유 입력 가능

SCM 기반 파이프라인에서 Jenkinsfile의 parameters 정의 → Jenkins UI에 자동 반영
별도 UI에서 추가 설정 불필요

5.5 Options 설정

options {
    timestamps()
    timeout(time: 30, unit: "MINUTES")
    skipDefaultCheckout()
    buildDiscarder(logRotator(numToKeepStr: '5', daysToKeepStr: '3'))
    disableConcurrentBuilds()
}
  • timestamps() → 콘솔 로그 시간 표시

  • timeout → 제한 시간 초과 시 빌드 중단

  • skipDefaultCheckout() → 기본 Git 체크아웃 생략

  • buildDiscarder → 오래된 빌드 기록 자동 삭제

  • disableConcurrentBuilds() → 동시 빌드 금지

5.6 Stages

각 Stage는 steps 블록으로 실제 작업 실행

- 예: sh, echo, checkout scm, docker.build 등

Stage 예시

  1. Build Start → Slack 알림 전송

  2. Delete Workspace → 조건부 워크스페이스 삭제 (deleteDir())

  3. Git Clone → Git repo 체크아웃

  4. Build Gradle → Gradle 빌드 실행 (sh ./gradlew clean installBootDist)

  5. Docker Push / Pull / Run / Stop / Clean

    • Docker 이미지 빌드, 푸시, 컨테이너 배포/삭제

    • 예:

      sh '''echo $SECRET_KEY | docker login -u $ACCESS_KEY --password-stdin'''
      def image = docker.build("${imageName}:${BUILD_ID}", "--build-arg ... .")
      image.push()
      image.push("latest")
    • Post Actions → 빌드 종료 후 Slack 알림, 상태별 로그

Stage 조건 제어

when { expression { return !params.USE_UPDATE_PIPELINE && params.USE_BUILD } }
  • Jenkins 파라미터에 따라 Stage 실행 여부 결정

5.7 Post 블록

  • 빌드 종료 후 실행
post {
    always { echo "${currentBuild.result}" }
    success { slackSend(...) }
    failure { slackSend(...) }
    unstable { slackSend(...) }
    changed { echo "결과 변경" }
}
  • 상태별 알림, Slack 전송 등 자동화 가능

5.8 Jenkinsfile와 터미널 명령어

Jenkinsfile은 터미널에서 하던 명령어를 코드화한 것이라고 생각하면 쉽습니다.

예시:

sh '''
echo $SECRET_KEY | docker login -u $ACCESS_KEY --password-stdin
'''

def image = docker.build("${imageName}:${BUILD_ID}",
    "--build-arg SERVICE_NAME=${SERVICE_NAME} --build-arg ROOT_PATH=${ROOT_PATH} --build-arg PROFILE=${PROFILE} --build-arg SERVICE_ENV=${SERVICE_ENV} .")
image.push()
image.push("latest")
  • Jenkins가 빌드를 실행하면 자동으로 순서대로 실행

  • 수동 터미널 입력 불필요

  • 파이프라인 Stage마다 실행 로그 확인 가능

6. Stage별 의미와 동작 흐름

Full Stage View에서 확인한 Stage 목록은 Jenkinsfile(파이프라인 스크립트)에 정의된 실행 단계입니다.

예시 Stage 리스트를 분석하면 다음과 같습니다.

Stage 이름설명주요 작업
Build Start빌드 프로세스 시작 알림 단계초기화, 파라미터 로깅
Delete Workspace이전 빌드 산출물/임시 파일 삭제workspace 클린업
Check Validation빌드 전 유효성 검사환경 변수 체크, 필수 파일 존재 여부
Git CloneGit 저장소에서 코드 가져오기git clone, 브랜치 체크아웃
Build Gradle애플리케이션 빌드Gradle 빌드 수행
Docker PushDocker 이미지 빌드 및 레지스트리에 푸시docker build, docker push
Docker Pull원격 레지스트리에서 이미지 가져오기docker pull
Docker Stop기존 실행 중인 컨테이너 중지docker stop
Docker Run새로운 컨테이너 실행docker run
Docker Clean불필요한 이미지/컨테이너 삭제docker rm, docker rmi
Declarative: Post Actions파이프라인 종료 후 후속 작업알림, 로그 정리

7. 정리

  • 나머지 (Job 설정, Build history, 콘솔 로그 등) → 전부 빌드 진행 상황, 결과 확인, UI 상의 편의 기능

  • Jenkinsfile → 핵심 로직, CI/CD 파이프라인을 정의하는 “코드로 된 설계도”

즉, Jenkins의 UI는 어디까지나 Jenkinsfile을 실행하고 모니터링하는 도구이고,
실제로 어떤 단계(빌드, 테스트, 배포 등)를 수행할지, 어떤 환경변수와 파라미터를 사용할지는 전부 Jenkinsfile이 책임집니다.

그래서 실무에서는:

  • Jenkins UI 설정은 최소화 (Git repo 연결, 기본 Job 설정 정도)

  • Jenkinsfile에 빌드/테스트/배포 파이프라인 로직을 모두 정의

  • 변경 사항도 Jenkinsfile을 Git으로 관리해서 버전 추적

결국 Jenkinsfile이 CI/CD 파이프라인의 소스 코드이고, Jenkins는 그걸 실행하는 런타임 + 모니터링 도구라고 이해하면 가장 정확합니다.

7.1 Pipeline 실행 흐름

Pipeline 시작
   │
   ├─ Build StartSlack 알림
   │
   ├─ Delete Workspace (조건부)
   │
   ├─ Git Clone (조건부)
   │
   ├─ Build Gradle (조건부)
   │
   ├─ Docker Push (조건부)
   │
   ├─ Docker Pull / Stop / Run / Clean (조건부)
   │
   └─ Post ActionsSlack 알림 및 결과 처리
profile
풀스택 개발자로 성장해가기 위한 개발 지식과 문제 해결을 위해 직접 경험해본 내용을 기록합니다.

0개의 댓글