선언형 vs 스크립트형: 젠킨스 파이프라인 문법 선택하기
선언형(Declarative)과 스크립트형(Scripted) 젠킨스 파이프라인 문법을 비교하고, 각각을 언제 사용해야 하는지에 대한 예제와 실용적인 지침을 제공합니다.
선언형 vs 스크립트형: 젠킨스 파이프라인 문법 선택하기
젠킨스 파이프라인은 Jenkinsfile을 작성하는 두 가지 방법, 즉 선언형(Declarative)과 스크립트형(Scripted)을 제공합니다. 둘 다 애플리케이션을 빌드, 테스트, 배포할 수 있지만, 가독성, 검증, Groovy 유연성 측면에서 서로 다른 트레이드오프를 요구합니다.
팀이 새 파이프라인을 위한 문법을 선택할 때는 6개월 후에도 유지 관리해야 할 워크플로우를 먼저 고려하세요. 선언형은 일반적으로 읽고 검토하기 쉽습니다. 스크립트형은 파이프라인이 진정으로 동적 동작을 필요로 할 때 더 직접적인 Groovy 제어를 제공합니다.
젠킨스 파이프라인 이해하기
문법을 자세히 살펴보기 전에, 젠킨스 파이프라인이 무엇인지 간단히 다시 설명하겠습니다. 파이프라인은 지속적 전달 파이프라인을 젠킨스에 구현하고 통합할 수 있도록 지원하는 플러그인 모음입니다. 이는 코드 커밋부터 배포까지의 전체 소프트웨어 전달 프로세스를 정의하는 일련의 자동화된 단계입니다. 이러한 단계는 일반적으로 Groovy로 작성된 Jenkinsfile에 정의되며, 복잡한 빌드, 테스트, 배포 시나리오를 관리하는 강력한 방법을 제공합니다.
젠킨스 Pipeline as Code는 몇 가지 주요 이점을 제공합니다:
- 버전 관리:
Jenkinsfile은 애플리케이션 코드와 마찬가지로 소스 제어에 저장되어 버전 관리, 감사, 협업이 가능합니다. - 반복 가능성: 다양한 환경과 실행에서 일관된 전달 프로세스 실행을 보장합니다.
- 가시성: 전체 전달 프로세스를 명확하고 이해하기 쉬운 방식으로 제공합니다.
- 내구성: 파이프라인은 젠킨스 마스터 재시작에도 견딜 수 있습니다.
- 확장성: 공유 라이브러리를 통해 복잡한 로직을 추상화하고 재사용할 수 있습니다.
선언형 파이프라인
선언형 파이프라인은 파이프라인 작성과 이해를 더 쉽게 만들기 위해 설계된 더 새롭고 독단적인 문법입니다. 미리 정의된 블록으로 구조화된 접근 방식을 제공하여 팀이 Jenkinsfile을 일관되게 유지할 수 있도록 돕습니다.
특징 및 문법
선언형 파이프라인은 pipeline, agent, stages, steps, post, environment, parameters, options, triggers, tools, input, when과 같은 최상위 블록으로 정의된 특정 구조를 강제합니다. 이 구조는 워크플로우의 다른 부분에 대한 명확한 경계를 제공하여 파이프라인 정의를 단순화합니다.
다음은 선언형 파이프라인의 기본 구조입니다:
pipeline {
agent any // 또는 'label', 'docker' 등
stages {
stage('Build') {
steps {
echo '애플리케이션 빌드 중...'
sh 'mvn clean install'
}
}
stage('Test') {
steps {
echo '테스트 실행 중...'
sh 'mvn test'
}
}
stage('Deploy') {
when {
branch 'main'
}
steps {
echo '프로덕션에 배포 중...'
script {
// 절대적으로 필요한 경우 스크립트형 로직을 여기에 넣을 수 있음
// 예: 공유 라이브러리 함수 호출
// mySharedLibrary.deployApplication()
}
}
}
}
post {
always {
echo '파이프라인이 완료되었습니다.'
}
success {
echo '파이프라인이 성공했습니다!'
}
failure {
echo '파이프라인이 실패했습니다.'
}
}
}
선언형 파이프라인의 장점
- 단순성 및 가독성: 미리 정의된 구조 덕분에 비전문가도 파이프라인을 쉽게 읽고 이해할 수 있습니다. 구성 파일처럼 느껴집니다.
- 구조화된 접근 방식: 파이프라인 전반에 걸쳐 모범 사례와 일관성을 강제하여 학습 곡선과 오류 가능성을 줄입니다.
- 내장 기능: 조건부 실행(
when), 빌드 후 작업(post), 병렬 단계 실행, 파이프라인 흐름 관리를 위한 다양한 옵션 등 일반적인 CI/CD 패턴을 위한 풍부한 내장 기능을 제공합니다. - 배우기 쉬움: 광범위한 Groovy 지식이 없는 개발자도 독단적인 문법 덕분에 빠르게 시작할 수 있습니다.
- 검증: 선언형은 더 엄격한 규칙을 가지므로 젠킨스가 실행 전에 구조를 더 많이 검증할 수 있습니다.
선언형 파이프라인의 한계
- 유연성 부족: 엄격한 구조는 미리 정의된 블록 외부에서 사용자 정의 Groovy 로직이 필요한 매우 복잡하거나 동적인 워크플로우에 제한적일 수 있습니다.
- 제한된 직접 Groovy 접근:
script블록을 사용하여 스크립트형 파이프라인 문법을 주입할 수 있지만, 과도하게 사용하면 선언형 문법의 이점을 저해하고 파이프라인을 읽기 어렵게 만들 수 있습니다.
선언형 파이프라인을 사용해야 하는 경우
선언형 파이프라인은 대부분의 일반적인 CI/CD 시나리오에 권장되는 선택입니다. 다음과 같은 경우에 이상적입니다:
- 젠킨스 또는 Pipeline as Code를 처음 접하는 팀.
- 간단하거나 중간 정도의 복잡성을 가진 빌드, 테스트, 배포 프로세스를 가진 프로젝트.
- 여러 파이프라인에서 일관성과 유지 관리성을 보장해야 하는 경우.
- 병렬 실행, 조건부 단계, 알림과 같은 일반적인 패턴을 위해 젠킨스의 내장 기능을 활용하는 경우.
스크립트형 파이프라인
스크립트형 파이프라인은 Groovy 프로그래밍 언어 위에 직접 구축되었으며, Jenkins Pipeline as Code의 원래 문법이었습니다. 최대한의 유연성과 성능을 제공하여 개발자가 고도로 사용자 정의되고 동적인 자동화 흐름을 구현할 수 있도록 합니다.
특징 및 문법
스크립트형 파이프라인은 전통적인 Groovy 스크립트처럼 위에서 아래로 순차적으로 실행됩니다. Groovy의 전체 문법을 사용하고 node, stage, checkout, sh, git 등의 메서드를 통해 Jenkins Pipeline DSL(Domain Specific Language)을 활용합니다. 이는 Jenkins API와 Groovy 언어의 모든 기능에 직접 접근할 수 있게 해줍니다.
다음은 스크립트형 파이프라인의 기본 구조입니다:
node('my-agent-label') {
stage('Prepare') {
echo '워크스페이스 준비 중...'
checkout scm
}
stage('Build') {
echo '애플리케이션 빌드 중...'
try {
sh 'mvn clean install'
} catch (err) {
echo "빌드 실패: ${err}"
// 사용자 정의 오류 처리
currentBuild.result = 'FAILURE'
throw err
}
}
stage('Test') {
echo '테스트 실행 중...'
// 동적으로 테스트 스위트 결정
def testSuites = sh(script: 'find tests -name "*.test"', returnStdout: true).trim().split('\n')
if (testSuites.isEmpty()) {
echo '테스트를 찾을 수 없습니다.'
} else {
for (suite in testSuites) {
echo "테스트 스위트 실행 중: ${suite}"
sh "./run-test.sh ${suite}"
}
}
}
stage('Deploy') {
// 복잡한 조건부 로직
if (env.BRANCH_NAME == 'main' && currentBuild.currentResult == 'SUCCESS') {
echo '프로덕션에 배포 중...'
sh './deploy-prod.sh'
} else if (env.BRANCH_NAME == 'develop') {
echo '스테이징에 배포 중...'
sh './deploy-staging.sh'
} else {
echo '이 브랜치에 대한 배포가 없습니다.'
}
}
// 빌드 후 작업은 try-finally 블록이나 사용자 정의 로직으로 구현 가능
// 예: 알림 보내기
if (currentBuild.result == 'SUCCESS') {
echo '파이프라인이 성공적으로 완료되었습니다!'
// notifySuccess()
} else {
echo '파이프라인이 실패했습니다.'
// notifyFailure()
}
}
스크립트형 파이프라인의 장점
- 최대 유연성: Groovy의 모든 기능을 제공하여 매우 복잡하고 동적인 로직, 사용자 정의 루프, 오류 처리, 데이터 조작이 가능합니다.
- 직접적인 Jenkins API 접근: Jenkins 및 Groovy API 사용에 더 많은 여유를 제공하지만, 일부 작업은 여전히 플러그인, 권한, 스크립트 보안 샌드박스에 의존합니다.
- 동적 동작: 동적 에이전트 할당, 런타임 조건에 따른 병렬 실행, 고급 리소스 관리가 필요한 워크플로우에 이상적입니다.
- 확장성: 선언형 파이프라인을 위해 재사용 가능한 복잡한 로직을 캡슐화하는 정교한 공유 라이브러리를 만드는 데 탁월합니다.
스크립트형 파이프라인의 한계
- 가파른 학습 곡선: Groovy에 대한 확실한 이해가 필요하며, 이는 언어에 익숙하지 않은 팀에게 장벽이 될 수 있습니다.
- 덜 독단적: 엄격한 구조가 없기 때문에 파이프라인이 일관성이 없어지고 프로젝트나 개발자 간에 읽거나 유지 관리하기 어려워질 수 있습니다.
- 오류 발생 가능성: Groovy의 유연성은 코딩 오류의 가능성을 더 많이 만들고, 선언형에 비해 내장 검증이 적습니다.
- 가독성 문제: 복잡한 스크립트형 파이프라인은 빠르게 구문 분석 및 이해가 어려워져 협업과 문제 해결을 방해할 수 있습니다.
- 파이프라인 특화 문법 부족: 많은 일반적인 CI/CD 패턴(예:
post작업 또는when조건)을 Groovy 구조(예:try-catch-finally,if문)를 사용하여 수동으로 구현해야 합니다.
선언형 vs 스크립트형: 나란히 비교
차이점을 요약하기 위해 비교표를 제공합니다:
| 기능 | 선언형 파이프라인 | 스크립트형 파이프라인 |
|---|---|---|
| 문법 구조 | 독단적, 미리 정의된 최상위 블록. | 유연함, Groovy 기반, 순차적 실행. |
| 학습 곡선 | 초보자에게 더 쉬움, Groovy 지식 덜 필요. | 가파름, Groovy 전문성 필요. |
| 가독성 | 구조화된 블록과 명확한 문법으로 높음. | 복잡한 스크립트의 경우 낮을 수 있음, 개발자 스타일에 따라 다름. |
| 유연성 | 미리 정의된 구조로 제한됨; Groovy를 위한 script 블록. |
무제한, Groovy의 모든 기능. |
| 내장 기능 | 일반적인 CI/CD 패턴을 위한 풍부한 기능 (post, when, parallel). |
Groovy 구조를 사용한 수동 구현 필요. |
| 오류 처리 | 전역 또는 단계별 작업을 위한 post 블록. |
수동 try-catch-finally 블록. |
| 확장성 | 복잡한 Groovy 로직을 위해 공유 라이브러리 활용. | 복잡한 Groovy 로직을 직접 작성. 종종 공유 라이브러리 생성. |
| 에이전트 제어 | 전역 agent 또는 단계 수준 agent. |
node 블록, 어디서든 에이전트 정의 가능. |
| 사용 사례 | 표준 CI/CD 워크플로우, 단순~중간 복잡성. | 고도로 동적이고 복잡하며 사용자 정의된 워크플로우; 공유 라이브러리 개발. |
| JSON/YAML 느낌 | 구성 언어에 더 가까움. | 순수 프로그래밍 언어. |
올바른 문법 선택하기
선언형과 스크립트형 파이프라인 사이에서 결정할 때 다음 요소를 고려하세요:
- 팀의 Groovy 전문성: 팀에 강력한 Groovy 기술이 부족한 경우, 선언형은 훨씬 더 얕은 학습 곡선을 가지며 더 빠른 채택을 촉진합니다.
- 워크플로우 복잡성: 대부분의 표준 CI/CD 워크플로우(빌드, 테스트, 배포)의 경우 선언형이 충분히 적합하며, 가독성과 내장 기능 덕분에 종종 우수합니다. 고도로 동적이거나 조건부이거나 사용자 정의 리소스 집약적인 작업의 경우 스크립트형이 필요할 수 있습니다.
- 유지 관리성 및 가독성: 선언형 파이프라인은 일반적으로 읽고 유지 관리하기 쉬우며, 특히 많은 파이프라인과 개발자가 있는 대규모 조직에서 그렇습니다. 이러한 일관성은 인지 부하를 줄입니다.
- 기존 파이프라인 생태계: 기존 스크립트형 파이프라인이나 스크립트형 문법으로 구축된 강력한 공유 라이브러리 세트가 있는 경우, 일관성을 위해 이를 유지하거나 적절한 경우 점진적으로 선언형으로 마이그레이션할 수 있습니다.
- 미래 성장: 선언형 파이프라인은 일반적으로 충분하며, 일반적으로 스크립트형 Groovy로 작성된 공유 라이브러리를 통해 사용자 정의 로직으로 확장할 수 있습니다. 이것이 종종 최상의 하이브리드 접근 방식입니다.
의사 결정을 위한 모범 사례
- 선언형으로 시작: 새 파이프라인의 경우 기본적으로 선언형을 사용하세요. 대부분의 CI/CD 사용 사례를 다루며 일관성과 가독성을 촉진합니다.
- 공유 라이브러리 활용: 선언형 파이프라인에서 반복적이거나 복잡한 로직을 발견하면 해당 로직을 공유 라이브러리로 추상화하세요. 공유 라이브러리는 주로 스크립트형 Groovy로 작성되므로, 선언형의 구조와 스크립트형의 유연성이라는 두 가지 장점을 결합할 수 있습니다.
- 선언형 과도한 스크립팅 피하기: 선언형은
script블록을 허용하지만, 이를 최소화하도록 노력하세요.script블록이 너무 크거나 복잡해지면 로직을 공유 라이브러리 함수로 이동해야 한다는 강력한 신호입니다. - 마이그레이션 고려: 유지 관리가 어려워지는 레거시 스크립트형 파이프라인이 있는 경우, 복잡한 부분을 공유 라이브러리로 이동하면서 선언형 문법으로 리팩터링하는 것을 고려하세요.
핵심 내용
새로운 Jenkinsfile의 경우, 구체적인 이유가 없는 한 선언형을 선택하세요. 반복되거나 복잡한 Groovy는 공유 라이브러리로 이동하고, 선언형 단계, 조건, 단계에 깔끔하게 맞지 않는 워크플로우를 위해 완전한 스크립트형 파이프라인을 예약하세요.