[Docker] Nginx - React - Nodejs - Mysql 연동 실습하기
이번 포스팅에서 구현할 시스템의 구성도는 위와 같다.
Ngnix, React, NodeJS, MySQL을 도커 컨테이너로 띄어 간단한 시스템을 구현해보겠다. Docker 관련 포스팅이니 React, Nodejs, MySQL 관련 소스는 블로그가 아닌 깃허브에 남겨놓으니 관심있는 분은 참고하기를 바란다.
Ngnix ( 리버스 프록시 서버 )
클라이언트(User)가 웹페이지를 요청하면 FrontEnd 서비스에서 제공 가능하지만, API로 데이터를 요구한다면 DB와 연동된 BackEnd 서비스가 필요하다. 두 개의 서비스가 하나의 시스템에 있다. 두 개의 서비스를 포트번호로 분리해서 구현하면 클라이언트는 서비스를 이용이 굉장히 불편할 것이다. 웹페이지 서비스에는 3000번 포트로, API 서비스에는 5000번 포트로 접근해야 하니 말이다.
그러므로 중간에서 요청을 적절한 서비스에 분산시켜줄 프록시 서버가 필요한데, 그것이 Nginx이다.
default.conf
# frontend
upstream frontend{
server frontend:3000; #3000번 포트
}
# backend
upstream backend{
server backend:5000; #5000번 포트
}
server {
# 80포트 열기
listen 80;
# / 경로는 웹페이지 서비스로 ( frontend )
location / {
proxy_pass http://frontend;
}
# /api 경로는 api 서비스로 ( backend )
location /api {
proxy_pass http://backend;
}
}
Dockerfile
FROM nginx
COPY ./default.conf /etc/nginx/conf.d/default.conf
docker-compose 파일이 실행되면 서비스 간 네트워크가 형성되는데, 이때 Nginx는 frontend 서비스와 backend 서비스를 파악한다. 열어놓은 80포트로 '/' 요청이 들어오면 웹페이지 서비스(frontend)로 보내고 '/api' 요청이 들어오면 API 서비스(backend)로 보낸다.
frontend 서비스 ( React )
Dockerfile
FROM node:alpine
WORKDIR /app
COPY package.json ./
RUN npm install
COPY ./ ./
CMD ["npm", "run", "start"]
npm run start로 리액트 개발서버를 띄웠다.
개발환경은 변경된 소스가 실시간으로 반영되어 브라우저에 보여야 한다. 리액트 개발서버를 띄우면 변경된 소스를 실시간으로 빌드하여 브라우저에 반영할 수 있다.
개발환경이 아닌 경우, 리액트는 npm run build로 정적소스를 생성하고 ngnix나 serve 패키지 같은 웹서버가 정적소스를 바라보는 구조로 구성된다. 운영환경은 실시간 반영보다는 빠른 반응이 중요하다. 정적소스를 미리 생성해놓고 요청이 들어오면 바로 응답할 수 있도록 웹서버를 구현하는 것이다.
backend 서비스 ( NodeJS )
server.js
// 중략...
//DB 테이블에 있는 모든데이터를 프론트 서버에 보내주기
app.get('/api/values',function(req,res){
//중략...
})
//클라이언트에서 입력한 값을 데이터베이스 리스트 테이블에 넣어주기
app.post('/api/value',function(req,res,next){
//중략...
})
//5000번 포트 열기
app.listen(5000,()=>{
console.log('애플리케이션이 5000번 포트에서 시작되었습니다.');
})
api 경로에 따라 get, post 응답을 구현하고 listen으로 5000번 포트를 오픈한다.
Dockerfile
FROM node:alpine
WORKDIR /app
COPY ./package.json ./
RUN npm install
COPY ./ ./
CMD ["npm","run","dev"]
package.json
# 중략 ...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js",
"dev": "nodemon server.js"
},
# 중략...
백엔드 서버의 경우 소스변경시 서버재기동이 필요하다.
자동으로 서버재기동이 가능하도록 package.json에 dev라는 명칭으로 nodemon을 모듈을 사용하도록 설정해놓았다. nodemon은 코드가 변경되면 자동으로 서버재기동을 가능하도록 도와주는 모듈이다. 그리고 Dockerfile에는 npm run dev로 소스변경시 자동으로 재기동하는 백엔드 서버를 기동하도록 설정한다.
db 서비스 ( MySQL )
initialize.sql
-- myapp DB 있으면 삭제하기
DROP DATABASE IF EXISTS myapp;
-- DB로 접근하는 모든 IP 접근권한 부여하기
GRANT ALL PRIVILEGES ON *.* TO root@'%' IDENTIFIED BY 'yourpw' WITH GRANT OPTION;
-- DB 생성
CREATE DATABASE myapp;
USE myapp;
-- 테이블 생성
CREATE TABLE lists(
id INTEGER AUTO_INCREMENT,
value TEXT,
PRIMARY KEY (id)
);
MySQL 서비스 실행시 자동으로 실행될 SQL문을 sql파일로 생성해놓는다.
my.cnf
[mysqld]
character-set-server=utf8
[mysql]
default-character-set=utf8
[client]
default-character-set=utf8
한글도 사용가능하도록 인코딩 설정을 한다.
Dockerfile
FROM --platform=linux/x86_64 mysql:5.7
ADD ./my.cnf /etc/mysql/conf.d/my.cnf
MySQL 이미지는 플랫폼 일관성을 유지해야 한다. 나는 Mac m1칩 환경(ARM64)이다. 도커는 환경과 유사한 베이스이미지를 가져오려고 하기 때문에 플랫폼 옵션으로 AMD64환경의 MySQL이미지를 가져올 수 있도록 설정해야 한다. 그리고 인코딩 설정파일을 복사한다.
Docker-Compose
docker-compose.yml
version: "3"
services:
frontend:
build:
dockerfile: Dockerfile
context: ./frontend
volumes: #/frontend 디렉토리를 공유하되 종속성모듈은 공유X
- /app/node_modules # 종속성은 공유x
- ./frontend:/app # 나머지 소스는 공유
stdin_open: true
nginx:
restart: always
build:
dockerfile: Dockerfile
context: ./nginx
ports: #포트포워딩
- "3000:80"
backend:
build:
dockerfile: Dockerfile
context: ./backend
container_name: app_backend
volumes: #/backend 디렉토리를 공유하되 종속성모듈은 공유X
- /app/node_modules # 종속성은 공유x
- ./backend:/app # 나머지 소스는 공유
mysql:
build:
dockerfile: Dockerfile
context: ./mysql
restart: unless-stopped
container_name: app_mysql
ports: #포트포워딩
- "3306:3306"
volumes:
- ./mysql/mysql_data:/var/lib/mysql #MySQL 컨테이너가 재기동해도 기존 데이터 유지 가능
- ./mysql/sqls/:/docker-entrypoint-initdb.d/ # 초기실행이 필요한 SQL 파일 공유
environment:
MYSQL_ROOT_PASSWORD: yourpw
MYSQL_DATABASE: yourdb
dockek-comose.yml에서 몇 가지 주요설정을 살펴보겠다.
포트포워딩 ( ports )
Nginx와 Mysql은 포트포워딩이 필요하다. 로컬PC의 포트와 컨테이너의 포트와 연결을 해주어야 localhost URL로 요청을 전달받을 수 있다.
볼륨 ( volumes )
frontend와 backend는 종속성 모듈 소스와 개발자가 개발한 소스가 있다. 종속성 모듈 소스는 변경되지 않지만 개발자가 개발한 소스는 끊임없이 변경된다. 그러므로 종속성 모듈은 제외한 나머지 소스를 호스트와 공유하기 위와 같이 설정한다.
mysql은 MySQL 컨테이너가 재기동되면 이전에 삽입된 데이터가 모두 사라져버린다. 그러므로 기존 데이터를 호스트와 공유하여 컨테이너가 재기동되어도 데이터가 유지될 수 있도록 한다.
테스트 해보기
docker-compose.yml이 위치한 디렉토리에서 아래 명령어를 실행한다.
docker-compose up
docker-compose up 명령어로 도커 컨테이너 4개가 모두 정상적으로 올라왔다. 그럼 서비스를 요청해보자.
URL : localhost:3000
localhost:3000은 '/' 요청으로 frontend 서비스에서 페이지를 가져왔다. 그럼 데이터를 입력하고 확인 버튼을 눌러보자.
확인버튼을 클릭하면 frontend서비스는 /api/value 경로로 DB INSERT 요청을 보낸다. 요청을 받은 Nginx는 /api 요청이므로 backend서비스로 요청을 전달하고 MySQL 서비스와 통신하여 데이터를 DB에 삽입한다. 저장이 완료되면 frontend 서비스는 /api/values 경로로 DB SELECT 요청을 보낸다. Nginx는 /api 경로이므로 backend 서비스에게 요청을 전달하고 backend 서비스는 MySQL 서비스와 통신하여 DB에 저장된 데이터를 '조회'한다.
이렇게 하여, 개발환경에서 Docker Compose로 도커 컨테이너 4개를 띄어 간단한 시스템을 구현해보았다. 다음에는 쿠버네티스를 활용하여 실제 운영환경에서 동작하는 간단한 시스템을 구현해보겠다.
참고자료