kimyu0218
  • [docker] 캐싱으로 이미지 빌드 속도 향상시키기 (Dockerfile/build/run)
    2023년 12월 29일 23시 40분 07초에 업로드 된 글입니다.
    작성자: @kimyu0218

    앞에서 알아본 도커 개념을 바탕으로 도커 컨테이너에 어플리케이션을 띄워볼 것이다. Dockerfile을 이용하여 이미지를 빌드하고 도커 CLI를 이용하여 컨테이너를 실행시켜보자.
     

    Dockerfile 작성하기

    Dockerfile이란?

    이미지를 빌드하기 위해서는 Dockerfile을 작성해야 한다. Dockerfile은 이미지를 빌드하기 위한 명령들이 포함된, 파일 확장자가 없는 텍스트 기반의 파일이다.

    FROM node:20
    
    RUN apt-get update && apt-get install -y tini
    
    WORKDIR /app/was
    
    COPY was/package*.json ./
    RUN npm install
    
    COPY was .
    RUN npm run build
    
    CMD ["tini", "--", "npm", "run", "start:prod"]
    EXPOSE 3000

    위의 코드는 node 어플리케이션을 빌드하고 실행하는 Dockerfile이다. `was` 디렉토리에 어플리케이션 소스코드가 위치하고 있다. 각 명령이 무엇을 의미하는지 알아보자.
     

    FROM node:20

    FROM은 도커 이미지를 빌드할 때 사용할 기본 이미지를 지정한다. 일반적인 FROM 명령의 형식은 다음과 같다.

    FROM [IMAGE_NAME][:TAG]

    `FROM node:20`은 `node:20`을 기본 이미지로 사용하여 미리 node가 설치된 환경을 사용하겠다는 의미다.
     

    RUN apt-get update && apt-get install -y tini

    RUN은 도커 컨테이너 안에서 실행할 명령어를 지정한다. `apt-get update`로 패키지 관리자를 업데이트하고, `apt-get install -y tini`로 tini*라는 프로세스 관리 도구를 설치한다.

    *tini : 컨테이너 환경에서 프로세스를 초기화하고 부모 프로세스로부터 전달된 종료 신호를 수신하는 도구
    graceful shutdown을 구현하기 위해 필요한 도구로, 이미지 빌드에 필수적인 명령은 아니다.

     

    WORKDIR /app/was

    WORKDIR은 작업 디렉토리를 설정하는 명령어다. WORKDIR 이후의 명령어들은 작업 디렉토리에서 실행된다. 만약 작업 디렉토리가 존재하지 않는 경우, 디렉토리를 생성한다.

    WORKDIR [WORKING_DIR_PATH]

    `WORKDIR /app/was`는 이후의 명령들을 `/app/was` 디렉토리에서 실행하겠다는 의미다.
     

    COPY was/package*.json ./

    로컬의 package.json을 현재 작업 디렉토리로 복사하는 명령어다.
     
    다음 명령어를 살펴보기 전에 Dockerfile을 좀 더 살펴보자. 뒷부분에서 로컬 디렉토리의 모든 파일을 복사하는데 왜 package.json만 먼저 복사할까?

    🙋‍♀️ : package.json 파일만 먼저 복사하는 이유가 있나요? 어차피 뒤에서 `COPY was .`으로 모든 파일을 복사하는데 그냥 여기서 모두 복사하는 게 좋지 않나요?

    Dockerfile의 각 명령들이 도커 이미지의 계층이 된다!

    이전 포스트에서도 언급했듯이 하나의 이미지는 여러 개의 계층으로 구성된다. 계층들은 캐싱되기 때문에 변경 내역이 없는 경우, 이전 빌드의 내용을 재사용한다. (만약 특정 계층이 변경되면 그 뒤의 계층들은 모두 새롭게 빌드된다!)

    package.json 파일을 복사한 다음에 등장하는 `RUN npm install`은 어플리케이션에 필요한 의존성을 설치한다. 만약 여기서 전체 파일을 복사하면, 소스코드만 변경되어도 모든 의존성을 다시 설치해야 한다. 하지만 package.json 파일을 먼저 복사한다면, 해당 파일이 변경되었을 때만 재설치하기 때문에 이미지 빌드 타임을 줄일 수 있다.
     

    RUN npm install

    `npm install` 명령어를 실행하여 어플리케이션에 필요한 의존성을 설치한다.
     

    COPY was .

    로컬 디렉토리의 모든 파일을 작업 디렉토리로 복사한다.
     

    RUN npm run build

    `npm run build` 명령어를 실행하여 소스코드를 컴파일하고 빌드한다.
     

    CMD ["tini", "--", "npm", "run", "start:prod"]

    CMD는 도커 컨테이너가 시작될 때 실행하는 명령어를 정의한다. (RUN은 도커 이미지를 빌드할 때 사용하는 명령어!) Dockerfile에서 한 번만 사용되며 여러 번 존재하는 경우 마지막에 사용한 `CMD`만 유효하다.
    해당 명령은 도커 컨테이너가 시작될 때, tini를 이용하여 프로덕션 환경의 node 어플리케이션을 실행하겠다는 의미다.
     

    EXPOSE 3000

    EXPOSE는 도커 컨테이너가 특정 포트에서 서비스를 제공할 것임을 도커에게 알리는 역할을 한다. `EXPOSE 3000`은 컨테이너가 3000번을 통해 서비스함을 의미한다.


    docker CLI로 이미지 빌드하고 실행하기

    앞에서 이미지를 빌드하기 위한 명령어를 Dockerfile로 작성했다. 이제 docker CLI를 이용하여 컨테이너를 실행해보자.

    이미지 빌드하기

    # docker build [OPTIONS] PATH | URL | -
    docker build -t was

    `docker build`는 Dockerfile을 이용하여 이미지를 빌드한다. PATH나 URL을 명시하지 않으면 기본적으로 루트에서 Dockerfile을 탐색한다.

    docker build에서 자주 사용되는 옵션
    • `-f`,`--file` : Dockerfile의 이름 지정
    • `-t`, `--tag` : 이미지의 이름과 태그를 지정
    • `--build-arg` : 빌드 타임 변수 설정
    • `--no-cache` : 이미지를 빌드하는 동안 캐시를 사용하지 않음

     

    컨테이너 실행하기

    # docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
    docker run -dp 3000:3000 was

    `docker run`은 도커 이미지를 기반으로 새로운 컨테이너를 생성하고 실행하는 명령이다. was 컨테이너가 3000번에서 서비스하고 있으므로 컨테이너에 접속할 수 있도록 호스트 포트와 3000번 포트를 연결해줘야 한다.

    docker run에서 자주 사용되는 옵션
    • `-d`, `--detach` : 백그라운드에서 실행
    • `-p`, `--publish` : 포트 공개
    • `-e`, `--env`, `--env-file` : 환경변수 설정
    • `-v`, `--volume` : 볼륨 마운트
    • `--name` : 컨테이너 이름 지정

    참고자료

    댓글