[Docker] Dockerfile 명령어와 이를 활용한 이미지 빌드 실습: 빌드 캐시 사용, 최적화

728x90
반응형

1. IaC & Dockerfile

IaC가 필요한 이유

  • 명령어 기반으로 인프라를 구성할 때 사용자 실수와 같은 인적 오류 가능성이 높다.
  • 이러한 오류 가능성을 줄이기 위해 인프라 구성을 코드로 관리할 수 있는 IaC(Infrastructure as Code)를 사용한다.

2. 최적의 Dockerfile 작성법

  • 경량의 컨테이너 서비스 제공: 불필요한 파일이나 라이브러리를 최소화하여 경량 컨테이너를 제공한다.
  • 레이어 최소화: Dockerfile의 레이어 수가 많아지면 빌드 시간이 늘어나고 이미지가 커질 수 있다. 불필요한 레이어를 줄여 효율을 높인다.
  • 하나의 애플리케이션, 하나의 컨테이너: 단일 컨테이너에 하나의 애플리케이션만 실행하도록 구성하는 것이 좋다.
  • 캐시 활용: 캐시 기능을 활용해 중복된 빌드 작업을 피할 수 있다.
  • 디렉토리 단위 작업: 디렉토리별로 작업을 나누면 가독성이나 관리가 용이하다.
  • 서버리스 환경에서 개발: 서버리스 환경에서 동작하도록 별도의 서버를 필요로 하지 않도록 한다.

3. Dockerfile 명령어

FROM

  • FROM은 필수 항목이며, 이미지를 빌드할 때 사용할 베이스 이미지를 지정하는 명령어이다.
  • 일반적으로 hub.docker.com에서 제공하는 다양한 베이스 이미지를 활용하는 것이 좋다. 특히 경량화된 이미지를 선호한다면, slim이나 Alpine과 같은 리눅스 배포판을 사용하는 것을 권장한다.
    • 예: FROM alpine:latest
      Alpine 이미지는 매우 가볍고, 작은 용량을 요구하는 서비스나 빠른 시작 속도가 필요한 경우에 적합하다.

MAINTAINER

  • 이미지를 만든 작성자의 정보(이름, 이메일 주소)를 명시하는 명령어이다.
    • 예: MAINTAINER adam <itstudy@kakao.com>
      이는 이미지의 소유자나 책임자가 누구인지 기록하여 추후에 문제가 발생했을 때 관련된 사람을 식별할 수 있게 한다.

LABEL

  • 이미지에 메타데이터(버전, 설명 등)를 추가하는 명령어이다. 여러 개의 라벨을 사용할 수 있으며, 이를 통해 이미지의 목적, 작성자, 버전 등의 정보를 기록할 수 있다.
    • 예: LABEL version="1.0" description="A PHP web application"
      이 정보를 활용하면 나중에 이미지가 어떤 용도로 생성되었는지, 어떤 버전을 사용하는지 쉽게 파악할 수 있다.

RUN

  • 기본 이미지에 추가적으로 필요한 패키지나 설정을 실행하는 명령어이다. 패키지 설치, 업데이트 등 다양한 작업을 처리할 수 있으며, 여러 개의 RUN 명령을 사용할 수 있다.
    • 예: RUN apt-get update && apt-get install -y nginx
      RUN 명령은 하나의 명령이 하나의 레이어로 저장되므로, 명령어의 개수를 최소화하여 이미지 크기를 줄이는 것이 중요하다. 예를 들어, 여러 패키지를 한 번에 설치하는 방식으로 명령을 합칠 수 있다.

CMD

  • 컨테이너가 시작될 때 실행할 명령을 지정하는 명령어이다. 여러 개의 CMD를 작성해도 마지막에 선언된 하나만 실행된다.
    • 예: CMD ["python", "app.py"]
      컨테이너 실행 시 기본적으로 실행될 명령을 지정하며, 일반적으로 애플리케이션을 실행하는 데몬 프로세스를 실행하는 데 유용하다.

ENTRYPOINT

  • CMD와 유사하게 컨테이너가 시작될 때 실행될 명령을 지정하지만, 필수적으로 실행해야 하는 명령을 지정할 때 사용된다.
    • 예: ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]
      ENTRYPOINT는 컨테이너 실행 시 꼭 필요한 명령을 지정할 때 유용하다. CMD는 기본 인자를 넘기는 용도로, ENTRYPOINT는 반드시 실행해야 하는 명령을 지정하는 데 사용된다.

COPY

  • 호스트의 파일이나 디렉토리를 이미지 내부로 복사할 때 사용하는 명령어이다.
    • 예: COPY ./src /app/src
      특정 파일이나 디렉토리를 이미지 빌드 과정에서 컨테이너 내에 복사한다.

ADD

  • COPY와 유사하지만, 추가적으로 URL에서 파일을 다운로드하여 이미지에 추가하거나, 압축 파일을 지정된 경로에 풀어서 추가할 수 있다.
    • 예: ADD http://example.com/file.tar.gz /tmp/
      URL을 통해 외부 파일을 가져와 압축을 풀고, 이미지에 포함시킬 수 있다.

ENV

  • 환경변수를 설정하는 명령어이다.
    • 예: ENV APP_ENV=production
      이는 컨테이너 내부에서 애플리케이션이 실행될 때 참조할 수 있는 변수를 미리 설정하는 용도로 유용하다.

EXPOSE

  • 컨테이너 내부에서 외부로 노출할 포트를 지정하는 명령어이다.
    • 예: EXPOSE 80
      해당 포트는 애플리케이션이 외부와 통신할 수 있도록 열려 있다.

VOLUME

  • 컨테이너 내부와 호스트 간의 데이터를 공유할 볼륨을 지정하는 명령어이다.
    • 예: VOLUME /data
      이는 데이터를 영구적으로 저장하거나 컨테이너 간에 공유할 때 사용한다.

USER

  • 기본적으로 root 사용자로 실행되는 컨테이너에서 다른 사용자를 지정할 때 사용하는 명령어이다.
    • 예: USER www-data
      보안 상 이유로 권한이 낮은 사용자를 사용하는 것이 권장된다.

WORKDIR

  • 컨테이너 내부에서 작업 디렉토리를 설정하는 명령어이다. 이 디렉토리를 기준으로 모든 명령이 실행된다.
    • 예: WORKDIR /app
      작업 디렉토리를 미리 설정하면, 이후의 RUN, CMD, COPY, ADD 명령이 모두 해당 디렉토리에서 실행된다.

ARG

  • 빌드 시점에 값을 전달하는 명령어이다.
    • 예: ARG version=1.0
      빌드 과정에서 필요한 값을 동적으로 전달할 수 있다. --build-arg 옵션을 통해 값을 전달할 수 있다.

HEALTHCHECK

  • 컨테이너 내부의 프로세스 상태를 주기적으로 확인하는 명령어이다. 여러 개의 HEALTHCHECK가 작성되었을 경우, 마지막에 선언된 것만 적용된다.
    • 예: HEALTHCHECK --interval=1m --timeout=3s --retries=5 CMD curl -f http://localhost || exit 1
      헬스체크 간격, 타임아웃, 재시도 횟수를 설정하여 프로세스가 정상적으로 작동하는지 확인할 수 있다.

순서 및 빌드 캐시

  • FROM은 가장 먼저 선언해야 하지만, 나머지 명령어들의 순서는 빌드 캐시 무효화와 관련이 있다. 캐시가 무효화되는 빈도가 적은 명령어부터 배치하는 것이 효율적이다.

Dockerfile 명령 순서

  • Dockerfile의 순서는 명령어의 캐시 무효화와 관련이 있기 때문에, 자주 변경되지 않는 명령을 먼저 배치하는 것이 좋다.

4. Docker 이미지 빌드

docker build [옵션] 이미지이름[:태그] 경로 | URL | 압축파일
  • -t: 이미지에 태그를 지정할 때 사용한다.
  • -f: 다른 파일명을 사용하는 경우 Dockerfile을 지정한다.

5. Dockerfile이 필요한 이유

Dockerfile을 사용하지 않으면, 우분투 이미지를 이용해 컨테이너를 생성한 후 직접 컨테이너에 접속해 일일이 필요한 소프트웨어(예: 아파치 웹 서버, PHP)를 설치하고 웹 애플리케이션을 설정해야 한다. 이는 매번 반복 작업을 해야 한다는 단점이 있다.

반면, Dockerfile을 사용하면 이러한 설치 및 설정 과정을 자동화할 수 있다. 모든 명령어를 Dockerfile에 작성해두면 단 한 번의 명령어로 동일한 환경을 가진 컨테이너를 손쉽게 생성할 수 있다.


6. Dockerfile 작성 및 실행

  1. 작업 디렉토리 생성 및 이동
$ mkdir phpapp
$ cd phpapp
  1. Dockerfile 생성 및 작성: `vi Dockerfile`
FROM ubuntu:14.04

MAINTAINER "yeonsu <bestdustn@gmail.com>"

LABEL title "IaC PHP application"

RUN apt-get update && apt-get -y install apache2 php5 git curl ssh wget

# Apache2 환경 변수 설정
ENV APACHE2_RUN_USER www-data \
APACHE2_LOG_DIR /var/log/apache2 \
APACHE2_WEB_DIR /var/www/html \
APACHE2_PID_FILE /var/run/apache2/apache2.pid

# 기본 웹 페이지 및 PHP 파일 작성
RUN echo 'Hello Docker Application' > /var/www/html/index.html
RUN echo '<?php phpinfo(); ?>' > /var/www/html/index.php

EXPOSE 80

WORKDIR /var/www/html

CMD ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]
  1. Dockerfile 빌드

현재 디렉토리에 Dockerfile 로 존재 → build

docker build -t myphpapp:1.0 .
  1. 컨테이너 생성 및 실행
docker run -dit -p 8101:80 --name phpapp1 myphpapp:1.0

 


7. 이미지 빌드 과정

이미지 빌드는 대화형이 아닌 자동화된 과정이다. 빌드가 시작되면 도커가 작성한 Dockerfile에 따라 자동으로 이미지를 생성하며, 사용자는 빌드 과정에 개입하지 않는다.

  • Ubuntu 기반 이미지 생성 시 주의사항: apt-get update 명령을 먼저 실행해야 패키지를 정상적으로 설치할 수 있다. 또한 -y 옵션을 추가해 사용자 입력 없이 설치가 자동으로 진행되도록 해야 한다.
RUN apt-get update && apt-get install -y python
  • .dockerignore 파일 사용: 빌드 시 제외하고 싶은 파일이나 디렉토리를 .dockerignore 파일에 명시해 빌드 컨텍스트에서 제외할 수 있다.
node_modules/
*.log

Python 개발 환경 설정 예시

  1. 디렉토리 생성 및 이동
mkdir python_lab
cd python_lab
  1. Dockerfile 작성

`nano Dockerfile` 명령을 통해 새로운 `Dockerfile`을 생성하고, 다음과 같은 내용을 작성한다:

FROM ubuntu:18.04
RUN apt-get update && apt-get install -y python

이 때, `apt-get update` 명령을 누락하게 되면 패키지 설치가 실패할 수 있으므로 주의해야 한다.

  1. 이미지 빌드
docker build -t mypyapp:1.0 .

apt-get update 명령을 반드시 포함해야 패키지 설치에 실패하지 않음.

  1. Ubuntu 버전 14.04로 변경하여 빌드할 수도 있다.
FROM ubuntu:14.04
RUN apt-get update && apt-get install python

이미지 빌드 확인

이미지를 빌드하고 압축 파일이 해제된 후의 디렉토리를 확인할 수 있다.

  • 이미지 용량 최적화: 불필요한 패키지와 파일을 삭제해 이미지 용량을 줄일 수 있다.
FROM ubuntu:latest

RUN apt-get update && apt-get -y install apache2 vim curl \
    && apt-get clean -y \
    && apt-get autoremove -y \
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

RUN 명령을 하나로 합치고, 가능한 경우 alpine 기반 이미지를 사용해 성능을 최적화할 수 있다.

FROM python:3.9.2-alpine
COPY app.py /app
RUN pip install -r requirements.txt
CMD python /app/app.py

Dockerfile 작성 시 명령어들의 레이어 생성과 빌드 캐시의 동작을 고려해 효율적으로 작성하는 것이 중요하다.

빌드 캐시 사용을 위한 실습

  • Dockerfile 생성
FROM ubuntu:latest

RUN apt-get update && apt-get install -y nginx curl vim

RUN echo 'Docker Container Application.' > /var/www/html/index.html

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
  • 이미지 빌드
docker build -f Dockerfile -t webapp:1.0 .
  • 이미지 빌드 (다른 태그 사용)
docker build -f Dockerfile -t webapp:2.0 .
  • 동일한 내용을 가지고 두 번째 빌드를 하면 "cached"라는 문구가 보인다. 이는 빌드 과정에서 이전 단계의 캐시를 사용한 것이며, 빌드 속도를 향상시킨다. 이렇게 사용되는 캐시를 빌드 캐시라고 한다.
  • 빌드 캐시는 동일한 명령어의 연속된 부분까지 적용되며, 그 이후부터는 캐시가 적용되지 않는다.

Dockerfile 최적화

  • Python 설치 및 app.py 복사 후 실행하는 Dockerfile
FROM ubuntu:20.04

COPY app.py /app

RUN apt-get update && apt-get -y install python python-pip

RUN pip install -r requirements.txt

CMD python /app/app.py
  • Dockerfile을 빌드할 때 RUN, ADD, COPY 명령어는 각각의 레이어를 생성한다. 반면에 CMD, LABEL, ENV, EXPOSE 등은 레이어를 생성하지 않는다.
  • 최적화 방법: 여러 RUN 명령어를 하나로 합치는 것이 좋다. 또한, 용량 최적화를 위해 Alpine과 같은 경량 이미지를 사용하는 것이 성능 향상에 유리하다.
FROM python:3.9.2-alpine

COPY app.py /app

RUN pip install -r requirements.txt

CMD python /app/app.py
  • 변경 사항이 적은 명령어는 상단에 배치하여 빌드 캐시를 최대한 활용할 수 있도록 한다.

8. 다양한 방법의 Dockerfile 작성

  • 쉘 스크립트를 이용한 환경 구성: 환경 변수들은 .sh 파일에 작성하고 이를 실행해 환경을 구성할 수 있다.
    • 디렉토리를 생성하고 프롬프트 이동
    • Dockerfile 생성 및 작성
FROM ubuntu:latest

RUN apt-get update && apt-get -y install apache2

RUN echo 'Docker Container Application' > /var/www/html/index.html

RUN mkdir /webapp

RUN echo './etc/apache2/envvars' > /webapp/run_http.sh && \
    echo 'mkdir -p /var/run/apache2' >> /webapp/run_http.sh

EXPOSE 80

CMD /webapp/run_http.sh
  • ADD 명령어의 자동 압축 해제 기능 활용: 압축 파일을 Dockerfile에서 가져와 자동으로 해제할 수 있다.
sudo apt install git
git clone <https://github.com/brayanlee/webapp.git>
  • 압축 파일이 있는 곳으로 프롬프트를 이동: `cd webapp`
  • `ls` 명령으로 압축 파일이 있는지 확인

  • Dockerfile이 저장될 디렉터리를 생성: `mkdir dockerfiles`
  • Dockerfile 생성하고 작성: `nano dockerfiles/Dockerfile`
FROM ubuntu:latest

RUN apt-get update && apt-get -y install apache2 vim curl

ADD webapp.tar.gz /var/www/html

WORKDIR /var/www/html

EXPOSE 80

CMD /usr/sbin/apachectl -D FOREGROUND
  • 이미지 빌드 후 컨테이너 실행
docker run -dit -p 8201:80 --name webapp8 webapp:8.0
  • 압축된 파일이 해제되었는지 확인
docker exec -it webapp8 /bin/bash
ls

→ 내가 생성하지 않은 pngs 폴더가 생긴 것을 확인할 수 있다.

  • 압축된 파일을 복사할 때는 개별 파일 복사보다 디렉토리 전체를 복사하거나 압축 파일 형태로 복사하는 것이 효율적이다.
  • 기본 이미지를 사용하는 경우 불필요한 파일을 제거하여 이미지 크기를 줄일 수 있다. apt-get clean, apt-get autoremove, rm -rf 명령어를 사용하여 임시 파일과 패키지 캐시를 삭제한다.

이전에 사용한 도커 파일을 수정(`nano dockerfiles/Dockerfile`)

FROM ubuntu:latest

RUN apt-get update && apt-get -y install apache2 vim curl && \
    apt-get clean -y && \
    apt-get autoremove -y && \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

ADD webapp.tar.gz /var/www/html

WORKDIR /var/www/html

EXPOSE 80

CMD /usr/sbin/apachectl -D FOREGROUND
  • 이미지 빌드 및 크기 확인
docker build -t webapp:9.0 -f ./dockerfiles/Dockerfile .
docker images

이전보다 이미지 크기가 줄어든 것을 확인할 수 있다.


9. Python Flask 애플리케이션 빌드

Flask 애플리케이션 디렉토리 생성 및 소스 코드 작성

mkdir py_flask && cd py_flask
mkdir app && nano app/py_app.py
from flask import Flask

py_app = Flask(__name__)

@py_app.route('/')
def python_flask():
    return "<h1>Hello Flask</h1>"

if __name__ == "__main__":
    py_app.run(host="0.0.0.0", port=9000, debug=True)

패키지 의존성 파일 생성: `app/requirements.txt` 생성

nano app/requirements.txt
Flask==1.1.2
  • 가상 환경을 사용하는 것이 의존성 관리에 유리하다.

`pip freeze > requirements.txt` 명령을 이용하여 파일 생성

→ 가상환경인 경우, 가상환경에 있는 패키지들만 올라가지만, 가상환경이 없으면 깔려있는 모든 패키지들이 몽땅 다 올라간다.

따라서 되도록이면 python을 이용할 때 가상환경을 사용하는 것이 좋다.

현재 디렉토리에 Dockerfile을 만들고 작성

FROM python:3.8-alpine

RUN apk update && apk add --no-cache bash build-base python3-dev py-pip

ENV LIBRARY_PATH=/lib:/usr/lib
ENV FLASK_APP=py_app
ENV FLASK_ENV=development

WORKDIR /py_app
COPY ./app/ .

RUN pip install -r requirements.txt

EXPOSE 9000

ENTRYPOINT ["python"]
CMD ["py_app.py"]

이미지 빌드 및 확인

docker build -t py_flask:1.0 .
docker images

py_flask 이미지가 만들어졌는지 확인

728x90
반응형