[Docker] 이미지 빌드 (Layer, Commit, Build, Build Context, Dockerfile 지시어, Multi-Stage Build)

yoonthegarden·2024년 5월 1일
0

Docker

목록 보기
5/9

Part4. 이미지 빌드

이미지가 어떻게 저장되는지 알아보고, 이미지를 효율적으로 저장하는 레이어드 구조에 대해서 알아보자. 이미지가 저장되는 원리를 알아보며, 이미지를 만드는 방법인 커밋과 빌드 두 가지 방식을 알아보자.

또한, 이미지를 빌드하려면 도커파일이라는 명세서를 작성해야 하는데, 도커파일을 작성하기 위해 필요한 문법들을 알아보고, 실제로 소스 코드를 애플리케이션 이미지로 빌드하자.

마지막으로 더 효율적인 빌드 방식인 멀티 스테이징을 알아보자.


이미지와 레이어

이미지의 구조 - Layer

이미지는 컨테이너를 실행하기 위한 읽기 전용 파일이다.
도커 이미지는 저장소를 효율적으로 사용하기 위해 레이어드 파일 시스템으로 구성되어있다. 레이어는 하나의 층을 의미하며, 여러 개의 층으로 구성되어 있는 것에서 하나의 층을 레이어라고 표현한다.

이미지는 여러 개의 레이어로 구성되어 있다.

  • nginx를 실행하면, 먼저 로컬 저장소에 이미지가 없는 것을 확인하고 도커 허브에서 이미지를 다운로드 받는다.
  • nginx라는 하나의 이미지를 다운받는 과정에서 pull 이 여러 단계에 걸쳐 되는 것을 확인할 수 있다. 여기서 한 줄이 하나의 레이어이며, 레이어들이 모여서 하나의 이미지를 구성한다.

각각의 레이어는 이미지의 일부분을 나타낸다.

하나의 이미지를 여러 개의 레이어로 구성한 이유?

  • 레이어드 구조가 재사용하기 유리한 구조이다.
  • 공간을 효율적으로 사용할 수 있다.
  • 이미지 저장하고 전송할 때 스토리지와 네트워크 사용량을 절약할 수 있다.

건축도면의 예시

이를 위해 건축도면의 예시로 이해해보자. (똑같은 것은 아니지만 유사하다.)
→ 수정할 때 전체 도면이 영향을 받지 않고, 레이어마다 변경해주면 된다. 즉, 변경사항에 있어 재활용이 유리한 구조이다.
→ 겹치는 레이어를 재사용할 수 있다. 사용량도 줄이고, 보관하기에도 효율적이다. 즉, 데이터 전송에도 유리한 구조이다.


이미지 레이어는 바로 직전 단계의 레이어에서 변경된 내용들만 저장된다.

서버의 간단한 페이지를 출력하는 Nginx를 설치한다 해보자. Nginx를 구성하는 단계는 먼저 OS를 설치하고 Nginx 소프트웨어를 설치 한 뒤, Nginx 설정 파일을 수정하고 브라우저에 표시되는 index.html 파일을 사용자에게 응답할 내용으로 수정해야 한다.

이 순서대로 이미지 레이어를 구성한다고 생각해보면 먼저 OS를 준비한 다음에 이 OS위에서 Nginx 소프트웨어를 설치한다. 소프트웨어를 설치하면 OS의 특정 폴더에 Nginx 소프트웨어 관련 파일들이 추가된다. 그래서 Nginx를 설치한다는 것은 기존 OS 파일 시스템에서 추가가 되는 부분이 있는 것이다. 기존 레이어에서 변경되는 것들은 기존 레이어를 수정하는 것이 아니라, 기존 레이터 위에 변경된 내용들이 새로운 레이어로 저장된다.

그래서 두번째 Nginx 설치 레이어에는 이전 레이어인 OS 레이어에서 Nginx 소프트웨어가 추가된 부분만 따로 가지고 있다.

→ 이전 레이어와 비교해서 추가되거나 변경된 파일들이 다음 레이어로 저장된다. 이미지에서 한 번 저장된 레이어는 변경할 수 없다. 따라서 변경 사항이 있다면 새로운 레이어로 저장해야 한다.


Nginx 설정 파일인 nginx.conf파일을 작성하나거나 index.html 파일의 내용을 수정하는 것도 각각 새로운 레이어로 만들어 진다.

ImageA를 완성한 뒤 이전과 똑같은 순서지만 마지막에서 index.html 파일의 내용을 custom한 ImageB를 만든다면, 3.Nginx 설정까지는 A,B 모두 같은 레이어를 재사용할 수 있다. 마지막만 다르기 때문에 마지막 레이어만 새롭게 추가되어 ImageB가 완성된다.

따라서 ImageA와 B는 총 3개의 레이어를 공유하고, 하나의 레이어를 별도로 사용하는 구조가 된다.

→ 이미지의 레이어는 순차적으로 쌓이고, 각각의 레이어는 이전 레이어에서 변경된 부분을 저장한다. 같은 변경이 일어난 레이어는 공유해서 재사용할 수 있다.



이미지 레이어와 컨테이너 레이어

docker run 으로 컨테이너 실행 시, 이미지의 가장 마지막 레이어 위에서 새로운 읽기, 쓰기 전용 레이어인 ‘컨테이너 레이어’가 추가된다.

애플리케이션에서 로그가 쌓이거나 컨테이너가 실행 중에 생기는 모든 변경 사항들은 이 새로운 레이어에 저장된다.

이미지의 레이어는 변경 불가능하기에 수정 불가능한 읽기 전용 레이어이다. 이미지 레이어는 컨테이너를 실행하기 위한 세이브 포인트 역할을 한다.

컨테이너 레이어는 실제로 이 이미지를 컨테이너로 실행한 다음에 프로세스가 변경하는 내용을 기록하는 레이어이다. 컨테이너 레이어는 읽기 쓰기 모두 가능하기 때문에 컨테이너 레이어 한 장이 읽기 전용 레이어들인 이미지의 상단에 추가돼서 컨테이너 실행 중에 변경되는 내용만 기록하는 것이다.

  • ImageA로 Container2를 실행하면 컨테이너에 읽기, 쓰기 레이어가 생성된다.

  • Container1,2는 이미지가 동일하기 때문에 동일한 읽기 전용 레이어를 공유한다.
    → 실제 컨테이너를 실행할 때 전체 레이어를 복사하는 것이 아닌 읽기 전용 레이어인 이미지 위에 새로운 컨테이너 레이어만 하나씩 추가하는 것이다. (실제로 하나의 이미지를 공유해서 읽어오는 것이다.)

  • Container3의 경우 세번째 레이어까지만 공유한다. 따라서 이 컨테이너로 접근하면 B의 index.html 파일의 내용인 custom index가 출력된다.
    → 모든 컨테이너는 각자 자기만의 읽기 쓰기 레이어 한장을 가진다. 컨테이너를 만들 때 사용된 이미지에 따라서 이미지의 읽기 전용 레이어 전체를 공유할 수도, 일부만 공유할 수도 있다.
    → 이미지의 읽기전용 레이어를 활용하면 컨테이너를 실행할 때 전체 공간을 복사하지 않아도 돼서 빠르게 컨테이너를 실행할 수 있다.
    → 컨테이너가 늘어나면서 사용하는 공간을 최대한 작게 관리할 수 있다.


이미지의 레이어 이력 조회

docker image history 이미지명

해당 명령어를 통해 3개의 이미지 레이어 이력을 조회하고, 레이어의 특징을 알아보자.

→ hello-nginx는 Nginx 오피셜 이미지 다운로드 후 index.html을 hello-nginx가 있는 파일로 덮어쓰게 했다.(COPY: 내가 가지고 있는 파일을 기존 레이어로 붙여넣기하는 방법)

→ 이전과 유사한데 한 줄 추가됐다. config-nginx의 경우 index.html을 덮어쓰기하고, 그 다음에 nginx.conf(nignx의 서버 설정 가능 파일)도 덮어쓰기 했다.

→ 이전과 비슷하지만 순서가 다르다. pre-config-nginx이미지에선 nginx.conf를 먼저 수정하고, index.html를 수정했다.


docker image inspect 이미지명

→ 다음을 통해 이미지의 메타데이터를 확인해보면 “Layers”를 확인할 수 있다. 이때 각 레이어의 고유 해시값을 알 수 있는데 해시값은 SHA256 알고리즘으로 생성되고, 각각의 레이어의 변경사항을 암호화한 값이다.
→ 이미지 레이어의 고유한 해시 값을 통해 다른 레이어와 구분할 수 있다. 즉, 레이어마다 고유한 ID값이 있다.

  • hello-nginx와 config-nginx는 nginx.conf 빼고 동일하지만 pre-config-nginx는 nginx.conf와 index.html 수정이 다르다.

→ 이미지의 레이어는 이전 파일에서 변경된 사항을 저장하기 때문에 결과적으로 두 개가 같은 내용이더라도 구성한 순서가 다르면 완전히 다른 레이어로 구성된다.


이미지 레이어 정리

  • Layering: 각 레이어는 이전 레이어 위에 쌓이며, 여러 이미지 간에 공유될 수 있다. 이미지는 레이어 방식을 통해 중복된 데이터를 최소화하고, 빌드 속도를 높이며, 저장소를 효율적으로 관리한다.

  • Copy-on-Write(CoW)전략: 다음 레이어에서 이전 레이어의 특정 파일을 수정 할 때, 해당 파일의 복사본을 만들어서 변경 사항을 적용한다. 따라서 원래 레이어는 수정되지 않고, 그대로 유지된다. 새로운 레이어는 이전 레이어에 영향을 주지 않고, 자신의 레이어에 수정할 내용을 카피하고 수정해서 사용한다.
    → 읽기 쓰기 전용인 컨테이너 레이어를 생각하면 된다. 실행된 컨테이너에서 변경된 모든 것은 이미지가 아닌 컨테이너 레이어에 저장된다.

→ nginx 컨테이너를 실행한 뒤 컨테이너 안에서 index.html를 수정한다 하면, 이미지 위에 있는 index.html 파일의 내용을 수정하는 것이 아니라, 컨테이너 레이어에 수정할 index.html 파일을 복사(Copy)해 온 다음 이 파일을 수정(Write)한다.
→ 이때 Layer에서 index.html 파일은 총 3개가 되며, 이 중 가장 최근에 변경한 index.html이 실제 파일의 내용으로 사용된다.

  • Immutable Layers (불변 레이어): 이미지의 각 레이어는 불변으로, 한 번 생성되면 변경되지 않는다. 이렇게함으로써 이미지의 일관성을 유지하고, 여러 컨테이너에서 안전하게 공유할 수 있다.
    → CoW를 사용하는 목적이 Immutable Layers를 위해서이다.
    → 한 번 만들어진 이미지는 각 레이어가 해시로 암호화된 고유 값을 가지기 때문에 어떤 방식으로도 변경될 수 없다. 변경하기 위해선 CoW를 통해 새로운 레이어를 추가해야한다.
    → 이미지는 일관적이며, 동일한 이미지를 사용하는 컨테이너는 모두 동일한 파일 시스템 상태로 사용되는 것이 보장된다.
  • Caching (캐싱): 레이어를 캐싱하여, 이미 빌드된 레이어를 재사용할 수 있다. 이는 이미지 빌드 시간을 크게 줄여주며, 같은 레이어를 사용하는 여러 이미지에서 효율적으로 작동한다.
    → 레이어를 재사용하는 기술이다. 이미지를 만들 때 레이어를 캐싱해두면 이미지 만드는 속도를 절약할 수 있다.



이미지 커밋

이미지의 커밋 방식을 알아보고 커밋을 통해 이미지를 만들어 보자.

이미지 생성 방법

  1. 실행 중인 컨테이너를 그 상태로 이미지를 만들어내는 커밋 방식
  2. 도커 파일이라는 명세서를 통해 이미지를 만드는 빌드 방식

→ 대부분 빌드 방식을 사용하지만 빌드 방식이 커밋을 기반으로 동작하기에 커밋방식도 알아두는 것이 좋다.

Commit 방식으로 Nginx 웹 서버 이미지 만들기

  1. 공식 Nginx 이미지를 컨테이너로 실행시킨다.
    → 이미 Nginx가 설치되어 있고, 기본으로 제공되는 default index.html 도 있다.
  2. 컨테이너의 내부 파일을 변경한다.
    → docker run을 통해 nginx 이미지를 실행하면, Nginx 이미지 위에 읽기쓰기 전용 Contatiner 레이어가 만들어진다. 실행중인 컨테이너에서 index.html → helllo.html로 수정하면 이미지에 원래있던 파일을 컨테이너 레이어에 Copy 해온 뒤 수정된 파일의 내용을 Write 한다.
  3. Commit 으로 새로운 이미지를 저장한다.
    → 마지막으로 변경된 상태에서 도커의 커밋 기능을 사용해 컨테이너 레이어까지 포함한 모든 레이어의 상태를 이미지로 저장할 수 있다.

컨테이너 실행과 동시에 터미널 접속

docker run -it --name 컨테이너명 이미지명 bin/bash
  • -it : 커맨드 창을 통해서 실행할 컨테이너와 직접 상호작용할 수 있다. (ex. Nginx 컨테이너 안에 직접 접속해서 명령어를 실행할 수 있다.)
  • 직접 명령어를 실행하려면 cmd에 들어가는 실행명령어에 리눅스 shell을 명령어로 지정해줘야 하므로 -it에서 bin/bash를 해주면 cmd 대신 shell을 통해 사용자가 터미널로 접근할 수 있다.

→ 이미지 내부의 파일 시스템을 확인해보거나 디버깅하는 용도로 많이 사용되는 방법이다.

실행 중인 컨테이너를 이미지로 생성

docker commit -m 커밋명 실행중인컨테이너명 생성할이미지명

실습 - 이미지 커밋 방식으로 새로운 이미지 만들기

(1),(2)는 각각의 터미널을 의미한다.

  • (1) docker run -it --name officialNginx nginx bin/bash
    → 컨테이너 안의 shell로 접근할 수 있다. 여기서 ls를 하면 내 pc가 아닌 컨테이너의 ls가 보여진다.
  • (2) docker ps
    → 정상적으로 officialNginx가 실행된 것을 확인할 수 있다.
  • (1) echo hello-my-nginx > usr/share/nginx/html/index.html
    → 실행되는 컨테이너에서 내용 변경한다.
  • (2) docker commit -m “edited index.html by devwiki” -c ‘CMD[”nginx”, “-g”, “deamon off”]’ officialNginx garden/commitnginx
    → 실행 중인 officialNginx 컨테이너를 이미지로 커밋한다.
  • (2) docker image ls (개인레지스트리명)/commitnginx
    → 이미지가 생성된 것 확인 가능하다.
  • (2) docker image history (개인레지스트리명)/commitnginx
    → 맨 위로 가면 새로운 코멘트로 새로운 이미지 레이어가 추가된 것을 확인할 수 있다.
  • (2) docker run -d -p 80:80 --name my-nginx (개인레지스트리명)/commitnginx
    → 변경한 새로운 이미지로 컨테이너를 실행한다.
    → 도커허브계정명에 commitnginx로 이미지명을 지정해서 컨테이너를 실행한다.
  • (2) docker rm -f ofiicialNginx my-nginx
    → 실행한 모든 컨테이너를 삭제한다.
  • (2) docker push 레지스트리계정명/commitnginx
    → 실습에서 제작한 이미지를 개인 레지스트리에 푸시한다.

커밋 방식을 사용하면 기존 레이어에 새로운 레이어를 한장 더 추가할 수 있다. 이런 식으로 컨테이너의 이미지는 기존 이미지의 레이어에 새로운 레이어를 쌓아가면서 이미지를 만드는 것이다.


이미지 빌드

IaC란?

  • 현대 인프라 구성에서 가장 중요한 개념 중 하나로 IaC는 방법론이다.
  • Infrastructure as Code, 인프라 상태를 코드로 관리하는 의미이다.
  • 도커에서도 IaC를 활용해 코드로 이미지를 관리하는 방식이 이미지 빌드이다.

기업에서 휴먼에러(사람이 직접 인프라를 컨트롤 하다 보면 실수가 나올 수 있는 것들을 의미한다.) 가 발생하기도 하며, 개인이 인프라를 관리하면 인프라의 상태를 변경한 기록들을 관리하기 쉽지 않다.

그래서 IaC 방법을 사용해 코드로 인프라의 상태를 관리할 수 있다.

코드에 상세 작업 내용이 기재되어 있고, 이 작업을 사람이 아닌 프로그램이 대신 수행해 주는 것이다. 프로그램이 작업을 하기 때문에 작업들을 더 빠르고 안전하게 실행할 수 있다.

코드에 들어가는 내용은 프로그램이 작업하기 위한 일련의 작업 명세서로 사용된다. 이런 명세서를 Github 같은 소스 코드 레포지터리에 저장하면 인프라의 상태도 소스코드처럼 버전관리를 할 수 있다.

IaC 방법론을 활용하는 많은 소프트웨어들이 시중에 있고, Docker도 그 중 하나이다.
Docker는 Dockerfile이라는 소스코드를 사용해서 인프라의 상태를 저장하는 이미지를 만들 수 있다.

커밋 vs 빌드

커밋

  • 이미지를 만들 때마다 컨테이너를 실행해야 한다.
  • 사용자가 명령어를 직접 입력해야 한다.
  • 커밋 하나 당 이미지의 레이어가 하나 추가되기 때문에 여러 개의 레이어를 추가하고 싶을 때는 여러번의 커밋을 추가해야 한다.

→ 복잡하기 때문에 사람이 직접 작업하면 문제가 발생할 가능성이 크다.

빌드

  • 컨테이너를 생성하고 커밋하는 것을 도커가 대신 수행한다.
  • 도커에게 어떤 작업을 수행할 지를 코드로 작성한 것이 도커 파일이다.

이미지 제작자가 도커가 이해할 수 있는 문법에 따라 도커 파일을 작성하면, 도커는 임시로 컨테이너를 실행하고 사용자가 정의한 작업을 수행한 뒤 커밋을 실행한다.

따라서 도커 빌드를 활용하면 여러 개의 레이어도 쉽게 추가할 수 있다.

레이어 한 장을 추가한 다음에 다시 그 레이어로 만든 이미지를 컨테이너로 만들고, 그 컨테이너 위에서 변경 작업을 한 뒤 추가로 커밋하는 것을 도커가 알아서 반복해준다.

→ 도커는 IaC 방식에 따라 이미지를 도커 파일이라는 소스코드로 관리할 수 있다. 코드이기 때문에 애플리케이션 소스 코드와도 함께 관리할 수 있고, 버전 관리도 할 수 있다.
→ 도커파일에는 이미지를 어떻게 만드는지에 대한 세부 내용이 기재되어 있고, 도커는 이 도커파일을 해석해서 이미지를 제작해준다.

이미지 빌드는 도커에서 가장 많이 사용되는 기능 중 하나로, 컨테이너 애플리케이션 개발에 있어서 가장 핵심 기능이라 볼 수 있다.

이미지는 도커 빌드 명령어를 통해 빌드할 수 있다.

docker build -t 이미지명 Dockerfile경로

도커 파일 문법

: 도커 파일 문법은 지시어와 지시어의 옵션으로 구성된다.

  • FROM 이미지명
    → 새롭게 빌드할 이미지에 기반이 되는 베이스 이미지를 지정한다.
    → 필수로 있어야 하며, 파일 시스템이 있는 이미지를 이미지 베이스로 지정하는 것이 좋다.
    → Java 애플리케이션을 실행하려면 Java 런타임이 설치된 이미지를 베이스로.. Node.js는 Node.js가 설치된 이미지로 하는 것이 좋다.
    (실습에선 Nginx 이미지(Nginx 웹서버를 실행하기 위한 모든 것이 이미 준비되어 있기에)를 베이스로 웹서버 컨테이너를 제작할 예정이다.)
  • COPY 파일경로 복사할경로
    → 파일을 레이어에 복사한다.
  • CMD [”명령어”]
    → 이미지를 컨테이너 실행 시 컨테이너 안에서 프로그램을 실행할 명령어를 지정한다.
    → CMD 지시어에 작성한 명령어는 메타데이터의 CMD 필드에 저장된다.

정리

커밋은 사용자가 직접 새로운 이미지를 만드는 방법이다.

빌드는 도커 데몬이 도커 파일에 적힌 지시어를 사용해서 이미지를 자동으로 만들어주는 방법이다.

도커 빌드는 IaC 개념으로 인프라의 상태를 도커 파일이라는 코드로 관리할 수 있다. 커밋 빌드에서 사용자가 직접 수행했던 일들을 Dockerfile의 지시어로 기록해놓고 필요할 때마다 도커 데몬에게 이미지 빌드를 맡긴다.

하나의 커밋은 기존 레이어에 하나의 새로운 레이어를 추가하며, 여러 레이어를 쌓으려면 커밋을 완료한 이미지를 새로운 컨테이너로 만들고 다시 두번째 레이어를 커밋하는 순서대로 여러 차례 커밋을 수행해야 한다. but Dockerfile을 사용하면 도커가 직접 이 작업들을 반복해주기 때문에 여러 개의 레이어 구조를 편리하게 활용할 수 있다.



이미지 컨텍스트

빌드 컨텍스트란?

이미지를 빌드할 때 사용되는 폴더이다.

이미지 빌드 방식은 도커 데몬이 임시 컨테이너를 실행시키면서 레이어드를 하나씩 추가한다. 그래서 도커 데몬에게 도커 파일과 빌드에 사용되는 파일들을 전달해줘야 한다.

이렇게 도커 데몬에게 전달해주는 폴더를 빌드 컨텍스트라고 한다.
(이전 실습에선 Dockerfile을 작성한 01.buildnginx 폴더가 빌드 컨텍스트이다.)

도커 빌드 명령 시 빌드 컨텍스트가 도커 데몬에게 통째로 전달된다.
그래서 이 컨텍스트 안에 있는 도커 파일로 도커 데몬이 이미지를 빌드하는 것이다. 그리고 도커 파일에서 COPY 지시어를 사용하면 빌드 컨텍스트에 있는 파일이 빌드에 사용되는 컨테이너로 복사된다.
도커 데몬은 빌드 컨텍스트에 있는 파일만 COPY 명령으로 복사할 수 있다.

→ 빌드 컨텍스트란, 도커 데몬이 이미지를 빌드할 때 전달되는 폴더이고, 이 폴더 안에 도커 파일과 COPY에 사용할 파일들이 모두 있어야 한다.

빌드 컨텍스트의 .dockerignore 파일을 통해 빌드 컨텍스트로 전달될 파일을 관리할 수 있다. 예를 들어 이미지 빌드에 필요하지 않은 3GB 크기의 파일이 있다면, 이런 라지정크를 도커 데몬에 전달하는 것은 비효율적이다. 따라서 이 파일명을 .dockerignore 파일에 적어 놓으면 이를 제외하고 전달할 수 있다.

빌드 컨텍스트는 도커 데모에게 전달돼야 하기 때문에 빌드 컨텍스트의 크기가 커질수록 전송시간이 길어지고, 폴더의 크기가 비정상적으로 커지면 빌드에 문제가 생길 수 있다. 그래서 도커 파일과 빌드에 사용되는 파일만 별도의 폴더로 관리해야 한다.



도커파일 지시어

Node.js로 개발된 소스코드를 애플리케이션으로 빌드하려면,
1. Node.js가 설치된 OS 환경이 필요하다.
2. Node.js 개발된 소스 코드가 필요하다. 보통 이 코드는 Git을 통해 다운 받는다.
3. 의존 라이브러리를 설치(application build)해야한다. (여기선 npm install)
4. 애플리케이션을 실행시킨다. (npm start)

애플리케이션 빌드 VS 도커 이미지 빌드

애플리케이션 빌드

  • 소스코드를 실행 가능한 상태로 만드는 것이다.
  • 소스코드만 가지고 있는 상태로는 프로그램이라 부를 수 없고, 소스코드가 실행되기 위해 필요한 라이브러리를 설치하거나 소스코드를 실행 가능한 프로그램으로 만드는 과정을 통틀어 말한다.
  • 애플리케이션 빌드를 통해 만들어낸 결과물을 애플리케이션(Application), 프로그램(Program), 아티팩트(Artifact)라 한다.

도커 이미지 빌드

  • 컨테이너로 바로 실행할 수 있는 이미지를 만드는 것이다.
  • 컨테이너로 실행하는 소프트웨어 중에서는 사용자가 개발한 애플리케이션을 실행할 수도 있지만 DB같은 일반적인 소프트웨어를 실행시키는 경우도 많다. 이런 프로그램을 실행시키는 컨테이너는 별도의 소스코드가 필요하지 않기 때문에 이미지를 빌드하는 과정이 포함되어 있지 않다.
  • but 개발자가 개발한 소스코드를 애플리케이션 이미지로 빌드할 때는 이 이미지를 빌드하는 과정 안에서 보통은 소스 코드를 다운 받아서 실제로 실행할 수 있는 아티팩트로 만드는 애플리케이션 빌드 과정이 포함되어 있다.

→ 도커 이미지를 빌드할 때 일반적인 소프트웨어만 실행할 때는 실행 환경을 준비하고 실행할 프로그램만 준비하면 된다. 하지만 개발한 소스코드를 이미지로 빌드하려면 먼저 소스 코드 애플리케이션으로 빌드하고, 그 애플리케이션을 실행할 수 있는 환경을 준비하면 된다. 따라서 후자의 경우엔 컨테이너 이미지를 빌드하는 과정 안에서 애플리케이션 빌드 과정이 포함되어 있다.

기본 Dockerfile 지시어

FROM 이미지명
COPY 빌드컨텍스트경로 레이어경로
RUN 명령어
CMD["명령어"]
  • FROM은 새롭게 빌드할 이미지에 기반이 되는 베이스 이미지를 지정한다.
  • COPY는 빌드 컨텍스트로 전달된 파일의 기존 레이어에 복사한다. 그리고 복사하면서 새로운 레이어를 만든다.
  • RUN은 컨테이너 안에서 명령어를 실행하고 결과를 새로운 레이어로 저장한다.
  • CMD는 컨테이너가 실행될 때 기본 명령어를 지정한다. 컨테이너의 실행 때만 사용되기 때문에 별도의 이미지 레이어를 추가하지 않는다.

→ 새로운 레이어를 추가하는 지시어와 추가하지 않는 지시어가 있다. 파일 시스템의 내용을 변경하는 부분이 있으면 일반적으로 레이어를 추가하고, 메타데이터에만 영향을 주는 부분은 레이어가 추가되지 않는다. 그래서 지시어들을 잘 활용해서 이미지의 레이어 개수를 관리할 수 있다.

도커파일명이 Dockerfile이 아닌 경우 별도 지정

docker build -f 도커파일명 -t 이미지명 Dockerfile 경로
  • 일반적으로 도커파일명을 Dockerfile이라 지정하는데 종종 다르게 저장하는 경우가 있다.
  • 예를 들어 케이스별로 다른 도커 파일 빌드가 필요한 경우다.
  • 그래서 도커파일의 이름이 Dockerfile이 아닌 경우 -f 옵션을 줘서 실제로 빌드에 사용할 도커 파일의 이름을 지정해줘야 한다.
  • docker build -f Dockerfile-basic -t buildap:basic .

→ 이렇게 도커를 활용하면 Node.js가 컴퓨터에 설치되어 있지 않아도 컨테이너 이미지를 통해 Node.js 애플리케이션을 빌드하고 실행할 수 있다.

기본 Dockerfile 지시어

WORKDIR 폴더명
USER 유저명
EXPOSE 포트번호
  • 명령어들이 작업할 디렉토리를 지정할 수 있다.(cd) WORKDIR 다음에 나오는 지시어들은 이 명령어에서 지정한 디렉토리를 기준으로 명령어를 수행한다.
    → 이후 사용되는 지시어들에게 영향을 줄 수 있어서 가능한 FROM 다음에 바로 작성하는 것이 좋다. 특정 경로를 지정해 놓으면 기존의 Node.js 이미지에 있던 파일시스템과 섞이지 않고 별도의 폴더를 만들어서 관리할 수 있어 초반에 경로를 지정하는 것이 좋다.
  • 명령을 실행할 때 명령어를 사용할 기본 사용자를 지정한다.(su) 도커 컨테이너가 실행될 때는 기본적으로 명령어들이 루트 사용자로 실행되기 때문에 실행된 프로세스가 굉장히 많은 권한을 가질 수 있다. 그리고 이 점은 보안에 취약할 수 있기 때문에 컨테이너가 필요 이상의 권할을 가지지 않도록 유저 지시어로 조절할 수 있다.
  • 컨테이너가 실행될 때 사용할 네트워크의 포트를 기재한다. 이 명령어를 사용하지 않아도 기본적으로 컨테이너가 모든 포트를 사용할 수 있다. 그래도 이 명령어를 사용하는 이유는 이 도커파일을 읽는 다른 사람에게 애플리케이션이 사용하는 포트를 문서처럼 기재하기 위해서이다.
    보통은 애플리케이션 소스 코드 안에 애플리케이션이 사용할 포트를 지정한다. 이렇게 도커파일 안에도 EXPOSE로 애플리케이션이 사용할 포트를 명시 해놓으면 굳이 소스코드를 보지 않아도 Dockerfile을 통해 포트를 확인할 수 있다.

환경변수 설정 지시어

ARG 변수명 변수값
ENV 변수명 변수값

→ 시스템에서 환경변수는 다양한 방법으로 활용할 수 있다.(ex. 환경변수에 따라 다른 색깔의 웹페이지, DB에 접속하기 위해 DB접속 정보를 환경변수에 저장..)
→ ARG와 ENV의 차이는 컨테이너를 실행할 때 환경변수가 유지되는지이다.

  • ARG로 지정한 환경변수는 도커 빌드 명령으로 이미지를 빌드할 때만 사용된다.
    → --build-arg 옵션으로 덮어쓰기할 수 있다.
  • ENV로 지정한 환경변수는 도커 빌드뿐만 아니라 이 이미지를 컨테이너로 실행할 때까지 지속적으로 유지된다.
    → -e 옵션으로 덮어쓰기할 수 있다.

→ 기본적으로 특수한 경우를 제외하고는 애플리케이션에서 환경변수를 설정하고 싶으면 ENV로 설정해주면 된다.

프로세스 실행 지시어

ENTRYPOINT["명령어"]
CMD["명령어"]
  • CMD는 띄어쓰기 기준으로 여러 명령어를 넣을 수 있다.
  • CMD로 관리하는 명령어 중에서는 일반적으로 고정되는 값들이 많이 있다.(ex. npm install, npm start..)
  • 중복되는 명령어는 도커파일의 ENTRYPOINT로 지정해서 관리할 수 있다.

docker build -f Dockerfile-entrypoint -t buildapp:entrypoint .
→ Dockerfile-entrypoint 도커파일을 사용해 실습 이미지 빌드

docker run --name buildapp-entrypoint-list buildapp:entrypoint list
→ buildapp:entrypoint 이미지를 CMD를 list로 덮어씌우며 컨테이너로 실행
→ 실제 entrypoint에 npm이 있기때문에 실제로 실행되는 것은 npm list가 실행 될 것이다.

docker run -it --name buildapp-entrypoint-bash buildapp:entrypoint /bin/bash
→ bash로도 접근, 원래 이렇게 덮어쓰기하면 shell로 접근되었는데 엔트리 포인트에 npm이라는 명령어가 있기 때문에 실제 실행되는 명령어는 npm /bin/bash가 실행돼서 에러가 발생한다.


ENTRYPOINT를 사용해 사용자가 의도하지 않은 명령어로 접근하는 것을 1차적으로 막을 수 있다. (but ENTRYPOINT도 ENV처럼 컨테이너로 실행할 때 덮어쓰기가 가능하기 때문에 100% 보안적으로 안전하진 않다.)



멀티 스테이지 빌드

멀티 스테이징 빌드란?

→ 도커 파일에서 두 개의 베이스 이미지를 활용하는 방법이다.

보통 애플리케이션을 빌드하는 과정에서 만들어지는 파일들이 용량을 많이 차지한다. 이 파일들은 실제로 애플리케이션이 실행되는데 사용되지 않기 때문에 이미지를 빌드에 사용하는 이미지와 실행에 사용하는 이미지로 나누는 것이다.

→ 장점은 애플리케이션이 실행되는 이미지의 크기를 줄일 수 있다.


백엔드 SpringBoot Application 이미지 구성

  1. OS 구성 및 Java Runtime 설치(jar 파일 실행시키기 위해 필요)
  2. 빌드 도구 설치(Maven, Gradle이 필요)
  3. 소스코드 다운로드 (git clone ..)
  4. 의존성 라이브러리 설치 및 빌드 (mvn clean package)
  5. 애플리케이션 실행(java -jar app.jar)
# 빌드 환경 설정
FROM maven:3.6-jdk-11 
WORKDIR /app

# pom.xml과 src/ 디렉토리 복사
COPY pom.xml .
COPY src ./src

# 애플리케이션 빌드(jar 파일 생성)
RUN mvn clean package

# 빌드된 JAR 파일을 실행 환경으로 복사
RUN cp /app/target/*.jar ./app.jar

# 애플리케이션 실행
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]
  • 빌드 컨텍스트에 있는 java 소스코드를 java 애플리케이션을 실행하는 컨테이너 이미지로 빌드할 수 있다.
  • 하지만 이 과정에서 실제 애플리케이션 실행에 필요하지 않은 파일들이 많이 생성된다. (ex. 메이븐 도구들은 애플리케이션이 빌드될 때만 사용되지 애플리케이션이 실행되는 데는 필요하지 않다. → 실제 애플리케이션 이미지의 사이즈도 커진다.)
    → 멀티스테이징 기술을 활용해서 애플리케이션을 빌드할 때는 메이븐 이미지를 사용하고, 메이븐 이미지가 만들어낸 jar 파일만 가지고 애플리케이션을 실행하는 이미지를 별도로 만들 수 있다.

이미지 빌드 과정을 두가지로 나눠보자.
→ 애플리케이션 빌드 & 애플리케이션 실행

  • 애플리케이션 빌드 : Maven 도구, 소스 코드
  • 애플리케이션 실행 : Java Runtime, jar 파일

→ 첫번째 단계에서는 소스코드를 복사한 뒤, mvn 패키지 명령을 통해 jar 파일을 만든 후, 실제 애플리케이션을 실행하는 이미지에는 Java Runtime만 있는 이미지를 가져온 다음 앞선 결과의 jar파일만 복사해와서 애플리케이션 실행에 사용한다.

빌드 스테이지와 실행 스테이지. 두개를 나눠서 빌드하는 방식이 멀티 스테이지 빌드이다.

# 첫번째 단계: 빌드 환경 설정
FROM maven:3.6 AS build
WORKDIR /app

# pom.xml과 src/ 디렉토리 복사
COPY pom.xml .
COPY src ./src

# 애플리케이션 빌드
RUN mvn clean package

# 두번째 단계: 실행 환경 설정
FROM openjdk:11-jre-slim
WORKDIR /app

# 빌드 단계에서 생성된 JAR 파일 복사
COPY --from=build /app/target/*.jar ./app.jar

# 애플리케이션 실행
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]
  • AS build를 통해 maven 이미지를 사용한 스테이지에 이름을 지정해준다.
  • openjdk:11-jre-slim은 Java 애플리케이션 실행에 필요한 Java Runtime만 가지고 있는 이미지이다.
  • RUN mvn clean package를 통해 maven이미지 target 폴더에 jar 파일이 생성된다.
  • COPY --from=build /app/target/*.jar ./app.jar를 통해 jar파일을 복사해온다. --from 옵션으로 다른 스테이지 이름을 쓰고, 다른 스테이지에서 파일을 가져온다.
  • FROM이 두 개이면 도커가 두 개의 컨테이너를 동시에 생성하고, 첫 번째 컨테이너에서 만들어진 파일을 두 번째 컨테이너로 복사해올 수 있다.

→ 첫 번째 컨테이너는 maven 컨테이너이고, 이 컨테이너를 통해 만들어낸 jar 파일을 jdk 컨테이너에서 복사해 와서 사용하는 것이다.

→ 멀티 스테이징 기술을 잘 활용하면 애플리케이션의 이미지 크기를 크게 줄일 수 있다.

profile
https://garden-ying.tistory.com/

4개의 댓글

comment-user-thumbnail
2024년 5월 3일

도커마스터의 도커이미지 멀티빌드.. 나중에 도커어플리케이션 배포할때 써보겠습니다
멀티빌드..메모...

답글 달기
comment-user-thumbnail
2024년 5월 3일

도커마스터의 도커이미지 멀티빌드.. 나중에 도커어플리케이션 배포할때 써보겠습니다
멀티빌드..메모...

답글 달기
comment-user-thumbnail
2024년 5월 6일

엄청 꼼꼼하고 대단한 도커 포스팅이네요..! 이 포스팅을 보니 도커에 대해 더 공부해보고 싶습니다 잘보고갑니다😀

답글 달기
comment-user-thumbnail
2024년 5월 7일

역시 도스장..완전 꼼꼼하군요 종종 이미지와 텍스트 줍줍하러 오겠습니당 크크

답글 달기