젠킨스(Jenkins) 파이프라인(Pipeline)을 통한 배포 작업 수행기(Param, Input, Google Chat,Revision 선택)

2024. 6. 20. 18:23Jenkins

반응형

 

애증의 젠킨스를 다시 만나다.

젠킨스 할아버지를 다시 만났다...!

배포자동화는 약간 애증의 관계에 서있다고 생각한다.

첫 회사부터 현재의 회사까지 맨날 배포 자동화를 하고싶어 설치하고 머리 싸메고 고민하는 시간을 쏟았던 대상이라 더 그런 것 같다.

회사들이 배포자동화가 구성이 안되어있는 경우는 다음과 같은 케이스들이 있는 것 같다.(참고로 모두 내가 들었던 이야기들이다)

1. 수동으로 하면되지 그게 필요해?
2. 그냥 파일 업로드 하고 압출 풀고 해주는 것에 대해서 쉽게 해둔거 아니야?
3. 운영쪽에 올라가는 파일인데 위험하게 자동화를 꼭 해야해?
4. 다른일이 더 바쁘지 않아? 거기에 리소스를 쏟아야 하는거야?

등등 여러가지 이유를 참 많이 들었고, 현재는 악착같이 시간을 쪼개서 테스트하고 확인하여 현재 회사에서는 사용중에 있긴 하다.......!

젠킨스와 관련된 기본적인 세팅이나 그런것들에 대한 건 자세히 설명된 블로그나 여러가지가 있다. 그래서 해당부분들에 대한 기본 세팅들은 간단하게만 설명하고 날 힘들게 만든 파이프라인 기준으로 설명하고자 한다...!
(현재설명하는 배포서버는 도커 형태가 아닌 클라우드 서버에 yum install로 설치된 jenkins를 기준으로 동작중이다.)

기본사항에 대한 정리

설치되어 있어야 하는 플러그인

1. SVN기준 프로젝트가 현재는 중심이라 svn 관련 플러그인이 필요하다.

2. SSH로 접근하여 서버에 파일을 push하기 위해서 해당 플로그인이 필요하다.

세팅이 잡혀있어야 하는  Tool들 관련 정보 

Jenkins 관리 > Tools 에서 내부적으로 빌드 할 때 사용할 JDK버전과 Maven버전등에 대한 설정은 되어있어야 한다.

1. JDK

2. 메이븐

 

파이프라인에 대해서 자세히 설명하는 블로그들이 없다

그 이유는 거의 새로 배워야하기도 하고.. 일단 자세히 몰랐는데 알아보니 젠킨스 내부에서 Pipeline Syntax에 대해서 Generate 기능을 어느정도 제공하고 있다.

예를들어 svn check을 하고싶다고 하면..? 신규 문법을 기반으로 pipeline을 생성 해야하는데 이 경우 다음과 같이 되어있는 예시로 만들어보고 테스트를 수행해보면 된다.

코드 작성전 선택화면

Generate Pipeline Script를 누른 결과물 모습

그에 따라 내가 구성한 파이프라인은 다음과 같다.(Pipeline 문법에 대한 자세한)

파이프 라인 구성 순서

  1. 최초 배포 수행시 특정 revision에 대해서 선택하여 배포 할 것인지 booleanParam을 받는다.
    다음과 같은 모습이다.
    입력받는 모습
  2. 빌드시 사용하는 tools에 대한 버전을 확인한다.
  3. 1에서 입력한 값에 따라 boolean값이 true인 경우에만(특정 revision을 입력받는 경우에만) revision정보를 입력받는다.
    해당 값에 따라 있는 경우 revision을 받고 없는 경우 통과 한다.(아래)
    Revision input 모습


  4. SVN checkout을 수행한다.
  5. Maven Build 를 수행한다.
  6. Publish Over SSH를 통하여 빌드된 파일을 배포대상 서버로 전송 및 기타 작업을 수행 한다.
  7. Google Chat을 통하여 배포에 대한 내용을 전달받는다.(구글챗 스페이스에서 전달받을 Hook 주소를 생성해두어야만 한다. + Google Chat Notification  플러그인 설치 필요)
    플러그인 모습
    Google Chat Notification 플러그인 설치 모습

위와같은 스텝으로 동작할 때 최신버전의 젠킨스에서는 다음과 같은 파이프라인의 구성 모습을 시각적으로 보여준다....!

파이프라인 Stages 모습

실제로 Google Chat으로 보내지는 모습의 경우 대략적으로 다음과 같은 모습을 한다...!

이전 배포 버전과 svn상에서 변화가 없는 경우

이전 배포 버전과 svn상 변화가 있는 경우(실제 운영상의 내용이라 살짝 블러처리를 하였다..!)

 

실제로 적용된 파이프라인의 경우 다음과 같다.

// svn
def credentialsId = env.credentialsId
def remoteSvn = 'SVN주소'
// ssh
def sshServer = '환경변수에 ssh서버주소'
def sourceFiles = env.sourceFiles
def removePrefix = env.removePrefix
def execCommand = '배포시 수행할 스크립트'
def userInput = null

pipeline {
    agent any
    
    parameters {
        booleanParam(name: 'CONFIRMATION', defaultValue: false, description: '배포시 특정 버전(Revision)을 선택하는 경우 체크해주세요.')
    }

    tools {
        maven 'MAVEN'
        jdk 'JDK1.8.0_202' 
    }

    stages {
        
        stage('Check Java and Maven Versions') {
            steps {
                script {
                    echo 'Java version and Maven version checking....'
                    sh 'java -version'
                    sh 'mvn --version'
                }
            }
        }
        
        stage('Revision Input') {
            
            when {
                expression {
                    params.CONFIRMATION == true
                }
            }
            steps {
                script {
                    userInput = input(
                        id: 'userInput',
                        message: 'Deploy to production?', 
                        submitterParameter: 'submitter',
                        submitter: 'admin',
                        parameters: [
                            [$class: 'TextParameterDefinition', description: '특정버전으로 배포하는 경우 Revision값을 입력해주세요. 값이 없는 경우 가장 최신버전으로 Checkout 됩니다.', name: 'revision']
                        ],
                    )
                }
            }
        }
        
        stage ('Checkout') {
            steps {
                script {
                    //Revision
                    def checkoutUrl = userInput ? "${remoteSvn}@${userInput['revision']}" : remoteSvn
                    
                    //check out
               	    def svnInfo = checkout([
                    	$class: 'SubversionSCM'
                    	, additionalCredentials: []
                    	, excludedCommitMessages: ''
                    	, excludedRegions: ''
                    	, excludedRevprop: ''
                    	, excludedUsers: ''
                    	, filterChangelog: false
                    	, ignoreDirPropChanges: false
                    	, includedRegions: ''
                    	, locations: [
                    		[cancelProcessOnExternalsFail: true
                    			, credentialsId: credentialsId
                    			, depthOption: 'infinity'
                    			, ignoreExternalsOption: true
                    			, local: '.'
                    			, remote: checkoutUrl]
                    	]
                    	, quietOperation: true
                    	, workspaceUpdater: [$class: 'UpdateUpdater']
                    ])
					
					echo "checkout Revision : ${svnInfo.SVN_REVISION}"
					env.SVN_REVISION = svnInfo.SVN_REVISION
                    
                }
            }
        }
        
        stage('Maven Build') {
            steps {
                // Maven 빌드 실행
                script {
                    sh 'mvn clean package -Dmaven.test.skip=true'
                }
            }
        }
        
        stage('Push over SSH') {
              steps {
                  //ssh 접근 및 publush 수행동작
                  sshPublisher(
                  	publishers: [
                  		sshPublisherDesc(
                  			configName: sshServer
                  			, transfers: [
                  				sshTransfer(
                  					cleanRemote: false
                  					, excludes: ''
                  					, execCommand: execCommand
                  					, execTimeout: 120000
                  					, flatten: false
                  					, makeEmptyDirs: false
                  					, noDefaultExcludes: false
                  					, patternSeparator: '[, ]+'
                  					, remoteDirectory: ''
                  					, remoteDirectorySDF: false
                  					, removePrefix: removePrefix
                  					, sourceFiles: sourceFiles
                  					, usePty: true
                  				)
                  			]
                  			, usePromotionTimestamp: false
                  			, useWorkspaceInPromotion: false
                  			, verbose: true
                  		)
                  	]
                  )
                  
              }
              
        }
        

    }//end stage
    
    post {
        always {
            script {
                def message = createBuildMessage()
                googlechatnotification url: env.GOOGLE_CHAT_WEBHOOK_URL, message: message
            }
        }
    }//post end
    
}//pipeline end


def createBuildMessage() {
    def buildResult = currentBuild.currentResult ?: 'SUCCESS'
    def statusIcons = [SUCCESS: '✔️', UNSTABLE: '⚠️', FAILURE: '❌']
    def colors = [SUCCESS: 'green', UNSTABLE: 'yellow', FAILURE: 'red']

    def buildStatusIcon = statusIcons[buildResult] ?: '🔵'
    def color = colors[buildResult] ?: 'gray'
    def changes = getChangeString()

    def message = "${buildStatusIcon} ${buildResult}: *${env.JOB_NAME}* - Build ${env.BUILD_ID} (<${env.BUILD_URL}|Details>)\n현재적용(revision):${env.SVN_REVISION}\n${changes}"
    
    return message
}

def getChangeString() {
    def changeString = ""
    def changeSetItems = []

    for (changeSet in currentBuild.changeSets) {
        for (entry in changeSet.items) {
		        def revisionInfo = entry.commitId ?: entry.revision
				changeSetItems << " #️⃣ ${entry.msg} by ${entry.author} (${revisionInfo})"
        }
    }

    if (changeSetItems.isEmpty()) {
        changeString = "No changes"
    } else {
        changeString = "Changes:\n" + changeSetItems.join("\n")
    }

    return changeString
}

 

파이프라인 중에서 몇몇 부분을 나눠서 보자면 다음과 같다.

최초 빌드시 boolean 값을 입력받는 부분이다.

    parameters {
        booleanParam(name: 'CONFIRMATION', defaultValue: false, description: '배포시 특정 버전(Revision)을 선택하는 경우 체크해주세요.')
    }

최초 빌드시 입력받은 boolean 값이 있는 경우에만 사용자로부터 input 값을 입력받는 스테이지 이다.

        stage('Revision Input') {
            
            when {
                expression {
                    params.CONFIRMATION == true
                }
            }
            steps {
                script {
                    userInput = input(
                        id: 'userInput',
                        message: 'Deploy to production?', 
                        submitterParameter: 'submitter',
                        submitter: 'admin',
                        parameters: [
                            [$class: 'TextParameterDefinition', description: '특정버전으로 배포하는 경우 Revision값을 입력해주세요. 값이 없는 경우 가장 최신버전으로 Checkout 됩니다.', name: 'revision']
                        ],
                    )
                }
            }
        }

구글쳇에 보낼 SVN의 정보를 가공하는 메서드이다.

def createBuildMessage() {
    def buildResult = currentBuild.currentResult ?: 'SUCCESS'
    def statusIcons = [SUCCESS: '✔️', UNSTABLE: '⚠️', FAILURE: '❌']
    def colors = [SUCCESS: 'green', UNSTABLE: 'yellow', FAILURE: 'red']

    def buildStatusIcon = statusIcons[buildResult] ?: '🔵'
    def color = colors[buildResult] ?: 'gray'
    def changes = getChangeString()

    def message = "${buildStatusIcon} ${buildResult}: *${env.JOB_NAME}* - Build ${env.BUILD_ID} (<${env.BUILD_URL}|Details>)\n현재적용(revision):${env.SVN_REVISION}\n${changes}"
    
    return message
}

def getChangeString() {
    def changeString = ""
    def changeSetItems = []

    for (changeSet in currentBuild.changeSets) {
        for (entry in changeSet.items) {
		        def revisionInfo = entry.commitId ?: entry.revision
				changeSetItems << " #️⃣ ${entry.msg} by ${entry.author} (${revisionInfo})"
        }
    }

    if (changeSetItems.isEmpty()) {
        changeString = "No changes"
    } else {
        changeString = "Changes:\n" + changeSetItems.join("\n")
    }

    return changeString
}

 

그외 부분에서 env로 불러오는 환경변수의 경우 젠킨스 시스템내에서 등록해둔 전역변수이다. 여러가지 프로젝트에서 적용되기를 원하는 경우 사용하면 좋을 듯 하다.

Jenkins관리 > System 내의 Global properties 에서 등록이 가능하다.(예를들면 다음과 같은 모습이다.)

 

 

그리고 다른사람이 젠킨스를 통해서 배포를 수행하는데 위 글이 조금이나마 도움이 되었으면 합니다...! 현재는 위 세팅만으로 매우 행복하게 배포 중이지만 여러가지 추가적인 시도가 더 있을 수도 있습니다..!

반응형