📦 ETC/그냥 쓰고 싶어서요

[Jenkins] 젠킨스 파이프라인 when의 tag 표현식, 왜 안되는거니?

1HOON 2022. 3. 8. 01:19

이미지 출처 :: https://memegenerator.net/instance/66455716/whyyy-jenkins-why-do-you-hate-me

 

저는 지금 팀 내부에서 작은 프로젝트를 진행하고 있습니다. 개발은 어느 정도 완료되었고, 팀 알파 서버에 배포하는 단계가 남았죠.

이 프로젝트로 사내 오픈소스와 기술 발표를 노리고 있기 때문에 지속적으로 발전시켜나갈 예정입니다.

 

그러기 위해 필수적인 CI/CD(지속적인 통합, 지속적인 제공) 환경을 구축하게 되었는데, 만만치 않았습니다. 사소한 문제는 공식 가이드나 레퍼런스로 해결이 가능했지만, 결정적인 부분에서 관련 가이드나 레퍼런스가 부실해 결국 우회하게 되었습니다.

 

제가 이 포스팅을 작성하는 이유는 저와 같은 문제가 발생해 이런저런 시도를 해보았는데도 해결하지 못한 분께 해결책은 아니지만 우회 방법을 공유하고자 함에 있습니다.

 

 

Jenkins 파이프라인 설계


우선 이 프로젝트는 팀 전용 파일럿 Github 리파지터리에서 관리되고 있습니다. 파일럿 리파지터리는 멀티 모듈 프로젝트로 세팅되어있으며, 한 개의 리파지토리에 여러 개의 파일럿 프로젝트가 존재하고 있었습니다.

 

 

리파지토리를 통째로 파이프라인으로 구성한다면?

  • Github 릴리즈 태그가 발행되어 빌드될 경우, 모든 모듈이 같은 버전으로 빌드됩니다. (다른 모듈은 변경사항이 없었음에도)
  • 개발 중인 모듈에서 빌드 혹은 테스트가 실패할 경우 전체 빌드가 실패합니다.
  • 한 개 모듈만 빌드하면 되는데 전체가 빌드되어 빌드 시간이 불필요하게 길어집니다.

 

위 이유로 Github 릴리즈 태그 발행 시 한 개 모듈만 빌드되도록 파이프라인을 설계했습니다.

 

  1. 파일럿 리파지터리에서 모듈명 + / + 버전 형태로 릴리즈 태그를 발행합니다.
  2. 젠킨스에서 태그 발행을 감지하고, 릴리즈 태그명에서 모듈명을 추출합니다.
  3. 파이프라인의 빌드 스테이지를 순회하면서 빌드 스테이지에 사전에 정의된 모듈명과 추출한 모듈명이 일치하는지 검사합니다.
  4. 일치할 경우 해당 스테이지를 수행하고, 일치하지 않을 경우 해당 스테이지를 스킵합니다.

 

 

Jenkinsfile 작성


앞선 설계 내용을 바탕으로 jenkinsfile.groovy를 작성했습니다.

pipeline {
  stages {
    stage('SCM') {
      steps {
        checkout scm
      }
    }
    
    stage('BUILD MODULE A') {
      when {
        tag 'module-a/*'
      }
      
      stages {
        stage('BUILD') {
          ...
        }
        stage('TEST') {
          ...
        }
        stage('DOCKER PUSH') {
          ...
        }
      }
    }
    
    stage('BUILD MODULE B') {
      when {
        tag 'module-b/*'
      }
      
      stages {
        stage('BUILD') {
          ...
        }
        stage('TEST') {
          ...
        }
        stage('DOCKER PUSH') {
          ...
        }
      }
    }
  }
  
  environment {
    TAG_NAME = getCurrentTag()
  }
}

def getCurrentTag() {
  script {
    return sh(regurnStdout: true, script: "git tag --sort=creatordate | tail -1").trim();
  }
}

Jenkins Pipeline Syntaxwhen을 사용하면 표현식 결과가 참일 경우에만 해당 스테이지를 실행합니다.

when의 표현식 중 tagTAG_NAME 환경변수 값이 GLOB 스타일(Ant Path)의 정규표현식을 만족하면 true를 반환한다고 합니다.

 

자, 이렇게 작성하면 생각한대로 동작할 줄 알았습니다. 그리고 결과는... 이 포스팅의 이유가 되었습니다.😭

 

 

릴리즈 태그명과 상관없이 모든 스테이지가 동작했습니다!!


 

jenkinsfile.groovy를 리파지터리에 푸시하고 릴리즈 태그를 module-a/0.0.1 로 발행했습니다. 그런데 태그명과 상관없이 모든 스테이지가 수행돼버렸습니다. 이게 뭔...

 

 

표현식이 문제가 있는 것일까요? 표현식이 정상적인지 확인하는 스크립트를 각 모듈별 스테이지의 BUILD 스테이지에 추가했습니다.

stage("BUILD") {
  steps {
    script {
      def isMatchA = (env.TAG_NAME ==~ /^module-a\/.*/)
      if (isMatchA) {
        echo 'isMatchA = true'
      } else {
        echo 'isMatchA = false'
      }
      
      def isMatchB = (env.TAG_NAME ==~ /^module-b\/.*/)
      if (isMatchB) {
        echo 'isMatchB = true'
      } else {
        echo 'isMatchB = false'
      }
      
      ...
    }
  }
}

그리고 다시 릴리즈 태그를 module-a/0.0.2 로 발행했는데, 결과는 아래와 같았습니다.

isMatchA = true
isMatchB = false

표현식은 정상적인 것이 분명해졌습니다. 그래서 tag의 comparator를 GLOB에서 REGEXP로도 변경해보았으나 결과는 마찬가지였습니다. Jenkins Pipeline Syntax 공식 가이드와 여러 사이트의 레퍼런스를 뒤져보았지만, 제가 위에서 작성한 대로 한 사람들은 모두 잘 되었다는 글뿐이었습니다.

 

 

결국은 우회했습니다


열세 번의 수정과 배포를 거친 뒤 우회하기로 결정했습니다. 이 망할 놈의 젠킨스는 제가 맘에 안 드는 게 분명합니다.

그래도 아래 두 가지 상황이 우회를 가능하게 해 주었습니다. 고오맙다.

  • TAG_NAME 환경 변수에는 getCurrentTag() 메서드를 통해 유효한 태그명이 세팅되었습니다.
  • when 에서 tag 는 정상적으로 동작하지 않았지만, expression 은 정상적으로 동작했습니다.

 

그렇다면 태그명과 매개변수를 비교해 일치 여부를 반환하는 메서드를 선언하고, when에서 expression으로 호출해주면 되는 일이었습니다.

stage("BUILD MODULE A") {
  when {
    expression {isTagMatchModuleName("module-a") == true}
  }
  
  stages {
    ...
  }
}

stage("BUILD MODULE B") {
  when {
    expression {isTagMatchModuleName("module-b") == true}
  }
  
  stages {
    ...
  }
}

...

def isTagMatchModuleName(moduleName) {
  return env.TAG_NAME ==~ /^${moduleName}\/.*/
}

 

 

결과는 대.성.공이었습니다. 이 포스팅에서는 생략했지만 각 모듈의 빌드 스테이지는 병렬로 구성했으며, 릴리즈 태그의 모듈명에 따라 해당 모듈만이 빌드되고 나머지는 스킵되었습니다.

 

드디어 성공했어...!!

반응형