본문 바로가기
CI.CD/Jenkins

[Jenkins] Upstream/Downstream: 거대 파이프라인 쪼개기 아키텍처

반응형

지금까지 우리는 하나의 거대한 Jenkinsfile 안에 빌드, 테스트, 소나큐브 검사, 도커 이미지 푸시, K8s 배포까지 모든 로직을 때려 넣는 단일 파이프라인(Monolithic Pipeline) 방식을 배웠습니다. 프로젝트 규모가 작을 때는 이것이 편합니다. 하지만 회사가 성장하여 수십 개의 마이크로서비스(MSA)가 얽히고, "A 서비스와 B 서비스가 모두 성공적으로 빌드된 후에야 C라는 통합 결제 서버를 빌드하고 배포하라"는 극악의 워크플로우(Workflow) 요구사항이 떨어지면 어떻게 될까요? 이 모든 것을 파일 하나에 욱여넣으면 코드는 2천 줄이 넘어가고, 중간에 작은 에러 하나만 터져도 2천 줄짜리 거대 공장이 통째로 멈춰버리는 끔찍한 사태가 발생합니다.

이번 [Jenkins 실무 완벽 가이드] 시리즈에서는 "크고 뚱뚱한 것을 작고 날렵하게 쪼개어 연결하라"는 소프트웨어 공학의 진리를 젠킨스에 적용한 'Upstream(상위) / Downstream(하위) Job 연쇄 호출 트리 아키텍처'에 대하여 다뤄보겠습니다.

⛓️ 1. Upstream과 Downstream의 개념 이해

강물이 상류에서 하류로 흐르듯, 젠킨스의 작업(Job)들도 의존성(Dependency)의 흐름을 가집니다.

  • Upstream (상위 Job): 나보다 먼저 실행되어야 하는 원인 제공자입니다. (예: 소스코드 컴파일 Job)
  • Downstream (하위 Job): 상위 Job이 무사히 성공한 직후, 그 바통을 이어받아 실행되는 결과 수신자입니다. (예: 운영 서버 배포 Job)

이렇게 파이프라인을 기능별로 잘게 쪼개어 독립적인 Job으로 분리해 두면, "배포 로직"만 고치고 싶을 때 수천 줄의 코드를 건드릴 필요 없이 딱 'Deploy Job' 하나만 열어서 수정하고 독립적으로 테스트해 볼 수 있는 엄청난 유지보수성의 향상을 가져옵니다.


⚙️ 2. build job 스텝: 바통 터치의 핵심 문법

A 파이프라인(Upstream)이 끝나기 직전에 B 파이프라인(Downstream)을 호출하려면, Declarative Pipeline의 steps 안에 build 라는 내장 명령어를 사용하면 됩니다.

// [Upstream 파이프라인: 빌드 및 테스트 전용]
pipeline {
    agent any
    stages {
        stage('Unit Test') {
            steps { echo "유닛 테스트 100% 통과!" }
        }
        
        stage('Trigger Downstream Deploy') {
            steps {
                echo "테스트가 완벽하므로, 즉시 하위 배포(Deploy) 파이프라인을 방아쇠(Trigger) 당깁니다!"
                
                // 🌟 핵심 문법: 젠킨스에 등록된 'Production-Deploy-Job' 이라는 다른 Job을 연쇄 호출합니다.
                build job: 'Production-Deploy-Job', 
                      wait: false // 하위 Job이 끝날 때까지 여기서 멈춰서 안 기다림 (비동기 방치)
            }
        }
    }
}

위 코드에서 wait: false 옵션은 실무에서 매우 중요합니다. 만약 true(기본값)로 둔다면, 상위 파이프라인은 하위 배포 작업이 끝날 때까지 젠킨스의 귀중한 워커(Executor) 쓰레드 1개를 멍하니 점유한 채 기다리게 됩니다(자원 낭비). wait: false를 줌으로써 상위 잡은 "내 할 일 끝! 바통 넘겼다!" 하고 쿨하게 퇴근(Success)하며 쓰레드를 반환합니다.


🎁 3. 런타임 파라미터(매개변수) 전달하기 (고급)

실무에서는 단순히 호출만 하고 끝나는 게 아닙니다. 상위 Job이 방금 구워낸 '도커 이미지 태그 번호''Git 커밋 해시값'을 하위 Job에게 매개변수(Parameter)로 넘겨주어야 하위 Job이 "아, V42 버전을 배포하라는 거구나!" 하고 알 수 있습니다.

stage('Call Downstream with Params') {
    steps {
        // 상위 Job의 빌드 번호(42)를 하위 Job의 'TARGET_VERSION' 이라는 파라미터로 예쁘게 포장해서 던집니다.
        build job: 'Production-Deploy-Job', 
              wait: false,
              parameters: [
                  string(name: 'TARGET_VERSION', value: "${env.BUILD_NUMBER}"),
                  booleanParam(name: 'ENABLE_SLACK_NOTIFY', value: true)
              ]
    }
}

이제 하위 배포 Job은 params.TARGET_VERSION 변수를 까보면 42라는 값을 받아 완벽하게 배포를 수행하게 됩니다.


🎯 4. 마무리 및 다음 단계

지금까지 뚱뚱하고 유지보수하기 힘든 거대 파이프라인을, 역할별로 작고 예쁘게 쪼개어 서로 런타임 데이터를 주고받으며 연쇄 폭발을 일으키게 만드는 'Upstream / Downstream 아키텍처'에 대해 다루어 보았습니다. 이 패턴을 마스터하면 수십 개의 MSA 서버 배포를 하나의 거미줄처럼 아름답게 엮어낼 수 있습니다.

파이프라인을 여러 개로 쪼개다 보니 한 가지 문제가 생겼습니다. 쪼개진 5개의 파이프라인마다 최상단에 def slackNotify() { ... } 같은 똑같은 커스텀 함수들이 중복으로 적혀있는 것입니다. 코드가 복붙(Copy & Paste)의 늪에 빠지고 말았습니다. 이어지는 38단계 포스팅에서는 "전사의 모든 파이프라인 공통 함수를 딱 한 곳에서 묶어서 통제한다!" 앞서 살짝 맛보았던 공유 라이브러리의 심화 버전, 'Jenkins 글로벌 라이브러리 vs 폴더 라이브러리의 완벽 해부'에 대해 아주 뼈 때리는 실무 노하우와 함께 파헤쳐 보겠습니다!

반응형