효율적인 Dockerfile 작성하기 - node.js

요약

애플리케이션의 모든 의존성을 설치한 후, 앱 코드를 컨테이너에 추가하기 전에 다음 코드 스니펫(또는 변형)을 사용하세요. 이렇게 하면 컨테이너를 다시 빌드할 때마다 모듈을 다시 빌드하지 않아도 됩니다. package.json 파일이 변경되면 모듈이 다시 빌드됩니다. 전체 예제는 이 깃헙 Gist를 참조하세요.

dockerfile코드 복사ADD package.json /tmp/package.json
RUN cd /tmp && npm install
RUN mkdir -p /opt/app && cp -a /tmp/node_modules /opt/app/

모듈을 위한 캐시된 레이어 사용

이 글은 Docker 레이어를 효율적으로 사용하는 방법에 관한 것입니다. 부수적인 효과로 Docker 컨테이너에 호스팅된 Node.js 애플리케이션의 개발 및 디버깅 시간을 줄이는 방법을 알아봅니다. 모든 것을 개발 호스트 시스템에서 Docker로 마이그레이션할 때, 주로 인터랙티브한 수정 및 테스트 워크플로우와 관련된 몇 가지 어려움이 있습니다.

과거에는 애플리케이션에 약간의 수정을 가할 때마다 Docker 컨테이너가 다시 빌드되기를 기다리는 데 시간을 많이 보냈습니다. 주로 모듈이 다시 설치되기를 기다리는 시간이 문제였습니다. 문제를 실제로 해결하는 것보다 의존성을 빌드하는 데 더 많은 시간을 소비했습니다. 이 글이 다른 사람들이 이러한 순환에서 벗어나는 데 도움이 되기를 바랍니다.

효율적인 Dockerfile을 작성하는 것은 Docker와 같은 새로운 기술을 사용할 때의 즐거움 중 하나입니다. Docker는 당신에게 다르게 생각하도록 강요합니다. 올바른 사고방식을 갖추면 새로운 트릭을 발명하게 될 것입니다.

Docker 레이어 이해하기

핵심 중 하나는 Docker 레이어가 어떻게 작동하는지 이해하는 것입니다. 현재로서는 Docker와 관련된 다양한 레이어를 보여주는 그래픽을 보려면 문서를 참조하세요. Dockerfile의 명령은 새로운 레이어를 생성합니다. 가능하면 Docker는 기존의 캐시된 레이어를 사용하려고 합니다. 명령을 특정 순서로 정리하여 가능한 한 레이어를 최대한 활용해야 합니다. 애플리케이션의 Node 모듈을 다루는 순서에 대해 곧 설명하겠습니다.

먼저 Node의 의존성 파일 예제를 보겠습니다:

예제 - package.json

json코드 복사{
  "name": "myApp",
  "description": "This is my awesome app...",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "docker.io": "*",
    "redis": "*",
    "restify": "*"
  }
}

그리고 다음은 기존 Dockerfile에 삽입할 내용입니다:

dockerfile코드 복사ADD package.json /tmp/package.json
RUN cd /tmp && npm install
RUN mkdir -p /opt/app && cp -a /tmp/node_modules /opt/app/

이 스니펫은 일반적으로 애플리케이션의 모든 의존성이 설치된 후, 애플리케이션의 코드를 컨테이너에 추가하기 전에 위치해야 합니다.

잘못된 Dockerfile 예시

다음은 잘못된 Dockerfile의 예입니다:

dockerfile코드 복사FROM ubuntu

RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
RUN apt-get update
RUN apt-get -y install python-software-properties git build-essential
RUN add-apt-repository -y ppa:chris-lea/node.js
RUN apt-get update
RUN apt-get -y install nodejs

WORKDIR /opt/app

ADD . /opt/app
RUN npm install
EXPOSE 3001

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

이 Dockerfile은 문제점이 있습니다. 12번째 줄에서 애플리케이션의 작업 디렉토리(여기에는 package.json이 포함되어 있음)를 컨테이너에 복사한 후 모듈을 빌드합니다. 이로 인해 애플리케이션의 파일을 변경할 때마다 모듈이 다시 빌드됩니다.

개선된 Dockerfile 예시

더 나은 구현의 전체 예시는 다음과 같습니다:

dockerfile코드 복사FROM ubuntu
MAINTAINER David Weinstein <[email protected]>

# 의존성과 Node.js 설치
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
RUN apt-get update
RUN apt-get -y install python-software-properties git build-essential
RUN add-apt-repository -y ppa:chris-lea/node.js
RUN apt-get update
RUN apt-get -y install nodejs

# package.json 변경 시 캐시를 사용하지 않도록 설정하여 애플리케이션의 Node.js 의존성이 변경될 때 npm install을 다시 실행하도록 함
ADD package.json /tmp/package.json
RUN cd /tmp && npm install
RUN mkdir -p /opt/app && cp -a /tmp/node_modules /opt/app/

# 여기부터 애플리케이션의 코드를 로드하므로 이전에 캐시된 Docker 레이어가 가능하면 사용됨
WORKDIR /opt/app
ADD . /opt/app

EXPOSE 3000

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

여기서의 아이디어는 package.json 파일이 변경되면(Dockerfile의 14번째 줄) Docker가 npm install 시퀀스를 다시 실행하도록 하고, 그렇지 않으면 캐시를 사용하여 해당 부분을 건너뛰는 것입니다.

빌드 로그 예시

아래는 위의 Dockerfile을 빌드할 때 모듈 의존성 단계에서 캐시가 사용되는 로그 예시입니다:

sql코드 복사Uploading context 4.608 kB
Uploading context 
Step 0 : FROM ubuntu
 ---> 9cd978db300e
Step 1 : MAINTAINER David Weinstein <[email protected]>
 ---> Using cache
 ---> 67aeca8f12ae
Step 2 : RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
 ---> Using cache
 ---> be8f73b1204f
Step 3 : RUN apt-get update
 ---> Using cache
 ---> 70395f80789a
Step 4 : RUN apt-get -y install python-software-properties git build-essential
 ---> Using cache
 ---> 58821e45ea25
Step 5 : RUN add-apt-repository -y ppa:chris-lea/node.js
 ---> Using cache
 ---> 79afb0c0539a
Step 6 : RUN apt-get update
 ---> Using cache
 ---> 18fc6aa866d8
Step 7 : RUN apt-get -y install nodejs
 ---> Using cache
 ---> 1f1f41f47329
Step 8 : ADD package.json /tmp/package.json
 ---> Using cache
 ---> 0331fd81b4c8
Step 9 : RUN cd /tmp && npm install
 ---> Using cache
 ---> 95ee8b27b72b
Step 10 : RUN mkdir -p /opt/app && cp -a /tmp/node_modules /opt/app/
 ---> Using cache
 ---> 40102f5ce4f1
Step 11 : WORKDIR /opt/app
 ---> Using cache
 ---> 6a1ad0dca915
Step 12 : ADD . /opt/app
 ---> 6b9bdaa0e7a2
Step 13 : EXPOSE 3000
 ---> Running in 722c8f0b88e2
 ---> d97a3d372bda
Step 14 : CMD ["node", "server.js"]
 ---> Running in 3309a2dab1cc
 ---> a0b19d7625d3
Successfully built a0b19d7625d3

이 예제는 이 깃헙 Gist에 포함되어 있으므로, 정확히 반복할 수 있습니다.

컨테이너를 한 번 빌드한 후(docker build -t testProject .), 예제 server.js의 7번째 줄을 주석 해제했다고 가정하면, 위 로그는 컨테이너를 다시 빌드할 때(즉, 애플리케이션의 로직 변경을 시뮬레이션할 때) 어떤 일이 발생하는지 보여줍니다. 로그를 보면 32번째 줄에서는 캐시가 사용되었지만 38번째 줄에서는 캐시가 사용되지 않았습니다.

Last updated