이미지 빌더 도구 Jib를 통해 자바 애플리케이션 빌드 속도 개선하기

lango·2024년 10월 10일
1

데브옵스(DevOps)

목록 보기
1/3
post-thumbnail

들어가며

 최근 실무에서 백엔드 모듈의 빌드 배포를 담당하는 CI/CD 파이프라인을 리뷰하고 최적화할 기회가 있었어요. 이전에 개발환경을 급하게 구축하게 되면서, 파이프라인의 여러 부분에 개선이 필요한 상황이었는데요.

 특히, 운영하는 애플리케이션 모듈이 약 10개나 있었고, 각각의 모듈을 이미지로 빌드하는 시간이 최대 5~6분까지 걸려 이를 줄이는 것이 최우선 목표였어요.

 여기서 Jib라는 이미지 빌더 도구를 사용하여 자바 애플리케이션 이미지 빌드 시간을 거의 1~2분 정도로 단축시킬 수 있었는데, 적은 시간을 투자했음에도 불구하고 꽤 만족스러운 결과를 얻을 수 있었어요. 그래서 이번 글에서는 Jib를 통해서 자바 애플리케이션의 이미지 빌드 속도를 어떻게 개선했는지에 대해 이야기해보려 해요.




기존 파이프라인의 이미지 빌드 방식은?

 기존에는 도커(Docker)를 이용해 이미지를 빌드하고 레지스트리에 빌드한 이미지를 업로드하는 방식으로 백엔드 자바 애플리케이션 이미지를 구성하고 있었어요.

 젠킨스를 제공하는 머신의 도커 데몬을 통해서 이미지를 빌드하는 방식이죠. 애플리케이션 이미지 빌드에 참조되는 도커파일(Dockerfile)의 명세는 간단하게 이루어져 있어요. 자바 애플리케이션을 Gradle로 빌드하고, 빌드한 결과물인 jar 파일을 jdk 베이스 이미지 기반으로 실행시키는 두 단계의 멀티 스테이지를 통해 이미지 스펙을 정의하게 되어요.

🔍 2단계의 멀티 스테이지 구성
1. 빌드 스테이지: Gradle 베이스 이미지를 기반으로 Gradle을 통해 자바 애플리케이션을 빌드한다.
2. 배포 스테이지: JDK 베이스 이미지 기반으로 빌드된 jar파일 실행한다.


도커로 이미지 빌드하는게 뭐가 문제야?

 앞에선 살펴본 이미지 빌드 과정은 젠킨스의 CI/CD 파이프라인 수행 시간을 기준으로 빠르면 3~4분, 느릴 때는 6~7분 정도까지도 소요됐어요. 사실 CD 단계는 GitOps로 구성된 메니페스트 저장소의 파일을 변경하는 작업만 수행하기 때문에 CI에서 가장 오랜 시간이 걸린다고 볼 수 있겠네요.

 사실 프로젝트 개발 초기에는 백엔드 모듈 수가 많지 않아서 큰 문제는 아니었는데요. 프로덕트의 아키텍처가 확장되면서 모듈 수가 점점 늘어났어요. 문제는 여기서 발생합니다. 소프트웨어 개발자와 인프라 개발자 시점에서 어떤 부분이 문제가 되었는지 간단하게 설명해볼게요.

🤔 소프트웨어 개발자 입장에서는!

 팀 스프린트로 할당받은 티켓을 통해 업무분장을 받아 개발할 때, 1~2개의 정도의 모듈 변경점을 배포해야 하는 정도라면 몇 분 정도는 기다릴 수 있는데, 3~4개 이상의 모듈 변경점을 배포해야 한다면, 개발환경에 배포하기까지 꽤 오랜 시간을 기다려야 했어요.

 저희 팀의 특성상 개발환경에 자주 배포해야 하는지라 젠킨스의 리소스도 많이 잡아먹을 수밖에 없었고, 오래 걸리는 파이프라인 잡이 여러 개 자주 쌓이곤 했어요. 결국, 이미지를 빌드하는 CI 단계에서의 대기 시간이 소프트웨어 개발자들의 능률과 생산성에 영향을 주게 된거죠.

🧐 인프라 개발자 입장에서는!

 프로덕트 서비스를 제공할 개발환경과 검증환경, 그리고 운영환경을 구축할 때는 보통 CI/CD 파이프라인을 통해 단일 모듈을 하나씩 배포하기보다는 전체 모듈을 모두 배포할 수 있는 파이프라인을 구성할 것 같은데요. 저희도 같아요. 소프트웨어 개발자들을 위한 단일 모듈을 배포하는 파이프라인도 구성하지만, 전체 모듈을 빌드하고 배포할 수 있는 파이프라인도 구성한답니다.

 물론, 새로운 환경을 구축하는 일이 그리 자주 생기는 업무는 아니지만, 애플리케이션의 프로파일마다 다른 네임스페이스로 구분하여 배포해야 할 경우도 있기 때문에 인프라 개발자에게도 이미지 빌드 시간은 꽤나 신경쓰이는 부분이었어요.


Jib 이미지 빌더 도구 도입!

 위와 같은 상황에서 여러모로 골칫거리인 자바 애플리케이션들의 이미지 빌드 시간을 어떻게 줄여볼 수 없을까 팀원분과 고민하고 PoC 해보다가 Jib라는 도구를 도입해보기로 했어요. 개인적으로 Jib를 처음 접했을 때 직관적이어서 마음에 들었고, 아래와 같은 이유들로 팀원들에게 도입 의사를 제안하고 승낙받을 수 있었어요.

🙋🏻‍♂️ Jib를 선택한 이유는!

  • 손쉽고 간편하게 개발하고 빠르게 파이프라인에 반영할 수 있다.
  • 도커 데몬(Docker Daemon) 없이도 이미지를 만들 수 있다.
  • 단순히 이미지를 빌드하는 것뿐만 아니라, 원격 이미지 레지스트리에 이미지를 업로드하는 기능도 지원한다.

Jib가 뭘까?

 Jib는 도커 데몬(Docker Daemon) 없이 자바로 구성된 애플리케이션을 컨테이너 이미지로 만들 수 있도록 지원하는 도구에요. Google에서 개발한 오픈 소스로 Maven 및 Gradle 빌드 도구에서도 사용할 수 있고 CLI 형태로도 빌드 기능을 지원합니다. 구글에서 정리한 자료를 첨부하니 참고하시기 바래요.

Jib는 어떻게 동작하길래 도커보다 빠른거야?

 Jib에 대한 구체적인 내용이나 동작원리는 이미 많은 분들이 잘 정리해두신 것 같아서 핵심만 간단하게 짚고 넘어가려 합니다.

 Docker를 이용한 이미지 빌드 과정에서는 레이어 캐싱을 활용하여 빌드 속도를 보장하는데 애플리케이션 소스 코드 변경점이 생길 경우 모든 레이어를 다시 빌드하는 방식으로 동작합니다. 즉, 변경점이 매우 적은데도 jar 파일을 다시 빌드하고 이미지로 구성하는 방식인거죠.

 그에 반해, Jib는 자바 애플리케이션을 기본 이미지 레이어(Base Image Layer), 의존성 레이어(Dependency Layer, 리소스 레이어(Resource Layer), 클래스 레이어(Class Layer) 등의 여러 레이어로 분리하여 빌드해요. 그래서 애플리케이션의 의존성은 변경되지 않고 리소스와 클래스만 변경되었다면 의존성 레이어는 빌드하지 않고 리소스 레이어와 클래스 레이어만 빌드하게 됩니다. 결국, 사소한 변경점이 생길때마다 모든 레이어에서 다시 빌드하는 것과 일부 레이어에서만 빌드하는 것의 차이가 속도 측면에서 이점을 가져다 준다고 생각이 드네요.

💡 애플리케이션에 변경점이 생긴 경우 모든 레이어를 다시 빌드하는 Docker와 달리, 변경점에 대한 레이어만 재빌드하여 컨테이너 이미지로 교체하는 Jib가 빌드 속도 측면에서는 빠르다!


Gradle Jib 플러그인 적용해보기

 앞에서 Docker를 통한 이미지 빌드보다 Jib를 이용한 이미지 빌드가 더 빠를 것이다라고 이야기 했으니 이제 직접 자바 애플리케이션 빌드에 Jib를 활용하여 얼마나 빌드 속도를 줄일 수 있는지 소스 코드에 반영해보고 얼마나 빨라지는지 직접 비교 분석 해볼까요?

1. Jib 플러그인 의존성 추가하기

 애플리케이션 의존성을 관리하는 build.gradle 파일 내 plugins 블록에 jib-gradle-plugin 플러그인 정보를 추가합니다.

plugins {  
	... // 생략
	id 'com.google.cloud.tools.jib' version '3.4.3'
}

2. 애플리케이션의 jib 빌드 설정 명세하기

 플러그인 의존성을 추가했다면, 다시 한번 build.gradle 파일 내 jib 블록을 확장하여 컨테이너 이미지화에 필요한 속성들을 명세하면 됩니다.

jib {
	// 베이스 이미지 설정  
    from {  
		image = '<레지스트리 베이스 이미지 경로>/openjdk:17-ea-17-slim' 
    }  
    
    // 이미지 산출물 설정  
    to {  
		image = '<레지스트리 배포 이미지 경로>/<대상 이미지명>:<태그명>' 
    }    
    
    // 컨테이너 내부 환경 변수 설정    
    container {  
		environment = [  
             'SPRING_PROFILES_ACTIVE': 'dev',  
             'TZ': 'Asia/Seoul'  
       ]  
    }
}

 jib 블록의 from 구문에서는 애플리케이션 이미지 구성을 위한 베이스 이미지를 지정합니다. 그리고 to 구문에서는 배포할 이미지 명세를 선언해야 해요. 이미지 레지스트리 경로와 대상이 될 이미지명과 더불어 태그를 기입하면 됩니다.

 마지막으로 container 구문은 애플리케이션 이미지가 실행될 때 매개변수로 주입해줄 설정들을 관리할 수 있어요. 위 명세에서는 애플리케이션을 dev 프로파일로 실행시키기 위한 설정과 타임존 설정을 반영해주었어요.

3. jib로 빌드하기

 1번과 2번 설정을 적용했다면 jib 명령으로 빌드를 수행하면 됩니다.

./gradlew jib

여기서 잠깐!

앞에서 살펴본 2번 사항을 설정하기 위해 모든 애플리케이션의 build.gradle 파일을 수정해야 하는데, jib 명령을 수행할 때 필요한 정보들을 아래와 같이 jib 명령 파라미터로 부여하여 빌드할 수 있어요. 만약 모든 애플리케이션의 컨테이너 이미지 구성을 위한 설정이 중복된다면 jib 명령시 파라미터를 부여하여 2번 설정을 생략할 수 있답니다!

./gradlew jib \  
	-Djib.from.image=<레지스트리 베이스 이미지 경로>/openjdk:17-ea-17-slim \  
	-Djib.to.image=<레지스트리 배포 이미지 경로>/<대상 이미지명>:<태그명>

Docker와 Jib의 이미지 빌드 속도 비교

 자, 마지막으로 기존 Docker 빌드로 이미지를 만드는 시간과 Jib 빌드로 이미지를 만드는 시간을 비교해볼게요. time 명령을 통해서 Docker 빌드와 Jib 빌드 시간을 측정해보았어요.

Docker 빌드 소요 시간

Docker 빌드 시간 측정을 위해 사용한 명령은 다음과 같아요.

# docker build 명령과 docker push 명령을 수행하는 시간 측정
time ( docker build -t <레지스트리 배포 이미지 경로>/<대상 이미지명>:<태그명> . && docker push <레지스트리 배포 이미지 경로>/<대상 이미지명>:<태그명> )

 Docker 빌드를 사용했을 때는 도커파일에 명세된 gradle build를 수행하고 도커 이미지를 만들고 레지스트리에 업로드하기까지 대략 1분 56초 정도가 걸렸네요.

Jib 빌드 소요 시간

Jib 빌드 시간 측정을 위해 사용한 명령은 다음과 같아요.

time ( ./gradlew jib

 Jib 빌드 결과는 대략 6.1초 정도가 나왔어요. 결과만 봐도 직관적으로 차이가 날 정도로 빠르죠! 심지어 재빌드했을 때는 jib 캐시를 참조할 수 있어서 3.5초까지도 단축되었어요.


CI/CD 파이프라인 수행 시간 약 66% 단축!

 Jib를 도입하기 전후로 백엔드 모듈의 CI/CD 파이프라인 수행 시간을 비교해보니 약 3분에서 6분 정도 걸렸던 이전 파이프라인과는 달리 1분에서 2분 정도로 66% 가량 빨라진 파이프라인을 볼 수 있었어요. CI 프로세스 중 이미지 빌드 성능만 개선했는데도 이렇게 눈에 보일 정도로 파이프라인 속도가 빨라지니 정말 기분이 좋았습니다.

짧은 리뷰

 이렇게 기존의 도커 데몬을 이용한 빌드 프로세스를 Jib를 이용한 빌드 프로세스로 바꿀 수 있었어요. 개선한 파이프라인을 릴리즈한지 얼마 되지 않아 지속적으로 모니터링하며 이슈 파악은 해야겠지만, 도커 데몬 의존 없이 간편하게 빌드할 수 있게 되었고, 그만큼 백엔드 빌드 속도도 빨라진 것 같아 만족스럽습니다.

 다만, Jib는 자바 애플리케이션을 빌드하는 것에만 특화되어 있어서 여러 프로그래밍 언어에 대해서 범용적이지 못하다는 점이 아쉬워요. 그리고 추후 빌드 프로세스가 복잡해지거나 도커파일의 멀티 스테이지를 다양하게 구성해야 할 경우 여러가지 제한적인 사항이 있다고 합니다. 그래도 배포해야할 프론트엔드 모듈 수에 비해 압도적으로 백엔드 모듈의 수가 많았기 때문에 Jib 도입은 나쁘지 않은 결과라고 생각하고 있어요.


마치며

 Jib를 도입하는 과정에서 기술적으로 난이도가 높았던 것도 아니고, 노가다성이 짙은 반복작업이 난무했던 것도 아니었어요. 정말 큰 고민 없이 손쉽게 반영할 수 있어서 놀라웠던 기억이 납니다. 팀 동료가 Jib 도입을 제안해주지 않았다면, 이번 기회를 놓치고 여러가지 업무를 핑계로 하지 못했을 것 같아요. 다시 한번 팀 동료에게 감사의 인사를 전합니다.

 이전에 작성했던 글이 5월 28일이나 되었어요. 거의 반 년만에 다시 글을 쓰기 시작했는데, 꾸준히 글을 작성한다는 것이 쉽지 않네요. 그래도 이왕 다시 시작한 김에 나름 효율적으로 글을 써보려고 글 쓰는 시간을 측정해보기 시작했답니다. 앞으로는 글을 작성할 때마다 걸린 시간을 가능하다면 기록하려 해요.

이번 글은 5일동안 9시간을 투자하여 작성했습니다.




참고자료

profile
찍어 먹기보단 부어 먹기를 좋아하는 개발자

1개의 댓글

comment-user-thumbnail
2024년 10월 14일

덕분에 배포할때 기다림이 짧아졌어요(우리팀 구세주) 감사해요 ~!

답글 달기