- Published on
Jenkins Declarative Pipeline when+matrix 조건 누락 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
Jenkins Declarative Pipeline의 matrix는 멀티 플랫폼/멀티 JDK/멀티 환경 조합을 깔끔하게 병렬화해주는 강력한 기능입니다. 하지만 실무에서 when과 결합하면 “분명 조건을 걸었는데 특정 조합에서 스테이지가 실행된다/반대로 실행되지 않는다” 같은 조건 누락(혹은 무시) 문제를 종종 만납니다.
이 글은 다음 상황을 집중적으로 다룹니다.
matrix내부stage에when을 걸었는데, 일부 axis 조합에서 조건이 기대와 다르게 동작when { expression { ... } }에서env.*/params.*/BRANCH_NAME등을 참조했는데 평가가 엇갈림matrix의exclude/when을 섞었더니 특정 조합만 “새는(leak)” 현상 발생
핵심은 Declarative의 평가 시점과 스코프(특히 matrix axis 환경변수 주입 시점) 를 이해하고, 조건을 “어디에” 두어야 안정적으로 동작하는지 패턴화하는 것입니다.
문제 증상: when이 matrix 축 조합을 제대로 반영하지 못함
가장 흔한 형태는 아래와 같습니다.
- 기대:
axis OS=windows일 때만 특정 스테이지 실행 - 실제:
OS=linux에서도 실행되거나, 반대로OS=windows에서도 스킵됨
또는 브랜치 기반 조건이 섞이면 더 헷갈립니다.
- 기대:
main브랜치에서만deploy실행 - 실제: matrix 일부 조합에서만 실행/스킵이 뒤섞임
이런 문제는 대개 “조건식 자체가 틀렸다”기보다, 조건이 평가되는 시점에 axis 값이 아직 준비되지 않았거나, 혹은 조건이 stage 레벨이 아닌 잘못된 레벨에 위치해서 발생합니다.
원인 1: matrix axis 환경변수는 각 조합 실행 컨텍스트에서 주입됨
matrix의 axis 값은 각 조합(예: OS=linux, JDK=17)이 실행될 때 해당 조합 컨텍스트에 주입됩니다. 즉,
matrix바깥(상위 stage, pipeline 전역)에서 axis 환경변수를 기대하면 안 됨when이 axis 값을 참조하더라도,when이 평가되는 타이밍에 axis 값이 확정되어 있어야 함
특히 다음과 같은 경우가 위험합니다.
matrix바깥 stage에when을 걸고 axis를 참조when에서env.OS같은 값을 보는데 실제로는 아직 전역 env에 없음
원인 2: when의 평가 시점(특히 beforeAgent)과 agent 할당
Declarative의 when은 기본적으로 stage 진입 시 평가되지만, beforeAgent true를 주면 agent 할당 전에 평가됩니다.
- 장점: 불필요한 agent 점유를 줄임
- 단점: 조건식에서 필요한 컨텍스트(워크스페이스, 체크아웃 결과, 일부 환경)가 없을 수 있음
matrix 조합에서 agent { label ... }을 쓰는 경우, beforeAgent 설정에 따라 조건 평가가 달라져 “어떤 조합에서는 조건이 먹고 어떤 조합에서는 안 먹는” 것처럼 보일 수 있습니다.
재현 예제: 잘못된 when 위치/스코프
아래는 흔히 보는 실수 패턴입니다. matrix 밖에서 axis를 기준으로 필터링하려고 합니다.
pipeline {
agent none
stages {
stage('Test Matrix') {
matrix {
axes {
axis {
name 'OS'
values 'linux', 'windows'
}
}
stages {
stage('Run tests') {
steps {
echo "OS=${env.OS}" // 조합에서는 찍힘
}
}
}
}
}
stage('Windows-only stage (WRONG)') {
when {
expression { env.OS == 'windows' } // matrix 축을 전역에서 기대
}
steps {
echo 'Do something for windows'
}
}
}
}
Windows-only stage는 matrix 이후의 전역 stage이므로, env.OS는 의도대로 존재하지 않습니다. 결과는 Jenkins 버전/플러그인/실행 경로에 따라 다르게 보일 수 있으나, 요지는 axis 값은 matrix 조합 컨텍스트에만 유효하다는 점입니다.
해결 1: axis 기반 조건은 matrix 내부 stage의 when으로 이동
가장 안전한 해결책은 “axis 기반 조건은 axis가 존재하는 곳에서 평가”하는 것입니다.
pipeline {
agent none
stages {
stage('Test Matrix') {
matrix {
axes {
axis {
name 'OS'
values 'linux', 'windows'
}
axis {
name 'JDK'
values '17', '21'
}
}
agent { label "builder-${OS}" }
stages {
stage('Unit Test') {
steps {
echo "Run unit test on OS=${OS}, JDK=${JDK}"
}
}
stage('Windows-only Integration') {
when {
expression { OS == 'windows' }
}
steps {
echo "Integration test only for windows: OS=${OS} JDK=${JDK}"
}
}
}
}
}
}
}
포인트:
OS,JDK를env.OS대신 바로OS,JDK로 참조(Declarative에서 axis는 변수처럼 노출되는 경우가 많음)when은 matrix 내부 stage에 둬서 조합 컨텍스트에서 평가
이 패턴은 “조건 누락”을 가장 확실하게 줄입니다.
해결 2: exclude를 적극 활용해 조합 자체를 제거
조건이 “특정 조합은 아예 실행하면 안 된다”라면 when보다 exclude가 더 명확합니다. exclude는 조합 생성 단계에서 제거되므로, 스테이지 조건 평가 타이밍 문제를 회피할 수 있습니다.
pipeline {
agent none
stages {
stage('Build Matrix') {
matrix {
axes {
axis {
name 'OS'
values 'linux', 'windows'
}
axis {
name 'JDK'
values '17', '21'
}
}
excludes {
exclude {
axis {
name 'OS'
values 'windows'
}
axis {
name 'JDK'
values '21'
}
}
}
stages {
stage('Build') {
steps {
echo "Build OS=${OS} JDK=${JDK}"
}
}
}
}
}
}
}
권장 기준:
- “조합 자체가 불필요/금지” →
exclude - “조합은 필요하지만 특정 단계만 스킵” →
when
해결 3: beforeAgent 사용 시 조건식 의존성 점검
beforeAgent true는 비용 최적화에 좋지만, 조건식에서 다음을 참조하면 흔들릴 수 있습니다.
fileExists(...)(워크스페이스/체크아웃 필요)sh(...)결과- SCM 체크아웃 이후에만 생기는 파일/환경
예를 들어, 특정 파일이 있을 때만 실행하려고 아래처럼 쓰면:
stage('Optional Step') {
when {
beforeAgent true
expression { fileExists('ci/enable_optional') }
}
agent { label "builder-${OS}" }
steps {
echo 'Run optional step'
}
}
beforeAgent true라서 워크스페이스가 준비되지 않으면 fileExists가 기대대로 동작하지 않습니다.
해결 방법은 2가지입니다.
beforeAgent를 제거(가장 단순)
when {
expression { fileExists('ci/enable_optional') }
}
- 체크아웃을 보장하거나, 조건을 파일 기반이 아닌 파라미터/브랜치 기반으로 바꾸기
해결 4: 브랜치 조건 + matrix 조건을 AND로 묶을 때는 allOf 사용
브랜치 조건과 axis 조건을 함께 걸 때는 allOf로 명시적으로 묶는 것이 안전합니다.
stage('Deploy (main + linux only)') {
when {
allOf {
branch 'main'
expression { OS == 'linux' }
}
}
steps {
echo "Deploy from main on linux only: OS=${OS}"
}
}
이렇게 하면 조건이 분산되어 “한쪽만 적용된 것처럼 보이는” 상황을 줄일 수 있습니다.
디버깅 팁: 조건 평가에 필요한 값을 강제로 출력하기
조건 누락을 잡을 때는 “조합에서 실제로 어떤 값이 들어왔는지”를 먼저 확인해야 합니다.
matrix 내부에 진단 스테이지를 하나 둡니다.
stage('Debug Context') {
steps {
echo "BRANCH_NAME=${env.BRANCH_NAME}"
echo "CHANGE_ID=${env.CHANGE_ID}"
echo "OS=${OS} JDK=${JDK}"
echo "NODE_NAME=${env.NODE_NAME}"
}
}
그리고 when 조건식에서 참조하는 값이 정말 그 시점에 존재하는지 확인합니다.
env.BRANCH_NAME는 Multibranch Pipeline에서만 기대대로 들어옵니다.- PR 빌드라면
CHANGE_ID,CHANGE_TARGET같은 값이 더 중요할 수 있습니다.
운영 관점: 조건 누락이 장애로 번지는 대표 시나리오
조건이 새면 단순히 “불필요한 테스트가 한 번 더 돈다”로 끝나지 않습니다.
- 특정 조합에서만 배포가 실행되어 잘못된 아티팩트가 프로덕션에 반영
- 특정 노드 라벨에서만 자격증명/네트워크가 달라 외부 시스템 호출 실패
- 병렬로 돌아야 할 검증이 누락되어 품질 게이트가 무력화
특히 클라우드 권한/토큰과 결합되면 원인 파악이 더 어려워집니다. 예를 들어 CI 러너에서 AWS 권한이 꼬이면 “어떤 조합에서만 AccessDenied”가 나기도 합니다. 이런 류의 이슈를 다룬 글로는 EKS Pod에서 S3 403 AccessDenied 원인 10가지 같은 체크리스트가 진단에 도움이 됩니다(비록 Jenkins 주제는 아니지만, 권한/환경 분기 문제의 접근법은 유사합니다).
권장 템플릿: matrix + 조건을 안정적으로 설계하는 패턴
실무에서 재사용하기 좋은 형태로 정리하면 아래처럼 가져갈 수 있습니다.
pipeline {
agent none
options {
timestamps()
ansiColor('xterm')
}
parameters {
booleanParam(name: 'RUN_INTEGRATION', defaultValue: false, description: 'Run integration tests')
}
stages {
stage('CI Matrix') {
matrix {
axes {
axis {
name 'OS'
values 'linux', 'windows'
}
axis {
name 'JDK'
values '17', '21'
}
}
// 조합 자체 제거가 필요하면 excludes를 우선 고려
excludes {
exclude {
axis { name 'OS'; values 'windows' }
axis { name 'JDK'; values '21' }
}
}
agent { label "builder-${OS}" }
stages {
stage('Debug') {
steps {
echo "BRANCH=${env.BRANCH_NAME} OS=${OS} JDK=${JDK}"
}
}
stage('Build') {
steps {
echo "Build on ${OS}/${JDK}"
}
}
stage('Integration') {
when {
allOf {
expression { params.RUN_INTEGRATION }
expression { OS == 'linux' }
}
}
steps {
echo "Integration on ${OS}/${JDK}"
}
}
stage('Deploy') {
when {
allOf {
branch 'main'
expression { OS == 'linux' && JDK == '21' }
}
}
steps {
echo "Deploy from main on ${OS}/${JDK}"
}
}
}
}
}
}
}
이 템플릿의 의도:
- axis 기반 조건은 matrix 내부에서만 평가
- 조합 제거는
exclude로 처리 - 브랜치/파라미터/axis 조건은
allOf로 명확히 결합 beforeAgent는 정말 필요할 때만 사용(조건식 의존성 검증 후)
체크리스트: when+matrix 조건 누락을 막는 7가지 점검
- axis 값을 matrix 밖에서 참조하지 않았는가? (
env.OS같은 전역 접근 금지) - 조건은 matrix 내부 stage에 있는가? (조합 컨텍스트에서 평가)
- 조합 제거가 목적이라면 exclude를 썼는가? (when 대신 exclude)
- when에 beforeAgent true를 썼다면, 조건식이 워크스페이스/체크아웃에 의존하지 않는가?
- 브랜치 조건 + axis 조건을 allOf로 묶었는가?
- 디버그 출력으로 BRANCH/OS/JDK가 실제로 무엇인지 확인했는가?
- Jenkins/플러그인 버전 차이로 동작이 달라질 수 있음을 감안했는가? (특히 Pipeline Model Definition 관련)
결론
Jenkins Declarative Pipeline에서 when과 matrix를 함께 쓸 때 발생하는 조건 누락 문제는 대부분 조건이 평가되는 시점과 스코프를 잘못 잡아서 생깁니다. 해결의 핵심은 간단합니다.
- axis 기반 조건은 matrix 내부에서 평가하라
- 조합 자체를 막아야 하면 exclude를 써라
beforeAgent는 비용 최적화 도구일 뿐, 조건식의 의존성이 충족되는지 먼저 확인하라
이 원칙만 지켜도 “왜 어떤 조합만 새지?” 같은 디버깅 시간을 크게 줄일 수 있습니다.