이번에는 Docker를 통해 Spring Boot 프로젝트를 실행하는 과정을 거치며, 해당 과정에서 새로운 개념들을 배워보도록 하겠습니다.
Spring을 사용해보지 않으셨더라도, 굉장히 간단한 프로그렘을 작성할 것이기에 부담은 없을거라 생각합니다.
큰 과정은 다음과 같습니다.
DockerFile ->
Docker 이미지 ->
Docker Container( 컨테이너 내부에서 Spring Boot Application jar 파일 실행 )
(대부분 맥북 M1 기준의 설명이라 윈도우는 조금 다를 수 있습니다)
이번 포스팅에서 완성되는 최종 Dockerfile은 다음과 같습니다.
FROM openjdk:17-alpine
WORKDIR /usr/src/app
ARG JAR_PATH=./build/libs
COPY ${JAR_PATH}/demo-0.0.1-SNAPSHOT.jar ${JAR_PATH}/demo-0.0.1-SNAPSHOT.jar
CMD ["java","-jar","./build/libs/demo-0.0.1-SNAPSHOT.jar"]
SpringBoot 프로젝트 생성하기
다음 링크로 이동합니다.
다음과 같이 설정해주세요
- Project : Gradle Project
- Language : Java
- Spring Boot : SNAPSHOT이 아닌 것들 중 가장 최신 버전
- Project Metadata : 기본값 그대로
- Packaging : JAr
- Java : 17
- Dependencies : Spring Web, Lombok
이후 Generate를 눌러 압축 파일을 생성해줍니다.
바탕 화면에 해당 프로젝트 파일을 저장할 폴더를 하나 만들어주세요
그리고 다운받은 압축 파일을 해제해줍니다.
저는 다음과 같이 바탕화면 -> docker-spring-app 에 압축을 풀어주었습니다.
이제 terminal을 열어 해당 위치로 이동합니다
idea . 를 통해 인텔리제이를 열어주세요 (이클립스를 쓰시면 이클립스 여시는걸로..)
기본 프로젝트 구조
환경 설정
Preferences(윈도우는 Setting) -> Build, Execution, Deployment -> Compiler -> Annotation Processors로 이동해주세요
Enable annotation processing을 클릭해줍니다.
코드 작성
배포할 어플리케이션 코드를 작성하겠습니다.
package com.example.demo;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DockerController {
@GetMapping("/")
public ResponseEntity<String> hello(){
return ResponseEntity.ok("Hello Docker-Spring World!");
}
}
이후 DemoApplication을 실행하여 어플리케이션을 실행시켜서 잘 작동하는지 확인합니다.
다음 주소로 요청을 보내보겠습니다.
http://localhost:8080/
다음과 같은 메세지가 떴다면 잘 작동하는 것입니다.
이제 해당 어플리케이션에 대한 DockerFile을 작성해 보겠습니다.
Dockerfile 작성하기
Docker를 사용하지 않고 프로젝트를 빌드하기 위해서는 다음과 같은 명령어가 필요합니다.
./gradlew build
java -jar build/libs/demo-0.0.1-SNAPSHOT.jar
혹은
./gradlew build && java -jar build/libs/demo-0.0.1-SNAPSHOT.jar
이를 도커파일에서 작성해 보도록 할텐데, build된 jar파일을 실행하는 Dockerfile만 작성하도록 하겠습니다.
Dockerfile의 위치는 다음과 같습니다.
Dockerfile
FROM openjdk:17-alpine
CMD ["java","-jar","/build/libs/demo-0.0.1-SNAPSHOT.jar"]
demo-0.0.1-SNAPSHOT.jar 부분은 프로젝트의 Module 이름과 build.gradle 파일의 version 부분의 이름대로 정해집니다.
build를 통해 jar파일을 생성해 보겠습니다.
다음과 같이 jar 파일이 생성되었다면 잘 된 것입니다.
이제 Dockerfile을 build하고 실행해 보도록 하겠습니다.
다음 명령어를 통해 빌드합니다.
docker build . -t springbootapp
Mac M1 에서는 이런 오류가 발생해요 🧐
MAC M1 운영체제는 기본적으로 arm기반 아키텍처이기 때문에
m1으로 도커파일을 빌드하여 도커이미지를 생성하면 platform이 linux/arm64/v8을 베이스로 생성된다고 합니다.
그러나 아직 arm64에 해당하는 이미지가 없는 경우도 있기 때문에 이러한 이미지를 사용하려면 빌드 단계에서 --platform 옵션으로 linux/amd64로 지정해줘야합니다.
다음 명령어를 추가합니다.
--platform linux/amd64
즉 Mac M1의 경우 최종 빌드 명령어는 다음과 같습니다.
docker build . -t springbootapp --platform linux/amd64
이제 생성된 Docker 이미지를 실행시켜 보도록 하겠습니다.
docker run springbootapp
그러면 다음과 같은 Error가 발생합니다.
발생한 오류는 Unable to access jarfile입니다
이는 jar 파일의 경로가 잘못되었거나, 존재하지 않는 등의 이유로 jar 파일을 찾을 수 없을 때 발생하는 에러입니다.
분명 jar 파일의 경로는 올바르고, jar 파일도 생성한 상태인데 이러한 오류가 발생하는 이유를 알아보겠습니다.
왜 파일을 못 찾는다는 오류가 발생하나요? 🧐
Docker 이미지는 파일 스냅샷과 명령어를 가지고 있습니다.
Docker 이미지를 통해 Docker 컨테이너를 실행할 때, 컨테이너 내부의 하드디스크에는 Docker 이미지의 파일 스냅샷만이 존재합니다.
이외 저희가 새롭게 만들었던 폴더나 파일들은 컨테이너 내부가 아닌 외부에 존재합니다.
따라서 컨테이너 내부에는 빌드한 jar 파일이 없으며, 따라서 jar 파일을 찾을 수 없다는 오류가 발생합니다.
그럼 어떻게 해결하나요? 🧐
COPY 속성을 사용합니다
저희는 빌드한 jar 파일을 Docker 컨테이너 내부로 옮겨주는 것이 목적이며, 이를 위한 명령어가 바로 COPY입니다.
COPY 속성
COPY (복사할 파일[컨테이너 외부]) (복사될 위치[컨테이너 내부])
저는 다음과 같이 작성하겠습니다.
COPY ./build/libs/demo-0.0.1-SNAPSHOT.jar /build/libs/demo-0.0.1-SNAPSHOT.jar
이제 다시 이미지를 빌드하고 실행해 보겠습니다.
docker build . -t springbootapp --platform linux/amd64
docker run springbootapp
다음과 같이 잘 실행될 것입니다.
이제 http://localhost:8080/ 으로 접속하여 잘 작동하는지만 확인하면 됩니다.
그런데 들어가보면 또 오류가 발생할 것입니다.
분명 실행은 됐는데 왜 연결할 수 없다고 뜰까요?
왜 접근할 수 없나요? 🧐
컨테이너 외부의 파일을 컨테이너 내부에서 접근할 수 없어서 발생하는 오류와 비슷합니다.
네트워크 역시 로컬 네트워크와 컨테이너 내부의 네트워크를 연결시켜주는 작업이 필요합니다.
해결방법은요? 🧐
-p 옵션을 사용합니다.
-p (port) 옵션
docker run -p 로컬 포트번호:컨테이너 내부 포트번호 이미지 이름
저의 경우 로컬 포트번호를 50000으로 정하기 위해 다음과 같이 입력하겠습니다.
docker run -p 50000:8080 springbootapp
이제 다음 주소로 접근하겠습니다.
http://localhost:50000/
간단하게는 이렇게 어플리케이션 배포를 해보았는데, 아직 조금 문제점들이 남아있습니다.
이제 무슨 문제점이 남았는지와, 이를 해결하는 방법을 알아보도록 하겠습니다.
COPY의 문제점 🧐
다음 명령어를 통해 Docker 컨테이너 내부의 Directory를 확인해 보도록 하겠습니다.
docker run -it springboot ls
이때 build는 저희가 COPY해 준 파일이며, 나머지 파일들은 Base Image가 가진 파일 스냅샷으로 인해 생겨난 파일들입니다.
지금은 저희가 COPY해 준 파일이 build 하나밖에 없지만, 이러한 파일들이 늘어난다면 이를 Base Image의 파일 스냅샷들과 구분하기 어려워질 것입니다.
즉 모든 파일이 한 디렉토리에 들어가 정리정돈이 안되는 상황이 발생합니다.
또한 정말 심각한 문제점은 Base Image가 가진 파일과 COPY 하는 파일의 이름이 동일하다면 Base Image의 폴더가 덮어씌워집니다.
어떻게 해결하나요? 🧐
모든 어플리케이션 소스들을 Working Directory를 따로 만들어서 해당 위치에 보관하여 해결할 수 있습니다.
Working Directory 명시방법
WORKDIR 속성을 사용합니다
WORKDIR 속성
WORKDIR working directory 경로
저는 /usr/src/app을 Working Directory로 지정하겠습니다.
WORKDIR /usr/src/app
COPY는 Working Directory에 해야 하기 때문에, COPY보다 먼저 WORKDIR를 통해 위치를 지정해주어야 합니다.
전체 Dockerfile은 다음과 같습니다.
FROM openjdk:17-alpine
WORKDIR /usr/src/app
COPY ./build/libs/demo-0.0.1-SNAPSHOT.jar ./build/libs/demo-0.0.1-SNAPSHOT.jar
CMD ["java","-jar","./build/libs/demo-0.0.1-SNAPSHOT.jar"]
COPY 와 CMD 부분을 보면 /build/libs/~~ 경로에 .이 추가되었습니다. (절대경로 -> 상대경로)
다음과 같이 WORKDIR 위치에 저희가COPY한 파일들이 저장되며, 모든 작업이 WORKDIR을 기준으로 일어납니다.
어플리케이션 코드의 변경으로 인해 다시 빌드하는 상황의 문제점 🧐
현재 저희가 도커 컨테이너로 어플을 실행하는 과정은 다음과 같습니다.
도커 파일 작성 $\overset{build}{\rightarrow}$ 도커 이미지 생성 $\overset{run}{\rightarrow}$ 도커 컨테이너 생성 후 앱 실행
이때 변경된 소스코드를 반영하여 어플리케이션을 실행하기 위해서는
jar 파일을 다시 생성하고,
이미지를 다시 생성하고,
컨테이너를 다시 실행해야 합니다.
jar 파일의 생성이야 어쩔 수 없지만, jar파일이 변경되었다고 해서 Docker Image 자체를 다시 생성하고 컨테이너를 다시 실행하는 것은 매우 비효율적입니다.
원인이 뭘까요? 🧐
COPY를 통해 어플리케이션 실행에 필요한 파일을 컨테이너 내부로 넣어주기 때문입니다.
따라서 어플리케이션 코드가 변경되었다면, 그에 따라 파일들을 다시 컨테이너 내부로 넣어주어야 하는 상황이 발생한 것입니다.
어떻게 해결하나요? 🧐
Volume 옵션을 사용합니다
COPY를 통해 파일을 복사하는 것이 아닌, 실행에 필요한 파일을들 컨테이너 내부에서 참조할 수 있도록 해줍니다.
이를 가능하게 하는 것이 바로 Volume입니다.
Volume 옵션
Volume을 사용하면 로컬 경로에 존재하는 모든 파일들을 도커 컨테이너 내부에서 사용할 수 있습니다.
다음과 같이 사용합니다.
docker run <다른 옵션들> -v 참조할 경로(로컬):참조하는 경로(컨테이너 내부) <이미지 식별자>
저의 경우에는 다음과 같습니다.
docker run -p 50000:8080 -v $(pwd):/usr/src/app springbootapp
pwd는 현재 디렉토리의 경로를 출력하는 명령어입니다.
참조하는 경로는 Working Directory에서 참조하기 때문에 Working Directory의 경로를 적어주었습니다.
이렇게 된다면 COPY 옵션은 없어도 되고 있어도 상관없습니다만
run을 할 때 Volume을 지정해주지 않으면 COPY한 파일을 실행하기 때문에 COPY를 적어주는 것이 좋아보입니다.
ARG 속성
여러번 사용되는 문자열이나 숫자 등을 변수로 만들어주는 속성입니다.
ARG key=value
저는 이를 사용하여 다음과 같이 파일을 작성했습니다.
FROM openjdk:17-alpine
WORKDIR /usr/src/app
ARG JAR_PATH=./build/libs
COPY ${JAR_PATH}/demo-0.0.1-SNAPSHOT.jar ${JAR_PATH}/demo-0.0.1-SNAPSHOT.jar
CMD ["java","-jar","./build/libs/demo-0.0.1-SNAPSHOT.jar"]
Reference
https://kim-dragon.tistory.com/152
https://velog.io/@kikiki0611/Mac-M1-docker-build-%EB%AC%B8%EC%A0%9C
https://seokhyun2.tistory.com/61
'Docker' 카테고리의 다른 글
[Docker] - (2) 도커 이미지 만들기 (0) | 2022.06.23 |
---|---|
[Docker] - (1) 도커 명령어 (0) | 2022.06.22 |
[Docker] - (0) 도커 컨테이너와 이미지 (3) | 2022.06.22 |