Dev/DevOps, Infra

[Docker] 도커 환경에서 실행되는 Nodejs 애플리케이션

HJChung 2021. 1. 28. 15:00
// index.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')
}

app.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`)

이런 간단한 nodejs 애플리케이션이 도커환경에서 실행될 수 있도록 해보자. 

즉, nodejs 앱을 만든 후, 그를 실행하기 위한 도커 이미지 생성 후 컨테이너에서 앱이 실행되도록 해볼 것이다. 

1. 먼저 Node.js 앱을 만든다. 

// index.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')
}

app.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`)
// package.json
{
  “name”: “node”,
  “version”: “1.0.0”,
  “description”: “”,
  “main”: “index.js”,
  “scripts”: {
    “test”: “echo "Error: no test specified" && exit 1”,
    “start”: “node server.js”
},
“author”: “”,
“license”: “ISC”,
“dependencies”: {
  “express”: “^4.17.1”
  }
}

2. Dockerfile 작성하기

Nodejs 앱을 도커 환경에서 실행하려면 

① 이미지를 생성하고

② 그 이미지를 이용해서 컨테이너를 실행 한 후

③ 그 컨테이너 안에서 Nodejs앱을 실행한다.

그래서 먼저 도커 이미지를 생성하기 위해 Dockerfile을 작성해야한다. ([Docker] 도커 이미지 생성 에 정리)

//Dockerfile

//베이스 이미지 명시
FROM node:10 //npm이 있는 베이스이미지인 node

//추가적으로 필요한 파일들 다운로드 명시
RUN npm install //package.json에 적혀있는 종속성 패키지들을 다운받아서 설치

//컨테이너 시작시 실행 될 명령어 명시
CMD ["node", "index.js"]

이렇게 이미지를 생성하고 docker build ./하면 될까? 

saveError ENOENT: no such file or directory, open '/package.json'이라는 에러가 발생한다. 

왜냐하면 베이스 이미지의 파일 스냅샷을 임시 컨테이너에 넣은 후, npm install를 하는데 이때  필요한 package.json이 임시 컨테이너가 아닌 로컬에 있기 때문이다. package.json 뿐만 아니라 index.js도 마찬가지이다. 

 

그래서 package.json도 임시 컨테이너 내부에 넣어주어야 한다. 

COPY [로컬에 있는 특정 파일] [컨테이너 내에서 파일이 복사될 경로]

그래서  COPY package.json ./ 이 되거나 아니면 전체 파일을 COPY하고 싶다면 COPY ./ ./ 을 해주어야 한다. 여기까지 하면 Dockerfile은 

//Dockerfile

//베이스 이미지 명시
FROM node:10 //npm이 있는 베이스이미지인 node

COPY package.json ./ || ././

//추가적으로 필요한 파일들 다운로드 명시
RUN npm install //package.json에 적혀있는 종속성 패키지들을 다운받아서 설치

//컨테이너 시작시 실행 될 명령어 명시
CMD ["node", "index.js"]

이 된다. 

 

이제 이미지를 다시 build하고, 컨테이너를 실행해볼까?

왜일까?

3. PORT 매핑

앞서 로컬 파일(package.json과 index.js)를 컨테이너에 넣어주고, 이를 도커 컨테이너 환경에서 실행되도록 하였다. 그렇다면 이 8080PORT가 실행되고 있는 것도 도커 환경 내에서 이다. 즉, 네트워크도 마찬 가지로 로컬 포트를 컨테이너 내부 포트로 연결(매핑)시켜주어야 한다.

$ docker run -p [로컬 PORT]:[docker PORT] [이미지 이름]

그래서 docker run -p 49160:8080 [이미지 이름] 을 해주면 49160 로컬 브라우저의 포트로 가면, 컨테이너의 8080으로 매핑된다.

 

또한 호스트의 특정 IP를 사용하려면 192.168.0.100:8000:8080 과 같이 바인딩할 IP와 포트를 명시하면 되며, 

$ docker run -p [특정 IP]:[로컬 PORT]:[docker PORT]

여러개의 포트를 외부에 개방하려명 -p 옵션을 여러번 쓰면 된다. 

$ docker run -p [로컬 PORT]:[docker PORT] -p [로컬 PORT]:[docker PORT][이미지 이름]

4. Working Directory 명시

Working Directory란

이미지안에서 애플리케이션 소스 코드를 가지고 있을 디렉토리를 지정해주는 것으로, 이 디렉토리가 애플리케이션의 working directory가 된다. 

앞서서는 단순히 COPY ./ ./를 해 주었는데, 이렇게 해주면 root 디렉토리에 애플리케어션 소스 코드가 그냥 다 올라가게 된다. 

그러면 기존 root 디렉토리에 애플리케어션 소스 코드 파일과 동일한 파일이 있다면? 덮어씌워져버릴 위험이 있고, 폴더 단위의 정리가 되지 않는다. 

그래서 Dockerfile에

WORKDIR [애플리케이션 소스 코드를 가지고 있을 디렉토리]

 

 

그러면 

5. Docker Volumne

그런데 소스코드가 변경 될 때마다 COPY를 통해 이미지를 다시 build하고 컨테이너를 다시 실행하는 것은 너무 비효율적이다.

Docker Volume을 이용하면 도커 컨테이너에서 계속 로컬의 파일을 바라보고(매핑) 있게 함으로써 이미지를 다시 build하지 않고도 컨테이너 실행(run)으로 바로바로 변경된 소스코드 내용이 적용되게 할 수 있게 할 수 있다.

Volume을 이용해서 run

$ docker run -p 5000:8080 -v /usr/src/app/node_modules -v $(pwd):/usr/src/app [이미지 아이디]
  • -v /usr/src/app/node_modules
    • 호스트 디렉토리에 node_modules는 없기에 컨테이너에 맵핑을 하지 말라고 하는 것
  • -v $(pwd):/usr/src/app
    • pwd 경로에 있는 디렉토리 혹은 파일을 /usr/src/app 경로에서 참조
    • pwd :: print working directory
      • 현재 작업중인 디렉토리의 이름을 출력하는데 쓰임

여기까지 하면 개발환경에서

까지 한 한 것이다. 

 

 

reference

Node For Docker Node.js 앱 만들기

따라하며 배우는 도커와 CI환경