맞춤형 Docker Image 로 가치 더하기

Beginner

This tutorial is from open-source community. Access the source code

소개

이번 랩에서는 컨테이너를 실행하기 위해 Docker 명령어를 사용했던 랩 1 에서 얻은 지식을 바탕으로 진행합니다. Dockerfile 로부터 사용자 정의 Docker Image 를 생성할 것입니다. Image 를 빌드한 후, 중앙 레지스트리에 푸시하여 다른 환경에 배포하기 위해 풀 (pull) 할 수 있도록 합니다. 또한, Image 레이어와 Docker 가 "copy-on-write" 및 union file system 을 통합하여 Image 를 효율적으로 저장하고 컨테이너를 실행하는 방법에 대해 간략하게 설명합니다.

이번 랩에서는 몇 가지 Docker 명령어를 사용할 것입니다. 사용 가능한 모든 명령어에 대한 전체 문서는 공식 문서를 참조하십시오.

이것은 가이드 실험입니다. 학습과 실습을 돕기 위한 단계별 지침을 제공합니다.각 단계를 완료하고 실무 경험을 쌓기 위해 지침을 주의 깊게 따르세요. 과거 데이터에 따르면, 이것은 초급 레벨의 실험이며 완료율은 83%입니다.학습자들로부터 100%의 긍정적인 리뷰율을 받았습니다.

Python 앱 생성 (Docker 미사용)

다음 명령을 실행하여 간단한 파이썬 프로그램이 포함된 app.py라는 파일을 생성합니다. (전체 코드 블록을 복사하여 붙여넣기)

cd ~/project
echo 'from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "hello world!"

if __name__ == "__main__":
    app.run(host="0.0.0.0")' > app.py

이것은 flask 를 사용하여 포트 5000(5000 은 flask 의 기본 포트) 에서 http 웹 서버를 노출하는 간단한 파이썬 앱입니다. 파이썬이나 flask 에 익숙하지 않더라도 걱정하지 마십시오. 이러한 개념은 모든 언어로 작성된 애플리케이션에 적용될 수 있습니다.

선택 사항: 파이썬과 pip 가 설치되어 있다면 이 앱을 로컬에서 실행할 수 있습니다. 그렇지 않은 경우 다음 단계로 진행하십시오.

$ python3 --version
$ pip3 --version
$ pip3 install flask

$ python3 app.py
 * Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

http://0.0.0.0:5000/를 사용하여 새 브라우저 탭에서 앱을 엽니다.

Flask app browser output

Docker Image 생성 및 빌드

이제 로컬에 파이썬이 설치되어 있지 않다면 어떻게 해야 할까요? 걱정하지 마세요! 필요하지 않으니까요. 컨테이너를 사용하는 것의 장점 중 하나는 호스트 머신에 파이썬이 설치되어 있지 않아도 컨테이너 내에서 파이썬을 빌드할 수 있다는 것입니다.

다음 명령을 실행하여 Dockerfile을 생성합니다. (전체 코드 블록을 복사하여 붙여넣기)

echo 'FROM python:3.8-alpine
RUN pip install flask
CMD ["python","app.py"]
COPY app.py /app.py' > Dockerfile

Dockerfile 은 Docker Image 를 빌드하는 데 필요한 지침을 나열합니다. 위의 파일을 줄별로 살펴보겠습니다.

FROM python:3.8-alpine 이것은 Dockerfile 의 시작점입니다. 모든 Dockerfile 은 위에 레이어를 구축할 시작 Image 인 FROM 라인으로 시작해야 합니다.

이 경우, 애플리케이션을 실행하는 데 필요한 파이썬과 pip 버전이 이미 포함되어 있으므로 python:3.8-alpine 기본 레이어를 선택합니다 ( Dockerfile for python3.8/alpine3.12 참조).

alpine 버전은 Alpine Linux 배포판을 사용한다는 의미입니다. 이는 다른 많은 Linux 버전보다 훨씬 작으며, 크기가 약 8MB 이고, 디스크에 최소 설치 시 약 130MB 정도입니다. Image 가 작을수록 다운로드 (배포) 속도가 훨씬 빠르며, 공격 표면이 작기 때문에 보안 측면에서도 장점이 있습니다. Alpine Linux는 musl 과 BusyBox 를 기반으로 하는 Linux 배포판입니다.

여기서는 파이썬 Image 에 "3.8-alpine" 태그를 사용하고 있습니다. Docker Hub에서 공식 파이썬 Image 에 사용 가능한 태그를 살펴보십시오. 상위 Image 의 변경 사항을 제어하기 위해 상위 Image 를 상속할 때는 특정 태그를 사용하는 것이 가장 좋습니다. 태그가 지정되지 않으면 "latest" 태그가 적용되며, 이는 Image 의 최신 버전을 가리키는 동적 포인터 역할을 합니다.

보안상의 이유로, Docker Image 를 구축하는 레이어를 이해하는 것이 매우 중요합니다. 따라서 docker hub에서 찾을 수 있는 "공식" Image 또는 docker-store 에서 찾을 수 있는 비 커뮤니티 Image 만 사용하는 것이 좋습니다. 이러한 Image 는 특정 보안 요구 사항을 충족하도록 검증되었으며, 사용자가 따를 수 있는 매우 훌륭한 문서를 갖추고 있습니다. 이 파이썬 기본 Image 및 사용할 수 있는 다른 모든 Image 에 대한 자세한 정보는 docker hub에서 확인할 수 있습니다.

더 복잡한 애플리케이션의 경우, 체인 상위에 있는 FROM Image 를 사용해야 할 수 있습니다. 예를 들어, 파이썬 앱의 상위 DockerfileFROM alpine으로 시작한 다음 Image 에 대한 일련의 CMDRUN 명령을 지정합니다. 더 세밀한 제어가 필요한 경우, FROM alpine(또는 다른 배포판) 으로 시작하여 해당 단계를 직접 실행할 수 있습니다. 하지만 처음 시작할 때는 요구 사항에 가장 잘 맞는 공식 Image 를 사용하는 것이 좋습니다.

RUN pip install flask RUN 명령은 패키지 설치, 파일 편집 또는 파일 권한 변경과 같이 애플리케이션에 대한 Image 를 설정하는 데 필요한 명령을 실행합니다. 이 경우 flask 를 설치하고 있습니다. RUN 명령은 빌드 시 실행되며 Image 의 레이어에 추가됩니다.

CMD ["python","app.py"] CMD는 컨테이너를 시작할 때 실행되는 명령입니다. 여기서는 CMD를 사용하여 파이썬 앱을 실행하고 있습니다.

Dockerfile 당 하나의 CMD만 있을 수 있습니다. 둘 이상의 CMD를 지정하면 마지막 CMD가 적용됩니다. 상위 python:3.8-alpine 도 CMD(CMD python3) 를 지정합니다. 공식 python:alpine Image 에 대한 Dockerfile 은 여기에서 확인할 수 있습니다.

호스트에 파이썬을 설치하지 않고도 공식 파이썬 Image 를 직접 사용하여 파이썬 스크립트를 실행할 수 있습니다. 하지만 오늘은 애플리케이션을 포함하는 사용자 정의 Image 를 생성하여 애플리케이션으로 Image 를 빌드하고 다른 환경으로 배포할 수 있도록 합니다.

COPY app.py /app.py 이것은 로컬 디렉토리 (여기서 docker image build를 실행합니다) 의 app.py 를 Image 의 새 레이어로 복사합니다. 이 지침은 Dockerfile 의 마지막 줄입니다. 소스 코드를 Image 로 복사하는 등 자주 변경되는 레이어는 Docker 레이어 캐시를 최대한 활용하기 위해 파일 하단에 배치해야 합니다. 이렇게 하면 그렇지 않으면 캐시될 수 있는 레이어를 다시 빌드하지 않아도 됩니다. 예를 들어, FROM 지침에 변경 사항이 있으면 이 Image 의 모든 후속 레이어에 대한 캐시가 무효화됩니다. 이 랩의 뒷부분에서 이를 시연할 것입니다.

CMD ["python","app.py"] 라인 뒤에 배치하는 것은 직관적이지 않을 수 있습니다. CMD 라인은 컨테이너가 시작될 때만 실행되므로 여기서는 file not found 오류가 발생하지 않습니다.

자, 아주 간단한 Dockerfile 이 완성되었습니다. Dockerfile 에 넣을 수 있는 전체 명령 목록은 여기에서 확인할 수 있습니다. 이제 Dockerfile 을 정의했으므로 이를 사용하여 사용자 정의 Docker Image 를 빌드해 보겠습니다.

Docker Image 를 빌드합니다.

-t를 전달하여 Image 의 이름을 python-hello-world로 지정합니다.

docker image build -t python-hello-world .

Image 목록에 Image 가 표시되는지 확인합니다.

docker image ls

참고 기본 Image python:3.8-alpine도 목록에 있습니다.

history 명령을 실행하여 Image 및 해당 레이어의 기록을 표시할 수 있습니다.

docker history python-hello-world
docker history python:3.8-alpine

Docker Image 실행

이제 Image 를 빌드했으므로 실행하여 작동하는지 확인할 수 있습니다.

Docker Image 실행

docker run -p 5001:5000 -d python-hello-world

-p 플래그는 컨테이너 내에서 실행되는 포트를 호스트에 매핑합니다. 이 경우, 컨테이너 내부의 포트 5000 에서 실행되는 파이썬 앱을 호스트의 포트 5001 에 매핑하고 있습니다. 호스트의 다른 애플리케이션에서 포트 5001 을 이미 사용하고 있는 경우 5001 을 5002 와 같은 다른 값으로 바꿔야 할 수 있습니다.

터미널 창에서 PORTS 탭으로 이동하여 링크를 클릭하여 새 브라우저 탭에서 앱을 엽니다.

Terminal ports tab link

터미널에서 curl localhost:5001을 실행하면 hello world!가 반환됩니다.

컨테이너의 로그 출력을 확인합니다.

애플리케이션의 로그를 보려면 docker container logs 명령을 사용할 수 있습니다. 기본적으로 docker container logs는 애플리케이션에서 표준 출력으로 전송된 내용을 출력합니다. 실행 중인 컨테이너의 ID 를 찾으려면 docker container ls를 사용하십시오.

labex:project/ $ docker container ls
CONTAINER ID   IMAGE                COMMAND           CREATED         STATUS         PORTS                                       NAMES
52df977e5541   python-hello-world   "python app.py"   2 minutes ago   Up 2 minutes   0.0.0.0:5001->5000/tcp, :::5001->5000/tcp   heuristic_lamport
labex:project/ $ docker container logs 52df977e5541
 * Serving Flask app 'app'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:5000
 * Running on http://172.17.0.2:5000
Press CTRL+C to quit
172.17.0.1 - - [23/Jan/2024 02:43:10] "GET / HTTP/1.1" 200 -
172.17.0.1 - - [23/Jan/2024 02:43:10] "GET /favicon.ico HTTP/1.1" 404 -

Dockerfile 은 애플리케이션에 대한 재현 가능한 빌드를 생성하는 방법입니다. 일반적인 워크플로우는 CI/CD 자동화가 빌드 프로세스의 일부로 docker image build를 실행하도록 하는 것입니다. Image 가 빌드되면 중앙 레지스트리로 전송되어 해당 애플리케이션의 인스턴스를 실행해야 하는 모든 환경 (예: 테스트 환경) 에서 액세스할 수 있습니다. 다음 단계에서는 사용자 정의 Image 를 공개 Docker 레지스트리인 Docker Hub 에 푸시하여 다른 개발자 및 운영자가 사용할 수 있도록 합니다.

중앙 레지스트리에 푸시

Docker Hub로 이동하여 아직 계정이 없다면 계정을 만듭니다. 또는 https://quay.io를 사용할 수도 있습니다.

이 랩에서는 Docker Hub 를 중앙 레지스트리로 사용합니다. Docker Hub 는 공개적으로 사용 가능한 Image 를 저장하는 무료 서비스이며, 유료로 개인 Image 를 저장할 수도 있습니다. Docker Hub 웹사이트로 이동하여 무료 계정을 만듭니다.

Docker 를 많이 사용하는 대부분의 조직은 자체 레지스트리를 내부적으로 설정합니다. 간소화를 위해 Docker Hub 를 사용하지만, 다음 개념은 모든 레지스트리에 적용됩니다.

로그인

터미널에서 docker login을 입력하거나, podman 을 사용하는 경우 podman login을 입력하여 Image 레지스트리 계정에 로그인할 수 있습니다.

labex:project/ $ export DOCKERHUB_USERNAME=<your_docker_username>
labex:project/ $ docker login docker.io -u $DOCKERHUB_USERNAME
Password:
WARNING! Your password will be stored unencrypted in /home/labex/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

사용자 이름으로 Image 태그 지정

Docker Hub 명명 규칙은 [dockerhub username]/[image name]으로 Image 에 태그를 지정하는 것입니다. 이를 위해 이전에 생성한 Image python-hello-world에 해당 형식에 맞게 태그를 지정합니다.

docker tag python-hello-world $DOCKERHUB_USERNAME/python-hello-world

Image 를 레지스트리에 푸시

적절하게 태그가 지정된 Image 가 있으면 docker push 명령을 사용하여 Image 를 Docker Hub 레지스트리에 푸시할 수 있습니다.

docker push $DOCKERHUB_USERNAME/python-hello-world

브라우저에서 Docker Hub 에서 Image 확인

Docker Hub로 이동하여 프로필로 이동하여 새로 업로드된 Image 를 https://hub.docker.com/repository/docker/<dockerhub-username>/python-hello-world에서 확인합니다.

이제 Image 가 Docker Hub 에 있으므로 다른 개발자와 운영자는 docker pull 명령을 사용하여 Image 를 다른 환경에 배포할 수 있습니다.

참고: Docker Image 에는 Image 내에서 애플리케이션을 실행하는 데 필요한 모든 종속성이 포함되어 있습니다. 이는 모든 배포 환경에 설치된 종속성에 의존할 때 환경 드리프트 (버전 차이) 를 더 이상 처리할 필요가 없기 때문에 유용합니다. 또한 이러한 환경을 프로비저닝하기 위한 추가 단계를 거칠 필요가 없습니다. 단 한 단계: Docker 를 설치하면 됩니다.

변경 배포

"hello world!" 애플리케이션은 과대평가되었으므로, 대신 "Hello Beautiful World!"라고 표시되도록 앱을 업데이트해 보겠습니다.

app.py 업데이트

app.py에서 문자열 "Hello World"를 "Hello Beautiful World!"로 바꿉니다. 다음 명령으로 파일을 업데이트할 수 있습니다. (전체 코드 블록을 복사하여 붙여넣기)

echo 'from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "hello beautiful world!"

if __name__ == "__main__":
    app.run(host="0.0.0.0")' > app.py

Image 다시 빌드 및 푸시

이제 앱이 업데이트되었으므로, 앱을 다시 빌드하고 Docker Hub 레지스트리에 푸시하기 위해 위의 단계를 반복해야 합니다.

먼저 다시 빌드합니다. 이번에는 빌드 명령에 Docker Hub 사용자 이름을 사용합니다.

docker image build -t $DOCKERHUB_USERNAME/python-hello-world .

1-3 단계에 "Using cache"가 표시되는 것을 확인합니다. Docker Image 의 이러한 레이어는 이미 빌드되었으며 docker image build는 이러한 레이어를 다시 빌드하는 대신 캐시에서 사용합니다.

docker push $DOCKERHUB_USERNAME/python-hello-world

레이어를 푸시하기 위한 캐싱 메커니즘도 있습니다. Docker Hub 에는 이전 푸시에서 모든 레이어가 이미 존재하므로 변경된 레이어 하나만 푸시합니다.

레이어를 변경하면 그 위에 빌드된 모든 레이어를 다시 빌드해야 합니다. Dockerfile 의 각 줄은 이전 줄에서 생성된 레이어 위에 빌드되는 새 레이어를 빌드합니다. 이것이 Dockerfile 의 줄 순서가 중요한 이유입니다. 가장 자주 변경될 가능성이 있는 레이어 (COPY app.py /app.py) 가 Dockerfile 의 마지막 줄이 되도록 Dockerfile 을 최적화했습니다. 일반적으로 애플리케이션의 경우 코드가 가장 빈번하게 변경됩니다. 이러한 최적화는 자동화가 가능한 한 빨리 실행되기를 원하는 CI/CD 프로세스에 특히 중요합니다.

Image 레이어 이해

Docker 의 주요 설계 속성 중 하나는 유니온 파일 시스템 (union file system) 을 사용하는 것입니다.

이전에 생성한 Dockerfile을 생각해 보겠습니다.

FROM python:3.8-alpine
RUN pip install flask
CMD ["python","app.py"]
COPY app.py /app.py

이러한 각 줄은 레이어입니다. 각 레이어에는 이전 레이어의 델타, 차이 또는 변경 사항만 포함되어 있습니다. 이러한 레이어를 단일 실행 컨테이너로 함께 넣기 위해 Docker 는 유니온 파일 시스템을 사용하여 레이어를 단일 보기로 투명하게 오버레이합니다.

Image 의 각 레이어는 실행 중인 컨테이너를 위해 생성된 최상위 레이어를 제외하고 읽기 전용입니다. 읽기/쓰기 컨테이너 레이어는 "copy-on-write"를 구현합니다. 즉, 하위 Image 레이어에 저장된 파일은 해당 파일에 대한 편집이 수행될 때만 읽기/쓰기 컨테이너 레이어로 가져옵니다. 그런 다음 해당 변경 사항은 실행 중인 컨테이너 레이어에 저장됩니다. "copy-on-write" 기능은 매우 빠르며 거의 모든 경우 성능에 눈에 띄는 영향을 미치지 않습니다. docker diff 명령을 사용하여 어떤 파일이 컨테이너 수준으로 가져왔는지 검사할 수 있습니다. docker diff 사용 방법에 대한 자세한 내용은 여기에서 확인할 수 있습니다.

understanding image layers

Image 레이어는 읽기 전용이므로 Image 및 실행 중인 컨테이너에서 공유할 수 있습니다. 예를 들어, 유사한 기본 레이어를 가진 자체 Dockerfile 로 새 Python 앱을 생성하면 첫 번째 Python 앱과 공통으로 가지고 있는 모든 레이어를 공유합니다.

FROM python:3.8-alpine
RUN pip install flask
CMD ["python","app2.py"]
COPY app2.py /app2.py

understanding image layers

동일한 Image 에서 여러 컨테이너를 시작할 때 레이어 공유를 경험할 수도 있습니다. 컨테이너가 동일한 읽기 전용 레이어를 사용하므로 컨테이너 시작이 매우 빠르고 호스트에 매우 적은 공간을 차지한다고 상상할 수 있습니다.

이 Dockerfile 과 이 랩에서 이전에 생성한 Dockerfile 에 중복된 줄이 있는 것을 알 수 있습니다. 이는 매우 사소한 예이지만, 두 Dockerfile 의 공통 줄을 "base" Dockerfile 로 가져와서 FROM 명령을 사용하여 각 자식 Dockerfile 로 가리킬 수 있습니다.

Image 레이어링은 빌드 및 푸시를 위한 Docker 캐싱 메커니즘을 활성화합니다. 예를 들어, 마지막 docker push의 출력은 Image 의 일부 레이어가 이미 Docker Hub 에 존재함을 보여줍니다.

$ docker push $DOCKERHUB_USERNAME/python-hello-world

레이어를 자세히 살펴보려면 생성한 Python Image 의 docker image history 명령을 사용할 수 있습니다.

$ docker image history python-hello-world

각 줄은 Image 의 레이어를 나타냅니다. 상위 줄이 생성한 Dockerfile 과 일치하고 아래 줄이 상위 Python Image 에서 가져온다는 것을 알 수 있습니다. "<missing>" 태그에 대해 걱정하지 마십시오. 이것들은 여전히 정상적인 레이어입니다. Docker 시스템에서 ID 가 부여되지 않았을 뿐입니다.

정리

이 랩을 완료하면 호스트에 많은 수의 실행 중인 컨테이너가 생성됩니다. 이를 정리해 보겠습니다.

실행 중인 각 컨테이너에 대해 docker container stop [container id]를 실행합니다.

먼저 docker container ls를 사용하여 실행 중인 컨테이너 목록을 가져옵니다.

$ docker container ls

그런 다음 목록의 각 컨테이너에 대해 명령을 실행합니다.

$ docker container stop <container_id>

중지된 컨테이너 제거

docker system prune은 시스템을 정리하는 데 매우 유용한 명령입니다. 중지된 모든 컨테이너, 사용하지 않는 볼륨 및 네트워크, 그리고 댕글링 (dangling) Image 를 제거합니다.

$ docker system prune
WARNING! This will remove:
- all stopped containers
- all volumes not used by at least one container
- all networks not used by at least one container
- all dangling images
Are you sure you want to continue? [y/N] y
Deleted Containers:
0b2ba61df37fb4038d9ae5d145740c63c2c211ae2729fc27dc01b82b5aaafa26

Total reclaimed space: 300.3kB

요약

이 랩에서는 사용자 정의 Docker 컨테이너를 생성하여 가치를 추가하기 시작했습니다.

핵심 내용:

  • Dockerfile 은 애플리케이션에 대한 재현 가능한 빌드를 생성하고 애플리케이션을 Docker 와 CI/CD 파이프라인에 통합하는 방법입니다.
  • Docker Image 는 중앙 레지스트리를 통해 모든 환경에서 사용할 수 있습니다. Docker Hub 는 레지스트리의 한 예이지만, 직접 제어하는 서버에 자체 레지스트리를 배포할 수 있습니다.
  • Docker Image 는 Image 내에서 애플리케이션을 실행하는 데 필요한 모든 종속성을 포함합니다. 이는 모든 환경에 배포할 때 설치되는 종속성에 의존할 때 더 이상 환경 드리프트 (버전 차이) 를 처리할 필요가 없기 때문에 유용합니다.
  • Docker 는 Image 레이어를 재사용하기 위해 유니온 파일 시스템 (union file system) 과 "copy on write"를 사용합니다. 이는 Image 저장 공간을 줄이고 컨테이너 시작 성능을 크게 향상시킵니다.
  • Image 레이어는 Docker 빌드 및 푸시 시스템에 의해 캐시됩니다. 원하는 시스템에 이미 있는 Image 레이어를 다시 빌드하거나 다시 푸시할 필요가 없습니다.
  • Dockerfile 의 각 줄은 새 레이어를 생성하며, 레이어 캐시로 인해 더 자주 변경되는 줄 (예: Image 에 소스 코드 추가) 은 파일 하단에 나열되어야 합니다.