도커라이징(dockerize)의 장점 중 하나는 개발/배포시 환경에 신경을 덜 쓰면서 앱 개발에만 신경쓸 수 있다는 점이라고 생각한다. 윈도우 랩톱에서 개발된 웹 어플리케이션이 리눅스 서버에서 정상적으로 동작한다는 것을 장담하기가 매우 어렵다. 물론 docker가 윈도우나 Mac 상에서 리눅스 가상머신을 사용하기 때문에 리눅스와 100% 동일한 환경에서 동작할 것이라고 장담하기는 어렵지만, 그래도 전자의 경우보다는 정상적으로 동작활 확률이 더 높다고 본다.
Node.js 앱을 개발할 때도 배포환경과 유사하게 도커라이징하면, 개발/배포 환경 간의 간극을 줄일 수 있지 않을까 고민이 들어서 오늘은 개발 환경을 위한 Node.js 도커라이징 방법을 소개한다.
Node.js 앱
프로젝트 디렉토리를 적당히 하나 생성한 후, 프로젝트 디렉토리에 `package.json`을 만든다. `express`를 이용한 웹 앱을 만들어 볼 것이고, `pm2`로 개발환경에서 웹 앱을 watch, restart 등을 하기 위해 설치한다.
pm2 옵션을 좀 더 살펴보자.
--restart-delay: 웹 애플리케이션이 어떤 이유에 의해 종료된 후 재시작하게되는 delay로 ms 단위이다.
--watch: 소스 코드 파일의 변화를 감지한다.
--ignore-watch: 특정 디렉토리의 변화는 감지하지 않는다.
--no-daemon: 애플리케이션을 실행할 때 attach 해서 log가 출력되게 한다. pm2는 기본적으로 detach 된다.
nodemon이 아니라 pm2를 사용한 이유는 pm2가 더 기능이 많아서인데, pm2가 nodemon보다 무겁기 때문에 pm2를 사용하는 것이 반드시 좋다고 할 수는 없다. 많은 기능을 사용할 필요가 없다면 nodemon을 사용해도 충분할 것 같다.
{
"name": "web_app_on_docker",
"version": "1.0.0",
"description": "Node.js development on Docker",
"author": "First Last <first.last@example.com>",
"main": "src/server.js",
"scripts": {
"start": "pm2 start --restart-delay=5000 --watch --ignore-watch=\"node_modules\" --no-daemon node -- src/server.js"
},
"dependencies": {
"express": "^4.17.1"
},
"devDependencies": {
"pm2": "^4.2.1"
}
}
그 후, 터미널에서 `npm install` 를 입력해서 express를 설치한다.
$ npm install
`~/src/server.js`에 서버를 실행하는 소스코드를 작성한다.
const express = require('express');
const PORT = 8080;
const HOST = '0.0.0.0';
const app = express();
app.get('/', (req, res) => {
res.send('Hello world\n');
});
app.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`);
작성 후, 터미널에서 `npm run start`를 입력하면 pm2가 실행되고 웹 브라우저에서 `http://localhost:8080`로 접속하면 "Hello world"가 출력되는 것을 확인할 수 있다.
Dockerfile
프로젝트 디렉토리에 `Dockerfile`을 만들고 아래와 같이 작성하자.
FROM node:12
# 앱 디렉터리 생성
WORKDIR /usr/app
# 앱 의존성 설치
# 가능한 경우(npm@5+) package.json과 package-lock.json을 모두 복사하기 위해 와일드카드를 사용
COPY package*.json ./
RUN npm install
# 프로덕션을 위한 코드를 빌드하는 경우
# 앱 소스 추가
COPY . .
EXPOSE 8080
CMD [ "npm", "run", "start" ]
`dockerignore`도 만들어 `node_modules`가 이미지를 빌드할 때 추가되지 않도록 하자.
node_modules
Build & Run Docker image
`package.json`을 열어 docker-build, docker-run을 하는 스크립트를 추가한다.
docker-build: image를 빌드한다.
docker-run: docker image를 실행한다.
-v: 현재 소스 디렉토리와 컨테이너 안의 소스 디렉토리를 연결해서 파일 수정시 변화를 감지하도록 한다.
--rm: 컨테이너 종료시 삭제되도록 한다.
-p: 8080:8080 컨테이너 밖과 안을 모두 8080으로 연결한다.
{
...
"scripts": {
"start": "pm2 start --name web_app --restart-delay=5000 --watch --ignore-watch=\"node_modules\" --no-daemon node -- src/server.js",
"docker-build": "docker build -t ikay/web-app .",
"docker-run": "npm run docker-build && docker run -v $(pwd):/usr/app --rm -p 8080:8080 ikay/web-app"
},
..
}
`npm run docker-run`을 실행시키면 도커 이미지가 빌드되고 애플리케이션이 실행된다.
애플리케이션이 실행된채로 소스코드, `src/server.js`를 수정해보자.
const express = require('express');
const PORT = 8080;
const HOST = '0.0.0.0';
const app = express();
app.get('/', (req, res) => {
// 소스 코드 수정
res.send('Hello world, 안녕 세계!\n');
});
app.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`);
실시간으로 변경된다.
'NodeJS' 카테고리의 다른 글
AsyncHooks로 context에 DB connection 저장하기 (0) | 2020.06.14 |
---|---|
setImmediate(), setTimeout() 그리고 process.nextTick() (0) | 2019.02.08 |
이벤트 루프(Event Loop)란? (0) | 2019.01.27 |
NodeJS, 블로킹(Blocking)과 넌블로킹(Non-Blocking) (0) | 2019.01.20 |
NodeJS, NodeJS란 무엇일까? (0) | 2019.01.19 |