본문 바로가기

AI/MLOps

[12/11] 아이펠 리서치 15기 TIL | Docker 빌드

반응형

저번 포스팅에 이어 도커를 더 알아보자.

이번 포스팅은 깃헙을 참고한다.


먼저, 클론 후 파일 구조를 보자.

git clone https://github.com/KennethanCeyer/mlops-quicklab.git
tree -L 2 .

├── README.md
├── airflow
│   ├── README.md
│   └── basic
├── assets
│   └── logo.png
├── dockers
│   ├── README.md
│   ├── basic
│   ├── compose
│   ├── optimization
│   └── swarm
├── mlflow
│   ├── Dockerfile
│   ├── README.md
│   ├── docker-compose.yaml
│   ├── requirements.txt
│   ├── train_and_register.py
│   └── trainer.py
└── vertexai
    └── predictions

12 directories, 10 files

 

이번 포스팅은 dockers 폴더를 사용할 것이다.

cd dockers

tree -L 2 .
.
├── README.md
├── basic
│   ├── Dockerfile
│   ├── README.md
│   ├── app
│   └── requirements.txt
├── compose
│   ├── Dockerfile
│   ├── README.md
│   ├── app
│   ├── docker-compose.yml
│   └── requirements.txt
├── optimization
│   ├── Dockerfile
│   ├── Dockerfile.base
│   ├── README.md
│   ├── app
│   └── requirements.txt
└── swarm
    ├── Dockerfile
    ├── README.md
    ├── app
    ├── docker-compose.yml
    ├── locustfile.py
    └── requirements.txt

9 directories, 17 files

Docker 빌드

docker build란?

이미지를 만드는 과정이다.

이 이미지를 만들기 위해서는 "어떤 방식으로 이미지를 만들 것인지"를 알아야 하는데, 이 정보들은 보통 Dockerfile에 있다.

cd basic
cat Dockerfile


FROM python:3.11
WORKDIR /code

COPY ./requirements.txt /code/requirements.txt
COPY ./app /code/app

RUN apt-get update
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]

 

 

1. FROM python:3.11 - 기반 이미지 설정

이건 Docker Hub에 있는 python의 3.11 버전이라는 뜻이다. https://hub.docker.com/_/python/tags?name=3.11

 

python Tags | Docker Hub

python •• Python is an interpreted, interactive, object-oriented, open-source programming language.

hub.docker.com

(dockerhub -> python 검색 -> Tag -> 3.11 검색)

 

FROM은 빌드할 이미지의 시작점을 지정하는 것이고,

여기선 python 3.11이 설치된 공식 Docker 이미지를 사용한다는 뜻이다.

 

2. WORKDIR /code - 작업 디렉터리 설정

이후의 명령어들(COPY, RUN, CMD)이 실행될 컨테이너 내부의 기본 디렉터리를 /code로 지정하고,

컨테이너 내부에서 작업할 위치를 통일한다.

 

3. COPY

COPY ./requirements.txt /code/requirements.txt
COPY ./app /code/app

 

Dockerfile에 있는 로컬 디렉터리의 파일을 빌드 중인 이미지 내부의 경로로 복사한다.

(왼쪽은 호스트, 오른쪽은 컨테이너)

 

4. RUN - 이미지 만들 때 동작

apt-get update - 시스템 업데이트

  • 컨테이너 내부에서 리눅스 패키지 목록을 최신 상태로 업데이트한다.

pip install --no-cache-dir --upgrade -r /code/requirements.txt

  • txt파일 안에 있는 패키지들을 설치한다.

5. CMD - 이미지를 실행(컨테이너화)할 때 최초로 실행됨

이 이미지를 기반으로 컨테이너가 시작될 때 실행될 기본 명령어를 정의한다.

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]

  • app 폴더 안에 main.py 파일이 들어있고, 이걸 열어보면 root 라고 하는 함수가 존재.
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def root():
    return {"message": "Hello World"}

(선택)

conda로 가상환경 만들기 (로컬에서 여러 플젝 할 때 충돌 안 나게)

conda create -n quicklab-modu python=3.11

conda activate quicklab-modu

 

이제, 필요한 패키지들을 모두 설치하자.

pip3 install -r requirements.txt

 

설치된 uvicon 실행

uvicorn app.main:app --host 0.0.0.0 --port 8000


INFO:     Started server process [11716]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

마지막 줄의 링크에 접속하면 아래와 같은 웹페이지가 뜬다.

 

 

이것도 이전 포스팅에서 언급했었던 것과 마찬가지로, 실시간으로 로그를 볼 수 있다.

 

커멘드라인도 없으므로, 제어권은 유비콘에게 있고, 계속 실행중이라는 뜻 (종료하려면 CTRL+C)


여기서 WSL을 사용중이라면 host가 0.0.0.0이 아님

hostname -I

이 코드 결과를 0.0.0.0대신 작성

 

예시:

uvicorn app.main:app --host 172.26.58.106 --port 8001

이제, 이미지 빌드를 해보자.

docker build . -t fastapi-app:latest


[+] Building 67.2s (11/11) FINISHED                              docker:default
 => [internal] load build definition from Dockerfile                       0.0s
 => => transferring dockerfile: 295B                                       0.0s
 => [internal] load metadata for docker.io/library/python:3.11             2.3s
 => [internal] load .dockerignore                                          0.0s
 => => transferring context: 52B                                           0.0s
 => [1/6] FROM docker.io/library/python:3.11@sha256:38639aa0267125ab0def  50.8s
 => => resolve docker.io/library/python:3.11@sha256:38639aa0267125ab0def2  0.0s
 => => sha256:258d02e80207372ffa988e0886f193f22d069828517 6.35kB / 6.35kB  0.0s
 => => sha256:2981f7e8980b9f4b6605026e1c5f99b4971ebba1 49.29MB / 49.29MB  18.2s
 => => sha256:b22766554d6bfa95c7325b00ee002f2705a7b860 25.61MB / 25.61MB  11.0s
 => => sha256:58f2d358b447d091790c5ef0943550bbcf57bac4 67.78MB / 67.78MB  17.2s
 => => sha256:38639aa0267125ab0def2778f0aa8453043178ec6 10.32kB / 10.32kB  0.0s
 => => sha256:65a37c5d066bac59dccfa5f3fac4b99a2e8eea11987 2.32kB / 2.32kB  0.0s
 => => sha256:dd420cee8193b72cf70974a80e88896c8e58d9 235.97MB / 235.97MB  46.5s
 => => sha256:5c5320e2b85aa6a73d5a99c3b0ed13ed25e19735ef 6.08MB / 6.08MB  19.4s
 => => extracting sha256:2981f7e8980b9f4b6605026e1c5f99b4971ebba15f626e46  0.8s
 => => sha256:385f8897758201c7b09aa909cc7dadd97c0c16c3 23.95MB / 23.95MB  23.9s
 => => extracting sha256:b22766554d6bfa95c7325b00ee002f2705a7b8605908c3eb  0.3s
 => => sha256:8493300c7ffc400508d0f0f98eb8e5e8c6da8c7a073c82 250B / 250B  19.9s
 => => extracting sha256:58f2d358b447d091790c5ef0943550bbcf57bac46c4b8bfc  1.2s
 => => extracting sha256:dd420cee8193b72cf70974a80e88896c8e58d925edd1cdc5  3.6s
 => => extracting sha256:5c5320e2b85aa6a73d5a99c3b0ed13ed25e19735efa6b8fd  0.1s
 => => extracting sha256:385f8897758201c7b09aa909cc7dadd97c0c16c3e2e286a5  0.4s
 => => extracting sha256:8493300c7ffc400508d0f0f98eb8e5e8c6da8c7a073c827a  0.0s
 => [internal] load build context                                          0.0s
 => => transferring context: 856B                                          0.0s
 => [2/6] WORKDIR /code                                                    0.6s
 => [3/6] COPY ./requirements.txt /code/requirements.txt                   0.0s
 => [4/6] COPY ./app /code/app                                             0.0s
 => [5/6] RUN apt-get update                                               2.8s
 => [6/6] RUN pip install --no-cache-dir --upgrade -r /code/requirements  10.3s
 => exporting to image                                                     0.4s
 => => exporting layers                                                    0.4s
 => => writing image sha256:96b0090a8b537601ae0837bbf6d4b5eee3508b0e01af3  0.0s
 => => naming to docker.io/library/fastapi-app:latest                      0.0s

dockerfile이 있는 폴더 위치를 지정하는데, 지금은 현재 위치에 있으므로 . 사용.

-t는 tag 옵션: 특정 이미지의 이름을 지정해준다.

fastapi-app는 이미지 이름

latest는 태그 옵션 (보통 여기는 버전을 씀. ex: v.1) 이것도 마찬가지로 디폴트로 latest.

 

이제 이미지가 잘 만들어졌나 확인해보면,

docker images
 
REPOSITORY    TAG       IMAGE ID       CREATED         SIZE
fastapi-app   latest    96b0090a8b53   3 minutes ago   1.19GB

잘 됐다.

 

이제 이 이미지로 docker run (컨테이너화) 시켜보면,

docker run --name fastapi-app -p 8000:80 fastapi-app

INFO:     Started server process [1]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit)

잘 되는데, 로컬에서는 http://0.0.0.0:80 이게 아니라 http://127.0.0.1:8000 로 접근해야 정상적으로 된다.

 

하지만, run 할 때 -dit 옵션을 안 줬으므로 제어권이 없다. -> 종료해야 터미널에서 다른 명령어 실행 가능.

ctrl+c 누르면 아래와 같이 종료된다.

^CINFO:     Shutting down
INFO:     Waiting for application shutdown.
INFO:     Application shutdown complete.
INFO:     Finished server process [1]

 

그리고 docker ps -a 해보면?

CONTAINER ID   IMAGE         COMMAND                   CREATED          STATUS                     PORTS     NAMES
e3b7b49e081f   fastapi-app   "uvicorn app.main:ap…"   10 minutes ago   Exited (0) 2 minutes ago             fastapi-app

Exited (0) 2 minutes ago -> 프로그램이 종료됐기 때문에 컨테이너도 종료됐다고 나온다.

 

다시 띄울 수도 있음

docker start fastapi-app

 

이제 모든 컨테이너를 삭제하자

docker rm -f $(docker ps -qa)

$로 괄호 안의 결과값들을 묶어서 argument로 줄 수 있다.

 

같은 방법으로 이미지들도 삭제

docker rmi $(docker images -a)

 

모든 컨테이너, 네트워크, 이미지, 캐시를 제거하는 정석 방법은 아래와 같다.

docker system prune --all


WARNING! This will remove:
  - all stopped containers
  - all networks not used by at least one container
  - all images without at least one container associated to them
  - all build cache

Are you sure you want to continue? [y/N] y
Deleted build cache objects:
ga5q4klj56sxfprje00uw8sik
vcqj7tioq5x3r8y2i2imwh0o2
u7ktw313rg6vq7xoymnauz5og
w1ahhfzf0hk1whcmk9xrufn9b
zpfvkcijqgiqqhqfdmrzlysf1
n4e1ly5ga2qgpajgzcod8emax
si11qt7tpgropjzghfupll0e0
m972jpz97yxqfm0hedmn5s2pv
83uyl4ylfdpwfvq8fen1dgzl4
o85xnzziahg64q0jvjjlpss1g
jecyah4dybpon3rech6h839ar
fjzge2x6u0qt0y0mwrj82c9wr
2qcr2mng8959v16s9l5njmowj
suhd5w28ce293zie957p8rehd
bp6im011frx0kvphn8kjf38al

Total reclaimed space: 85.88MB

 

Docker 빌드 과정

이미지들을 레지스트리에 저장하는데, 대표적인 레지스트리는 Docker Hub이다.

또는 구글 클라우드 플랫폼의 레지스트리도 있음.

 

갑자기 생긴 궁금증인데, 
docker의 이미지는 이미지 파일도 아니면서 왜 이미지라고 부르나? 를 찾아봤는데,
"스냅샷" 개념으로 이해하면 될 것 같다.(그 당시의 환경을 그대로 담고 있다.)

 

Docker에서 자주 사용하는 명령어

 


회고

기록하면서 공부하니 머리에 잘 들어오긴 하는데 시간이 꽤 오래 걸린다.

반응형