Spring Boot 프로젝트 Docker image를 Docker Hub에 업로드하기

728x90

서론


Spring 프로젝트를 도커 이미지로 만들어 클라우드에서 구동하려고 했으나 이에 관련하여 one way로 깔끔하게 정리되어있는 문서를 보지 못한 것 같아서 정리해두려고 한다.
또한 코드를 업데이트하고 나서 BootJar로 빌드 하지 않아서 코드의 변경사항이 반영되지 않는 실수를 방지하고자 한다.

해당 글은 build 도구로 Gradle을 사용하며, IDE로 IntelliJ를 사용하는 것을 기준으로 한다.

또한 해당 실습을 하기 위해서는 Docker Hub 계정이 있고, Docker가 local 환경에 설치되어 있어야 한다.


Spring Project로 Docker image 만들기


프로젝트 생성

 

컨트롤러를 생성해서 RestContoller를 사용하여 라우팅한다.
현재 프로젝트는 루트 주소에 대해서만 라우팅되어있다. 간단한 텍스트를 반환하는 어플리케이션이다.



서버 가동 포트는 80번으로 지정한다.


Dockerfile 문법


어떤 언어를 사용하던, 어떤 프레임워크를 사용하던 간에 Dockerfile이 있어야 도커 이미지를 빌드할 수 있다. JAVA 프로젝트의 이미지를 빌드하기 위한 Dockerfile 작성 방법은 스프링 공식 문서에서 참고할 수 있다.

https://spring.io/guides/topicals/spring-boot-docker/


- FROM은 베이스 이미지 정보를 의미한다. (Dockerfile에서 베이스 이미지를 지정하면 Docker가 해당 버전을 위한 셋업을 알아서 가져온다. 베이스 이미지는 Docker Hub에 등록되어있다. 사실상 필수 문법)
- VOLUME은 저장소로 사용할 디렉토리를 의미한다. 원래 컨테이너가 가동하다가 중지되어 사라지면 컨테이너 내부에 있던 데이터는 모두 사라진다. 하지만 Volume을 Named Volume으로 지정해서 사용하면 컨테이너가 사라져도 Volume 내 데이터가 살아남아서 추후 다른 컨테이너가 구동되어도 이전 데이터를 계속 사용할 수 있다. 더 알고 싶다면 'Docker Named Volume'으로 검색해보자. 이번 실습에서 데이터 저장은 다루지 않을 것이므로 Volume 문법은 제외한다.
- ARG은 Docker 파일 내 변수로 사용할 수 있다. 특정 위치에 하드코딩된 값을 변수로 분리할 수 있다. Docker 파일 내 위치를 참조하므로 원 위치에서는 ${}를 사용하여 표현해야 한다.
- COPY는 이미지 파일로 만들 파일을 지정하여 이미지로 빌드한다. COPY 첫번째 인수는 빌드로 만들 목표 파일 또는 폴더를 의미하며, 두번째 인수는 이미지로 만들었을 때의 파일 또는 폴더의 이름을 의미한다.
- ENTRYPONT는 이미지가 빌드되고 나서 컨테이너로 구동될 때 실행되는 명령어를 의미한다. 유사한 역할을 하는 문법인 CMD가 있는데 미묘한 차이만 있고 역할이 거의 같다. 차이점이 궁금하다면 ENTRYPOINT vs CMD로 검색해보자.

그러므로 아래와 같은 형식으로 Dockerfile을 만들면 된다.

FROM (jdk버전)
ARG JAR_FILE=(실행가능한 jar 파일 위치)
COPY COPY ${JAR_FILE} (이미지 내 파일 이름)
ENTRYPOINT ["java","-jar","(이미지 내 파일 이름)"]



여기서 ARG는 사용해도 되고 안 해도 된다. ARG는 그냥 문자를 대체하는 역할을 하므로 ARG를 사용하지 않고 실행가능 jar 파일 위치를 그대로 하드코딩하여 넣어도 상관없다.

JDK버전은 지금 프로젝트에 사용 중인 java 버전을 넣으면 될 것이고, 이미지 내 파일 이름은 개발자가 임의로 지정해서 넣으면 된다. (확장자만 .jar로) 그러면 남은 것은 단 하나. 실행 가능한 jar 파일의 위치를 지정해야한다.

## Gradle BootJar로 실행가능한 jar 파일 만들기

안타깝게도 Spring Boot에서 Application을 Run해도 실행가능한 jar파일은 생성되지 않는다. Gradle build의 기본 설정은 build 폴더에 jar 파일을 생성하지 않는다. jar 파일을 생성하기 위한 방법은 여러 가지가 있는 데, 두 가지 방법을 꼽자면

1. build.gradle 파일에 몇 가지 설정을 덧붙여서 build 할 때 jar 파일을 생성하게 한다.
2. BootJar를 사용하여 executable jar를 생성한다.

1번 방법을 시도해보려면 다음 블로그를 참고하면 되겠다.
https://lemontia.tistory.com/1056
다만 이 방법을 시도할 때 반드시 dependency가 포함되는 executable jar가 생성되도록 해야한다.
여기서는 복잡한 설정이 필요없는 2번 방법을 선택할 것이다.

intelliJ에서 오른쪽 Gradle 탭을 누르고 BootJar를 선택한다.



우클릭을 누르고 Run을 실행한다.


디렉토리를 확인하면 build/libs 에 executable jar 파일이 생성되있다.



BootJar는 Spring boot Gradle plugin이 제공하는 특수한 기능인데, 이를 사용하면 상당히 간편하게 jar 파일을 생성할 수 있다. 다만 주의할 점은 코드가 바뀔 때마다 BootJar를 가동해서 수동으로 빌드해줘야 한다는 점이다. 이 작업을 하지 않으면 수정된 코드가 jar 파일에 반영되지 않는다.  이 작업이 귀찮다면, 1번 방법을 참고하여 build.gradle 파일의 설정을 변경하여 application run할 때마다 jar 파일이 생성되는 방법을 선택하면 된다. 

 

Dockerfile 작성


이제 실행 가능한 jar 파일이 있으므로 Dockerfile을 작성하고 Image를 빌드할 수 있다. 작성자는 해당 프로젝트를 JAVA 19를 사용했으므로 FROM에 jdk 19를 선택했다. EXPOSE는 사용하려고 설정한 포트를 표시하는 문법인데, 필수적인 문법은 아니므로 작성해도 되고, 안 해도 된다. 다만 유지보수를 위해 가급적 작성하는 편이 낫다. EXPOSE 80 이면 현재 80번 포트를 사용한다고 표시하는 것이다.

 




이미지 빌드 후 Docker Hub에 업로드

도커 허브에 접속하여 저장소를 생성한다. 저장소 이름은 나중에 이미지 업로드를 할 때 사용되므로 염두에 둬야 한다.




터미널에서 이미지 빌드를 실행한다. -t 를 사용하면 이름을 붙일 수 있다. 이름은 자유롭게 정할 수 있으나, 도커 허브에 업로드하려면 '계정명/저장소명'으로 지정해야 업로드 할 수 있다. 물론 이미지를 빌드하고 나중에 tag 문법을 통해 이미지 이름을 바꾸어 이미지를 새로 생성할 수 있다. 하지만 여기선 그냥 한 번에 이미지 저장소 이름을 이미지명으로 지정했다.

 



터미널에서 이미지 업로드를 실행한다. docker push + 이미지 이름을 사용하면 바로 업로드가 실행된다.

 


이렇게 하면 도커 이미지 업로드까지 완료된다.

 



다음 글에서


이렇게 도커 허브에 업로드 한 이미지를 클라우드 서비스에서 불러들여서 컨테이너로 구동할 수 있다. 배포를 위한 서비스로 AWS를 사용했는데, 이 AWS만 하더라도 서술할 내용이 상당히 많다. 글이 상당히 길어질 것으로 예상되어, 다음 글에서 AWS ECS에서 도커 이미지를 가져와 컨테이너를 구동하는 법을 서술할 것이다.