Generic Webhook Trigger 플러그인 활용 Jenkins 파이프라인 조건부 처리

오형택·2024년 3월 23일
0

Play-Spark

목록 보기
8/8
post-thumbnail

SSAFY에서 제공하는 배포 환경이 EC2 인스턴스 하나인 데다가 GitLab도 레포 하나를 프론트/백이 같이 쓰다보니 CI/CD를 파트별로 분리하는 데에 어려움이 많았다. 프론트↔백의 경우 2개의 develop 브랜치를 사용함으로써 브랜치명으로 파이프라인 트리거를 설정해서 나누는 것이 어렵지는 않았지만, 백엔드 내에서 여러 기능 서버를 띄울 경우는 서버별로 브랜치를 나누는 데에 제한이 있었다.

우리 프로젝트의 경우도 Spring/Play(추천) 두 개의 서버를 운영하는데 기능 개발은 두 서버가 다른 브랜치에서 수행한다고 할지라도 결국은 하나의 develop 브랜치에서 합쳐져야 하기에 기존에 사용하던 방식으로는 Spring 서버에 대한 변경이 생기든 Play 서버에 대한 변경이 생기든 두 서버를 모두 빌드/배포해야하는 문제가 있었다.

각 서버의 작업 주기가 다른데 Spring에서 변경 사항이 생겼다고 멀쩡히 동작하던 Play 서버가 다시 빌드되는건 말이 안된다 싶었고, 당연히 반대 경우도 마찬가지여서 이 과정을 분리해야겠다고 느꼈다.

이 상황을 해결하기 위해서는 이벤트가 일어난 target branch의 이름 뿐만 아니라 merge의 source branch의 이름을 파이프라인 트리거에서 활용할 수 있어야 했다. 기존에 사용하던 GitLab 플러그인을 사용할 경우는 source branch 정보를 얻어올 수가 없어서 다른 방법을 찾아야 했고, 또다시 숱한 구글링+GPT 끝에 Jenkins의 Generic Webhook Trigger 플러그인(이제 GWT라고 부르겠음)에 대해 알게 되었다.

GWT는 GitLab을 포함한 다양한 소스로부터 오는 webhook 요청을 처리할 수 있는 보다 범용적인 플러그인으로 webhook 요청 내의 특정 데이터를 분석하여 Jenkins 파이프라인의 변수로 사용할 수 있다. 범용 플러그인여서 그런지 GitLab 플러그인과 달리 webhook 요청 url을 Jenkins pipeline 별로 설정할 수 없고 모든 파이프라인이 http://JENKINS_URL/generic-webhook-trigger/invoke을 요청 url로 사용해야한다. 요청 source 별로 다른 파이프라인을 지정하고 싶다면 GWTtoken 필드를 이용하면 된다. token 필드는 webhook 요청의 쿼리 파라미터로 전달되며, 파이프라인별로 다른 token 문자열을 사용해 어떤 source로부터의 webhook인지 구분할 수 있다.

GitLab Webhook 트리거를 설정할 때, push event와 달리 merge request의 경우 webhook이 발생할 브랜치를 지정할 수 없기 때문에 해당 repository에서 발생하는 모든 MR 이벤트에 대한 webhook이 발생하고, Jenkins에서 webhook 요청 데이터를 활용해서 파이프라인의 트리거 여부를 판단해줘야한다.

GitLab Webhook 요청의 데이터는 json 형태이고, 현 상황에 유용한 데이터는 아래와 같다.

{
	"object_kind": String,     // Webhook 이벤트 종류(푸시 이벤트: "push", MR 이벤트: "merge_request")
	...,
	"object_attributes": {
		...,
		"state": String,         // Merge Request 상태(opened, closed, **merged**, ..etc), 여기서는 merged를 사용.
		"source_branch": String, // merge source branch 이름
		"target_branch": String, // merge target branch 이름
	}
}

해당 데이터는 아래와 같이 GWTPost content parameters 섹션에서 받아와 환경 변수로 저장하 pipeline script나 이어 설명할 Optional filter 섹션에서 활용할 수 있다.

빌드/배포 파이프라인을 트리거하기 위한 요청 데이터의 요구사항은 아래와 같았다.

  • EVENT_TYPEmerge_request이다.
  • MR_STATEmerged이다.
  • MR_SOURCE_BRANCH<spring-서버-작업-브랜치명> 또는 <play-서버-작업-브랜치명>이다.
  • MR_TARGET_BRANCH<백엔드-develop-브랜치명>이다.

이를 처리하기 위한 방법으로는 ①pipeline script에서 조건문을 통해 필터링해주는 방법과 ②Optional filter 섹션을 사용하는 방법이 있는데 ①의 경우 필터링 조건에 걸릴 때마다 강제로 error step을 발생시키는 방식으로 MR의 merged를 제외한 매 이벤트마다 Jenkins 빌드 히스토리에 빌드 실패 기록이 남아 보기가 좀 안 좋아서 ② 방식을 사용하기로 했다.

Optional filter 섹션에서는 정규표현식을 활용한 필터링을 통해 파이프라인 실행 여부 자체를 결정할 수 있어 빌드 히스토리에 실패 기록을 남기지 않을 수 있다. Optional filter에서는 Expression 필드와 Text 필드를 설정할 수 있는데, Expression은 필터링에 사용될 정규표현식이고 Text는 필터링될 문자열이다. 앞서 저장한 환경 변수는 아래와 같이 prefix에 ‘$’를 붙여서 Text에서 사용할 수 있다.

사용한 Optional filter
Expression: merge_request merged (<spring-서버-작업-브랜치명>|<play-서버-작업-브랜치명>) <백엔드-develop-브랜치명>
Text: $EVENT_TYPE $MR_STATE $MR_SOURCE_BRANCH $MR_TARGET_BRANCH

pipeline script에서는 아래와 같이 환경 변수를 사용할 수 있다.

pipeline {
	agent any

  	environment {
		// Optional filter를 통해 트리거 조건은 모두 만족시킨 시점이므로,
		// 파이프라인에는 source branch만 필요
    	SOURCE_BRANCH = "${env.MR_SOURCE_BRANCH ?: 'manual-build'}"
  	}

  	stages {
		stage('build') {
			steps {
				script {
					if (SOURCE_BRANCH == '<spring-서버-작업-브랜치명>') {
				  		buildSpring()
					} else if (SOURCE_BRANCH == '<play-서버-작업-브랜치명>') {
						buildPlay()
					} else {   // 수동 빌드 시 전체 빌드 (parallel 블록으로 병렬 처리)
                    	parallel springBuild: {
							buildSpring()
                        }, playBuild: {
							buildPlay()
                        }
					}
                }
  ...
}

Reference

[Jenkins CI/CD] 4. WebHook을 이용한 자동 배포

profile
개발자 지망생

0개의 댓글