-
[Docker] Docker(도커)+NodeJs(Sequelize)+Mysql 연동하기 / Docker-Compose 구성 및 Docker-Compose 순서 설정.프로그래밍 2019. 11. 11. 17:06
최근 학교 데이터베이스 과제를 진행하면서 간단한 연락처를 구성하는 웹페이지를 짜게 되었다.
나는 기술 스택을 NodeJS + Mysql 로 사용하고싶은데, 조교님이 기술 스택을 Python(flask) + Postgresql을 사용하셔서
다른 기술스택을 도커에 올려서 제출해도 되냐고 여쭤보니
흔쾌히 OK하셨다.
원래 조교님이 쓰시는 기술스택을 사용하면, 그냥 코드를 알집파일로 압축해서 제출해도 되고
(조교님의 컴퓨터에 파이썬이랑 flask가 다 깔려있다)
docker를 통해 제출하고싶으면 조교님이 만들어주신 docker-compose 파일을 사용해, 내부 코드만 짜면 되는데
내가 스스로 다른 기술스택을 위해
(왜그랬을까...ㅠㅠ)Dockerfile.dev부터 .dockerignore, docker-compose.yaml, docker-entrypoint.sh을 짜다보니 진짜 삽질을 꽤 많이 했는데,
이에 대한 지식을 간단히 정리해두려고 한다.
도커는 처음이다보니 코드가 좀 허접할수도 있음.일단 내 어플리케이션의 구조는 아래와 같다.
어플리케이션 : Web application
서버 : NodeJS
데이터베이스 : Mysql
서버-데이터베이스 연동을 위해 Sequelize 모듈을 사용했다.
서버와 데이터베이스는 각각 컨테이너에 담았다.
내 프로젝트의 전반적인 구조는 아래와 같다. sequelize를 위해 sequelize-cli를 사용해 폴더 구조를 잡았다.
Dockerfile.dev : 도커 이미지를 만들때 사용하는 파일이다.
.dockerignore : 도커 설정 진행시 무시하는 파일이다. 나는 여기서 node_modules 폴더를 ignore시켜서 해당 폴더가
도커 컨테이너 안으로 복사되지 않도록 했다.(너무 많아서 시간이 오래걸림, 컨테이너 내에서 npm install을 진행하게끔)
docker-compose.yaml : 도커 컨테이너를 쉽게 만들 수 있도록 도와주는 파일이다.
docker-entrypoint.sh : 컨테이너 내부에서 cmd작업을 할 수 있도록 해주는 파일이다. (cmd와 entrypoint에 관한 차이는 아래의 링크 참고)
https://bluese05.tistory.com/77
<.dockerignore>
node_modules npm-debug.log
도커가 무시할 파일 리스트를 나타냈다. node_modules는 너무 용량이 크기 때문에
도커 컨테이너 내부에서 다시 다운로드 받을 수 있게 Dockerfile.dev에 설정했다.
<Dockerfile.dev>
FROM node:10 ENV DOCKERIZE_VERSION v0.2.0 RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ && tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz # Create app directory RUN mkdir -p /usr/src/app WORKDIR /usr/src/app # Install dependencies COPY package.json . RUN npm install # Bundle app source COPY . . RUN chmod +x docker-entrypoint.sh ENTRYPOINT ./docker-entrypoint.sh # Exports EXPOSE 3000
<docker-entrypoint.sh>
dockerize -wait tcp://mysql:3306 -timeout 20s echo "Start server" node app.js
docker-hub의 node:10 이미지를 사용했다.
node_modules는 ignore해놓고 app source를 복사할때 복사가 되지 않게 한 후에,
컨테이너 내부에서 npm install을 통해 모듈을 다운로드받았다.
RUN wget을 통해 DOCKERIZE라는 프로그램을 다운로드 했다.
dockerize라는 프로그램은 도커 컨테이너의 실행순서를 결정해줄 수 있는 프로그램이다.
이를 도입한 이유는, 내가 mysql 컨테이너가 완전히 사용 가능한 상태까지 기다린 후에, nodejs 컨테이너를 실행해야만
sequelize sync를 올바르게 진행할 수 있기 때문이다.
(나와 같은 문제를 겪은분에게 힌트를 얻었다)
해결책으로,
먼저 app 컨테이너가 실행되면 docker-entrypoint.sh을 실행시킨 뒤에,
docker-entrypoint.sh에서, dockerize -wait tcp://mysql:3306 -timeout 20s
를 통해서 mysql컨테이너가 켜진 후 20초정도 기다렸다가 실행시켰더니 정상적으로 작동했다!
해당 방법에 대한 힌트는 아래의 블로그를 참고했다.
https://jupiny.com/2016/11/13/conrtrol-container-startup-order-in-compose/
<docker-compose.yaml>
version: "3.1" networks: app-tier: driver: bridge services: mysql: networks: - app-tier image: mysql container_name: mysql_container restart: always ports: - "3306:3306" environment: - MYSQL_ROOT_PASSWORD=password1 - MYSQL_USER=user1 - MYSQL_PASSWORD=password1 - MYSQL_DATABASE=user1 app: networks: - app-tier build: context: . dockerfile: Dockerfile.dev volumes: - ./:/usr/src/app - /usr/src/app/node_modules # Remove this if you have pure JS dependencies restart: always ports: - "3000:3000" links: - mysql depends_on: - mysql
mysql 컨테이너와, app을 하나의 네트워크로 묶었다. mysql container에는 docker-hub에서 제공하는 mysql 이미지 파일을 사용했고,
app container는 위에서 설정한 Dockerfile.dev파일을 이용해 빌드했다.
restart: always는 만약, 에러가 발생해 컨테이너가 exit될 경우, 다시 실행시켜주는 항목이다.
ports : "3306:3306" -> 컨테이너를 실행시키는 host의 3306포트와 컨테이너의 3306포트를 포트포워딩 하겠다는것이다.
즉, 컨테이너를 실행시키는 host의 3306번 포트로 들어오는 녀석들을 자신 컨테이너의 3306번으로 넘겨달라는 뜻이다.
links: 도커의 link구조에 관한 자세한 설명은 아래의 글에 아주 잘 나와있다. (진짜 너무너무 고맙습니다..ㅠㅠㅠㅠ. 주인장님)
https://bluese05.tistory.com/54?category=559611
depends_on:을 통해서 mysql컨테이너가 실행되고 난 뒤에 app 컨테이너가 실행될 수 있도록 했다.
나는 처음에 여기서 depends_on 설정을 통해 mysql 컨테이너를 실행한 후에 app 컨테이너를 실행하는데, 뭐가문제야!! 싶었다.
하지만 여기서 컨테이너가 만들어지는 시점과, 컨테이너가 사용 가능한 시점은 완전히 달랐다.
docker의 공식문서에도 나와있음. 그래서 컨테이너가 만들어지는 시점에 다른 컨테이너를 실행시키는 depend_on 설정만으로
sequelize 연동을 할 수는 없었다. 따라서 위에 적혀있는 것처럼 DOCKERIZE라는 프로그램을 도입하게 되었다.
https://docs.docker.com/compose/startup-order/
<config/config.json>
{ "development": { "username": "user1", "password": "password1", "database": "user1", "host": "mysql", "port": 3306, "dialect": "mysql", "operatorsAliases": false }, "test": { "username": "root", "password": null, "database": "database_test", "host": "127.0.0.1", "dialect": "mysql", "operatorsAliases": false }, "production": { "username": "root", "password": null, "database": "database_production", "host": "127.0.0.1", "dialect": "mysql", "operatorsAliases": false } }
내 sequelize에 관한 config설정이다. 나는 development를 사용했고, docker-compose에서 설정해준 environment대로
sequelize설정을 했다.
<models/index.js>
const Sequelize = require("sequelize"); const env = process.env.NODE_ENV || "development"; const config = require(__dirname + "/../config/config.json")[env]; const db = {}; const sequelize = new Sequelize( config.database, config.username, config.password, config ); db.User = require("./user")(sequelize, Sequelize); db.sequelize = sequelize; db.Sequelize = Sequelize; module.exports = db;
해당 파일에서 sequelize객체를 만들면서, config정보를 불러온다.
<app.js>
var express = require("express"); var app = express(); var bodyParser = require("body-parser"); const db = require("./models"); // contact.csv파일을 읽기 위한 준비. const fs = require("fs"); const parse = require("csv-parse/lib/sync"); const csv = fs.readFileSync("csv/contact.csv"); const records = parse(csv.toString("utf-8")); // 데이터베이스 싱크하기. db.sequelize.sync(); ... 중략
이후 app.js에서 db를 sequelize.sync()했다.
이후에는 싱크가 성공해서, 정상적으로 프로그램이 작동하는것을 볼 수 있다.
전체 소스코드 파일은 내 깃허브에 올려두겠다. 테스트 해볼사람은 얼마든지 해봐도 좋다.
'프로그래밍' 카테고리의 다른 글
[nodejs] Request모듈을 이용해서 html을 불러올 때 깨지는 문제 해결(cheerio, iconv-lite, request, charset) (0) 2019.11.27 [Network] TCP Socket Programming - File Download [Server - Client] (0) 2019.11.13 [자료구조] 이진탐색트리(Binary Search Tree, BST)의 시간복잡도 (0) 2019.08.14 [자료구조] 자바로 트리(Tree) 구현하기, 트리의 탐색 (0) 2019.08.08 [자료구조] JAVA로 그래프(Graph)구현하기, BFS, DFS (0) 2019.08.07