도커를 이용한 노드js 어플
이번에는 노드js 어플리케이션을 도커를 이용해 컨테이너로 만들어보도록 하겠습니다.
노드 어플리케이션은 기본적으로 package.json에서 설정을 해줍니다. 여기서 필요한 라이브러리도 적어줍니다. 그리고 npm install을 하면 라이브러리들이 모두 설치됩니다. 그리고 server.js에 포트지정, api작성 등을 해줍니다. 그리고 node server.js를 터미널에 치면 서버가 뜨면서 어플리케이션이 돌아갑니다
package.json
{
"name": "nodejs-docker-app",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"dependencies": {
"express":"4.17.2"
},
"author": "",
"license": "ISC"
}
server.js
const express = require('express');
//APP
const app = express();
app.get('/', (req,res) => {
res.send("hello")
});
app.listen(8080);
이제는 로컬에서 직접 서버를 띄우는게 아닌 도커를 이용해서 컨테이너안에서 작동을 시키려고 합니다! 그렇다면 이미지를 먼저만들어줘야합니다. dokerfile을 작성하도록하겠습니다.
FROM node:10
WORKDIR /usr/src/app
COPY ./ ./
RUN npm install
CMD [ "node", "server.js" ]
먼저 baseimage는 node:10으로 사용하도록 하겠습니다. 그리고 WORKDIR을 지정해서 작업위치를 정해줍니다. 그리고 COPY ./ ./는 로컬의 현재위치(./)에 있는 모든 파일을 컨테이너의 현재위치(./)로 복사하겠다는 뜻입니다. 위에서 workdir을 설정해줬기 때문에 컨테이너의 /usr/src/app에 복사가 될겁니다.
만약 copy없이 컨테이너를 만들면 베이스이미지에서만 넘어온 파일들이 있을 것이고 RUN npm install이나 CMD시에 필요한 package.json 과 server.js는 없는 상태가 됩니다. 그럼 오류가나겠죠?
dockerfile을 통해서 이미지를 빌드합니다
> docker build -t rmswjdtn/hello ./
빌드한 이미지로 컨테이너를 만들고 실행합니다. 여기서 -p 5000:8080은 포트를 매칭해주는 것입니다. 만약 이렇게 하지 않으면 localhost:8080에 접속해도 연결이 되어있지 않습니다!! 컨테이너 내부에 네트워크가 있고 컨테이너는 그 네트워크를 통해서 실행됩니다. 지금은 server.js에 포트를 8080으로 전해줬기 때문에 컨테이너에서는 8080에 열립니다. 하지만 로컬 네트워크에서의 8080이 컨테이너의 네트워크에서 8080과 매치는 되어있지 않습니다. 그렇기 때문에 <-p 로컬포트 : 컨테이너 포트> 를 넣어줘야합니다. 지금은 5000포트와 연결해줬습니다.
> docker run -p 5000:8080 rmswjdtn/hello
이번에는 한번 hello가 아닌 hello-world로 문구를 고쳐보도록 하겠습니다
server.js
const express = require('express');
//APP
const app = express();
app.get('/', (req,res) => {
res.send("hello-world")
});
app.listen(8080);
소스코드가 변경되었기때문에 이미지를 재빌드 해줘야합니다. 컨테이너에 있는 server.js파일은 처음 빌드시에 COPY한 것이기 때문입니다. 여기서 단계별로 자세히 살펴보도록하면 workdir은 캐시를 이용해서 이전 것을 그대로 가져옵니다. 하지만 copy, run는 내용이 변경되었기 때문에 캐시를 쓰지 않습니다. 즉 변경된 부분이 아니면 이미지 빌드시 캐시를 활용합니다!
그런데 여기서 문제는 server.js만 변경이 되었을 뿐인데 package.json도 다시 copy를 하고 또 install을 한다는 것입니다. 의존성패키지에 변경이 없는데도 말이죠.. 이런 비효율을 해결하기위해 dockerfile를 조금 수정해보도록 하겠습니다
> docker build -t rmswjdtn/nodejs ./
=> [1/4] FROM docker.io/library/node:10@sha256:59531d2835edd5161c8f9512f9e095b1836f7a1fcb0ab73e005ec46047384911 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 8.66kB 0.0s
=> CACHED [2/4] WORKDIR /usr/src/app 0.0s
=> [3/4] COPY ./ ./ 0.0s
=> [4/4] RUN npm install 3.1s
npm install 전에 package.json만 따로 복사를 해주고 설치를 진행한뒤에 소스코드가 변경되었다면 두번째 COPY부터 캐시를 쓰지 않도록 효율적으로 변경했습니다
FROM node:10
WORKDIR /usr/src/app
COPY package.json ./
RUN npm install
COPY ./ ./
CMD [ "node", "server.js" ]
=> CACHED [2/5] WORKDIR /usr/src/app 0.0s
=> CACHED [3/5] COPY package.json ./ 0.0s
=> CACHED [4/5] RUN npm install 0.0s
=> [5/5] COPY ./ ./
성공적으로 반영되었습니다.
지금까지 컨테이너에 로컬에 있는 파일들을 넘겨주기 위해 copy를 사용했었습니다. 이 방법의 단점은 매번 수정이 일어날 때마다 이미지를 다시 빌드해줘야한다는 것입니다. 그에 비해 volume은 참조를 걸어주는 방식이기때문에 컨테이너를 껐다가 키면 변경사항이 반영됩니다.
컨테이너를 생성할 때 volume을 통해서 매핑을 해줍니다. 이때 처음 -v /usr/src/app/node_modules는 컨테이너에서 node_modules는 매핑에서 제외한다는 뜻입니다. 그 이유는 node_modules가 package.json을 install한 결과기 때문에 로컬에는 존재도 하지않습니다.(install을 로컬에서 안하고 컨테이너에서 하기 때문)
-v $(pwd):/usr/src/app 이부분은 <로컬의 위치 : 컨테이너 위치> 를 매핑해서 그 위치에서 서로 매핑을 해주겠다는 뜻입니다. pwd는 현재위치의 절대경로를 알려주는 명령어 입니다.
> docker run -p 5000:8080 -v /usr/src/app/node_modules -v $(pwd):/usr/src/app rmswjdtn/hello
지금은 아래와 같이 변경전 화면이 나옵니다.
이제 volume테스트로 바꾼뒤 컨테이너를 껐다가 켜보겠습니다. 바로 아래와 같이 변경사항이 반영된것을 볼 수 있습니다!
> docker stop 46f0ebb5424e
> docker start 46f0ebb5424e