앞서 docker-compose를 사용하여 서비스들을 묶어 실행하는 방법을 배웠다. 하지만 이런 방식은 개발 및 테스트 단계에서만 사용하거나 프로젝트 규모가 작은 경우에 사용한다고 한다. 서비스 하나의 코드를 수정하거나 재배포를 하게 되면 모든 서비스를 중단하고 다시 실행해야 하기 때문이다.
이 때문에 서비스별 Dockerfile을 사용하여 개별 배포하는 방법을 알아보았다.
사용한 파일들은 이전 글에서 사용한 파일들을 그대로 사용했다.
FROM mysql:8.0
ENV MYSQL_ROOT_PASSWORD=root1234
ENV MYSQL_DATABASE=metadb
# 해당경로에 sql파일이 들어가면 실행과 동시에 수행된다. (기본 데이터들 삽입)
COPY init.sql /docker-entrypoint-initdb.d
CMD ["--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci"]
FROM openjdk:11-jdk-slim
WORKDIR /app
COPY . .
RUN chmod +x ./gradlew
RUN ./gradlew clean build
ENV JAR_PATH=/app/build/libs
RUN mv ${JAR_PATH}/*.jar /app/app.jar
ENV SPRING_DATASOURCE_URL=jdbc:mysql://mysql-container:3306/metadb?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false&allowPublicKeyRetrieval=true
ENV SPRING_DATASOURCE_DRIVER=com.mysql.cj.jdbc.Driver
ENV SPRING_DATASOURCE_USERNAME=root
ENV SPRING_DATASOURCE_PASSWORD=root1234
# application-prod.yml 사용
ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=prod", "app.jar"]
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
upstream backend {
server spring-container:8080;
}
server {
listen 80;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
proxy_redirect off;
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-Host $server_name;
proxy_set_header X-NginX-Proxy true;
}
location /api/ {
proxy_pass http://backend;
rewrite ^/api(/.*)$ $1 break; # /api/ 제거 된다.
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_redirect off;
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-Host $server_name;
}
}
}
기존에는 upstream 블록에서 docker-compose.yml에서 생성한 서비스명(backend)을 사용했지만 이번에는 사용하지 않을 것이므로 스프링 컨테이너를 실행할 때 사용할 이름을 지정한다. 나머지는 동일하다.
FROM node:alpine as build
WORKDIR /app
# 라이브러리 설치
COPY package.json /app
RUN npm install --silent
# node_modules가 이미 생성된 시점이기 때문에 복사해오지 않는다.
COPY . /app
RUN npm run build
FROM nginx
# 생성된 build 이미지로부터 리엑트의 build 폴더 내부의 내용(결과물)을 /usr/share/nginx/html 로 옮긴다.
COPY --from=build /app/build /usr/share/nginx/html
# 내가 설정한 nginx.conf 파일 덮어씌우기
COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
ENTRYPOINT ["nginx", "-g", "daemon off;"]
생성한 이미지들을 컨테이너로 실행시키더라도 docker-compose 처럼 묶지 않는다면 서로간의 통신이 되지 않는다. 이를 위해 두가지 옵션을 사용할 수 있다. --link 와 --network 옵션이다.
--link는 예전에 사용하던 방식으로 지금은 권장하지 않고 --network 옵션을 주로 사용한다고 한다.
--network를 사용하기 위해서는 먼저 도커 네트워크를 생성해야 한다.
# 도커 네트워크 생성 (docker-network라는 이름의 네트워크 생성)
$ docker network create docker-network
# 도커 네트워크 리스트 조회
$ docker network ls
# 도커 네트워크에 연결된 컨테이너 목록 확인
$ docker network inspect docker-network
# 도커 네트워크 삭제
$ docker network rm docker-network
도커 네트워크까지 생성을 완료했다. 이제 mysql, spring, react 이미지들을 해당 네트워크로 실행시킨다.
# mysql
$ docker run -dit --name mysql-container -p 3306:3306 --network docker-network {imageId}
# spring
$ docker run -dit --name spring-container -p 8080:8080 --network docker-network {imageId}
# react & nginx
$ docker run -dit --name react-container -p 80:80 --network docker-network {imageId}
--network 옵션을 사용하여 연결할 도커 네트워크를 명시하고 실행시킨다. docker network inspect 명령어를 통해 연결된 컨테이너들을 확인해보자.
여기서 주의할 점은 스프링부트의 컨테이너 이름은 nginx.conf 파일에서 upstream 블록에서 지정한 이름(여기서는 spring-container)으로 지정해줘야 한다. nginx는 /api/ 경로로 들어온 요청이 있다면 ‘/api’를 제거해서 upstream에서 지정된 컨테이너(spring-container)로 요청을 전달해준다.