버전 정보 생성

HEUI SUNG LEE·2025년 9월 28일

개발(Dev)·검증(QA/Stage)·운영(Prod) 배포를 위한 버전 정보”를 일관되게 만들고, 추적·승격이 쉬운 기준을 정리하고 바로 쓸 수 있는 Jenkinsfile 패턴

핵심 원칙(요약)

한 번 빌드 → 다단계 승격(Dev→QA→Prod)
환경마다 새로 빌드하지 말고, 동일 아티팩트를 승격하세요. 버전은 빌드 순간에만 확정됩니다.

버전 = 사람 친화(SemVer/CalVer) + 기계 추적(Git SHA)
예) 1.4.0-rc.7+gabc1234 또는 2025.09.28-rc.7+gabc1234

환경별 접미사 규칙 통일

Dev: -alpha.<branch>.<build>

QA : -rc.<build>

Prod: 접미사 제거(정식 릴리스), 태그로 고정

여러 태그를 동시에 푸시(동일 이미지/패키지)
app:1.4.0-rc.7+gabc1234, app:1.4.0-rc.7, (선택) app:1.4.0 등.
단, Prod에서만 major.minor/major 추가 태그를 허용하고 latest는 운영 금지.

버전 전파는 원자적
산출물(JAR/WHL), Docker 이미지, Helm/Compose, 앱 메타(properties) 모두 동일 버전 문자열로 주입.

재현성과 감사성 강화
버전 문자열에 Git SHA, Build URL, (선택) SBOM/체크섬을 함께 보관. version.txt로 아티팩트에 포함.

권장 버전 체계(샘플)

기본: SemVer + 프리릴리스 + 빌드메타

Dev : 1.4.0-alpha.feature-login.153+gabc1234

QA : 1.4.0-rc.7+gabc1234

Prod : 1.4.0+gabc1234 (또는 단순 1.4.0 태그로 릴리스)

CalVer 선호 시: YYYY.MM.DD[-rc.N][-alpha.x]+gSHA 유지.
예) QA: 2025.09.28-rc.7+gabc1234

Jenkinsfile 공용 함수 (버전 생성)

아래 블록을 Jenkinsfile 최상단 script 또는 공유 라이브러리로 두고 호출하세요.

// 환경값 예: env.DEPLOY_ENV in ['dev','qa','prod']
// 필수: Multibranch/SCM 체크아웃 완료 상태

def computeVersion(String deployEnv) {
    // 1) Git 기초정보
    def sha   = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
    def dirty = sh(script: "git status --porcelain | wc -l", returnStdout: true).trim()
    def isClean = (dirty == "0")
    def branch = env.BRANCH_NAME ?: sh(script: "git rev-parse --abbrev-ref HEAD", returnStdout: true).trim()

    // 2) 태그 기반(없으면 CalVer로 폴백)
    def base
    def lastTag = sh(script: "git describe --tags --abbrev=0 2>/dev/null || echo ''", returnStdout: true).trim()
    if (lastTag) {
        base = lastTag
    } else {
        base = sh(script: "date +%Y.%m.%d", returnStdout: true).trim()
    }

    // 3) 환경별 규칙
    if (deployEnv == "dev") {
        // 브랜치명 안전화
        def safeBranch = branch.replaceAll('[^A-Za-z0-9._-]', '-')
        return "${base}-alpha.${safeBranch}.${env.BUILD_NUMBER}+g${sha}${isClean ? '' : '.dirty'}"
    }
    if (deployEnv == "qa") {
        return "${base}-rc.${env.BUILD_NUMBER}+g${sha}${isClean ? '' : '.dirty'}"
    }
    if (deployEnv == "prod") {
        // 정식 릴리즈(접미사 제거). base가 SemVer가 아니라 CalVer일 수도 있음
        return "${base}+g${sha}"
    }
    error "Unknown DEPLOY_ENV=${deployEnv}"
}

파이프라인 예시(빌드 1회, 환경별 승격)

Build 파이프라인: 한 번만 빌드 → 레지스트리/아티팩트에 보관

Deploy 파이프라인: 환경 파라미터로 버전 선택 후 배포(재빌드 금지)

(A) Build 파이프라인(JAR/Docker 생성)
pipeline {
  agent any
  options { timestamps() }
  stages {
    stage('Checkout'){ steps { checkout scm } }

    stage('Compute Version for Dev Build') {
      steps {
        script {
          env.DEPLOY_ENV = 'dev' // 빌드 단계는 dev 스냅샷 성격
          env.APP_VERSION = computeVersion(env.DEPLOY_ENV)
          sh "echo ${env.APP_VERSION} > version.txt"
        }
      }
    }

    stage('Package / Image Build') {
      steps {
        sh """
          # 예: Maven/Jar
          mvn -B -Drevision=${APP_VERSION} package

          # Docker
          docker build -t myrepo/myapp:${APP_VERSION} .
        """
      }
    }

    stage('Push Artifacts') {
      steps {
        sh """
          docker push myrepo/myapp:${APP_VERSION}
          # 아티팩트 저장소에도 version.txt/JAR 업로드 등
        """
      }
    }

    stage('Metadata') {
      steps {
        script {
          currentBuild.displayName = "#${env.BUILD_NUMBER} ${env.APP_VERSION}"
        }
      }
    }
  }
}

(B) Deploy 파이프라인(Dev/QA/Prod 공용)

실행 시 입력으로 버전을 고르거나, 브랜치/환경 규칙으로 자동 선택.

pipeline {
  agent any
  parameters {
    choice(name: 'DEPLOY_ENV', choices: ['dev','qa','prod'], description: '배포 환경')
    string(name: 'VERSION', defaultValue: '', description: '배포할 정확한 버전(빈값이면 규칙에 따라 계산/선택)')
  }
  stages {
    stage('Checkout'){ steps { checkout scm } }

    stage('Resolve Version') {
      steps {
        script {
          if (!params.VERSION?.trim()) {
            env.APP_VERSION = computeVersion(params.DEPLOY_ENV)
          } else {
            env.APP_VERSION = params.VERSION.trim()
          }
          echo "Deploy Version => ${env.APP_VERSION}"
        }
      }
    }

    stage('Retag for Env (if needed)') {
      when { expression { return params.DEPLOY_ENV != 'dev' } }
      steps {
        script {
          // 동일 이미지 재활용(빌드 금지)
          sh """
            docker pull myrepo/myapp:${APP_VERSION} || true
          """

          if (params.DEPLOY_ENV == 'qa') {
            // QA 표준 별칭(선택)
            sh """
              docker tag myrepo/myapp:${APP_VERSION} myrepo/myapp:qa-current
              docker push myrepo/myapp:qa-current
            """
          }

          if (params.DEPLOY_ENV == 'prod') {
            // Prod 릴리스 태그 고정 (SemVer일 경우만 추가 별칭 권장)
            def semver = sh(script: "echo '${APP_VERSION}' | sed -n 's/\\([0-9]\\+\\.[0-9]\\+\\.[0-9]\\+\\).*/\\1/p'", returnStdout: true).trim()
            if (semver) {
              def majorMinor = semver.split('\\.')[0..1].join('.')
              sh """
                docker tag myrepo/myapp:${APP_VERSION} myrepo/myapp:${semver}
                docker push myrepo/myapp:${semver}

                docker tag myrepo/myapp:${APP_VERSION} myrepo/myapp:${majorMinor}
                docker push myrepo/myapp:${majorMinor}
              """
            }
          }
        }
      }
    }

    stage('Deploy') {
      steps {
        // Compose/Helm/Ansible 등 버전 주입
        sh """
          sed -i "s#\\(image: myrepo/myapp:\\).*#\\1${APP_VERSION}#g" deploy/compose.yaml
          docker compose -f deploy/compose.yaml up -d
        """
      }
    }

    stage('(Prod Only) Git Tag') {
      when { allOf { expression { return params.DEPLOY_ENV == 'prod' } } }
      steps {
        script {
          // 보호된 브랜치에서만 수행 권장
          def tagName = sh(script: "echo '${APP_VERSION}' | sed -n 's/+.*//p'", returnStdout: true).trim()
          if (tagName) {
            sh """
              git config user.email "ci@example.com"
              git config user.name "jenkins"
              git tag -a ${tagName} -m "Release ${tagName}"
              git push origin ${tagName} || true
            """
          }
        }
      }
    }
  }
}

실무 체크리스트

 빌드 1회 원칙: QA/Prod는 재빌드 금지, 이미지/아티팩트만 승격

 환경별 접미사: Dev=-alpha.*, QA=-rc.*, Prod=없음(정식)

 Git 태그: Prod 릴리스에만 주석 포함(annotated) 태그 발행

 동일 버전 전파: Docker/Helm/Compose/앱 설정 모두 같은 문자열 사용

 추적 정보 포함: +g<shortSHA>, BUILD_URL, SBOM/체크섬(옵션)

 태그 폴백: 태그 없으면 CalVer로 폴백

 금지: 운영에서 latest 사용, 운영에서 재빌드
profile
Platform Engineer(MSA,DevOps,CA)/Data Engineer

0개의 댓글