재시도 로직
Jenkins Pipeline 에서 재시도 로직을 구현하는 방법에 대해 알아보겠습니다.
도입하게 된 계기
이 방법을 찾게 된 계기는 Jenkins Pipeline 을 사용하면서, 특정 스텝에서 실패할 경우 재시도를 해야하는 상황이 발생했기 때문입니다. 저 같은 경우, 현재 블로그를 개인 로컬 서버에서 운영 중입니다. 그런데 가끔 인터넷이 불안정하여, 빌드가 실패할 때가 있습니다.
잠시 발생한 일이라 다시 빌드를 하면 성공하지만, 매번 제가 빌드 실패를 확인하고 다시 빌드를 하는 것은 번거로운 일이었습니다. 그래서 재시도를 해보자! 라는 생각이 들었습니다.
환경
저는 groovy 를 사용하여 Jenkins Pipeline 을 작성하고 있습니다. 그래서 이 글에서는 groovy 를 사용하여 재시도 로직을 구현하는 방법에 대해 설명하겠습니다.
구현
재시도 로직을 구현하기 전에, 모든 stage 에서 실패 가능성이 있습니다. 따라서 이를 함수화하여 작성하면 편리합니다.
아래는 제가 구현한 재시도 로직입니다. 참고로, pipeline 자식요소와 동일한 depth 에 위치해야 모든 step 에서 전역적으로 사용할 수 있습니다.
def runWithRetry(Closure body) {
def retries = 3
while (retries > 0) {
try {
body()
break // 성공하면 루프 종료
} catch (Exception e) {
retries--
if (retries == 0) {
error "Failed after 3 retries"
}
echo "Retrying... ${retries} attempts left"
sleep(3) // 3초 대기 후 재시도
}
}
}
사용법
위에서 작성한 runWithRetry
함수를 사용하여 재시도 로직을 구현할 수 있습니다.
저는 보통 stage 내부에서 인터넷 연결이 불안정할 경우, 해당 로직만 재시도하도록 구현하고 있습니다.
아래는 사용 예시입니다.
pipeline {
agent any
stages {
stage('Retry') {
steps {
script {
// 재시도 이전 동작할 로직 (optional)
runWithRetry {
sh 'npm install'
}
// 재시도 이후 동작할 로직 (optional)
}
}
}
}
}
위와 같이 사용하면, npm install
명령어가 실패할 경우, 3번까지 재시도를 합니다.
분석
작성한 코드를 분석해보겠습니다.
1. 함수 정의와 역할
def runWithRetry(Closure body)
- runWithRetry 함수
- 파라미터로 Closure(익명 함수 또는 코드 블록)를 받습니다.
- 이 함수는 전달받은 body 클로저를 실행하며, 실패할 경우 최대 3번까지 재시도합니다.
2. 변수 초기화
def retries = 3
- retries 변수
- 재시도 횟수를 나타내는 변수입니다.
- 최대 3번까지 재시도합니다.
3. 루프를 통한 재시도 로직
while (retries > 0) {
try {
body()
break // 성공하면 루프 종료
} catch (Exception e) {
retries--
if (retries == 0) {
error "Failed after 3 retries"
}
echo "Retrying... ${retries} attempts left"
sleep(3) // 3초 대기 후 재시도
}
}
-
while (retries > 0)
- retries가 0이 될 때까지 루프를 반복합니다.
- 매번 body()를 실행하며, 성공 시 break로 종료합니다.
-
주요 로직
- try-catch 블록
- body() 실행 중 예외가 발생하면, catch 블록으로 이동합니다.
- 성공 시
- body()가 예외 없이 실행되면, break를 통해 루프를 종료합니다.
- 실패 시
- retries—: 재시도 횟수를 1 감소합니다.
- 마지막 시도 확인: retries가 0이면 error를 발생시켜 파이프라인을 실패 상태로 만듭니다.
- echo: 재시도 횟수를 출력합니다.
- sleep(3): 3초 대기 후 재시도합니다.
- try-catch 블록
마무리
이렇게 구현한 재시도 로직을 사용하면, 특정 스텝에서 실패할 경우 재시도를 할 수 있습니다.
추가로 개선점도 생각 중인데요, 아래 내용을 확인해주세요.
재시도 간 대기 시간 설정
현재는 고정된 3초 대기 시간이 설정되어 있지만, 지수 백오프(Exponential Backoff) 방식을 도입하면 더 효과적일 것 같습니다.
sleep(2 ** (3 - retries)) // 2초, 4초, 8초 점진적 대기
재시도 횟수 파라미터화
현재는 retries = 3으로 고정되어 있지만, 함수 호출 시 파라미터로 받으면 유연성이 증가할 것 같습니다.
def runWithRetry(int maxRetries, Closure body) {
def retries = maxRetries
// 이후 로직은 동일
}