Docker

[Docker] Dockerfile Instruction과 이미지 레이어 개념 (Dockerfile 최적화)

LeeJaeJun 2025. 5. 11. 21:33
728x90
728x90

Dockerfile Instruction과 이미지 레이어는 1대1 관계

FROM node:18
COPY . /app
RUN npm install
  • 도커 파일이 위와 같이 구성되어 있다면 이미지 레이어는 총 3개가 생성되는 것입니다.
    • FROM node:18 -> Node.js base 이미지 레이어
    • COPY: 애플리케이션 파일 복사 레이어
    • RUN: 의존성 설치 레이어
  • 즉, 각 명령어 1개는 1개의 레이어를 만듭니다.
  • 이 개념이 중요한 이유는 밑에서 자세히 설명하겠지만, 도커 레이어는 공유 가능하고 캐싱이 가능하며 수정할 수 없기 때문입니다.

 

도커 이미지는 이미지 레이어들의 논리적 묶음

Docker 이미지는 하나의 단일 파일처럼 보이지만, 내부적으로 여러 개의 레이어로 구성되어 있습니다.

레이어들은 논리적으로 묶여서 하나의 이미지처럼 동작하게 됩니다. 

# Dockerfile 1
FROM python:3.10
COPY app1/ /app
CMD ["python", "/app/main.py"]
# Dockerfile 2
FROM python:3.10
COPY app2/ /app
CMD ["python", "/app/worker.py"]

위의 예시에서 Dockerfile 1와 Dockerfile 2는 서로 다른 이미지를 생성하지만 FROM python:3.10가 공통으로 사용되고 있음. 그렇기에 이 이미지 레이어는 또 생성되는게 아니라 만약 Dockerfile1로 먼저 이미지를 생성했었다면 Dockerfile2에서는 이미 생성된 FROM python:3.10에 대한 이미지 레이어를 가져다가 씁니다.

 

그리고 이러한 기반 이미지들을 구성하는 이미지 레이어들 사이에서도 공통되는 것이 있다면 공용으로 사용하게 됩니다.

예를들어, 공식 python 이미지는 보통 다음과 같은 구성을 따르는데

FROM debian:bullseye-slim   <-- 공통 베이스
+ install build-essential, libssl-dev, ...   <-- 공통 설정
+ install Python 3.8                         <-- 3.8 전용
+ install Python 3.10                        <-- 3.10 전용
  • python:3.8과 python:3.10은 서로 다른 Python 버전을 설치하지만,
  • 그 기반 베이스 이미지(debian:bullseye-slim)와 일부 공통 라이브러리 설치는 동일

Docker는 명령어 내용과 그에 따른 결과가 동일하면 해시값도 동일하기에, 공통 단계의 이미지 레이어는 동일한 레이어 해시를 가지게 됩니다. 그렇다면 실제 디스크에는 한 번만 저장되고, 여러 이미지가 공유하여 사용하게 되는 것입니다.

 

이미지 SIZE와 실제 디스크 상에서의 용량이 다른 이유

docker Image ls 명령어를 실행하면 이미지 목록과 함께 SIZE가 나타납니다.

하지만 이 SIZE는 해당 이미지의 자체의 논리적 용량일 뿐, 실제 디스크에서 차지하는 실제 크기가 아닙니다.

이는 여러 이미지 간에 레이어를 공유하기 때문입니다. 공유해서 사용하는 이미지 레이어가 있으면 디스크 상에는 한 번만 저장이 되지만, 이미지의 SIZE를 논리적으로 구할 때는 각각 이를 포함해서 계산하기 때문입니다.

* 실제 디스크 사용량을 확인하고자할 때는 docker system df 명령어를 사용하면 됩니다.

 

레이어는 직접 수정 불가

도커 이미지는 Union File System을 기반을 동작하는데, 이 시스템의 가장 큰 특징은 불변성(Immutable)입니다. 그렇기에 이미지는 다음과 같은 특성을 지니게 됩니다.

  • 이미지의 각 레이어는 읽기 전용
  • 컨테이너를 실행할 때만 쓰기 가능한 레이어가 임시로 추가
  • 기존 레이어를  직접 수정할 수 없음

만약 레이어를 수정할 수 있게 됨다면, 여러 이미지에서 공유 중인 레이어를 변경했을 때 다른 이미지나 컨테이너에도 영향을 줄 수 있기 때문에 Docker는 레이어를 읽기 전용으로 유지하게 됩니다. 기존 이미지를 수정하기 위해서는 Dockerfile을 변경하고 새로운 이미지를 빌드하게 됩니다. (수정된 부분들은 새로운 이미지 레이어를 생성하고 이것들을 쌓아서 새로운 이미지)

 

이미지 레이어 캐시

도커는 해시값을 통해 이전에 동일한 작업을 한 적이 있는지 판단하게 됩니다.

  • 이 해시는 Dockerfile 명령어 + 해당 명령어가 사용하는 파일의 실제 내용을 기반으로 계산됩니다. -> 따라서 파일을 수정하면 이미지 레이어도 새로 생성
  • 하나라도 변경하게 되면 해시값이 달라지므로 cache miss가 발생하여, 새로운 이미지 레이어를 생성합니다.
  • 중간 레이어가 변경되면 이후 Instruction은 캐시가 있다고 해도 이를 무시하고 모두 재실행 됩니다. -> 최적화의 핵심
FROM node:18
COPY package.json /app/
RUN npm install
COPY . /app

만약에 이 예시에서 package.json이 바뀌게 되었다면 COPY package.json가 캐시 미스가 되어 그 뒤에 오는 두 Instruction들도 모두 캐시 무효화가 되어 재실행됩니다.

 

Dockerfile 최적화

도커 이미지 레이어의 캐시를 최대한 활용하기 위해서 변경 가능성이 낮은 Instruction을 위에, 자주 바뀌는 Instruction을 아래에 배치합니다.

COPY . /app
RUN npm install
  • 전체 소스는 바뀔 가능성이 높으므로(코드 한 줄만 수정해도 바로 cache miss) 캐시를 활용하지 못함
COPY package.json /app/
RUN npm install
COPY . /app
  • 상대적으로 잘 변하지 않는 package.json만 먼저 복사한 후에 npm install을 하도록 하고, 그 뒤에 나머지를 복사하도록 하면 package.json만 바뀌지 않으면 RUN npm install까지는 캐싱된 이미지 레이어를 그대로 활용가능

또한 Instruction 한 줄이 한 레이어 이므로 ENV Instruction 등은 묶어서 쓰는게 좋습니다.

# 비효율적인 방식
ENV APP_ENV=production
ENV DEBUG=False

# 최적화된 방식
ENV APP_ENV=production DEBUG=False

추가로 CMD의 경우는 FROM 이후에 어디에나 위치할 수 있고, 이는 자주 바뀌지 않는 명령어이기에 초반에 위치시키는 것이 좋습니다.

728x90
300x250