Docker file을 이용하여 Node.js를 배포하는 방법을 알아보려고 합니다.
docker 설치와 웹, 앱 개발은 기본적으로 알고 있어야 한다고 합니다.
Docker를 사용하면 애플리케이션과 모든 의존성을 소프트웨어 개발에서 컨테이너라고 부르는 표준화된 단위로 패키징할 수 있습니다. 컨테이너는 리눅스 운영체제의 간단 버전이고 이미지는 컨테이너에 로드하는 소프트웨어를 말합니다.
DOCKER 란?
Docker는 컨테이너 가상화 기술을 기반으로 하는 오픈소스 플랫폼입니다. 간혹 'Docker라는 회사가 컨테이너 가상화 기술을 개발하여 컨테이너 가상화 기술의 또 다른 이름이다' 라고 오해하는 분들이 있지만 사실 컨테이너 가상화 기술은 Docker 이전에 이미 개발되어 존재하던 기술입니다. Docker는 최초의 컨테이너 기술인 LXC(Linux Container)에 여러 가지 중요한 기술을 가미해 강력한 효율성과 유연성을 제공합니다.
Docker는 컨테이너 가상화 환경 위에 애플리케이션 배포 엔진을 더함으로써 사용자의 코드를 어디서든 빠르고 가볍게 실행시킬 수 있습니다. 게다가 요즘 널리 사용되는 MSA(Micro Service Architecture), CI/CD와 아주 잘 어울리기 때문에 각광받고 있습니다. 실제로 Docker는 한 컨테이너당 하나의 애플리케이션이나 프로세스를 실행하는 것을 권하고 있으니 이는 MSA의 철학과 일맥상통한다고 볼 수 있습니다.
DOCKER의 구조
Docker의 엔진은 클라이언트가 명령어를 서버에 전달하고 서버가 전달받은 명령어를 실행하는 Client-Server Architecture 를 사용하고 있습니다.
Docker 오브젝트를 관리하는 주체가 서버이며 오브젝트를 조작하기 위해 클라이언트가 명령어를 서버에 전달하면 서버의 dockerd(Docker daemon)라는 프로세스가 동작하여 해당 명령어 실행합니다. 이러한 Client-Server Architecture 를 아래 그림과 같이 크게 3가지로 구분할 수 있습니다.
1. 클라이언트(Client)
클라이언트는 사용자가 Docker 오브젝트를 관리하기 위해 사용하는 가장 근본적인 수단이며 CLI(Command-line interface)를 제공합니다. 만약 사용자가 docker run 과 같은 명령어를 입력하면 해당 명령어를 수행하는 dockerd 프로세스로 전달합니다. Docker 명령어는 Docker API를 이용하며 클라이언트가 한 개 이상의 dockerd 와 상호작용할 수 있도록 합니다.
2. 호스트(Host)
호스트는 Docker 오브젝트를 관리하는 주체이면서 마치 운동장과 같습니다. dockerd라는 프로세스가 클라이언트로부터 전달받은 명령어를 실행하여 다양한 Docker 오브젝트를 관리하게 됩니다. 이때 이미지, 컨테이너, 볼륨과 같은 Docker 오브젝트는 운동장에서 놀고 있는 학생들과 같습니다.
3. 저장소(Registry) - DockerHub
Docker는 공식적으로 DockerHub라는 저장소를 지원합니다. 마치 GitHub에 소스 코드를 저장하여 관리하는 것과 같이 이미지를 저장하여 관리할 수 있습니다. 또한 GitHub과 마찬가지로 Public, Private이 존재합니다.
DOCKER Object
Docker를 사용한다면, 이미지, 컨테이너, 볼륨 등 다양한 Docker 오브젝트를 활용하게 됩니다. 그중 가장 핵심이 되는 이미지와 컨테이너에 대해 알아보겠습니다.
1. 이미지(Image)
이미지는 컨테이너를 생성하기 위해 필요한 템플릿입니다. Docker에서 공식적으로 제공해주는 이미지가 있으며 사용자는 해당 이미지 혹은 다른 개발자들이 만들어 놓은 이미지를 사용하여 본인만의 커스텀 이미지를 만들 수 있습니다.
예를 들어 Ubuntu를 기반으로 하는 커스텀 이미지를 빌드(생성)하려고 하는데, 아파치 웹 서버(Apache Web Server)와 나의 애플리케이션이 올바르게 동작할 수 있어야 한다면 단순히 Docker에서 제공해주는 공식 Ubuntu 이미지만 가져와서는 완성할 수 없습니다. 따라서 기반이 되는 이미지(예를 들어 Docker가 제공하는 Ubuntu 이미지)를 선택하고 필요한 것들을 알맞게 이미지 안에 추가하여 사용자만의 커스텀 이미지를 생성할 수 있습니다.
2. 컨테이너(Container)
컨테이너는 이미지의 실행 가능한 인스턴스입니다. 즉, 이미지를 실행시켜 컨테이너라는 가상화 공간을 만들게 됩니다. Docker만 올바르게 설치되었다면 어디서든지 저장소로부터 이미지를 가져와 컨테이너를 실행시킬 수 있습니다.
그외 Docker 특징
- Logs - 로그는 현재 서비스의 상태를 파악할 수 있기 때문에 어떠한 서비스를 운영하더라도 매우 중요합니다. 컨테이너는 stdout, stderr, stdin 을 통해서 생성되는 로그를 전부 수집하여 분석 혹은 trouble-shooting을 가능하게 해줍니다.
- Resource Isolation and Grouping - 리눅스 커널의 Cgroups 기능을 통해 각각의 컨테이너마다 독립적인 CPU와 메모리가 할당될 수 있습니다.
- 독립적 - 컨테이너는 독립된 파일 시스템입니다. 이처럼 컨테이너는 호스트와 완전히 분리되어 있는 프로세스 환경을 가지고 있으므로 독립적인 가상 네트워크 인터페이스와 IP 주소를 가질 수 있습니다.
이제 docker에 대한 기본적인 것을 공부해 봤으니 저희가 만든 서버를 docker를 이용해 배포해야겠죠?
Node.js 앱 생성
모든 파일을 넣은 새로운 디렉터리를 만들겠습니다. 이 디렉터리 안에 애플리케이션과 의존성을 알려주는 package.json 파일을 생성합니다.
{
"name": "docker_web_app",
"version": "1.0.0",
"description": "Node.js on Docker",
"author": "First Last <first.last@example.com>",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.16.1"
}
}
예시니까 사용자 입맛에 맞게 작성해주세요.
package.json 파일을 만든 후, npm install을 실행하세요. 버전 5 이상의 npm을 사용한다면, Docker 이미지에 복사할 package-lock.json 파일을 npm에서 생성할 것입니다.
DOCKER FILE 생성
Dockerfile이라는 빈 파일을 생성합니다.
touch Dockerfile
선호하는 텍스트 에디터로 Dockerfile을 엽니다.
가장 먼저 해야 할 것은 어떤 이미지를 사용해서 빌드할 것인지를 정의하는 것입니다. 여기서는 Docker Hub에 있는 node의 최신 LTS(장기 지원) 버전인 16을 사용할 것입니다.
FROM node:16
다음으로 이미지 안에 애플리케이션 코드를 넣기 위해 디렉터리를 생성할 것입니다. 이 디렉터리가 애플리케이션의 작업 디렉터리가 됩니다.
# 앱 디렉터리 생성
WORKDIR /usr/src/app
이 이미지에는 이미 Node.js와 NPM이 설치되어 있으므로 npm 바이너리로 앱의 의존성을 설치하기만 하면 됩니다. 버전 4 이하의 npm은 package-lock.json 파일을 생성하지 않을 것입니다.
# 앱 의존성 설치
# 가능한 경우(npm@5+) package.json과 package-lock.json을 모두 복사하기 위해
# 와일드카드를 사용
COPY package*.json ./
RUN npm install
# 프로덕션을 위한 코드를 빌드하는 경우
# RUN npm ci --only=production
Docker 이미지 안에 앱의 소스코드를 넣기 위해 COPY 지시어를 사용합니다.
COPY . .
앱이 8080포트에 바인딩 되어 있으므로 EXPOSE 지시어를 사용해서 docker 데몬에 매핑합니다.
EXPOSE 8080
마지막으로 런타임을 정의하는 CMD로 앱을 실행하는 중요 명령어를 정의해야 합니다. 여기서는 서버를 구동하도록 node server.js을 실행하는 기본 npm start을 사용할 것입니다.
CMD [ "node", "server.js" ]
Dockerfile은 다음과 같아야 합니다.
FROM node:16
# 앱 디렉터리 생성
WORKDIR /usr/src/app
# 앱 의존성 설치
# 가능한 경우(npm@5+) package.json과 package-lock.json을 모두 복사하기 위해
# 와일드카드를 사용
COPY package*.json ./
RUN npm install
# 프로덕션을 위한 코드를 빌드하는 경우
# RUN npm ci --only=production
# 앱 소스 추가
COPY . .
EXPOSE 8080
CMD [ "node", "server.js" ]
.dockerignore 파일
Dockerfile과 같은 디렉터리에 .dockerignore 파일을 다음 내용으로 만드세요.
node_modules
npm-debug.log
이는 Docker 이미지에 로컬 모듈과 디버깅 로그를 복사하는 것을 막아서 이미지 내에서 설치한 모듈을 덮어쓰지 않게 합니다.
이미지 빌드
작성한 Dockerfile이 있는 디렉터리로 가서 Docker 이미지를 빌드하는 다음 명령어를 실행하세요. -t 플래그로 이미지에 태그를 추가할 수 있어 나중에 docker images 명령어로 쉽게 찾을 수 있습니다.
docker build . -t <your username>/node-web-app
Docker가 당신이 빌드한 이미지를 보여줄 것입니다.
$ docker images
# 예시
REPOSITORY TAG ID CREATED
node 16 1934b0b038d1 5 days ago
<your username>/node-web-app latest d64d3505b0d2 1 minute ago
이미지 실행
-d로 이미지를 실행하면 분리 모드로 컨테이너를 실행해서 백그라운드에서 컨테이너가 돌아가도록 합니다. -p 플래그는 공개 포트를 컨테이너 내의 비공개 포트로 리다이렉트합니다. 앞에서 만든 이미지를 실행하세요.
docker run -p 49160:8080 -d <your username>/node-web-app
앱의 로그를 출력하세요.
# 컨테이너 아이디를 확인합니다
$ docker ps
# 앱 로그를 출력합니다
$ docker logs <container id>
# 예시
Running on http://localhost:8080
컨테이너 안에 들어가 봐야 한다면 exec 명령어를 사용할 수 있습니다.
# 컨테이너에 들어갑니다
$ docker exec -it <container id> /bin/bash
이렇게 docker와 docker를 이용해 node.js를 실행하는 법을 알아 봤는데요... 사용해본 경험이 있지만 사실 누가 저의 서버를 직접 사용해 본 적은 없어서 잘되는지도 확인해봐야하고, 또 혼자서 사용하다보면 error가 나면 서버가 꺼지기도 하더라고요. 코드를 작성할 때나 테스트 할 때 서버가 다운이 된다면 에러핸들링을 잘 해야합니다.
제가 docker를 이용할 때 느낀 것은 바로 에러핸들링을 잘하자 입니다.
출처 : https://nodejs.org/ko/docs/guides/nodejs-docker-webapp/