Lined Notebook

[Docker] 06. Docker 기반 Node.js 앱 만들기

by ymkim

01. 섹션 설명

Node.js 공식 홈페이지에서 도커를 이용하여 Node.js를 이용하는 예시 부분을
사용하여 도커를 실정에 도입하는 방법 정리

  • Dockerizing a Node.js web app
    • Node.js 앱은 정말 간단한 앱을 만들 것이다
    • Node.js 보다는 Docker에 초점을 맞춰서 진행
    • Dockerfile을 어떤식으로 작성하는지 조금 더 딥하게 들어간다

02. Node.js 앱 만들기

  • package.json
    • 프로젝트 정보
    • 프로젝트에서 사용중인 패키지의 의존성 관리
  • server.js 파일 필요
    • node.js 시작 시 가장 먼저 실행되는 파일

02-1. package.json 파일 만들기

npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install ` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (nodejs-docker-app)
version: (1.0.0)
description:
entry point: (index.js) server.js
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to C:\\Users\\ymkim\\OneDrive\\바탕 화면\\도커와_CI환경\\nodejs-docker-app\\package.json:

{
  "name": "nodejs-docker-app",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "scripts": {
    "test": "echo \\"Error: no test specified\\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

Is this OK? (yes)
npm notice 
npm notice New minor version of npm available! 8.1.2 -> 8.13.2
npm notice Changelog: <https://github.com/npm/cli/releases/tag/v8.13.2>
npm notice Run npm install -g npm@8.13.2 to update!
npm notice
  • npm init을 입력하면 위와 같은 결과를 얻을 수 있다
  • package.json은 프로젝트의 정보와 프로젝트에서 사용중인 의존성 관리

02-2. express 추가

{
  "name": "nodejs-docker-app",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "scripts": {
    "test": "echo \\"Error: no test specified\\" && exit 1"
  },
  "dependencies": {
    "express": "4.17.1"
  },
  "author": "",
  "license": "ISC"
}
  • package.json 파일에 express 4.17.1 추가
  • express는 JS + JQuery 관계와 비슷한 느낌

02-3. express 모듈 사용

// Get express module
const express = require("express");

const PORT = 8080;

// APP
const app = express();
app.get("/", (req, res) => {
    res.send("TEST.....");
});

app.listen(PORT);
console.log(`Server is running`);
  • server.js 파일 생성
  • 이제 도커 환경에서 node.js app을 어떻게 실행하는지 확인 해보자

03. Dockerfile 작성하기

Node.js 앱을 도커 환경에서 실행하려면 먼저 이미지를 생성하고 해당 이미지를 이용해서 컨테이너를 실행한 후 해당 컨테이너 안에서 Node.js 앱을 실행해야 한다. (Dockerfile 작성 해보자)

03-1. Dockerfile 작성하기

# Dockerfile 
FROM node:10

RUN npm install

CMD ["node", "server.js"]

node.js 기반 이미지를 생성하기 위해 위와 같이 Dockerfile을 작성 해준다.

03-2. npm install이란?

  • node.js로 만들어진 모듈을 웹에서 받아 설치하고 관리해주는 프로그램
  • npm install을 하면 NPM Registry에서 모듈들을 다운로드 하여 가져온다
    • Local APP에 해당 모듈을 설치 해준다
    • 현재 alping 이미지는 사용 못하고, node 이미지 사용해야함

03-3. “node”, “server.js”란 무엇인가?

  • node 명령어를 통해 server.js 파일을 실행 시키는 것
  • 즉, 컨테이너 구동 되었을 때 해당 명령어 수행

03-4. Dockerfile 실행 중 에러 발생

$ docker build ./
Sending build context to Docker daemon   5.12kB
Step 1/3 : FROM node:10
10: Pulling from library/node
...
0da9fbf60d48: Pull complete
04dccde934cf: Pull complete
73269890f6fd: Pull complete
Digest: sha256:59531d2835edd5161c8f9512f9e095b1836f7a1fcb0ab73e005ec46047384911
Status: Downloaded newer image for node:10
 ---> 28dca6642db8        
Step 2/3 : RUN npm install
 ---> Running in 5b642de600b7
npm WARN saveError ENOENT: no such file or directory, open '/package.json'      
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN enoent ENOENT: no such file or directory, open '/package.json'
npm WARN !invalid#2 No description
npm WARN !invalid#2 No repository field.
npm WARN !invalid#2 No README data
npm WARN !invalid#2 No license field.
...

Dockerfile을 실행하였는데 위와 같은 에러가 발생한다.. 다음 장에서는 이러한 에러가 발생하는 이유에 대해 알아보자.

04. Package.json 파일이 없다고 나오는 이유

해결하기 위한 방법은 COPY를 사용하면 된다

04-1. 파일이 왜 없다고 나오는건지?

  • Node 베이스 이미지를 사용하여 임시 컨테이너를 생성
  • 임시 컨테이너를 통해 우리의 이미지를 생성한다
  • 또한 현재 베이스 이미지에는 파일 스냅샷이 존재하는 상황
    • 이러한 스냅샷들은 임시 컨테이너의 HDD에 옮겨진다
    • 하지만 현재 임시 컨테이너 안에 package.json은 존재 안함
  • 즉, package.json, server.js와 같은 파일들이 임시 컨테이너에 옮겨지지 않음
  • npm install
    • 애플리케이션에 필요한 종속성을 다운 받는다
    • 다운 받을 시 package.json을 보고 해당 파일에 명시된 부분 다운
    • 하지만 현재 package.json 컨테이너 안에 없기에 에러 발생

04-2. COPY 사용

FROM node:10

COPY package.json ./

RUN npm install

CMD ["node", "server.js"]

우리 로컬 컴퓨터에 존재하는 파일을 도커 컨테이너(임시 컨테이너)에 복사를 해준다.

COPY를 안해주면 컨테이너에 로컬에 있는 파일이 옮겨지지 않음

04-3. Dockerfile 빌드 후 컨테이너 실행

docker build -t youngmin1085/node-web-app ./

현재 디렉토리에 존재하는 Dockerfile 빌드.

$ docker run youngmin1085/node-web-app
internal/modules/cjs/loader.js:638
    throw err;
    ^

Error: Cannot find module '/server.js'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:636:15)
    at Function.Module._load (internal/modules/cjs/loader.js:562:25)
    at Function.Module.runMain (internal/modules/cjs/loader.js:831:12)
    at startup (internal/bootstrap/node.js:283:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:623:3)

server.js도 도커 컨테이너 안에 COPY 해줘야 한다.

그냥 현재 디렉토리에 있는 모든 파일 경로를 지정해서 COPY 하자.

FROM node:10

# COPY 추가
COPY ./ ./

RUN npm install

CMD ["node", "server.js"]

04-4. Docker 재 빌드

docker build -t younmin1085/nodejs-web-app-test ./
$ docker run youngmin1085/nodejs-web-app-test2
Server is running
  • 정상적으로 콘솔이 찍히는 것을 확인할 수 있음
  • 핵심은 컨테이너(임시 컨테이너) 안으로 파일을 밀어 넣어서 이미지를 생성해야 한다는 점

05. 생성한 이미지로 어플리케이션 실행 시 접근이 안되는 이유

이전 시간에 Dockerfile을 작성하고 서버를 띄웠지만 404 Error가 발생.

이러한 문제를 해결하기 위해서는 포트 포워딩이 사용되어야 한다.

05-1. 포트 포워딩

도커 컨테이너에 접근하기 위해서는 포트 포워딩을 사용해야 한다.

이러한 포워딩을 위한 명령어는 다음과 같다.

docker run -p 5000:8080 youngmin1085/nodejs-web-app-test2

위와 같이 로컬에서의 접근 포트(Port)는 5000번으로 지정하고 실제 컨테이너의 포트는 8080로 지정하여 접근하도록 포트포워딩을 수행한다. 즉, 해당 옵션을 통해 호스트의 포트와 Docker container가 expose한 포트를 mapping 한다. 만약 해당 설정 -p을 하지 않으면 호스트의 랜덤한 포트값이 지정된다.

05-2. 로컬 컨테이너 서버 접근 후 화면

포트 포워딩이 안료되면 위와 같은 화면이 출력된다.

06. Working Directory 명시해주기

이번 시간에는 Dockerfile에 추가해주어야 하는 WORKDIR에 대한 부분을 정리 해보자.

06-1. Dockerfile WORKDIR

# Create app directory
WORKDIR /usr/src/app
  • 이미지 안에서 애플리케이션 소스를 갖고 있을 디렉토리를 생성 하는 것
  • 해당 디렉토리가 애플리케이션의 working 디렉토리가 된다
  • 왜 이러한 working 디렉토리가 따로 있어야 하는가? 이 부분은 아래에서 알아보자

06-2. WORKDIR가 필요한 이유

$ docker run -it node ls
Unable to find image 'node:latest' locally
latest: Pulling from library/node
1339eaac5b67: Pull complete
4c78fa1b9799: Pull complete
14f0d2bd5243: Pull complete
76e5964a957d: Pull complete
cc4bb1a04a94: Pull complete
eba57464a96a: Pull complete
1c6bdae10107: Pull complete
93c6143f0972: Pull complete
5681e060b2f8: Pull complete
Digest: sha256:5244663c5cc6392808aa1c4a78f90369e75c3d9e9a27589cffed0ae73d1f0815
Status: Downloaded newer image for node:latest
bin   dev  home  lib64  mnt  proc  run   srv  tmp  var
boot  etc  lib   media  opt  root  sbin  sys  usr
  • 실제 node 이미지에 ls 명령어를 주면 위와 같이 출력이 된다
  • 그렇다면 우리가 이전에 만든 node.js 이미지가 어떻게 되있는지 확인 해보자
$ docker run -it youngmin1085/nodejs-web-app-test2 ls
Dockerfile  dev   lib    mnt           package-lock.json  root  server.js  tmp
bin         etc   lib64  node_modules  package.json       run   srv        usr
boot        home  media  opt           proc               sbin  sys        var
  • node 이미지를 다운 받았을때와는 다르게 다른 파일들이 존재한다
  • 이러한 문제가 발생한 이유는 workdir를 지정하지 않고 COPY를 했기 때문이다

06-3. COPY의 문제점

첫 번째로 만약 COPY를 하려는 파일의 이름이 원래 파일 시스템의 이름과 같을 경우 원래의 파일은 제거하고 COPY하는 파일이 덮어씌워진다는 문제가 있다.

 

두 번째 문제는 workdir이 존재하지 않기 때문에 파일들이 정리가 되지 않는다는 문제가 발생한다. (폴더와 파일이 중구난방으로 생성됨) 위에서 문제점에 대해 알아보았는데 이제 WORKDIR를 Dockerfile에 추가 해주자.

06-4. WORKDIR 추가

FROM node:10

WORKDIR /usr/src/app

COPY ./ ./

RUN npm install

CMD ["node", "server.js"]
  • 기존 Dockerfile에 WORKDIR 구문 추가
docker build -t youngmin1085/nodejs ./
  • -t : 이미지에 이름을 주기 위해 사용
  • ./ : 현재 경로에 모든 파일을 빌드 (Dockerfile)
docker run -it youngmin1085/nodejs sh
  • nodejs 컨테이너를 실행 하는데 shell로 접근 한다
# ls -al
total 52
drwxr-xr-x  1 root root  4096 Jul  5 14:41 .
drwxr-xr-x  1 root root  4096 Jul  5 14:41 ..
drwxr-xr-x  7 root root  4096 Jul  5 14:39 .git
-rwxr-xr-x  1 root root  2175 Jul  5 14:14 .gitignore       
-rwxr-xr-x  1 root root   145 Jul  4 14:23 .prettierrc.json 
-rwxr-xr-x  1 root root    98 Jul  5 14:40 Dockerfile       
drwxr-xr-x  2 root root  4096 Jul  5 14:15 js
drwxr-xr-x 52 root root  4096 Jul  5 14:41 node_modules     
-rw-r--r--  1 root root 15594 Jul  5 14:41 package-lock.json
-rwxr-xr-x  1 root root   263 Jul  4 14:19 package.json     
# pwd
/usr/src/app
#
  • shell 접근 후 ls -al 사용 내역
  • 현재 workdir이 /usr/src/app으로 잘 설정 되어있다

위에서 우리가 workdir을 설정 해주었기 때문에 다음과 같은 파일 시스템 구조를 갖는다.

07. 어플리케이션 소스 변경으로 다시 빌드하는 것에 대한 문제점

소스 이슈 발생 → 소스 수정 → 재 빌드 → 배포

어플리케이션을 만들다 보면 소스 코드를 계속 변경시켜야 하며 그에 따라서 변경된 부분을 확인하면서 개발을 해야 한다.

07-1. Dockerfile 빌드 과정

우리는 소스를 작성한 후에 위와 같이 Dockerfile을 Build하여 컨테이너를 실행 하였다.
하지만 소스코드를 변경할 때마다 즉각적으로 수정이 되지는 않았다.

07-2. Dockfile 빌드(Auto Reload Test)

docker run -d -p 5000:8080 youngmin1085/nodejs 
249b0dde6479611114aba8c71f1d13795fc793d1c8220efeb83d2441d007fe64
  • -d : 백그라운드 실행
  • 후에 소스 코드를 변경한다
  • 크롬 브라우저를 통해 확인을 해봐도 소스는 변경이 되지 않는다
  • 다시 Dockerfile을 빌드 하고 컨테이너를 실행 해야 변경 사항을 확인할 수 있다

08. 어플리케이션 소스 변경으로 재 빌드시 효율적으로 하는 법

이전에 소스를 변경할 때마다 자동 업로드가 되도록 시도하였지만 되지 않았다.

이번에는 Auto Reload가 되어 자동으로 업데이트 사항이 업데이트 되도록 수정을 해보자.

############################################################
# @since 20220701
# @author ymkim
# @desc 
#   - 20220706: package.json 먼저 COPY 후 npm install 진행
############################################################

FROM node:10

WORKDIR /usr/src/app

COPY package.json ./

RUN npm install

COPY ./ ./

CMD ["node", "server.js"]
  • package.json은 먼저 COPY 한다
  • 불필요한 종속성을 반복적으로 하지 않도록 변경

09. Docker Volumn에 대하여

위에서 npm install 전에 package.json만 따로 변경을 해서 쓸데없이 모듈을 다시 다운받지 않도록 Dockerfile을 수정하였다. 하지만 아직도 소스를 변경할때마다 변경된 소스 부분은 COPY한 후 이미지를 다시 빌드 해주고 컨테이너를 실행해야 소스가 화면에 반영된다. 이러한 문제점을 해결하기 위해 Docker의 Volume을 사용한다.

09-1. Volume 사용

지금까지는 COPY 명령어를 사용하여 로컬에 있는 파일들을 도커 컨테이너에 그대로 복사하여 컨테이너 환경에서 실행이 되게 하였다. 하지만 Volume을 사용한 로컬에 있는 파일을 그대로 복제하는 것이 아니라 도커 컨테이너 특정 영역에서 로컬 파일을 참조하는 형태로 사용이 가능하다. (참조를 하기 때문에 변경사항에 즉각 대응이 가능)

09-2. Volume을 사용해서 APP 실행

docker run -d -p 5000:8080 -v /usr/src/app/node_modules -v ${pwd}:/usr/src/app youngmin1085/nodejs
  • window는 위와 같이 작성
  • Mac, Linux는 다르게 사용해야 할 듯

참고 자료

블로그의 정보

기록하고, 복기하고

ymkim

활동하기