SpringBoot + Docker

유광진·2024년 2월 22일

이번 포스팅에서는 로컬환경에서 작업했던 SpringBoot 팀 프로젝트를 Docker Container 환경에서 구동시키는 과정을 기록을 남기고자 합니다.

먼저 전체적인 프로젝트 아키텍처와 기술 스택은 다음과 같습니다.

OS : Windows 11
Development Tool : IntelliJ IDEA
Programming Language : Java
Framework : Spring Boot, Thymeleaf, Nginx
Database : MariaDB, AWS RDS
Build : Gradle

1. 📌 Gradle 프로젝트를 JAR로 빌드하기

먼저 Docker 컨테이너 내에서 Java 애플리케이션을 실행하기 전에, 먼저 Gradle 또는 Maven 같은 빌드 도구를 사용하여 애플리케이션을 JAR 파일로 빌드해야합니다.

1-1. 🔎 JAR 파일이란?

JAR(Java Archive) 파일은 여러 Java 클래스 파일, 리소스(예: 텍스트, 이미지 등)들을 한 곳에 모아 둔 파일 형식입니다.

이 파일 형식은 ZIP 파일 형식을 기반으로 만들어졌으며, Java 애플리케이션 또는 라이브러리를 쉽게 배포하고 실행할 수 있게 해줍니다.

1-2. 🔎 왜 JAR로 빌드해야 하는가?

여러 파일을 하나로 묶어 관리하기 때문에, 프로젝트의 빌드, 배포, 실행 과정이 훨씬 간단해집니다.

JAR 파일 하나만으로 애플리케이션을 배포하고 실행할 수 있으므로, 복잡한 디렉토리 구조나 다수의 파일을 관리할 필요가 없는 이점이 있습니다.

여기선 Gradle 빌드 도구를 이용해서 JAR 파일로 빌드합니다.

다음과 같은 명령어를 터미널에 작성합니다.

// Windows 
gradlew.bat build

Linux/Unix 사용자는 ./gradlew build 명령어를 사용!

// Linux/Unix 
./gradlew build

성공적으로 빌드가 완료가 되면 build/libs 디렉토리 내에 실행 가능한 JAR 파일을 생성됩니다.

2. 📌 Dockerfile 작성

Dockerfile에서 COPY 명령어를 사용하여 앞서 빌드한 JAR 파일을 Docker 이미지 내부로 복사하고, ENTRYPOINT 또는 CMD 명령어를 통해 JAR 파일을 실행합니다.

2-1. 🔎 Dokerfile 이란?

Dockerfile은 Docker에서 컨테이너를 실행하기 위한 설정과 명령어들을 담고 있는 파일입니다. 마치 요리법처럼, 어떤 애플리케이션이나 서비스를 컨테이너 내에서 어떻게 구동할지에 대한 지침을 제공합니다!

2-1. 🔎 Dokerfile 작성

FROM openjdk:17-alpine

EXPOSE 8080

COPY ./build/libs/warehouseProject-0.0.1-SNAPSHOT.jar app.jar

ENTRYPOINT ["java", "-jar", "-Duser.timezone=Asia/Seoul", "/app.jar"]

1. FROM openjdk:17-alpine

컨테이너의 기반이 될 이미지를 지정한다. 여기서는 OpenJDK 17이 설치된 Alpine Linux 이미지를 사용합니다.

즉, Java 애플리케이션을 실행하기 위한 Java 런타임과 Linux 시스템을 제공받게 됩니다!

2. EXPOSE 8080

이 명령은 컨테이너가 8080 포트를 사용할 것임을 나타낸다. 웹 애플리케이션이 이 포트를 통해 외부와 통신할 수 있도록 합니다.

즉, 이 포트(8080) 으로 들어오는 요청을 애플리케이션이 받아들일 수 있게 해줍니다!

3. COPY ./build/libs/warehouseProject-0.0.1-SNAPSHOT.jar app.jar

호스트 컴퓨터(자신의 컴퓨터) 에서 컨테이너 내부로 파일을 복사합니다.
warehouseProject-0.0.1-SNAPSHOT.jar 라는 이름의 Java 애플리케이션 파일을 컨테이너 내 app.jar로 복사합니다.

이렇게 함으로써, 컨테이너 내에서 애플리케이션을 실행할 수 있게 됩니다!

4. ENTRYPOINT ["java", "-jar", "-Duser.timezone=Asia/Seoul", "/app.jar"]

컨테이너가 시작될 때 실행될 명령을 지정합니다.
여기서는 java -jar -Duser.timezone=Asia/Seoul /app.jar 명령을 실행하여, 앞서 복사한 app.jar 파일을 실행합니다.
-Duser.timezone=Asia/Seoul 은 애플리케이션의 시간대를 서울로 설정.

이 명령을 통해 Java 애플리케이션을 컨테이너 안에서 실행시키게 됩니다!

3. 📌 docker-compose.yml

docker-compose.yml 파일은 Docker 컨테이너를 함께 구성하고 관리하기 위한 YAML 형식의 파일입니다.

이 파일을 통해 여러 컨테이너를 동시에 관리할 수 있으며, 각 컨테이너의 설정을 세밀하게 조정할 수 있습니다.

version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    environment:
      - SPRING_DATASOURCE_URL=jdbc:mariadb://자신의 aws RDS ROOT:3306
      - SPRING_DATASOURCE_USERNAME=USERNAME
      - SPRING_DATASOURCE_PASSWORD=PASSWORD

  nginx:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - app

1. version

version : '3.8' 은 docker-compose.yml 파일의 버전을 명시합니다.

2. services

services 에는 실행할 각 컨테이너를 정의합니다.

여기서는 두 가지 서비스, app과 nginx를 정의하고 있고 DB는 AWS RDS를 사용하고 있기 때문에 따로 정의하지 않습니다.

3. app

app: 은 Dockerfile 에서 정의한 Spring Boot 애플리케이션을 실행하는 Java 컨테이너입니다.

build: Dockerfile을 사용하여 이미지를 빌드하는 방법을 정의합니다.

ports: 호스트와 컨테이너 사이의 포트 매핑을 정의합니다.

Docker 컨테이너를 사용하는 경우 호스트와 각각 독립된 실행환경이기 때문에 포트포워딩을 통한 연결 설정이 꼭 필요합니다!

4. 📌 Nginx

nginx 서비스는 웹 트래픽을 관리하고 정적 콘텐츠를 제공하는 역할을 하는 Nginx 웹 서버입니다.

nginx 웹 서버는 정적(html, css, imgage, javascript) 요소들을 집중적으로 처리할 수 있기 때문에 WAS에 부담을 덜 주게 됩니다.

volumes 설정을 통해 호스트와 컨테이너 사이에 볼륨을 마운트합니다.

Nginx의 설정 파일(default.conf)을 호스트에서 컨테이너의 설정 디렉토리(/etc/nginx/conf.d/)로 복사합니다.

마지막으로, depends_on: nginx 서비스가 app 서비스에 의존한다는 것을 나타냅니다.

app 서비스가 실행된 후에 nginx 서비스가 시작되는걸로 이해하시면 됩니다!

4-1. 🔎 default.conf

default.conf 파일은 Nginx가 설치된 직후 기본적으로 사용할 수 있는 초기 서버 설정 파일입니다.

이 파일은 일반적으로 기본 웹 서버 설정을 포함하며, Nginx를 처음 사용할 때 바로 웹 서버 기능을 테스트할 수 있게 해줍니다.

listen 80;

Nginx가 80번 포트에서 들어오는 HTTP 요청을 받아들이도록 설정합니다.

웹 서버는 80 port 에서 요청을 받도록 기본적으로 설정되어 있습니다!

location / {

특정 URL 패턴에 대한 요청을 어떻게 처리할지 결정합니다.
여기서 사이트의 모든 요청http://로 시작하는 모든 요청을 처리하도록 지정했습니다.

proxy_pass http://app:8080;

클라이언트에서 들어오는 요청을 http://app:8080으로 전달하라고 Nginx에 지시합니다.

여기서 appdocker-compose.yml 에서 정의된 서비스 이름이며, 8080은 해당 서비스가 가지고 있는 포트입니다.

proxy_set_header Host $host;

백엔드 서버가 정확한 호스트 정보를 알 수 있도록 클라이언트의 원래 호스트 이름을 프록시 요청에 포함합니다.

proxy_set_header X-Real-IP $remote_addr;

로깅이나 분석 등에 사용될 수 있게 클라이언트의 실제 IP 주소를 프록시 요청에 포함합니다.

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

클라이언트의 원래 IP 주소를 포함한 전달 경로를 나타내는 헤더를 설정합니다.
이 헤더는 리버스 프록시를 통과하는 동안 클라이언트의 IP 주소를 추적하는 데 사용됩니다.

proxy_set_header X-Forwarded-Proto $scheme;

클라이언트가 사용한 프로토콜(예: http 또는 https)을 나타내는 헤더를 설정

이 정보는 애플리케이션이 보안 연결을 요구하는지 여부를 결정하는 데 사용됩니다.

5. 📌 Docker Container 실행 명령문

로컬 환경에서 Docker 컨테이너를 빌드하고 실행하기 위한 명령어는 다음과 같습니다!

docker-compose up --build

5-1. 🔎 docker-compose up --build

이 명령어는 docker-compose.yml 파일에 정의된 모든 서비스(컨테이너)를 시작합니다.

-d 옵션(데몬 모드)을 추가하면 백그라운드에서 서비스를 실행할 수 있습니다!

--build 옵션은 Docker 이미지가 존재하지 않거나 Dockerfile이 변경 된 경우 이미지를 새로 빌드하라는 명렁어 입니다.

이 옵션을 사용하면 최신 코드 변경사항이 이미지에 반영되어 실행되도록 보장되는 이점이 있습니다.

정상적으로 실행이 되었다면 위 그림처럼 Spring 로고가 떠야합니다!

6. 📌 기타

Docker는 Linux 에서 동작하기 때문에 Windows에서 진행한 프로젝트에서 설정한 Windows 상대 경로들을 Linux가 읽을 수 있게 수정 작업이 필요합니다.

6-1. 🔎 수정 전

String uploadDirectory = "C:/work/AcornProject/src/main/resources/static/upload/";

6-2. 🔎 수정 후

String uploadDirectory = "src/main/resources/static/upload/";

7. 📌 정리

이상으로 Docker와 docker-compose를 사용하여 Spring Boot 애플리케이션과 Nginx를 함께 배포하는 방법에 대해 알아보았습니다.

Docker를 사용함으로써 개발 환경과 운영 환경의 일관성을 보장하고, 애플리케이션의 배포 및 운영을 보다 효율적으로 할 수 있는 장점들이 있습니다.

또한 언제든지 프로젝트의 docker-compose.yml 파일과 Dockerfile을 수정하여 컨테이너 환경을 자유롭게 구성하고 최적화할 수 있습니다.

단! Linux 에서 동작하는 Docker 이기 때문에 윈도우 운영체제에서 작업한 프로젝트에서 설정한 경로를 수정해야하는 작업이 번거로울 수 있습니다.

profile
백엔드 개발자 유광진 입니다.

0개의 댓글