Docker를 왜 쓰는가?
AWS EC2 인스턴스에 배포한다던지, 컴퓨터와 노트북을 번갈아가며 개발하다던지, 이러면 각각 개발 환경이 틀려 매번 설정을 해주어야 한다. 하지만 Docker가 있다면? 귀찮게 그럴 필요 없이 쉽게 배포를 진행할 수 있다. Docker에 관해서는 추후 다루겠지만, 아직 작성하지 않은 관계로 아래 블로그를 참고해주면 좋겠다.
https://beer1.tistory.com/category/DevOps/Docker
일반적인 Docker로 Spring 서버 배포하기
제일 직관직이고 간단한 방법부터 시작해보고자 한다. 우선 프로젝트 최상단에 Dockerfile을 하나 생성하고 아래처럼 작성해보자.
FROM openjdk:17-alpine
ARG JAR_FILE=./build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
이제 ./gradlew 또는 gradle clean build를 통해서 빌드를 진행하여 .jar 파일을 생성한다.
docker build -t [name]:[tag] .
image의 name과 tag를 지정한 뒤, 위 명령어를 통해 docker image를 생성한 후,
docker run -d [name]:[tag]
그런데 Docker를 명령어 한번으로 배포가 쉽게 될려고 하는건데, 이렇게 여러 과정을 거쳐야 되면 매우 불편하다. 또한, 해당 방식에는 jar 파일이 무거워지면 docker image를 만드는 과정이 비효율적으로 된다. Docker의 장점이 레이어마다 캐쉬를 사용하고, 이를 통해 빠르게 이미지를 만들 수 있다는 것이다. 하지만, 해당 방식으로 진행하면 소스 코드가 한줄만 바껴도 캐쉬가 깨지기 때문에 다시 연산을 해야되가지고, Docker를 사용하는 장점이 없어진다. 위와 같이 Dockerfile을 구성하면 Spring의 모든 애플리케이션 코드와 라이브러리가 Single layer에 배치되기 때문이다. 결국 컨테이너 환경에서의 시작 시간에도 영향을 미친다. 그래서 Spring 공식 Docs에 나와있는 방법을 하나 시도해보고자 한다.
Multi Stage Build
Multi Stage Build가 뭐죠?
우선 Multi Stage Build가 무엇인가? 아래 두 글을 참고해주기 바란다.
https://kimjingo.tistory.com/63
https://spring.io/guides/topicals/spring-boot-docker#_multi_stage_build
Multi Stage Build를 이용한 배포
이제 Multi Stage Build에 대해서 알았다면, Spring 공식 문서에 있는 가이드를 참조해서 한번 만들어보도록 하자. 아래 글을 참고해서 작성하였다.
https://spring.io/guides/topicals/spring-boot-docker#a-better-dockerfile
앞서 설명한 Multi Stage Build에 대해서 알았다면, 이번에는 Spring이 Jar 파일을 만들때 생기는 layer에 대한 개념을 알야아 한다. Spring 공식문서에 Gradle 방식 에 따르면, 아래와 같이 4가지 Layer로 나누어진다고 한다.
해당 문서에서, application layer가 가장 자주 바뀌고, 그다음은 snatshot, 그다음은 spring-boot-loader, dependencies... 순으로 자주 바뀌지 않는다고 한다. 즉, Dependencies가 가장 바뀌지 않는다는 것이다.
그러면, Docker image를 어떻게 작성해야 할까? Docker File은 제일 자주 바뀌지 않는 것부터 차례로 작성해주는 것이 좋다. 즉, 이것의 역순으로 Docker File을 작성하면 된다.
FROM eclipse-temurin:17-jdk-alpine AS build
WORKDIR /workspace/app
COPY . /workspace/app
RUN chmod +x ./gradlew
RUN --mount=type=cache,target=/root/.gradle ./gradlew clean bootJar
RUN mkdir -p build/dependency && (cd build/dependency; jar -xf ../libs/*-SNAPSHOT.jar)
FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
ARG DEPENDENCY=/workspace/app/build/dependency
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF
COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","com.example.eumserver.EumServerApplication"]
이렇게 하면, 우리는 어떤 부분이 수정되더라도 진짜 최소한 범위에서만 캐쉬가 깨지기 때문에 효율적으로 build가 가능해진다. 실제로, 앞서 소개한 방식으로 했을때는 docker image를 만드는데 거의 2~3분 시간이 걸렸지만, 해당 방식으로 바꾸니깐, 캐시가 적용됐다는 가정하에 10초만에 Build를 완료할 수 있었다.
그냥 천천히 하지, 뭐하러 빠르게 하냐라는 생각이 들 수도 있다. 그런데, 생각보다 이 Docker build하는 과정이 번거롭고 자주하면 오래걸렸을때 굉장히 스트레스 받는다. 또한, Git Actions 등으로 CI/CD를 적용한다고 가정해보자. 거기서도 빠르게 Build하고 배포가 가능해질 것이다. 따라서, 해당 방식을 통한 Docker File 작성은 매우매우 중요하다.
'BackEnd > 배포' 카테고리의 다른 글
[배포] Route 53 + Nginx + Certbot으로 Https 적용하기 (0) | 2024.06.23 |
---|---|
[배포] "외국민" 서비스 배포 과정 - (1) (0) | 2024.05.04 |
[배포] Docker로 Ruby On Rails 배포하기 (0) | 2024.05.03 |
[배포] Azure 발음 평가 사용시 SpeechConfig 초기화 오류 (1) | 2024.05.01 |