서론
최근 프로젝트에서 도커를 통해 Spring boot와 React 프로젝트를 진행하면서 리버스 프록시 역할을 수행하는 Nginx를 같이 실행해 보았다. 추가로 Mysql까지 실행하며 모든 컨테이너는 Docker compose에 의해 실행될 수 있도록 구현하였다.
Spring boot는 비즈니스 로직을 처리하며 Mysql과 연동하여 데이터를 저장, 처리, 조회하는 기능을 담당한다. React는 사용자와 상호작용하는 부분으로 Nginx 서버를 통해 구동되며, Nginx 서버는 사용자 요청을 url에 따라 Spring boot로 보낼지, React로 보낼지 정하는 리버스 프록시 역할을 수행한다.
이 글에서는 docker-compose를 통해 각 서비스를 배포한 과정을 기록한 것이다.
본론
우선 실행을 위한 폴더구조는 아래와 같다.(배포를 하는데 필요한 정보 위주로만 간략하게 작성하였다.)
📜docker-compose.yaml
📦frontend
┣ 📂node_modules
┣ 📂public
┣ 📂src
┣ 📜default.conf
┣ 📜Dockerfile
┣ 📜package.json
┣ 📜package-lock.json
📦backend
┣ 📂build
┣ 📂db
┣ 📂src
┣ 📜.env
┣ 📜.gitignore
┣ 📜build.gradle
┣ 📜Dockifile
┣ 📜gradlew
📦conf
┣ 📜nginx.conf
db 폴더는 mysql 컨테이너 내부의 데이터와 연동되는 폴더로 따로 생성하지 않아도 컨테이너 실행 시 자동으로 생성된다. 이후 해당 폴더를 제거하지만 않으면 컨테이너를 다시 실행할 때마다 mysql 내 데이터는 유지된다.
이제 핵심이 되는 docker-compose.yaml 파일을 봐보자.
version: '3'
name: test
services:
mysql:
image: mysql:8.0.34
networks:
- khds_network
volumes:
- ./backend/db/conf.d:/etc/mysql/conf.d
- ./backend/db/data:/var/lib/mysql
- ./backend/db/initdb.d:/docker-entrypoint-initdb.d
env_file: ./backend/.env
ports:
- "3306:3306"
restart: always
backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: backend
networks:
- khds_network
ports:
- "8080:8080"
depends_on:
- mysql
environment:
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/khds_pay?useSSL=false&allowPublicKeyRetrieval=true
- SPRING_DATASOURCE_USERNAME=mysqluser
- SPRING_DATASOURCE_PASSWORD=mysqlpw
- SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT=org.hibernate.dialect.MySQL8Dialect
- SPRING_JPA_HIBERNATE_DDL_AUTO=validate
restart: always
frontend:
depends_on:
- backend
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "3000:3000"
networks:
- khds_network
nginx:
image: nginx:latest
networks:
- khds_network
restart: always
volumes:
- ./conf/:/etc/nginx/conf.d
ports:
- 80:80
depends_on:
- backend
- frontend
networks:
khds_network:
driver: bridge
1. Mysql
mysql:
image: mysql:8.0.34
networks:
- khds_network
volumes:
- ./backend/db/conf.d:/etc/mysql/conf.d
- ./backend/db/data:/var/lib/mysql
- ./backend/db/initdb.d:/docker-entrypoint-initdb.d
env_file: ./backend/.env
ports:
- "3306:3306"
restart: always
Mysql:8.0.34 이미지로 부터 생성되는 Mysql 서비스는 volumes를 설정하여 컨테이너가 종료돼도 데이터가 유지되도록 하였다.
port는 3306을 그대로 사용하였고, 환경변수를 .env 파일 내에 작성해 보았다.
.env 파일 내용은 아래와 같다.
TZ=Asia/Seoul
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_ROOT_PASSWORD=rootpassword
MYSQL_DATABASE=khds_pay
MYSQL_USER=mysqluser
MYSQL_PASSWORD=mysqlpw
2. Spring boot
backend:
image: backend:0.0.1-SNAPSHOT
networks:
- khds_network
ports:
- "8080:8080"
depends_on:
- mysql
environment:
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/khds_pay?useSSL=false&allowPublicKeyRetrieval=true
- SPRING_DATASOURCE_USERNAME=mysqluser
- SPRING_DATASOURCE_PASSWORD=mysqlpw
- SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT=org.hibernate.dialect.MySQL8Dialect
- SPRING_JPA_HIBERNATE_DDL_AUTO=validate
restart: always
backend 서비스는 springboot를 실행한 이미지인데, 이 이미지는 backend 폴더 내 Docker 파일을 통해 생성할 수 있다.
아래는 backend 폴더 내에 있는 Dockerfile이다.
FROM gradle:7.6-jdk as builder
WORKDIR /app
COPY ./ ./
RUN gradle clean build --no-daemon
FROM openjdk:17.0-slim
WORKDIR /app
COPY --from=builder /app/build/libs/backend-0.0.1-SNAPSHOT.jar .
EXPOSE 8080
USER nobody
ENTRYPOINT ["java", "-jar", "backend-0.0.1-SNAPSHOT.jar"]
빌드 후 8080 포트로 배포하도록 되어있다. 주의할 점은 sprnigboot버전에 따라 gradle 버전이 다를 수 있다는 점이다.
환경변수(environment)는 application.properties or yml 파일 내부에 작성했던 것들인데, 이번엔 따로 지정해 보았다.
3. React
frontend:
depends_on:
- backend
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "3000:3000"
networks:
- khds_network
frontend 서비스는 frontend 폴더 내 Dockerfile을 실행시킴으로써 동작한다.
아래는 Dockerfile 내부코드이다.
FROM node:16-alpine as builder
WORKDIR /usr/src/app
COPY package.json .
RUN npm install
COPY ./ ./
RUN npm run build
FROM nginx
EXPOSE 3000
COPY ./default.conf /etc/nginx/conf.d/default.conf
COPY --from=builder usr/src/app/build /usr/share/nginx/html
node를 통해 build를 한 후 nginx를 통해 서버를 실행하도록 하였다.
'COPY --from=builder usr/src/app/build /usr/share/nginx/html'를 작성해줌으로서 빌드된 react 내부 파일들이 nginx 위에서 실행될 수 있게 해 준다.
추가로 확인할 사항은 default.conf로 내용은 아래와 같다.
server {
listen 3000;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
}
4. Nginx
nginx:
image: nginx:latest
networks:
- khds_network
restart: always
volumes:
- ./conf/:/etc/nginx/conf.d
ports:
- 80:80
depends_on:
- backend
- frontend
80 포트로 열어놨으며, 주로 살펴볼 내용은 conf 폴더 내 있는 nginx.conf 파일이다. volume 기능을 통해 외부의 파일을 내부와 연동하므로, 외부에서 작성한 nginx.conf 파일을 기반으로 서비스가 동작한다.
아래는 nginx.conf 파일 내부 코드이다.
server {
listen 80;
client_max_body_size 20M;
location /api {
proxy_pass http://backend:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://frontend:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
주로 살펴볼 내용은 listen, location이다. listen은 80포트로 실행될 시 작성된 내용으로 동작한다는 것이고, location은 해당 경로로 접근이 들어왔을 시 제어한다. proxy_pass를 설정하여 /api 경로로 도달 시 Springboot 서버로, / 경로 도달 시 react 서버로 연결되도록 하였다.
주의할 점은 http:// 뒤에 'backend', 'frontend'로 설정해 두었는데, 이는 dcoker-compose 내 작성한 network로 모든 컨테이너가 묶여있어 컨테이너 이름으로 서로를 호출할 수 있는 것이다.
화면과 같이 /api로 호출하면 Spring boot가, /로 호출하면 React가 호출되는 것을 확인할 수 있다.
결론
이 글에서는 서버를 이루는 간단한 설정들만을 하였다.
세부적으로 가면 더욱 복잡한 것들이 많을 것이다.
다음에는 추가적인 사항들을 넣어가며 서버를 운용해 보아야겠다.
아직 공부할 길은 멀었다..!
참고
https://adjh54.tistory.com/420#google_vignette
https://bgpark.tistory.com/132
https://velog.io/@cwangg897/Docker-MultiStage
'docker' 카테고리의 다른 글
도커 스웜(Docker Swarm)을 통해 여러 서버를 운용해보자(로드 밸런싱, 도커 컴포즈, 스택 등) (0) | 2023.12.04 |
---|---|
docker compose를 통해 배포해보자(springboot, react, redis) (0) | 2023.11.17 |
Gradle, 도커(docker)를 통한 springboot3 with mysql 배포(docker-compose) (0) | 2023.05.07 |