devops2023-08-01·9 min·343/348

Jenkins 파이프라인 빌드 실패 해결

Jenkins 파이프라인에서 발생하는 빌드 실패 문제를 진단하고 해결하는 방법을 알아봅니다.

Jenkins 파이프라인 빌드 실패 해결

Jenkins 파이프라인은 복잡한 CI/CD 워크플로우를 구현할 수 있지만, 다양한 원인으로 빌드가 실패할 수 있습니다.

Environment

$ java -version
openjdk version "17.0.9" 2023-10-17

$ jenkins --version
Jenkins 2.426.2

Problem: Pipeline Execution Failure

// Jenkinsfile
pipeline {
    agent any
    
    stages {
        stage('Build') {
            steps {
                sh 'python -m build'
            }
        }
        stage('Test') {
            steps {
                sh 'pytest tests/'
            }
        }
    }
}

에러 출력:

[Pipeline] sh
+ python -m build
ERROR: No module named build

[Pipeline] }
ERROR: script returned exit code 1

또는:

[Pipeline] End of Sandbox
java.io.IOException: script returned exit code 1
    at org.jenkinsci.plugins.workflow.cps.CpsStepContext$3.checkOutcome(CpsStepContext.java:463)

Analysis: Jenkinsfile 분석

# Jenkins 로그 확인
$ docker exec jenkins cat /var/log/jenkins/jenkins.log | tail -100

# Pipeline Syntax 확인
$ java -jar jenkins-cli.jar declarative-linter < Jenkinsfile

# Groovy 문법 검증
$ groovy -c Jenkinsfile

Solution: Pipeline 에러 해결

방법 1: Agent 및 환경 설정

// Jenkinsfile
pipeline {
    agent {
        docker {
            image 'python:3.11-slim'
            args '-v $HOME/.cache:/root/.cache'
        }
    }
    
    environment {
        PYTHONDONTWRITEBYTECODE = '1'
        PYTHONUNBUFFERED = '1'
    }
    
    stages {
        stage('Setup') {
            steps {
                sh 'pip install --upgrade pip'
                sh 'pip install -r requirements.txt'
            }
        }
        
        stage('Build') {
            steps {
                sh 'python -m build'
            }
        }
        
        stage('Test') {
            steps {
                sh 'pytest tests/ --junitxml=reports/junit.xml'
            }
        }
    }
    
    post {
        always {
            junit 'reports/junit.xml'
        }
    }
}

방법 2: Credentials 관리

pipeline {
    agent any
    
    environment {
        AWS_CREDENTIALS = credentials('aws-deployer')
        SONAR_TOKEN = credentials('sonar-token')
    }
    
    stages {
        stage('Build') {
            steps {
                withCredentials([
                    string(credentialsId: 'docker-hub-pass', variable: 'DOCKER_PASS')
                ]) {
                    sh 'echo $DOCKER_PASS | docker login -u username --password-stdin'
                }
            }
        }
        
        stage('Deploy') {
            steps {
                withAWS(credentials: 'aws-deployer', region: 'eu-west-1') {
                    sh 'aws s3 sync dist/ s3://my-bucket/'
                }
            }
        }
    }
}

방법 3: Parallel Execution

pipeline {
    agent any
    
    stages {
        stage('Build') {
            parallel {
                stage('Unit Tests') {
                    steps {
                        sh 'pytest tests/unit/ --junitxml=unit.xml'
                    }
                }
                stage('Integration Tests') {
                    steps {
                        sh 'pytest tests/integration/ --junitxml=integration.xml'
                    }
                }
                stage('Lint') {
                    steps {
                        sh 'flake8 src/'
                    }
                }
            }
        }
    }
    
    post {
        always {
            junit '**/*.xml'
            archiveArtifacts artifacts: 'dist/**', allowEmptyArchive
        }
    }
}

방법 4: Error Handling

pipeline {
    agent any
    
    stages {
        stage('Deploy') {
            steps {
                script {
                    try {
                        sh './deploy.sh'
                    } catch (Exception e) {
                        currentBuild.result = 'FAILURE'
                        slackSend(
                            channel: '#alerts',
                            message: "Deployment failed: ${e.message}"
                        )
                        throw e
                    }
                }
            }
        }
    }
    
    post {
        failure {
            sh 'echo "Deployment failed, rolling back..."'
            sh './rollback.sh'
        }
    }
}

방법 5: Shared Libraries

// vars/pythonPipeline.groovy
def call(Map config = [:]) {
    pipeline {
        agent {
            docker {
                image config.dockerImage ?: 'python:3.11-slim'
            }
        }
        
        stages {
            stage('Build') {
                steps {
                    sh "pip install -r ${config.requirementsFile ?: 'requirements.txt'}"
                    sh config.buildCommand ?: 'python -m build'
                }
            }
            
            stage('Test') {
                steps {
                    sh config.testCommand ?: 'pytest tests/'
                }
            }
        }
    }
}

// Jenkinsfile
@Library('my-shared-library') _

pythonPipeline(
    dockerImage: 'python:3.11',
    buildCommand: 'make build',
    testCommand: 'make test'
)

Troubleshooting Commands

# 1. Pipeline 로그 확인
$ curl -s http://jenkins:8080/job/myjob/lastBuild/consoleText

# 2. Pipeline Syntax 검증
$ java -jar jenkins-cli.jar declarative-linter < Jenkinsfile

# 3. Node 상태 확인
$ curl -s http://jenkins:8080/computer/api/json

# 4. 환경 변수 확인
$ java -jar jenkins-cli.jar groovysh
> println System.getenv()

# 5. Jenkins 재시작
$ sudo systemctl restart jenkins

Lessons Learned

  1. Docker Agent 활용: 파이프라인별로 필요한 도구를 Docker image로 관리하면 환경 일관성을 유지할 수 있습니다.

  2. Credentials 관리: Secrets는 Jenkins Credentials에 저장하고, 파이프라인에서만 사용하세요.

  3. 에러 핸들링: try-catch와 post 블록을 사용하여 빌드 실패 시 알림 및 롤백을 구현하세요.

  4. Shared Library: 여러 프로젝트에서 반복되는 파이프라인 로직은 Shared Library로 만드세요.

  5. 로그 관리: 상세 로그를 남기고, JUnit 같은 리포트로 테스트 결과를 추적하세요.


This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.