본문 바로가기
IT & Tech 정보

동적 파이프라인 생성: Jenkins Shared Library & Groovy 스크립팅 끝판왕 가이드

by 지식과 지혜의 나무 2025. 5. 26.
반응형




1. 서론: 왜 ‘동적’ 파이프라인인가
• 규모의 경제: 수십 개 프로젝트를 개별 Jenkinsfile로 관리하면 유지보수와 표준화가 불가능
• 유연한 로직 주입: 빌드·테스트·배포 단계를 코드로 모듈화하고, 메타데이터에 따라 런타임에 조합
• 확장성 확보: 새로운 팀·서비스가 생겨도 Shared Library에만 템플릿을 추가하면 자동 반영

실리콘밸리·심천 톱티어 엔지니어들은 공통 로직을 Shared Library에 담고, Groovy 메타프로그래밍으로 파이프라인을 ‘동적으로’ 생성하여 수백 개 파이프라인을 일원화합니다. 이제 그 비법을 파헤쳐 보겠습니다.



2. 전체 아키텍처 개관

Jenkins Master
└── Shared Library Repo (vars/, src/, resources/)
    ├── vars/
    │   ├── pipeline.groovy       ← 메인 엔트리 포인트
    │   └── stepRegistry.groovy   ← 스텝 정의 저장소
    ├── src/
    │   └── org/company/
    │       ├── StageFactory.groovy
    │       └── Utils.groovy
    └── resources/
        └── templates/
            └── stageTemplate.gtpl ← GString 기반 템플릿

1. vars/
• pipeline.groovy: call(Map config) 로 파이프라인 생성
• stepRegistry.groovy: 서비스별·언어별 스텝 콜렉션
2. src/
• StageFactory: createStages(config) 로 동적 스테이지 리스트 반환
• Utils: 공통 유틸 함수 (GitTag 파싱, Slack 알림 등)
3. resources/
• GString 템플릿으로 복잡한 스크립트·YAML을 동적으로 생성



3. Jenkins Shared Library 심화

3.1 구조화된 Entry Point

// vars/pipeline.groovy
def call(Map config) {
  node(config.nodeLabel) {
    checkout scm
    config = Utils.mergeDefaultConfig(config)
    List<Stage> stages = StageFactory.createStages(config)
    for (stage in stages) {
      stage.run()
    }
    Utils.notifyBuild(config)
  }
}

• config.nodeLabel, config.stages, config.notifications 등만 지정하면
• 나머지 로직(테스트, 패키징, 배포)은 모두 라이브러리 내부에서 처리

3.2 stepRegistry로 완전 분리

// vars/stepRegistry.groovy
def get(buildType) {
  switch(buildType) {
    case 'java': return ['compile', 'unitTest', 'codeCoverage', 'package']
    case 'node': return ['npmInstall', 'lint', 'unitTest', 'bundle']
    default:     return ['shell']
  }
}

• 서비스 언어·프레임워크별 기본 파이프라인 정의
• 신규 빌드 타입 추가 시 이 파일만 수정



4. Groovy 스크립팅 테크닉

4.1 CPS(Continuable Passing Style)와 non-CPS 조합
• @NonCPS: Groovy 컬렉션·메일 로직 같이 Jenkins CPS 변환이 필요 없는 함수에 적용

@NonCPS
def parseGitTag(String tag) {
  // 복잡한 문자열 처리, CPS 변환 비허용 로직
  return tag.tokenize('-')[1]
}

4.2 메타프로그래밍: AST 트랜스폼 활용

@groovy.transform.ASTTest(value={
  assert node instanceof org.codehaus.groovy.ast.stmt.BlockStatement
})
def call(Map config) { ... }

• ASTTest로 컴파일 타임 검증
• 커스텀 AST 변환기로 @WithRetry 어노테이션 삽입

4.3 GString 템플릿으로 스크립트 생성

// resources/templates/deploy.gtpl
#!/bin/bash
kubectl set image deployment/${deployment} ${container}=${image}:${version}

def renderTemplate(name, binding) {
  def tpl = libraryResource("templates/${name}.gtpl")
  return tpl.toString().replace(/\$\{(\w+)\}/) { _, key -> binding[key] }
}

• 복잡한 배포 스크립트도 코드 변경 없이 템플릿만 수정



5. 동적 파이프라인 생성 패턴

5.1 파라미터 주도 단계 주입

// StageFactory.groovy
class StageFactory {
  static List createStages(Map cfg) {
    def steps = stepRegistry.get(cfg.buildType)
    return steps.collect { stepName ->
      return new Stage(name: stepName, action: { Utils.invokeStep(stepName, cfg) })
    }
  }
}

• cfg.buildType = 'java' 면 java 빌드 스텝 리스트를 동적으로 로드
• 신규 스텝 추가 시 registry만 갱신

5.2 Parallel & Conditional 스테이지

if (cfg.parallelTests) {
  parallel cfg.testSuites.collectEntries { suite ->
    ["test-${suite}" : { Utils.runTestSuite(suite) }]
  }
}

• parallel 키워드로 테스트 스위트를 동시 실행
• 프로파일 환경변수에 따라 분기



6. 실전 예제: End-to-End Jenkinsfile

@Library('company-shared-lib') _
pipeline {
  agent none
  parameters {
    string(name: 'BUILD_TYPE', defaultValue: 'java', description: 'build type')
    booleanParam(name: 'PARALLEL_TEST', defaultValue: true)
  }
  stages {
    stage('Initialize') {
      agent { label 'master' }
      steps {
        script {
          pipeline([
            nodeLabel: 'docker',
            buildType: params.BUILD_TYPE,
            parallelTests: params.PARALLEL_TEST,
            notifications: [slack: '#builds']
          ])
        }
      }
    }
  }
}

• 한 줄 pipeline([...]) 호출로 전체 파이프라인 완성
• Shared Library 내부에서 복잡한 유효성 검사·Retry·캐시·알림 모두 처리



7. “아, 이런 방법도 있었구나” 팁
• Dynamic Agent Allocation

def agentLabel = Utils.chooseAgent(cfg.buildType)
node(agentLabel) { ... }


• Caching with Lock

lock(resource: "cache-${cfg.buildType}") {
  Utils.restoreCache(...)  
  Utils.saveCache(...)
}


• 스크립트 승인 최소화
• @NonCPS 메서드로 CPS 변환 최소화 → Jenkins sandbox 예외 줄이기
• Library 내 버전 관리
• Semantic Versioning으로 @Library('company-shared-lib@v1.2.3') 사용
• Self-Healing DSL

retry(3) { pipeline(config) }





결론: 끝판왕 동적 파이프라인
• 모듈화 & 재사용: Shared Library에 모든 공통 로직 통합
• 메타프로그래밍: CPS vs non-CPS, AST 변환으로 초경량 코드
• 템플릿화: GString 템플릿으로 복잡 스크립트 생성
• 파라미터 주도: 실행 시점 config로 런타임 파이프라인 조립

“이제 수백 개 Jenkinsfile을 한 곳에서 관리하며, 새로운 서비스가 생길 때마다 5줄 config만 추가하면 동작하는 지구 최강 파이프라인”을 직접 경험해 보세요! 여러분의 CI/CD가 한 단계 더 진화합니다.

반응형