Render와 Docker를 이용한 Spring Boot + MongoDB 배포

jinvicky·2024년 10월 24일
0
post-thumbnail

Intro


저번에 netlify에서 sqlite3가 한번 배포되면 function이 아닌 이상 DB 쓰기가 안된다는 걸 다 배포하고 깨달아서 아주 속이 쓰렸다;;
그래도 다시 도전해보니 Render와 Docker를 알게 되었으니 한잔하자.

💦 서버 유목민

📌 백엔드 배포하려면 결국 비용을 낼 수밖에 없다고 생각한다. 토이 프로젝트라고 할 지라도... 다만 내가 비용을 관리하고 사용성 측면에서 분명 더 나은 사이트가 있지.

정적 사이트 배포는 무료이며 좋은 사이트들이 많다. (난 netlify 선호) 하지만 백엔드는 사실상 돈을 내느냐 혹은 속 터지는 (희귀한) 무료 서버를 쓰느냐의 차이일 것 같다. 속 터지는 무료 서버도 사실상 거의 없다.

HerokuQoddiRenderAws
대부분 유료화 플랜무료래서 가입했는데 정적 사이트만 무료Docker로 쉽게 배포가 가능, 무료로 백엔드를 띄울 수 있지만 속이 터진다.사용 사례만큼이나 폭탄 요금 사례 또한 많다.

Render 무료 버전을 썼다가 난 결국 월별 $7달러 결제 플랜으로 전환했다. 아무리 토이 프로젝트라지만 리부트하는 시간이 1분 가까이, 혹은 넘게 걸려서 개발하는데도 방해가 되며, 사용자 측면에서도 답답하기 때문이다.

하지만 요금 문제를 떠나서 Docker를 사용해서 쉽게 프로젝트를 배포하는 경험을 Render를 통해 할 수 있다😊

프로젝트 생성부터 빌드까지

먼저 Spring Boot + MongoDB 프로젝트를 생성했다.
정말 신청과 리뷰만을 고려한 초소형 프로젝트여서 코딩을 굳이 보일 필요가 없었다;; 아래에 레퍼런스를 첨부한다.

https://velog.io/@joonghyun/Springboot-MongoDB-Springboot와-MongoDB-연결하기

원래 백엔드는 프로젝트를 띄울 서버와 DB를 띄울 서버 총 2개가 필요한데, 몽고디비에서 소규모 DB를 원격으로 무료 사용할 수 있었다. 물론 IP 주소를 전체 공개해야 했고, 규모가 크면 안되긴 했지만 이런 방법도 있다~

(아래는 당시 기록)

authentication 8000 에러 코드가 발생한 이유는 network access 때문이었다.
network access > ip access list로 가서 어디서든 접근 가능하게 설정했다.
0.0.0.0/0 으로 설정하면 된다 (좋은 방법은 아니다.)

JAR vs. WAR

🤔 JAR냐 WAR냐도 모르고 있다가 배포해야 하니 저절로 찾아보게 된다;; 그래서 작은 기능이라도 사적 시간이 나면 토이 프로젝트를 한다.

프로젝트 빌드 결과물이 jar일 때 war일 때 어떤 것이 다를까?

Spring Boot는 내장 톰캣과 같은 서블릿 컨테이너를 내장하고 있어, JAR로 빌드할 경우 별도의 서버 없이도 독립적으로 실행할 수 있다.

반면, WAR로 빌드할 경우 별도의 서블릿 컨테이너에 배포하고 해당 서버에서 실행해야 한다. WAR 파일은 일반적으로 외부 서버에 배포하여 실행되며, 독립적으로 실행되려면 외부 서버가 필요하다.

WAR 프로젝트로 이미 만들었는데? JAR로 어떻게 바꾸지?

그러면 build.gradle 가서

  • id: 'war' 부분을 제거한다.
  • providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat' 를 제거한다.

2번째 설정은 애플리케이션 빌드 결과물에 톰캣을 포함하지 않겠다는 의미다. jar의 독립적 실행을 위해 포함하도록 해당 부분을 제거했다.

Build

📌 해당 과정은 Mac으로 진행했다. 하지만 윈도우여도 그리 큰 차이는 없다.

빌드하려는데 너는 권한이 없다라고 해서

chmod +x ./gradlew

해당 프로젝트를 빌드해보자. (나는 gradle 사용했음)

./gradlew build

근데 내 mac의 자바 버전은 11인데 프로젝트는 자바 17이라서 호환성이 안 맞아서 에러가 난다.

> Could not resolve all artifacts for configuration ':classpath'.

> Could not resolve org.springframework.boot:spring-boot-gradle-plugin:3.3.4.

Required by:

root project : > org.springframework.boot:org.springframework.boot.gradle.plugin:3.3.4

> Dependency requires at least JVM runtime version 17. This build uses a Java 11 JVM.

- Try:

> Run this build using a Java 17 or newer JVM.

내 mac의 자바 버전을 바꾸는 방법도 있다. 하지만 인텔리제이에서 gui상으로 제공하는 gradle 빌드를 클릭하면 그럴 수고를 아낄 수 있다.
(프로젝트 설정을 자바 17로 했는데 왜 안되지;; 난 그랬다..)

결과적으로 build/libs 폴더에서 jar 파일이 생성된 것을 확인할 수 있다.

추가적으로 jar를 실행해보려면 아래처럼 해볼 수 있다.

java -jar build/libs/ktalk-review-0.0.1-SNAPSHOT.jar

🐳 Docker 입문

처음 해본 도커가 4년 전이었지.... mac 쓰면서 처음 입문했었다.

선행 조건

  • docker가 설치되어 있어야 한다.
  • docker hub이라는 사이트에 가입되어 있어야 한다.
  • docker에 java 17(또는 프로젝트 버전에 맞는 자바)가 설치되어 있어야 한다.

미리 알았다면 좋았던 것들

(틀릴 수 있습니다)

  • 도커 이미지를 생성할 때 docker hub의 userid/ 가 붙인 채로 이름을 정해야 했다.
    그렇게 안했다가 docker hub에 push가 안되어서 이미지 다시 생성했다.
  • 도커에 해당 자바 버전이 있어야 한다

TMI

  • 태그는 배포에 크게 중요하진 않다. 안 붙이면 lts인가 latest 디폴트로 된다.

1. 도커 이미지 생성하기

프로젝트 최상단에 Dockerfile 파일을 생성하고 아래처럼 작성했다. (확장자 없다. 그냥 말 그대로 이름이 Dockerfile)

# base image
FROM openjdk:17-jre-slim

# set the working directory
WORKDIR /app

# copy the jar file
COPY build/libs/ktalk-review-0.0.1-SNAPSHOT.jar app.jar

# run the jar file
ENTRYPOINT ["java", "-jar", "app.jar"]

build 앞에 / 붙였더니 동작 안했다;;

docker build -t jinvicky/ktalk-review-image .

hub 유저아이디/원하는 도커 이미지 이름 . 식으로 구성한다.

❌ 오류


Build an image from a Dockerfile
(base) namgungjin@namgungjin-ui-MacBookPro ktalk-review-back % docker build -t ktalk-review-image .
[+] Building 2.9s (4/4) FINISHED                                                                                                                                       
 => [internal] load build definition from Dockerfile                                                                                                              0.0s
 => => transferring dockerfile: 297B                                                                                                                              0.0s
 => [internal] load .dockerignore                                                                                                                                 0.0s
 => => transferring context: 2B                                                                                                                                   0.0s
 => ERROR [internal] load metadata for docker.io/library/openjdk:17-jre-slim                                                                                      2.7s
 => [auth] library/openjdk:pull token for registry-1.docker.io                                                                                                    0.0s
------
 > [internal] load metadata for docker.io/library/openjdk:17-jre-slim:
------
failed to solve with frontend dockerfile.v0: failed to create LLB definition: docker.io/library/openjdk:17-jre-slim: not found
(base) namgungjin@namgungjin-ui-MacBookPro ktalk-review-back % docker pull openjdk:17-jdk-slim

⭕ 해결

자바 언어와 맞는 pull을 땡긴다.

docker pull openjdk:17-jdk-slim

필요시 도커 로그인

docker login

❌ 권한 거부

왜 유저아이디 이야기가 나왔냐면 아래 이유 때문이다.

(base) namgungjin@namgungjin-ui-MacBookPro ktalk-review-back % docker push ktalk-review-image
Using default tag: latest
The push refers to repository [docker.io/library/ktalk-review-image]
801e4e37f449: Preparing 
d42dec9eb11a: Preparing 
6be690267e47: Preparing 
13a34b6fff78: Preparing 
9c1b6dd6c1e6: Preparing 
denied: requested access to the resource is denied

그냥 docker push 이미지이름 하니 저러더라;;

⭕ 해결

이미지 다시 생성

docker build -t jinvicky/ktalk-review-image .
docker push jinvicky/ktalk-review-image:lts

결과적으로 아래에 docker.io/~로 시작하는 docker registry url을 얻을 수 있다

The push refers to repository [docker.io/jinvicky/ktalk-review-image]
tag does not exist: jinvicky/ktalk-review-image:lts
(base) namgungjin@namgungjin-ui-MacBookPro ktalk-review-back % docker push jinvicky/ktalk-review-image    
Using default tag: latest
The push refers to repository [docker.io/jinvicky/ktalk-review-image]
801e4e37f449: Pushed 
d42dec9eb11a: Pushed 
6be690267e47: Pushed 
13a34b6fff78: Pushed 
9c1b6dd6c1e6: Pushed 
latest: digest: sha256:f54c1c5f53f6efc2636bf58cf0fb2dcbfda2fa88281956819b5f66e47860a9f1 size: 1371
(base) namgungjin@namgungjin-ui-MacBookPro ktalk-review-back % docker pull jinvicky/ktalk-review-image
Using default tag: latest
latest: Pulling from jinvicky/ktalk-review-image
Digest: sha256:f54c1c5f53f6efc2636bf58cf0fb2dcbfda2fa88281956819b5f66e47860a9f1
Status: Image is up to date for jinvicky/ktalk-review-image:latest
docker.io/jinvicky/ktalk-review-image:latest
(base) namgungjin@namgungjin-ui-MacBookPro ktalk-review-back % docker push jinvicky/ktalk-review-image:lts 

docker hub에 들어가서 확인해보자.

회고해보니 나는 docker hub에서 레퍼지토리 먼저 만들고 push했던 것 같다.
도커 레퍼지토리가 public 설정되어 있어야 별도 설정 없이 배포가 더 스무스하다.

최종 Render

📌 https://velog.io/@minu1117/Render로-Springboot-무료-호스팅

저 블로그 보면 90% 이상이 해결이 된다. 애초에 Render + Docker는 저 위의 push만 잘하면 그냥 url 등록이 끝이거든.
위 블로그의 5번째부터 따라하면 좋다.

나의 경우 free 테스트해보고 속이 터져서 추후 플랜을 바꿨는데, 정말 돈 7달러에 확연히 달라진 속도를 보여준다.

Outro


에러를 구구절절이 적느라 좀 길어졌다;;
개인적으로 aws보다 render를 선호한다.

도커 이미지 생성해봤으니 도커를 어디에 어떻게 활용하는지도 알면 좋겠다.
이렇게 백엔드를 모두 마쳤다.
다 개발하고 post, get을 테스트했을 때 실시간 반영되어서 아주 기뻤다ㅜㅜ

+) 🐾 지속적인 배포

내가 몽고디비 개발이 처음이어서 ObjectId 개념이 생소했고, 어노테이션을 사용해서 자동 insert한 Date 타입의 포맷을 간과했다... 열심히 뚝딱뚝딱 수정을 하고... 다시 도커 이미지를 빌드했다.

태그로 도커 이미지 버전 관리하기

사실 이미지 처음 만들 땐 태그가 뭣하러 있나 생각했다.
근데 같은 이미지를 버전별로 관리하고 싶다면 태그를 써서 관리할 수 있다.

Docker Layer 개념

변경된 코드로 이미지를 다시 빌드하는데 로그에 영어로 레이어가 이미 존재한다~ 식으로 뜨는 것을 봤다.
궁금해서 검색해봤더니 Layer 기능을 사용해서 도커는 변경된 사항에 대해서만 새로 이미지를 생성하고 기존 레이어는 재사용한다. 일종의 캐싱이고 효율적이라 할 수 있다😊

Dockerfile 명령어 까보기

주석에 설명을 추가했다.

# base image
FROM openjdk:17-jdk-slim

# set the working directory, 컨테이너 내부의 작업 디렉토리 설정이다. 네이밍은 자유.
WORKDIR /app

# copy the jar file, 위에서 정한 /app 디렉터리에 app.jar 라는 이름으로 jar가 복사된다.
COPY build/libs/ktalk-review-0.0.1-SNAPSHOT.jar app.jar

# run the jar file, 컨테이너가 시작될 때 실행할 명령어를 설정한다. 여기서는 app.jar를 실행한다.
ENTRYPOINT ["java", "-jar", "app.jar"]
profile
Front-End와 Back-End 경험, 지식을 공유합니다.

0개의 댓글