[Docker] Dockerfile 기본 명령어와 최적화

안나·2024년 1월 30일
0

DevOps

목록 보기
13/21
post-thumbnail

실행 환경

  • VirtualBox 6.1.50
  • CentOS


🐳Dockerfile 기본 명령어


FROM

FROM <image>:<tag>
FROM ubuntu:16.04
  • 베이스 이미지를 지정한다.
  • 반드시 지정해야 하며 어떤 이미지도 베이스 이미지가 될 수 있다.
  • tag 는 될 수 있으면 latest(기본값)보다 구체적인 버전(16.04)를 지정하는 것이 좋다
  • 이미 만들어진 다양한 베이스 이미지는 도커 허브에서 더 볼 수 있다.

MAINTAINER

MAINTAINER <name>
MAINTAINER anna@anna.com
  • Dockerfile을 관리하는 사람의 이름 또는 이메일을 정보를 적는다.
  • 빌드에 영향은 주지 않는다.

COPY

COPY <src>... <dest>
COPY . /usr/src/app
  • 파일이나 디렉토리를 이미지로 복사한다.
  • 일반적으로 소스를 복사하는데 사용한다.
  • 타겟 디렉토리가 없다면 자동으로 생성한다.

ADD

ADD <src>... <dest>
ADD . /usr/src/app
  • COPY 명령어와 매우 유사하다
  • src 에 파일 대산이 url을 입력할 수 있다.
  • src 에 압축 파일을 입력하는 경우 자동으로 압축을 해제하며 복사된다.

RUN

RUN <command>
RUN ["executable", "param1", "param2"]
RUN bundle install
  • 명령어를 그대로 실행한다.
  • 내부적으로 /bin/sh -c 뒤에 명령어를 실행하는 방식이다.

CMD

CMD ["executable","param1","param2"]
CMD command param1 param2
CMD bundle exec ruby app.rb
  • 도커 컨테이너가 실행되었을 때 실행되는 명령어를 정의한다.
  • 빌드 할 때는 실행되지 않는다.
  • 여러 개의 CMD가 존재하는 경우 가장 마지막 CMD만 실행된다.
  • 한꺼번에 여러 개의 프로그램을 실행하고 싶은 경우 run.sh 파일을 작성하여 데몬으로 실행하거나 supervisord나 forego와 같은 여러 개의 프로그램을 실행하는 프로그램을 사용한다.

WORKDIR

WORKDIR /path/to/workdir
  • RUN, CMD, ADD, COPY 등이 이루어질 기본 디렉토리를 설정한다.
  • 각 명령어의 현재 디렉토리는 한줄 한줄마다 초기화 되기 때문에 RUN cd /path하더라도 다음 명령어에서는 다시 위치가 초기화 되기 때문이다.
  • 그래서 같은 디렉토리에서 계속 작업하기 위해서는 WORKDIR을 사용한다.

EXPOSE

EXPOSE <port> [<port>...]
EXPOSE 4567
  • 도커 컨테이너가 실행되었을 때 요청을 기다리고 있는 (Listen) 포트를 지정한다.
  • 여러 개의 포트도 지정 할 수 있다.

VOLUME

VOLUME ["/data"]
  • 컨테이너 외부에 파일 시스템을 마운트 할 때 사용한다.
  • 반드시 지정하지 않아도 마운트 할 수 있지만 기본적으로 지정하는 것이 좋다.

ENV

ENV <key> <value>
ENV <key>=<value> ...
ENV DB_URL mysql
  • 도커파일 또는 컨테이너에서 사용할 환경변수를 지정한다.
  • 컨테이너를 실행할때 -e 옵션을 사용하면 기존 값을 오버라이딩 하게 된다.

ARG

ARG <key>=<value>
ARG myAddress = anna.com
  • ENV 명령어와 비슷하지만 ARG는 도커파일 내에서만 사용하는 환경변수를 지정한다.

자세한 내용은 공식문서를 참고하자.



🐳Dockerfile 빌드 과정


# 1. ubuntu 설치 (패키지 업데이트 + 만든사람 표시)
FROM       ubuntu
MAINTAINER anna@anna.com
RUN        apt-get -y update

# 2. ruby 설치
RUN apt-get -y install ruby
RUN gem install bundler

# 3. 소스 복사
COPY . /usr/src/app

# Gem 패키지 설치
WORKDIR /usr/src/app
RUN bundle install
RUN bundle add rackup

# 5. Sinatra 서버 실행 (Listen 포트 정의)
EXPOSE 4567
CMD    bundle exec ruby app.rb -o 0.0.0.0

지난 게시물 → [Docker] Docker 이미지 만들고 컨테이너 생성하기

지난번에 간단한 웹 어플리케이션을 이미지로 만들기 위해 Dockerfile을 작성하고 컨테이너를 생성해보았다. 위 코드는 그때 작성한 Dockerfile 이다.

이 Dockerfile을 참고해서 도커파일의 빌드 과정을 알아보자.

Sending build context to Docker daemon  5.12 kB   <-- (1)
Step 1/10 : FROM ubuntu                           <-- (2)
 ---> f49eec89601e                                <-- (3)
Step 2/10 : MAINTAINER anna@anna.com              <-- (4)
 ---> Running in f4de0c750abb                     <-- (5)
 ---> 4a400609ff73                                <-- (6)
Removing intermediate container f4de0c750abb      <-- (7)
Step 3/10 : RUN apt-get -y update                 <-- (8)
...
...
Successfully built 20369cef9829                   <-- (9)

위는 도커파일을 빌드하면 출력되는 로그들 이다.

한줄한줄 뜯어서 봐보자

(1) Sending build context to Docker daemon  5.12 kB
  • 빌드 명령어를 실행한 디렉토리 파일들을 빌드 컨텍스트(build context)라고 하고 이 파일들을 도커 서버로 전송한다.
  • 도커는 서버-클라이언트 구조이므로 도커 서버가 작업을 하려면 미리 파일을 전송해야 한다.
(2) Step 1/10 : FROM ubuntu 
  • 작성해두었던 Dockerfile을 한 줄 한 줄 수행한다.
  • 베이스 이미지로 지정한 ubuntu 이미지를 다운받는다.
(3) ---> f49eec89601e
  • 명령어 수행결과를 “이미지로 저장한다.”
  • ubuntu 이미지의 id가 출력된다.
(4) Step 2/10 : MAINTAINER anna@anna.com 
  • Dockerfile의 두 번째 명령어인 MAINTANIER 를 수행한다.
(5) ---> Running in f4de0c750abb
  • 명령어를 수행하기 위해 바로 이전에 생성한 f49eec89601e 이미지를 기반으로 f49eec89601e컨테이너를 임시로 생성하여 실행”한다.
(6) ---> 4a400609ff73
  • 명령어 수행 결과를 “이미지로 저장한다.”
(7) Removing intermediate container f4de0c750abb
  • 명령어 수행하기 위해 임시로 생성해 두었던 “컨테이너를 제거한다.”
(8) Step 3/10 : RUN apt-get -y update
  • Dockerfile의 세 번째 명령어를 수행한다.
  • 이전 단계와 마찬가지로
    • 전에 만들어진 이미지를 기반으로 임시 컨테이너를 생성하고
    • 명령어를 실행하고
    • 그 결과를 이미지로 만들어 저장하고
    • 임시 컨테이너를 제거한다.
  • 위 과정을 마지막 줄까지 반복한다.
(9) Successfully built 20369cef9829
  • 최종 성공한 이미지의 ID를 출력한다.

결론

도커 빌드는 임시 컨테이너 생성 > 명령어 수행 > 이미지로 저장 > 임시 컨테이너 삭제 > 새로 만든 이미지 기반 임시 컨테이너 생성 > 명령어 수행 > 이미지로 저장 > 임시 컨테이너 삭제 > … 의 과정을 계속해서 반복한다.

명령어를 실행할 때마다 이미지 레이어를 저장하고 다시 빌드할 때 Dockerfile이 변경되지 않았다면 기존에 저장된 이미지를 그대로 캐시처럼 사용한다.

이러한 레이어 개념을 잘 이해하고 있어야 최적화된 이미지를 생성할 수 있다.

  • 실제 log를 봤을때 이전에 한번 빌드 과정이 있었기 때문에 2, 3, 4번 과정은 Cache 되어 있던 레이어를 사용 했음을 확인 할 수 있었다.



🐳Dockerfile 리팩토링


# 1. ubuntu 설치 (패키지 업데이트 + 만든사람 표시)
FROM       ubuntu
MAINTAINER anna@anna.com
RUN        apt-get -y update

# 2. ruby 설치
RUN apt-get -y install ruby
RUN gem install bundler

# 3. 소스 복사
COPY . /usr/src/app

# Gem 패키지 설치
WORKDIR /usr/src/app
RUN bundle install
RUN bundle add rackup

# 5. Sinatra 서버 실행 (Listen 포트 정의)
EXPOSE 4567
CMD    bundle exec ruby app.rb -o 0.0.0.0

사실 앞서 만들었던 이미지에는 몇가지 최적화 문제가 있다.

이를 Dockerfile 수정을 통해 최적화를 해보자.

Base Image

  • 위에서 만든 Ruby 어플리케이션 이미지는 ubuntu를 베이스로 만들었지만 사실은 더 간단한 ruby 베이스 이미지가 존재한다.
  • 기존의 ruby를 설치했던 명령어는 ruby 이미지를 사용하는 것으로 간단하게 생략할 수 있다.
    • 진작 알려줬으면 .. 그고생은 …

      # before
      FROM ubuntu
      MAINTAINER anna@anna.com
      RUN apt-get -y update
      RUN apt-get -y install ruby
      RUN gem install bundler
      
      # after
      FROM ruby:3.0
      MAINTAINER anna@anna.com
  • ruby 외에도 node.js, python, java, go 등 다양한 베이스 이미지가 존재하며 세부적인 설정이 필요하지 않다면 그대로 사용하는 게 간편하다.

Build Cache

  • 도커파일을 다시 빌드하게 되면 굉장히 빠르게 빌드 되는데 이는 위에서 말했다시피 이미지를 빌드하는 과정에서 각 단계를 이미지 레이어로 저장하고 다음 빌드에서 캐시로 사용한다.
  • 도커는 빌드 할 때 도커 파일의 명령어가 수정되었거나 추가하는 파일이 변경 되었을 경우에만 캐시가 깨지고 그 이후 작업들은 새로 이미지를 만들게 된다.
  • ruby gem을 설치하는 과정은 꽤 많은 시간을 소요하는데 최대한 캐시를 이용하여 빌드시간을 줄일 수 있다.
  • 기존 소스에서 소스파일이 수정되면서 깨지는 부분은 다음과 같다.
COPY . /usr/src/app    # <- 소스파일이 변경되면 캐시가 깨짐
WORKDIR /usr/src/app
RUN bundle install     # 패키지를 추가하지 않았는데 또 인스톨하게 됨 ㅠㅠ
  • 복사하는 파일이 이전과 다르면 캐시를 사용하지 않고 그 이후의 명령어는 다시 실행된다.
  • ruby gem 패키지를 관리하는 파일은 Gemfile 이고 Gemfile은 잘 수정되지 않으므로 다음과 같이 순서를 변경하여 ruby gem을 캐시하여 사용하도록 바꿀 수 있다.
COPY Gemfile* /usr/src/app/ # Gemfile을 먼저 복사함
WORKDIR /usr/src/app
RUN bundle install          # 패키지 인스톨
COPY . /usr/src/app         # <- 소스가 바꼈을 때 캐시가 깨지는 시점 ^0^
  • gem 설치하는 부분을 소스파일 이전으로 옮겼다.
  • 이제 소스코드가 수정되더라도 매번 gem을 설치하지 않아 더욱 빠르게 빌드 가능하다.
  • 다른 언어 패키지를 쓰더라도 똑같이 적용하여 빌드시간을 줄이자.

명령어 최적화

  • 이미지를 빌드할때 불필요한 로그는 무시하는게 좋고 패키지 설치시 문제 파일도 생성할 필요가 없다.
# before
RUN apt-get -y update

# after
RUN apt-get -y -qq update
  • -qq 옵션으로 로그를 출력하지 않게 한다.
  • 각종 리눅스 명령어는 보통 quite 옵션이 있으니 적절하게 적용하자.
# before
RUN bundle install

# after
RUN bundle install --no-rdoc --no-ri
  • --no-doc과 --no-ri 옵션으로 필요 없는 문서를 생성하지 않아 이미지 용량도 줄이고 빌드 속도도 더 빠르게 할 수 있다.

이쁘게

  • 명령어는 비슷한 것끼리 묶어 주는 게 보기도 좋고 레이어 수도 줄일 수 있다.
  • 도커 이미지는 스토리지 엔진에 따라 레이어의 개수가 127개로 제한되어 있는 경우도 있어 너무 많은 명령어는 좋지 않다.
# before
RUN apt-get -y -qq update
RUN apt-get -y -qq install ruby

# after
RUN apt-get -y -qq update && \
    apt-get -y -qq install ruby

최종

# 1. ubuntu 설치 (패키지 업데이트 + 만든사람 표시)
FROM       ruby:3.0
MAINTAINER anna@anna.com
COPY Gemfile* /usr/src/app/
WORKDIR /usr/src/app
RUN bundle install --no-rdoc --no-ri
COPY . /usr/src/app
EXPOSE 4567
CMD bundle exec ruby app.rb -o 0.0.0.0

다음에는 도커허브에 대해 간단히 알아보고 배포에 대해서 간단히 알아보자.







📎 참고 링크

https://subicura.com/2017/02/10/docker-guide-for-beginners-create-image-and-deploy.html

0개의 댓글