Docker 간단한 node js 어플리케이션 실행하기
이번 포스트의 목표는 간단한 nodejs 어플리케이션을 docker 에서 실행해보는 것이다. 컨테이너 내부에서 어플리케이션이 실행되기 위한 도커의 원리를 위주로 알아보자.
우선 NodeJs 앱을 도커 환경에서 실행하려면,
- 이미지를 생성하고,
- 그 이미지를 이용해 컨테이너를 실행 후
- 컨테이너 내부에서 nodeJs 어플리케이션을 실행해야 한다.
즉 이미지를 생성하기 위해선 Dockerfile 을 먼저 작성해야만 한다.
디렉토리 구조
package.json
{
"name": "nodejs-docker-app",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"start":"node server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"express" : "^4.16.1"
}
}
server.js
실행하고자 하는 nodejs 어플리케이션의 코드다.
5000번 포트로 접속시 Hello world 문구를 띄우는게 전부인 간단한 어플리케이션이다. 이를 도커 환경에서 띄워보자.
const express = require("express");
const PORT = 5000;
// APP
const app = express();
app.get("/", (req, res) => {
res.send("Hello world");
});
app.listen(PORT);
console.log("server is running");
Dockerfile 작성하기
From node:10
COPY ./ ./
RUN npm install
CMD ["node", "server.js"]
FROM : 이미지 생성 시 기반이 되는 이미지 레이어 -> node:10 은 node 라는 이미지의 10 버전을 의미한다.
COPY : Dockerfile 의 현재 디렉토리에는 server.js 와 package.json
이 존재한다. 이 두 파일도 container 내부
에서 필요한데, COPY 명령어
를 누락하면 해당 파일들이 도커 컨테이너에 존재하지 않기 때문에 RUN npm install
명령어가 제대로 수행되지 않는다.
RUN : 노드 어프리케이션을 실행시키는 명령어
CMD : 노드 웹 서버를 작동시키기 위해선 node + 엔트리 파일명
을 입력해야 한다.
위와 같이 파일을 모두 작성 후 docker 이미지를 빌드하고 컨테이너를 실행해보자.
docker build -t tbnsok/nodejs ./
docker run tbnsok/nodejs
이제 localhost:8080 을 브라우저에 입력하여 "Hello world" 가 출력되는지 확인해보자.
왜 예상대로 출력되지 않는걸까? 정답은 포트 매핑에 있다. 포트 매핑을 제대로 이해하지 못하면 개발 시 에러를 자주 마주하기에 이번 기회에 알아보자.
포트 매핑
server.js 에서 PORT 변수엔 8080 값을 할당했다.
즉 도커 컨테이너 내부에서 실행된 nodejs 어플리케이션은 8080 포트에서 실행된다는 의미인데,
클라이언트 브라우저에서는 도커 컨테이너 내부의 8080 포트
로 접속하지 못한다. 즉 브라우저와 도커 컨테이너 포트 간의 매핑 작업
이 필요하다.
docker run -p 8080:8080 tbnsok/nodejs:latest
위 처럼 -p 옵션
을 사용해 브라우저에서 접속하는 포트와 컨테이너 내부의 포트를 매핑시켜주면, 우리가 원하는대로 node 어플리케이션에 접속할 수 있다.
WORKDIR 설정
WORKDIR 는 working directory 를 의미하는데, Dockerfile 에 이 WORKDIR 를 명시해줘야 한다.
뭐 때문에 추가해야하는 걸까? 우선 WORKDIR 는 무엇을 의미할까?
바로 이미지 내부에서 nodeJs 애플리케이션 소스 코드를 가지고 있을 디렉터리를 생성하는 것이다.
From node:10
WORKDIR /usr/src/app # 추가된 부분
COPY ./ ./
RUN npm install
CMD ["node", "server.js"]
Dockerfile 에 WORKDIR 설정을 해주고 다시 빌드한 후 실행해보자.
docker build -t tbnsok/nodejs ./
docker run -it tbnsok/nodejs sh
쉘 환경에서 ls 명령어로 디렉토리 구조를 살펴보면, 우리가 직접 생성한 server.js, Dockerfile, package.json
파일이 존재하는 것을 확인할 수 있다.
만약 WORKDIR
경로를 설정하지 않았다면, 아래 이미지 처럼 루트 디렉토리
에 우리가 생성할 파일이 모두 섞이게 된다.
Dockerfile 에 아래처럼 /usr/src/app 경로를 설정해주었기에, 빌드시에 해당 경로로 어플리케이션 파일이 생성된다. 이렇게 하여 모든 파일이 루트 디렉토리에 모여있는 혼잡한 구조를 피하고, 짜임새 있는 디렉토리 구조를 만들 수 있다.
WORKDIR /usr/src/app
어플리케이션 소스 변경으로 재빌드시 효율적으로 하는 법
현재 빌드 구조는, 소스코드의 작은 부분을 수정하더라도 처음부터 종속성을 설치하고 빌드하기 때문에 비효율적이다.
이런 문제는 어떻게 해결할 수 있을까?
우선 소스 코드가 변경될 시 소스 코드는 당연히 재빌드 시켜야 하지만, 패키지 종속성은 변경이 없다면 굳이 재빌드 할 필요가 없다.
# 변경 전
From node:10
WORKDIR /usr/src/app
COPY ./ ./
RUN npm install
CMD ["node", "server.js"]
# 변경 후
From node:10
WORKDIR /usr/src/app
COPY package.json ./
RUN npm install
COPY ./ ./
CMD ["node", "server.js"]
변경 후의 Dockerfile 을 보자.
npm install 이전에 COPY package.json ./
명령어가 추가됐다.
이렇게 하여 변경사항이 server.js 의 소스에만 있다면, 불필요하게 모듈(package)을 다시 받지 않도록 했다.
RUN npm install
이 수행되기 이전에 COPY
할 때는 오직 module 에 관한 것만 해주었다.
RUN npm install
이후에 다시 모든 파일들을 COPY
하는데, 이렇게 하면 모듈은 모듈에 변동사항이 있을 때만 다운로드 할 수 있게 되어, 불필요한 빌드과정을 배제할 수 있다.
Docker Volume
도커 볼륨은 무엇일까? 도커 볼륨이란 도커 컨테이너에서 호스트 디렉터리(로컬)에 있는 파일들을 매핑해서 사용하는 것을 의미한다.
즉 도커 컨테이너에서 로컬에 있는 파일들을 참조하게 만들어, 소스 코드에 수정이 있을 때 즉각적으로 대응할 수 있게 된다. (빌드 과정이 불필요해지는 것)
docker run -d -p 5000:8080 -v /usr/src/app/node_modules -v ${pwd}:/usr/src/app tbnsok/nodejs
첫번째 -v
옵션에는 /usr/src/app/node_modules
를 입력한다. 로컬에는 node_modules 가 없기 때문에 배제하는 것이며,
두번째 -v
옵션은 : 콜론을 사용해 현재경로(pwd)
와 도커 컨테이너 내부의 경로(/usr/src/app) 을 매핑해준다.
이렇게 volume 을 이용해서 실행하면 빌드 시 소스가 변경되더라도, 바뀐 코드가 바로 적용된다.
본 포스팅은 인프런의 따라하며 배우는 도커와 CI 환경 강의를 수강하고 정리한 내용을 바탕으로 정리됐습니다.
사용된 도표와 코드는 해당 강의에서 참조했음을 밝힙니다.