Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
05-19 10:17
관리 메뉴

nomad-programmer

[DevOps/Docker] Dockerfile을 사용한 구성 관리 및 Build 본문

DevOps/Docker

[DevOps/Docker] Dockerfile을 사용한 구성 관리 및 Build

scii 2020. 11. 29. 15:16

Docker에서는 인프라 구성을 기술한 파일을 'Dockerfile' 이라고 한다.

Dockerfile 이란?

  • 베이스가 될 Docker 이미지
  • Docker 컨테이너 안에서 수행한 명령
  • 환경변수 등의 설정
  • Docker 컨테이너 안에서 작동시켜둘 데몬 실행
Dockerfile은 이와 같이 Docker 상에서 작동시킬 컨테이너의 구성 정보를 기술하기 위한 파일이다.

docker build 명령은 Dockerfile에 기술된 구성 정보를 바탕으로 Docker 이미지를 작성한다.

Dockerfile과 Docker 이미지의 관계


Dockerfile의 기본 구문

Dockerfile은 텍스트 형식의 파일로, 에디터 등을 사용하여 작성한다. 확장자는 필요 없으며, 'Dockerfile' 이라는 이름의 파일에 인프라의 구성 정보를 기술한다. 또한 Dockerfile 이외의 파일명으로도 작동하지만, 이때는 Dockerfile에서 이미지를 빌드할 때 파일명을 명시적으로 지정해야 한다.

Dockerfile의 기본 구문은 다음과 같다.

// Dockerfile의 기본 서식
명령 인수

명령은 대문자든 소문자든 상관없지만 관례적으로 대문자로 통일해서 쓴다. Dockerfile에서 사용하는 주요 명령은 다음과 같다.

명령 설명
FROM 베이스 이미지 지정
RUN 명령 실행 (Dockerfile build 실행 시 수행함)
CMD 컨테이너 실행 명령 (만들어진 이미지가 실행될 때 컨테이너에서 실행. Dockerfile에서 한번만 사용할 수 있는 명령)
LABEL 라벨 설정
EXPOSE 포트 익스포트
ENV 환경변수
ADD 파일/디렉토리 추가
COPY 파일 복사
ENTRYPOINT 컨테이너 실행 명령 (docker container run 실행 시 수행함. 컨테이너를 실행가능한 형태로 만듦)
VOLUME 볼륨 마운트
USER 사용자 지정
WORKDIR 작업 디렉토리
ARG Dockerfile 안의 변수
ONBUILD 빌드 완료 후 실행되는 명령
STOPSIGNAL 시스템 콜 시그널 설정
HEALTHCHECK 컨테이너의 헬스 체크
SHELL 기본 쉘 설정

Dockerfile에 주석을 쓰는 경우는 다음과 같이 줄의 맨 앞에 #을 붙인다.

// Dockerfile의 주석 서식

# 이것은 주석이다.
명령 인수

명령 인수    # 이것도 주석

Dockerfile 작성

Dockerfile에는 'Docker 컨테이너를 어떤 Docker 이미지로부터 생성할지' 라는 정보를 반드시 기술해야 한다. 이 이미지를 베이스 이미지라고 한다. 베이스 이미지는 다음과 같은 서식으로 기술한다.

FROM [이미지명]
FROM [이미지명]:[태그명]
FROM [이미지명]@[digest]

이 FROM 명령은 필수 항목이다. 예를 들어 Centos의 버전 7을 베이스 이미지로 작성하여 Dockerfile을 작성할 때는 다음과 같이 작성한다.

// CentOS를 베이스 이미지로 한 Dockerfile
FROM centos:centos7

또한 태그명을 생략하면 베이스 이미지의 최신 버전(latest)이 적용된다. 이미지명이나 태그명은 작성자가 임의의 값을 붙일 수 있기 때문에 Dockerfile을 수정해도 똑같은 이름으로 몇 번이든 이미지를 만들 수 있다.

이미지를 고유하게 특정할 때는 digest를 이용한다. digest는 Docker Hub에 업로드하면 자동으로 부여되는 식별자를 말한다. 이 digest는 고유한 식별자이기 때문에 이미지를 고유하게 지정할 수 있다.

예를 들어 Docker Hub에서 취득한 tensorflow/tensorflow라는 이미지의 digest를 확인하려면 다음과 같이 docker image ls 명령에 --digest 옵션을 지정한다.

$ docker image ls --digests

REPOSITORY          TAG                 DIGEST            IMAGE ID            CREATED             SIZE
ubuntu              latest              sha256:abcd5...   f643c72bc252        3 days ago          72.9MB

DDockerfile에서 이미지를 고유하게 지정할 때는 다음과 같이 이미지명 다음에 @ 마크를 붙이고 digest 값을 지정한다.

// 베이스 이미지 설정
FROM tensorflow/tensorflow@sha:abcd5...

Dockerfile로부터 Docker 이미지 만들기

Dockerfile을 빌드하면, Dockerfile에 정의된 구성을 바탕으로 한 Docker 이미지를 작성할 수 있다.

Dockerfile로부터 이미지를 생성하려면 docker build 명령을 사용한다.
docker build -t [생성할 이미지명]:[태그명] [Dockerfile의 위치]

여기서는 실제로 FROM 명령으로 베이스 이미지만을 지정한 다음의 Dockerfile을 빌드하는 순서를 설명한다.

// Dockerfile 작성
$ mkdir sample && cd $_
$ touch Dockerfile

$ ls
Dockerfile

그 다음 Dockerfile을 다음과 같이 기술한다.

// 베이스 이미지 설정
FROM centos:centos7

이 Dockerfile로부터 sample이라는 이미지를 작성하려면 다음의 명령을 실행한다. 태그명은 1.0이라는 버전을 붙여 둔다. 또한 이 명령의 예에서는 Dockerfile의 저장 위치를 절대 경로로 지정하고 있지만 상대 경로로 지정할 수 있다.

$ docker build -t sample:1.0 /home/docker/sample

명령을 실행하면 /home/docker/sample에 저장된 Dockerfile로부터 sample이라는 이름의 Docker 이미지가 생성된다. 처음에는 Docker 저장소에서 베이스 이미지를 다운로드하는 처리가 있으므로 시간이 걸리지만 그 다음에 Dockerfile의 내용이 실행되고 있다는 것을 알 수 있다.

그리고 이미 sample이라는 이름에 태그명을 2.0이라는 버전으로 한 새로운 이미지를 작성하려면 다음과 같이 명령한다.

$ docker build -t sample:2.0 /home/docker/sample

두 번째 이후는 베이스 이미지를 Docker 저장소에서 다운로드하지 않으므로 이미지를 바로 작성할 수 있다. 여기서 확인해둬야 하는 것은 작성한 두 개의 이미지의 이미지 ID가 모두 똑같다는 점이다. 이것은 이미지로서는 각각 다른 이름이 붙어 있지만 그 실체는 모두 동일한 이미지라는 것을 나타낸다.

$ docker image ls

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
sample              1.0                 abcddeb8bdce        2 weeks ago         204MB
sample              2.0                 abcddeb8bdce        2 weeks ago         204MB
NOTE : IMAGE ID 가 동일한 이미지는 그 실체도 똑같다.

Dockerfile에는 임의의 파일명을 붙일 수도 있다. 파일명은 docker build 명령에서 -f 옵션으로 지정한다. 예를 들어 현재 디렉토리에 있는 'Dockerfile.base' 라는 이름의 파일을 지정하려면 다음의 명령을 실행한다.

// 파일명을 지정한 docker build 명령 실행
$ docker build -t sample -f Dockerfile.base .
단, 파일명이 Dockerfile 이외의 이름인 경우는 Docker Hub에서 이미지의 자동 생성 기능을 사용할 수 없으므로 주의해야 한다.

다음과 같이 표준 입력을 경유하여 Dockerfile을 지정하여 빌드를할 수도 있다. 표준 입력의 내용으로서 Dockerfile의 내용을 docker build 명령의 인수로 전달하므로 - (하이픈)을 지정한다.

// 표준 입력에서의 빌드
$ docker build - < Dockerfile

단, 이 경우는 빌드에 필요한 파일을 포함시킬 수 없기 때문에, 예를 들면 ADD 명령으로 이미지 안에 파일을 추가할 수 없다. 그래서 Dockerfile과 빌드에 필요한 파일을 tar로 모아두고 표준 입력에서 지정한다.

다음은 이미지 안에 추가하고 싶은 dummyfile과 Dockerfile을 하나로 모아놓은 압축 아카이브로부터 이미지를 빌드하는 예이다.

// 압축 아카이브에 의한 표준 입력에서의 빌드
tar tvfz docker.tar.gz
-rw-r--r--  0 method staff      20 Nov 29 22:17 Dockerfile
-rw-r--r--  0 method staff       0 Nov 30 00:17 dummyfile

$ docker build - < docker.tar.gz

// 혹은

$ docker build -t sample:3.0 - < docker.tar.gz

중간 이미지 재사용

Docker는 이미지를 빌드할 때 자동으로 중간 이미지를 생성한다.그리고 다른 이미지를 빌드할 때 중간 이미지를 내부적으로 재사용함으로써 빌드를 고속으로 수행한다. 이미지를 재사용하고 있을 때는 빌드 로그에 'Using cache' 라고 표시된다.

이미지 재사용을 하지 않으려면 docker build 명령에서 --no-cache 옵션을 지정한다.

Docker 이미지의 레이어 구조

Dockerfile을 빌드하여 Docker 이미지를 작성하면 Dockerfile의 명령별로 이미지를 작성한다. 작성된 여러 개의 이미지는 레이어 구조로 되어 있다. 예를 들면 다음과 같이 4개의 명령으로 되어 있는 Docker 파일로부터 이미지를 작성하는 경우를 생각할 수 있다.

// 4개의 명령을 갖고 있는 Dockerfile의 예

# STEP:1 Ubuntu (베이스 이미지)
FROM ubuntu:latest

# STEP:2 Nginx 설치
RUN apt-get update && apt-get install -y -q nginx

# STEP:3 파일 복사
COPY index.html /usr/share/nginx/html/

# STEP:4 Nginx 시작
CMD ["nginx", "-g", "daemon off;"]

다음의 예에서 나온 Dockerfile과 동일한 디렉토리에 임의의 'index.html' 이라는 이름의 파일을 마련하고, 다음 docker build 명령으로 이미지를 작성한다.

$ docker build -t webapp .

[+] Building 19.9s (8/8) FINISHED
 => [internal] load .dockerignore                                                           0.0s
 => => transferring context: 2B                                                             0.0s
 => [internal] load build definition from Dockerfile
 ...

작성한 이미지는 다른 이미지와도 공유된다. 예를 들어 공통의 베이스 이미지를 바탕으로 여러 개의 이미지를 작성한 경우, 베이스 이미지의 레이어가 공유된다.

이와 같이 이미지를 겹침으로써 Docker에서는 디스크의 용량을 효율적으로 이용한다.

Docker Hub의 이미지 관리
Docker 이미지를 공유하는 서비스인 Docker Hub 는 전 세계의 엔지니어가 작성한 이미지를 공개하고 있다. 이미지는 OS의 파일을 포함하고 있기 때문에 용량이 크다. 그래서 모든 이미지를 다 갖고 있자면 디스크 용량이 눈 깜짝할 사이에 늘어나 버린다.
따라서 Docker Hub 에서도 이미지를 레이어로 겹쳐서 작성함으로써 이미지를 공유하여 관리하고 있다. Docker Hub 에서는 이미지 ID가 같은 것은 하나의 실체를 공유하여 사용하고 있다.

멀티스테이지 빌드를 사용한 애플리케이션 개발

busybox 이미지는 무엇일까?

BusyBox는 기본적인 Linux 명령들을 하나의 파일로 모아놓은 것으로, 최소한으로 필요한 Linux 쉘 환경을 제공하는 경우 이용한다.

애플리케이션 개발 시에 개발 환경에서 사용한 라이브러리나 개발 지원 툴이 제품 환경에서 반드시 사용되는 것은 아니다. 제품 환경에서는 애플리케이션을 실행하기 위해 최소한으로 필요한 실행 모듈만 배치하는 것이 컴퓨팅 리소스를 효율적으로 활용할 수 있다는 점에서나 보안 관점에서 볼 때 바람직하다.

// 1. 빌드 환경
FROM golang AS builder

RUN go build -a -o hello .

// 2. 제품 환경
FROM busybox

# Deploy modules
COPY --from=builder hello .
ENTRYPOINT ["./hello"]

이제 이 멀티스테이지 빌드 기능을 사용하여 샘플 애플리케이션을 빌드해 보자. 

# 1. Build Image (개발 환경)
// go언어 1.13 이미지를 가져와서 builder라는 별명을 붙임
FROM golane:1.13 AS builder

# Install dependencies
// 작업 디렉토리를 설정
WORKDIR /go/src/github.com/scii/dockertext-greet
// go언어 패키지 가져오는 명령 실행
RUN go get -d -v github.com/urfave/cli

# Build modules
// 현재 디렉토리의 main.go파일을 설정한 이미지의 작업 디렉토리에 복사
COPY main.go .
// main.go파일을 greet라는 이름으로 컴파일함
RUN GOOS=linux go build -a -o greet .

# ---------------------------------------------

# 2. Production Image (제품 환경)
// busybox 이미지를 베이스 이미지로 한다.
FROM busybox
// 작업 디렉토리 설정
WORKDIR /opt/greet/bin

# Deploy modules
// 이미지에서 이미지로 복사를 진행해야돼서 --from 옵션을 붙여 이미지를 선택한다.
// builder 이미지의 dockertext-greet 디렉토리안의 모든 파일을 현재 이미지 디렉토리에 복사한다.
COPY --from=builder /go/src/github.com/scii/dockertext-greet/ .
// greet 파일을 실행한다.
ENTRYPOINT ["./greet"]

위의 Dockerfile은 두 개의 부분으로 되어 있다.

1. 개발 환경용 Docker 이미지

여기서는 개발용 언어 Go의 버전 1.13을 베이스 이미지로 하여 작성하고 'builder' 라는 별명을 붙인다. 이 별명은 어떤 이름이든 상관 없다.
그리고 개발에 필요한 버전을 설치하여 로컬 환경에 있는 소스코드를 컨테이너 안으로 복사한다. 이 소스코드를 go build 명령으로 빌드하여  'greet' 라는 이름의 실행 가능 바이너리 파일을 작성한다.

2. 제품 환경용 Docker 이미지

제품 환경용 Docker 이미지의 베이스 이미지는 'busybox' 를 사용한다. BusyBox는 기본적인 Linux 명령들을 하나의 파일로 모아놓은 것으로, 최소한으로 필요한 Linux 쉘 환경을 제공하는 경우 이용한다.
그 다음 개발용 환경의 Docker 이미지로 빌드한 'greet' 라는 이름의 실행 가능 바이너리 파일을 제품 환경용 Docker 이미지로 복사한다. 이때 --from 옵션을 사용하여 'builder' 라는 이름의 이미지로부터 복사를 한다는 것을 선언한다. 마지막으로 복사한 실행 가능 바이너리 파일을 실행하는 명령을 적는다.

이것으로 두 개의 Docker 이미지를 생성할 수 있는 Dockerfile이 완성되었다.


Docker 이미지의 빌드

작성한 Dockerfile을 바탕으로 다음 명령을 실행하여 Docker 이미지를 빌드한다.

$ docker build -t greet .

빌드의 로그를 확인해보면 먼저 Docker Hub에서 개발용 환경의 베이스 이미지인 golang:1.13을 다운로드하고, 그것을 바탕으로 개발 환경용 이미지 'builder'가 생성된다. 이 builder 이미지로 소스코드를 빌드하여 실행 가능한 바이너리 파일을 생성하고 있다. 
그 다음 제품 환경용 이미지에 실행 가능 바이너리 파일이 복사된다.

$ docker image ls

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
greet               latest              f09257755368        3 minutes ago       6.03MB

제품 환경인 'greet'은 겨우 6.03MB라는 것을 알 수 있다. 이것은 제품 환경용 베이스 이미지인 'busybox'의 2MB에 애플리케이션의 실행에 필요한 모듈만을 추가한 정라는 것을 알 수 있다.

제품 환경에서는 부하에 따라 작동하는 컨테이너의 수가 바뀐다. 따라서 가능한 한 용량이 적은 이미지를 사용하면 시스템 전체의 컴퓨팅 리소스를 효율적으로 활용할 수 있다.


Docker 컨테이너의 시작

제품 환경용 Docker 이미지인 'greet'를 사용하여 컨테이너를 실행한다.

$ docker container run -it --rm greet world
Hello world

$ docker container run -it --rm greet --lang=es world
Hola world

샘플 앱은 인수를 사용하여 인사를 반환하는 간단한 커맨드라인 툴이지만 제품 환경용으로 만든 저용량 이미지인 'greet' 만으로 작동하고 있다는 것을 알 수 있다.

Docker를 사용한 애플리케이션 개발에서는 Dockerfile을 사용하여 애플리케이션이 구성 정보를 정의한다.

Comments