- Published on
Jenkins Declarative Pipeline Groovy 에러 10선
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
Jenkins Declarative Pipeline은 겉으로는 선언형 DSL이지만, 실제 실행은 Groovy(CPS 변환)와 Jenkins 플러그인 스텝 컨텍스트 위에서 돌아갑니다. 그래서 에러 메시지는 Groovy 컴파일 에러처럼 보이기도 하고, 특정 스텝이 요구하는 컨텍스트(예: hudson.FilePath, Launcher, Node)가 없어 실패하기도 합니다.
이 글은 현장에서 가장 자주 보는 “Declarative Pipeline Groovy 에러” 10가지를 로그 패턴 중심으로 정리하고, 각 케이스별로 왜 터지는지와 어떻게 고치는지를 짧은 코드로 보여줍니다.
추가로 Jenkins 빌드 성능/캐시 이슈가 함께 터지는 경우가 많습니다. 빌드가 갑자기 느려졌다면 Jenkins 빌드가 갑자기 느릴 때 Docker 레이어 캐시 복구도 같이 확인해두면 좋습니다.
1) Expected a step (steps 블록 밖에서 스텝 호출)
증상 로그
WorkflowScript: xx: Expected a stepNot a valid section definition: ...
원인
Declarative 문법에서 stage 내부는 반드시 steps { ... } 안에서 스텝을 호출해야 합니다. sh, echo, checkout 같은 스텝을 stage { ... } 바로 아래에 두면 파서가 “스텝이 와야 하는데 섹션도 아니고 뭐냐”로 판단합니다.
잘못된 예
pipeline {
agent any
stages {
stage('Build') {
sh 'npm ci'
}
}
}
수정
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'npm ci'
}
}
}
}
2) No such DSL method (스텝/플러그인 미설치 또는 오타)
증상 로그
No such DSL method 'xyz' found among steps ...
원인
- 스텝 이름 오타
- 해당 스텝을 제공하는 플러그인이 미설치
- Scripted Pipeline에서만 쓰던 API를 Declarative에서 그대로 호출
예: readJSON가 없을 때
steps {
script {
def obj = readJSON text: '{"a":1}'
echo "${obj.a}"
}
}
위 스텝은 보통 Pipeline Utility Steps 플러그인이 필요합니다.
해결 체크
- Jenkins 관리
Manage Jenkins에서 플러그인 설치 여부 확인 - 스텝 레퍼런스 페이지에서 정확한 이름 확인
- 가능하면 대체 스텝(예:
jq로 파싱) 사용
3) MissingPropertyException (변수 스코프/바인딩 문제)
증상 로그
groovy.lang.MissingPropertyException: No such property: foo for class: WorkflowScript
원인
Declarative는 environment {}에 선언한 값은 env.FOO로 접근해야 합니다. 또한 script {} 밖에서는 일반 Groovy 변수 선언/대입이 제한적이며, 스코프가 생각보다 빨리 끊깁니다.
잘못된 예: environment 값을 바로 변수처럼 사용
environment {
APP_ENV = 'prod'
}
steps {
echo "${APP_ENV}"
}
수정
environment {
APP_ENV = 'prod'
}
steps {
echo "${env.APP_ENV}"
}
팁
- 파라미터는
params.NAME - 환경변수는
env.NAME - 현재 빌드 메타는
currentBuild(예:currentBuild.displayName)
4) CpsCallableInvocation / 직렬화 관련 에러 (NonCPS/객체 직렬화 불가)
증상 로그 패턴
java.io.NotSerializableException: ...org.jenkinsci.plugins.workflow.cps.CpsCallableInvocation관련 스택트레이스
원인
Pipeline은 중간중간 상태를 저장하기 위해 CPS로 실행 흐름을 직렬화합니다. 그런데 script {}에서 직렬화 불가능한 객체(예: File, 특정 플러그인 객체, Matcher)를 변수에 오래 들고 있으면 저장 시점에 폭발합니다.
흔한 실수: matcher를 변수로 유지
script {
def m = ("abc" =~ /a.*/)
if (m) {
echo "matched"
}
}
수정: 필요한 값만 추출해서 보관
script {
def matched = ("abc" ==~ /a.*/)
if (matched) {
echo 'matched'
}
}
추가 해결책
- 직렬화가 필요한 구간에서 객체를 오래 들고 있지 않기
- 로직을 함수로 빼고
@NonCPS적용(단,@NonCPS함수 안에서는 Jenkins 스텝 호출 불가)
5) Method calls on objects not allowed outside script blocks (Declarative의 제한)
증상 로그
Method calls on objects not allowed outside script blocks
원인
Declarative는 안전성과 가독성을 위해 steps에서 허용된 스텝 호출 중심으로 구성됩니다. 임의의 Groovy 메서드 호출/컬렉션 조작을 script {} 밖에서 하면 차단됩니다.
잘못된 예
steps {
def list = ['a', 'b']
list.each { echo it }
}
수정
steps {
script {
def list = ['a', 'b']
list.each { v ->
echo v
}
}
}
6) IllegalArgumentException: Expected named arguments (스텝 인자 형식 오류)
증상 로그
java.lang.IllegalArgumentException: Expected named arguments but got ...
원인
Jenkins 스텝은 대개 name: value 형태의 named argument를 기대합니다. 특히 checkout, withCredentials, retry, timeout 등에서 자주 발생합니다.
잘못된 예: withCredentials에 리스트 누락
steps {
withCredentials(string(credentialsId: 'token', variable: 'TOKEN')) {
sh 'echo ok'
}
}
수정
steps {
withCredentials([string(credentialsId: 'token', variable: 'TOKEN')]) {
sh 'echo ok'
}
}
7) Required context class hudson.FilePath is missing (노드/워크스페이스 컨텍스트 없음)
증상 로그
Required context class hudson.FilePath is missingPerhaps you forgot to surround the ... step with a node block
원인
sh, checkout, stash, archiveArtifacts 같은 스텝은 실행할 워크스페이스가 필요합니다. Declarative에서 agent none을 써놓고 해당 스텝을 실행하면 워크스페이스가 없어 실패합니다.
잘못된 예
pipeline {
agent none
stages {
stage('Test') {
steps {
sh 'npm test'
}
}
}
}
수정 1: stage에 agent 지정
pipeline {
agent none
stages {
stage('Test') {
agent any
steps {
sh 'npm test'
}
}
}
}
수정 2: Docker 에이전트라면
stage('Test') {
agent {
docker {
image 'node:20'
args '-u root:root'
}
}
steps {
sh 'node -v'
sh 'npm test'
}
}
8) org.jenkinsci.plugins.scriptsecurity... RejectedAccessException (스크립트 보안 샌드박스)
증상 로그
Scripts not permitted to use method ...org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException
원인
Jenkins는 Pipeline에서 위험한 메서드 호출을 샌드박스로 막습니다. 예를 들어 new File(...), System.getenv(...), 특정 리플렉션 호출 등이 제한됩니다.
흔한 실수
script {
def home = System.getenv('HOME')
echo home
}
해결
- 가능하면 Jenkins 제공 스텝/환경변수로 대체 (
env.HOME등) - 정말 필요하면 관리자 권한으로 Script Approval에서 승인
- 승인에 의존하는 코드는 이식성이 떨어지므로 최소화
9) MultipleCompilationErrorsException (따옴표/중괄호/괄호 불일치)
증상 로그
startup failed:MultipleCompilationErrorsException: ... expecting '}', found ...
원인
Declarative는 중괄호가 깊고, environment {}나 when {} 같은 블록이 많아서 괄호 하나만 빠져도 에러 위치가 엉뚱하게 보입니다. 특히 멀티라인 문자열과 함께 쓰면 더 헷갈립니다.
재현 예: 닫는 중괄호 누락
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'echo build'
}
}
}
수정
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'echo build'
}
}
}
}
팁
- Jenkinsfile을 IDE에서 Groovy 포매터/괄호 매칭 켜고 편집
- 변경 단위를 작게 커밋해서 어디서 깨졌는지 빨리 찾기
10) sh: ... not found 또는 exit code 127 (에이전트/도커 이미지에 도구 없음)
증상 로그
script returned exit code 127sh: 1: node: not found
원인
Groovy 에러처럼 보이진 않지만, Declarative Pipeline에서 가장 흔한 “Groovy가 잘못됐나?” 착각을 유발하는 실패입니다. 실제로는 실행 노드(에이전트)에 필요한 바이너리(node, java, docker, jq)가 없습니다.
해결: 도커 에이전트로 툴 고정
pipeline {
agent {
docker {
image 'node:20'
}
}
stages {
stage('Build') {
steps {
sh 'node -v'
sh 'npm ci'
sh 'npm run build'
}
}
}
}
성능 팁
도커 기반으로 빌드할 때 캐시가 꼬이거나 레이어 캐시가 날아가면 “갑자기 느려짐”이 같이 옵니다. 이 경우는 문법 에러가 아니라 인프라/캐시 문제일 수 있으니 Jenkins 빌드가 갑자기 느릴 때 Docker 레이어 캐시 복구에서 복구 루틴을 참고하세요.
자주 쓰는 디버깅 패턴 5가지
1) script {}로 최소 범위만 감싸기
Declarative를 최대한 유지하고, Groovy 로직만 script로 넣으면 에러 표면적이 줄어듭니다.
steps {
script {
def branch = env.BRANCH_NAME ?: 'local'
echo "branch=${branch}"
}
}
2) echo 대신 sh로 환경 출력
steps {
sh 'printenv | sort'
}
3) 실패 지점 주변에 set -eux 적용
steps {
sh '''
set -eux
node -v
npm -v
npm test
'''
}
4) post { always { ... } }로 로그/아카이브 남기기
post {
always {
archiveArtifacts artifacts: 'logs/**', allowEmptyArchive: true
}
}
5) 에러가 “Groovy처럼 보이면” 컨텍스트부터 의심
FilePath missing계열이면agent/workspace문제No such DSL method면 플러그인/오타 문제RejectedAccessException면 샌드박스 승인 문제
마무리: 에러를 줄이는 Jenkinsfile 운영 규칙
- Declarative는 선언형으로 유지하고, 복잡한 로직은
script {}로 격리 - 직렬화 불가 객체를 잡고 늘어지지 않기(필요 값만 문자열로 저장)
- 스텝이 요구하는 컨텍스트(노드, 워크스페이스, 자격증명)를 먼저 만족시키기
- 플러그인 의존 스텝은 문서화하고, 대체 경로도 준비
CI가 Jenkins에서 GitHub Actions로 일부 이동하는 팀도 많습니다. 캐시/디버깅 관점의 비교가 필요하면 GitHub Actions 캐시로 Node.js CI 2배 빠르게, 실패 디버깅도 함께 보면, “에러를 줄이는 파이프라인 설계” 관점에서 힌트를 얻을 수 있습니다.