🐳 Docker 시리즈 살펴보기 🐳
      1. Docker와 Container
➡️  2. Dockerfile과 Docker Image
      3. Docker Volume
      4. Docker Network
      5. Docker Compose

 

🐳 Dockerfile

  • Docker Image를 만들기 위한 설정파일이다.
  • 여러가지 명령어를 토대로 Docker File을 작성하면 설정대로 Docker Image를 만들 수 있다.
  • 이때 파일 이름을 'Dockerfile'로 해야한다. 이름을 변경하고 싶다면 빌드시에 옵션(-f/--file)을 주어야 한다.

 

✅ Dockerfile 항목

Dockerfile 예시는 다음과 같다.

FROM node:18-alpine # nodejs alpine 버전 이미지를 가져온다.
WORKDIR /usr/src/app # 작업 디렉토리를 지정환다.
COPY package.json . # 빌드 컨텍스트의 package.json 을 작업 디렉토리로 지정한 /usr/src/app 으로 복사한다.
RUN npm install # 실행한다.
EXPOSE 3000 # 3000번 포트를 열어준다.
CMD [ "npm", "start" ] # 실행한다.
명령어 설명
FROM 베이스 이미지를 지정한다. (필수)
RUN 새로운 레이어에서 명령어를 실행하고, 새로운 이미지 생성
- RUN <command>
- RUN ["executable", "param1", "param2"]
CMD 컨테이너를 생성 및 실행할 명령어. 컨테이너를 생성할때만 실행됨 (docker run)
- CMD ["executable", "param1", "param2"]
- CMD ["param1", "param2"] - CMD command param1 param2
ENTRYPOINT 컨테이너를 생성 및 실행할 명령어. 컨테이너를 시작할때마다 실행됨 (docker start)
- ENTRYPOINT ["executable", "param1", "param2"]
- ENTRYPOINT command param1 param2
LABEL 이미지에 메타데이터를 추가 (key-value 형태)
- LABEL <key>=<value> <key>=<value> <key>=<value> <key>=<value> ...
- LABEL <key>=<value> \
              <key>=<value> ,,, 
MAINTAINER 이미지를 생성한 개발자의 정보 (1.13.0 이후 deprecated)
EXPOSE Dockerfile의 빌드로 생성된 이미지에서 열어줄 포트를 의미
- docker run / create시 -p로 설정할 경우 EXPOSE 설정이 오버라이드 된다.
- 기본적으로 EXPOSE는 tcp로 설정된다. udp로 지정하려면 EXPOSE 80/udp 처럼 명시해줘야 한다.
ENV 이미지에서 사용할 환경 변수 값을 지정
- ENV <key>=<value> <key>=<value> ..
- ENV <key>=<value>
ADD build 명령 중간에 호스트의 파일 또는 폴더를 컨테이너의 이미지 안으로 복사하는 것
COPY build 명령 중간에 호스트의 파일 또는 폴더를 컨테이너의 이미지 안으로 복사하는 것
- RUN 처럼 체인 형태로 사용할 수는 없으나, 다수의 파일을 하나의 target으로 복사하는것은 가능하다.
- COPY file1 file2 /target
VOLUME 마운트 할 볼륨을 지정한다.
USER 이미지를 어떤 계정에서 실행하는지 지정 / 기본은 root
WORKDIR 작업 디렉토리 지정. 없으면 생성
ARG 빌드 시 전달할 수 있는 변수를 정의
ONBUILD 이미지가 다른 빌드의 기반으로 사용될 때 나중에 실행될 트리거 명령을 기재한다.
STOPSIGNAL 종료하기 위해 컨테이너로 전송될 시스템 호출 신호를 설정.
- SIGTERM / SIGKILL 설정 가능 - 기본값은 SIGTERM이다.
- docker run / create 에서 --stop-signal 옵션으로도 설정 가능하다.
HEALTHCHECK 컨테이너가 잘 작동하고 있는지 확인하기 위해 컨테이너를 테스트 하는 방법이다.
interval, timeout, start period, retries 를 옵션으로 설정할 수 있다.

status는 다음과 같다.

- 0: success
- 컨테이너가 healthy 하고 사용할 준비가 되었음
- 1: unhealthy - 현재 컨테이너가 잘 작동하고 있지 않음
- 2: reserved - do not use this exit code
SHELL 기본 셸 지정

 

RUN/CMD/ENTRYPOINT 차이

  • RUN : 도커 파일로부터 도커 이미지를 빌드하는 순간 실행되는 명렁어로 라이브러리 설치 부분에서 주로 활용
  • CMD : 이미지로부터 컨테이너를 생성하여 최초로 실행할 때(docker run) 실행되는 명령어이지만 변경할 수 있을때
    • 컨테이너 실행 시에 파라미터를 추가로 입력하면 파라미터가 우선순위를 갖게 된다. Dockerfile 내에서 딱 한번만 사용 가능하다.
    • ENTRYPOINT 의 default 파라미터를 설정하거나 추가 파라미터를 설정하는 용도로도 쓴다.
  • ENTRYPONT : docker start 할때 매번 실행 / CMD와 동일하나 최초 실행 시 '꼭' 실행되어야 하는 명령어가 있을 때 사용 / 파라미터 값과 관련 없이 항상 실행된다.
  • CMD나 ENTRYPOINT 에서 다수의 명령을 수행하려면 CMD ["/bin/bash", "-c", "echo FIRST COMMAND;echo SECOND COMMAND"] 로 가능하지만
    • .sh 파일 같은 스크립트 파일을 작성해서 실행시키는것을 권장

 

ADD/COPY 차이

  • ADD : 로컬 파일 디렉토리, url(원격파일) 복사, tar파일 자동 압축 해제 및 추출 기능이 있다.
  • COPY : 단순 로컬 파일 또는 디렉토리를 복사할때 사용한다. (파일의 출처 추적 가능)

 

ENV/ARG 차이

  • ENV : container 시작 시에 사용하는 변수. 해당 이미지를 실행한 컨테이너에서 사용할 수 있다.
  • ARG : Dockerfile build 시에 사용하는 변수 (Dockerfile 내에서 사용하거나, Dockerfile에 선언하고 docker build~ 할때 값을 넘겨주어 변수로 사용)

 

Shell From vs Exec From

  • RUN, CMD, ENTRYPOINT 에서 두가지 방식을 선택할 수 있다.
  • Shell From
    • [ ] 기호 없이 컨테이너의 shell 을 실행시키는 방식이다.
    • 를 넘겨주면 리눅스라면 /bin/sh -c , 윈도우라면 cmd /S /C 와 같은 형태로 실행된다.
  • Exec From
    • [ ] 기호로 문자열 배열로 나타낸다.
    • shell 을 통하는 것이 아니기 때문에 실행 가능한 명령과 그 인자만 넘겨줄 수 있다.
    • 예를들어 RUN ["echo", "$HOME"] 은 shell processing이 일어나지 않기 때문에 $HOME 라는 변수를 알 수 없다. exec form 으로 의도한 구문을 실행하려면 RUN ["sh", "-c", "echo $HOME"] 로 사용해야 한다.
  • 기본적으로 shell은 sh로 실행되기 때문에, bash 로 바꾸기 위해서는 exec form 을 통해 /bin/bash 로 지정해야 한다.
    • ex) ENTRYPOINT ["/bin/bash", "-c", "echo hello!"]

 

🐳 Dockerfile 사용법

$ docker build [PATH] # build context로 지정한다. 현재 디렉토리로 지정하려면 '.' 로 작성한다. -f 옵션을 주지 않으면 'Dockerfile'을 찾아 빌드
$ docker build [URL] # 원격에 있는 Dockerfile 을 이용할 수도 있다.
  • docker build 요청이 시작되면 Docker daemon에게 build context의 하위 모든 파일에 대한 정보를 전송한다.
  • build context 내에 build되지 않았으면 하는 항목이 있다면 '.dockerignore' 파일에 기재하면 된다.
  • '.' 는 현재 경로이며, build context 를 커맨드를 실행하는 현재 경로로 설정하려면 'docker build .' 라고 입력하면 된다.
  • Git 레포지토리 임을 특정해서 받으려면 'git://' 이나 'git@' 을 쓰면 된다.

 

옵션

옵션 설명
--tag, -t name:tag 형식으로 name을 짓거나 선택적으로 tag를 설정할 때 사용
--rm default로는 true 이며, 이미지가 성공적으로 빌드되었을때 임시 컨테이너를 즉시 삭제하는 옵션
--file, -f PATH/Dockerfile 이외의 다른 경로나, 다른 파일 이름에 대해 빌드할 때 사용
--no-cache 이미지를 빌드할 때 캐시를 사용하지 않는 옵션

 

🐳 Docker Image

  • 컨테이너 실행에 필요한 모든 파일과 설정값을 포함하고 있는 것으로, 더 이상의 의존성 파일을 컴파일하거나 설치할 필요 없는 상태의 파일이다.
  • 이미지는 Immutable하며, 하나의 이미지는 여러개의 컨테이너를 생성할 수 있다.
  • 컨테이너가 삭제되어도 이미지는 변하지 않는다.
  • Read-only이다.

 

 Image Layer

  • 하나의 이미지는 여러개의 레이어가 쌓여 만들어진다.
  • Dockerfile의 instruction(명령어) 와 이미지 레이어는 1:1 관계를 갖는다.

 

  • 이미지 레이어는 도커 엔진의 캐시에 물리적으로 저장된 파일이다.
  • 이미지 레이어는 여러 이미지와 컨테이너에서 공유된다.

  • 위 이미지를 보면, ubuntu 이미지는 레이어 A,B,C 를 기반으로 만들어졌다. 그다음 nginx 이미지를 빌드할 때는 레이어 A, B, C를 새롭게 생성하지 않고 이미 있는 것을 캐시에서 사용한다.
  • 그래서, 이미지는 공유되므로 이미지를 함부로 수정하면 레이어들을 공유하는 다른 이미지에도 영향을 미치게 된다. 따라서 이미지는 Read-only(읽기 전용) 이다.
  • Dockerfile 명령문 하나가 레이어 하나로 구성되는데, 레이어를 생성하기 전에 해당 instrunction 해시값을 조회하고, 캐시에 없다면 생성한다.
  • ADD, COPY 명령어는 명령어 String + 해당 파일의 변경 유무를 캐싱 / 그 이외의 명령어는 명령어 String을 캐싱
  • 한번 명령문이 실행되면 그 하위 명령문들은 캐시를 확인하지 않고 실행한다.

 

 예시

예를들어, 아래와 같이 서로 겹치는 레이어가 있는 네개의 이미지가 있다.

다음 명령어로 이미지 목록을 조회한다.

$ docker images

네 개의 이미지의 크기를 모두 합하면 총 12.24GB 임을 확인할 수 있다.

 

아래 명령어로 실제 디스크를 차지하는 용량을 확인해보자.

$ docker system df

이미지가 실제로 차지하는 용량은 3.684GB이다.

우리는 이로써, 도커는 이미지를 빌드할 때, 캐시에 있는 레이어를 공유하여 사용한다는 것을 알 수 있다

 

🐳 Dockerfile/Image 최적화

Docker image가 최적화 되면, :

  • 이미지 빌드와 배포가 빨라진다.
  • 애플리케이션의 보안이 지켜진다. (필요 없는 기능을 넣지 말아야 한다. 예를들어, 어플리케이션에 curl이 필요 없는데 설치했을 경우, 보안 취약점의 이유가 될 수 있다.

1. 꼭 필요한 파일만 이미지에 포함시킨다.

  • Dockerfile 스크립트의 명령문 하나하나 마다 이미지 레이어가 하나씩 생긴다.
  • 복사한 파일 중 일부를 삭제한다고 해도, 실제로 이미지에서 지워지지 않는다.
  • 다음 레이어에서 불필요한 파일을 제거해봐야 소용 없으므로(오히려 용량을 증가시킬 수 있다.), 각 레이어마다 최적화를 해야 한다.
  • '.dockerignore' 파일을 이용해 포함시키지 않아야 하는 디렉터리나 파일을 명시해 주는것도 좋다.
    • '.gitignore' 형식과 동일하다.

2. 좋은 기반 이미지를 골라야 한다.

  • 필수 기능만 있는 가벼운 기반 이미지에서 필요한 기능을 추가하는 것이 좋다.
  • 리눅스 컨테이너에서는 alpine 리눅스 또는 데비안 슬림 이미지가 기반 이미지로 좋다. 

3. Dockerfile 스크립트의 명령문은 자주 수정하는 순서대로 뒤에 오도록 배치해 캐시를 최대한 활용한다.

  • Dockerfile을 빌드할 때, 한번이라도 캐시에 없는 명령문을 수행하려고 하면 그 뒤는 모두 실행되게 된다.
  • 이 점을 이용해 Dockerfile을 최적화 할 수 있다.

4. 쉘 기능이 반드시 필요한 것이 아니라면 최대한 exec form 을 사용한다.

RUN ehco $VERSION # Shell
RUN ["echo", "$VERSION"] # Exec
  • shell form은 쉘을 통해 실행되므로 불필요한 프로세스를 실행시킨다.
  • 반드시 쉘 기능이 필요한 것이 아니라면 되도록 exec form 을 사용한다.

5. 캐시할 수 있는 단위를 구별해야 한다.

  • 각각의 RUN 명령어들은 캐시할 수 있는 단위이다.
  • 너무 많은 명령어들을 연결해 하나의 RUN으로 실행시키는 것도 캐시가 쉽게 무효화 되어 좋지 않고,
  • 너무 세세하게 나누어 RUN을 실행해도 효율적인 캐싱이 되지 않는다.
  • update-install 을 하나의 캐시 단위로 묶는것처럼, 잘 구별하여 사용해야 한다.

6. 멀티 스테이지 빌드를 이용해 런타임에 필요로 하는 것들만 포함시킨다.

  • 특정 언어로 작성된 소스코드는 빌드하기 위해 관련 빌드 툴과 라이브러리가 필요하다.
  • 하지만 이는 실행단계에 필요하지 않을 수 있다.
  • 이럴 경우, 멀티스테이지 빌드를 이용해 이를 분리한다.
# 첫번째 스테이지: maven 기본 이미지 지정, jar 패키지 생성
FROM maven:3.5.0-jdk-8-alpine AS builder
 
# add pom.xml and source code
ADD ./pom.xml pom.xml
ADD ./src src/
 
# package jar
RUN mvn clean package
 
# 두번재 스테이지: 생성한 jar 패키지 openjdk 이미지를 이용해 실행. 최종 이미지 생성
From openjdk:8-jre-alpine
 
# copy jar from the first stage
COPY --from=builder target/my-app-1.0-SNAPSHOT.jar my-app-1.0-SNAPSHOT.jar
 
EXPOSE 8080
 
CMD ["java", "-jar", "my-app-1.0-SNAPSHOT.jar"]​
  • 이렇게 하면, 마지막 스테이지의 이미지가 최종 이미지가 된다.
  • 스테이지에 별도로 이름을 부여하지 않으면 (AS ~ 로 지정할 수 있다.) 순서대로 0부터 정수값을 가진다.
  • 중간 빌드 결과물을 최종 이미지에 포함시키려면 COPY --from=NAME(or integer) 을 사용하면 된다.