본문 바로가기

프로그래밍/Spring

Spring Boot 를 Docker 에 올려보자(feat. Spring Boot 2.3) (2)

지난 글에서는 Spring Boot App을 layer 가 있는 형태로 만든뒤 이를 Docker 이미지로 만드는 방법을 알아보았다. 이번에는 그보다 더 간단한 방법을 하나 알려주려 한다. 저번 방법은 Dockerfile 자체는 프로그래머가 직접 만들어야 했지만 지금 소개할 방법은 그런 과정도 없이 만드는 것이다. 바로 BuildPack을 이용하는 방법이다. Cloud Foundry나 Heroku를 이용해본 개발자라면 BuildPack을 이용해본 경험이 있을것이다(참고로 나는 아직 이용해본 경험이 없다. 다만 클라우드 교육을 받을 당시 Cloud Foundry 에서 BuildPack을 이용해 개발하는 실습은 해본적은 있지만 그때 딴짓을 해야 할 상황이 있어서 제대로 듣지도 못했다..ㅠㅠ..) 단순하게 설명하자면 내가 만든 Spring Boot App만 가지고 이 BuildPack은 이 App이 실행되는 Docker Image를 생성하는 것이다. Docker를 알고 있는 개발자라면 Docker Image를 만들려면 Dockerfile 에 관련 설정을 한 뒤 docker build 명령을 사용해야 한다는 것은 알고 있을것이다. 그러나 BuildPack은 이러한 과정이 필요없다. Dockerfile을 만들지 않아도 알아서 생성해준다.

 

Maven을 기준으로 설명하자면(Gradle 사용자일 경우 여기를 보면 된다) Spring Boot Maven Plugin에 build-image라는 새로운 goal이 추가 되었다(Gradle에서는 bootBuildImage 라는 task가 추가 되었다) 이 goal을 이용하면 Spring Boot Project를 App으로 만든뒤 이를 Docker Image로 만들어 개발자가 사용중인 Docker Local Repository 에 넣어주게 된다. 여기서 전제조건은 환경변수로 DOCKER_HOST 환경 변수가 선언 되어 있어야 하는데 이 변수에 넣어야 할 값은 자신이 사용중인 docker daemon이 통신하는 host와 port를 설정해줘야 한다(문서에서는 DOCKER_TLS_VERIFY 와 DOCKER_CERT_PATH 도 설명하고 있지만 나는 이 둘은 설정하지 않아도 동작이 되어서 필수라고 보지는 않았다) 나의 경우를 얘기를 하자면 내 개발환경은 Windows 이지만 Docker for Windows를 이용해서 Docker를 사용하고 있지는 않다. Vagrant를 이용한 linux 가상머신을 만든뒤 여기에 docker를 설치해서 사용하고 있다. 그래서 DOCKER_HOST 환경변수에는 해당 가상머신의 ip 주소를 사용하고 있다. 명령어로 maven build를 하는 경우라면 mvn spring-boot:build-image 로 입력해서 실행하면 되고 IDE 툴을 이용할 경우 해당 goal이 있는 것을 볼 수 있으니 그 메뉴를 RUN 하면 된다. 아래의 그림은 Intellij 에서 해당 goal이 있는 그림이다.

 

spring boot maven plugin의 build-image goal

이렇게 실행하면 BuildPack 관련 Docker 이미지를 Docker Local Repository에 받은뒤 이것을 이용해서 내가 만든 Spring Boot App 이 들어가 있는 Docker Image를 만들게 된다. 하지만 개인적인 경험에 비추어서 이 build-image goal을 이용해서 만드는 방법은 비추하게 되었다. 이유는 이미지의 크기가 너무 크다는 것이다. 지난글과 이번글을 쓸때 언급했던 Spring Boot App은 내가 만든 Mybatis를 이용한 게시판 App이다. 단순 CRUD 기능이 들어가 있고 UI로 Boot Strap을 사용했으며 jquery를 자바스크립트 라이브러리로 사용한 단순한 게시판이다. 이렇게 해서 만든 jar 파일의 크기는 26메가 정도 되는 크기이다. 이 BuildPack을 이용해서 Docker 이미지를 만들경우 그 크기는 얼마나 되는지는 아래 그림에서 보여주고 있다

 

BuildPack을 이용해서 만든 Docker Image

맨 아래에 있는 mybatis:0.0.1-SNAPSHOT이 위에서 언급했던 Spring Boot Maven Plugin의 build-image goal을 이용해서 만든 이미지이다. 그리고 gcr.io/paketo-buildpacks/run 이미지와 gcr.io/paketo-buildpacks/builder 이미지는 build-image goal을 작업하기 위해 자동으로 별도로 받아지는 docker 이미지이다. mybatis 이미지의 생성된 일자가 40년전(40 years ago)로 나오는건 가벼운 버그로 생각하고 넘길수 있어도 가장 심각한건 240메가를 차지하고 있는 크기이다. 26메가 App을 실행시키기 위한 이미지의 크기가 240메가를 차지한다는건 받아들이기 어려운 크기이다. 이렇게 이미지가 크게된 원인은 무엇일까? 일단 이 이미지가 무엇을 기반으로 만든건지 아래의 그림에서 확인할 수 있다. 아래의 그림은 mybatis 이미지를 docker container로 실행시킨뒤 해당 container에 접속해서 실행한 명령의 결과이다.

 

docker container 안에서 명령어를 실행한 결과

이 내용을 보면 Ubuntu 18.04 LTS 이미지를 기준으로 만든것임을 알 수 있다. 흔히 Docker 이미지를 직접 만드는 개발자들이라면 Base 이미지를 alpine linux 기반의 작고 가벼운 경량화된 이미지를 Base Image로 사용하는 것에 비추어보면 Ubuntu 를 Base Image로 삼는 것은 좋은 방법이 아니다. 그래서 이 Base Image를 바꿀수 있는 방법을 찾아보았지만 적절한 Option을 발견하지 못했다(행여 이 글을 읽고 계신 분중 방법 아시는 분은 댓글로 방법 좀 부탁드립니다) 그래서 이 BuildPack을 이용하지 않고 첫번째 글에서 언급했던 Dockerfile을 기반으로 이미지를 만들면 아래의 그림과 같이 그 크기가 줄어든다. 

 

이전글에서 언급한 Dockerfile을 이용해서 만든 Docker Image

BuildPack을 이용해서 만든 결과가 240메가인것에 반해 Dockerfile을 이용해서 만들경우 112MB를 차지해서 크기가 반으로 줄어든 것을 확인할 수 있다. 이렇게 된 원인은 무엇보다도 jre만 설치되어 있는 alpine-linux 이미지(openjdk:8-jre-alpine)를 기본 이미지로 삼아 만든것이 주요한 원인으로 보고 있다. 위의 그림에서 none으로 보여지는 이미지는 이전글에서 multi-stage 빌드를 통해 만들어지게 되는 builder 이미지로 이 이미지는 만든뒤 삭제해도 무방하다.

 

지금까지 Spring Boot App을 Docker Image로 만드는 법에 대해 두번의 글에 나눠서 썼다. 편의성만 놓고 보면 BuildPack이 편하긴 하지만 세세한 옵션을 줘가며 Docker Image를 빌드하기에는 불편한점이 있다. 그래서 개인적으로는 이전 글에서 언급한 layer 계층의 app을 만든뒤 이를 Dockerfile을 이용해서 이미지로 만드는 것을 추천한다