FireDrago
[Moment] GitHub Actions CI 9분에서 1분으로 단축하기 (Gradle, Docker 최적화) 본문
문제 상황

프로젝트의 CI 과정에서 시간이 너무 오래걸리는 문제가 있었다.
개선하기 위해 CI 과정을 하나하나 살펴보며 개선점을 찾아보기로 했다.
개선 이전의 문제 가득한 코드는 CI/CD 에서 확인 할 수 있다.


CI 는 test 와 build and push 두 단계로 이루어져 있는데, test 과정에서 큰 시간 지연은 없다.
test with gradle 단계에서 1분 20초가 걸리긴 했지만,
Gradle 초기 설정 + 코드 컴파일 + 100개 이상의 단위/통합 테스트 를 실행한다.
문제가 전혀 없다고 판단된다.
Build and push Docker 단계를 보자
Build and push Docker image 단계에서 7분이 넘는 시간이 걸린다.
빌드하고, 도커 이미지 만들고, 도커허브에 푸시 과정에 문제가 있어 보인다.
Build and Push 과정 살펴보기

실행로그를 찬찬히 뜯어보았다.
도커 이미지를 만들기 위해 빌드되는 과정에서
멀티 플랫폼 빌드를 설정했는데, arm64 빌드 과정에서 유독 시간이 오래 걸리고 있다.
여기서 두가지 의문이 생겼다.
1. test 단계에서 gradle을 세팅하고 테스트하는데, 빌드 과정에서 다시 gradle을 세팅할 필요가 있을까?
2. 멀티 플랫폼 설정을 꼭 해야 할까?
Gradle 설정 변경

현재 CI 과정에서 gradle은 두번 설정된다.
1. test 과정에서 테스트 코드를 실행하기 위해 한번
2. docker 이미지를 만들기 위해 빌드하기 위해 또 한번
이렇게 비효율적인 코드가 탄생한 이유는
github actions의 작업(job) 이 각각 별개의 가상공간에서 실행되기 때문이다.
test 작업에서 사용된 gradle은 build-and-push-docker 작업에서 재사용될 수 없다.
따라서 테스트 - 빌드 작업을 분리하지 않고 한번에 빌드, 이미지 생성, 푸시 까지 끝내는것이 효율적이라고 판단했다.
jobs:
build_and_push:
runs-on: ubuntu-22.04-arm64
defaults:
run:
working-directory: ./server
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Cache Gradle dependencies
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
with:
gradle-home-cache-cleanup: true
- name: build with Gradle
run: ./gradlew build
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v5
with:
context: ./server
file: ./server/Dockerfile
push: true
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/moment:latest
${{ secrets.DOCKERHUB_USERNAME }}/moment:${{ github.sha }}
cache-from: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/moment:buildcache
cache-to: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/moment:buildcache,mode=max
변경된 CI 작업에서는 한번의 작업으로 CI를 정의하고
./gradle build 명령어를 사용하여 한번에 테스트와 빌드를 실행하도록 했다.
이렇게 되면 sever/build/libs 디렉토리에 jar 실행파일이 생성될 것이다.
이제 Dockerfile을 통해 도커 이미지가 가상공간 내부의 jar를 이미지 파일로 생성하도록 설정해주면 된다.
Build and push Docker image 작업에서 with: context: ./server 설정을 주목하자.
이 설정은 도커 이미지를 빌드하는 데 필요한 파일들의 범위를 server 디렉터리로 한정한다.
만약 이 설정이 없다면, 리포지토리 전체가 빌드 컨텍스트로 사용되어 불필요한 파일까지
도커 데몬으로 전송되므로 빌드 속도가 저하되고 보안에 취약해질 수 있다.
context를 명시적으로 설정하면, Dockerfile 내의 경로를 단순화할 수 있을 뿐만 아니라,
빌드 속도와 캐시 효율성을 높이고 보안을 강화하는 핵심적인 최적화이므로 반드시 설정해주자.
Dockerfile
FROM eclipse-temurin:21-jre-jammy
WORKDIR /app
COPY build/libs/*.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar", "--spring.profiles.active=${SPRING_PROFILES_ACTIVE}"]
이전의 Dockerfile에는 빌드를 위한 gradle 설정이 포함되어 있었다.
이미지를 생성하는 과정에서 빌드를 진행했기 때문이다.
하지만 이제는 빌드를 미리 끝내고 이미지를 생성한다.
따라서 Dockerfile은 jar를 실행하기 위한 JRE 설정과
jar를 COPY 하도록 수정했다.
jar 파일을 실행하기 위한 최소한의 프로그램만 있으면 되기때문에
jdk 가 아닌 jre로 설정해주었다.
JRE(Java Runtime Environment)는 자바 실행에 필요한 최소한의 요소만 포함하지만,
JDK(Java Development Kit)는 컴파일러 등 개발 도구까지 포함하기 때문에 훨씬 용량이 크다
COPY build/libs/*.jar app.jar 설정을 통해 GitbhubActions 디렉토리의 jar 파일을 도커 컨테이너 내부로 옮기고,
ENTRYPOINT 명령어로 jar 파일을 실행하기 위한 명령어를 명시해준다.
멀티 플랫폼 설정 변경
우리 팀의 개발 환경과 배포 서버는 모두 arm64 아키텍처를 사용한다.
하지만 CI를 실행하는 GitHub Actions Runner는 amd64 기반이다.
따라서 Docker는 Runner 환경에 맞춰 amd64 이미지를 기본으로 생성했다.
이 문제를 해결하기 위해 멀티 플랫폼(amd64, arm64) 빌드를 설정했다.
하지만 amd64 Runner는 에뮬레이터(QEMU)를 통해 arm64 환경을 흉내 내야 한다.
이 에뮬레이션 과정이 빌드 속도를 크게 저하 시키는 원인이라는 것을 알게되었다.
해당 이슈를 설명한 Github Issue 링크
GithubActions가 2024년 부터 arm64 기반의 환경을 지원한다는 사실을 알게되었다.
Github 블로그 링크
jobs:
ci:
## arm-64를 명시하면 githubactions가 arm64기반으로 작동
runs-on: ubuntu-22.04-arm64
이렇게 한줄만 변경하면 githubActions가 arm64 기반으로 동작하게 되고,
멀티 플랫폼 설정을 제거 할 수 있다.
개선 결과

CI 속도가 1분 14초로 많이 개선 된 것을 확인 할 수 있다.
1. gradle의 중복 설정과 두번의 가상공간 생성 제거
2. 멀티플랫폼 설정으로 인한 중복 작업 제거
두 작업을 통해 CI 과정을 개선했다.
'프로젝트' 카테고리의 다른 글
| [Moment] 프로젝트 쿼리 실행속도 50초 단축하기 (0) | 2025.10.19 |
|---|---|
| [Moment] Spring Boot 로깅 전략: AOP, Logback, Docker Volume, AWS CloudWatch 적용기 (0) | 2025.08.17 |
| [bobzip] nginx Request Entity Too Large 오류 (0) | 2024.08.02 |
| [bobzip] QueryDsl 활용하여 냉장고 재료로 레시피 검색하기 (0) | 2024.07.11 |
| [bobzip] Ajax 비동기 요청을 통한 댓글 수정 (0) | 2024.07.05 |