Docker를 알아보도록 하자

용가리·2024년 12월 17일

반갑습니다.

Docker에 대해 블로그를 작성하며 머리에 넣어보겠습니다.
Docker는 간단하게 밀키트라고 보시면 됩니다.
한 음식(소프트웨어)를 다양한 가정집(환경)에서 같은 맛으로 만들고 싶은 경우가 있겠죠.
그런 경우에 Docker을 사용합니다.

한번 차근차근 설명해보겠습니다.

가상화

가상화란, 특정 소프트웨어 환경을 만들고 Local Production 서버에서 그대로 활용할 수 있도록 하는 과정입니다.

뭐 Mac os, Window, Linux 다양한 환경에서 똑같이 실행시켜야 하는 프로그램이 있다면, 그것을 가능하게 만들어주는 방법입니다.

VM(Virtual Machine)

Docker 전에는 VM(Virtual Machine)을 주로 사용했습니다.
VM은 실제 물리적인 컴퓨터(호스트 머신)위에 OS를 포함한 가상화 소프트웨어를 두는 방식입니다.

Mac이든 Window든 Linux든 전부 OS 위에서 또 OS를 실행하는것이지요.
그것을 Window로 정했다면 Mac, Linux에서도 Window를 실행하여 그 위에서 프로그램을 실행합니다.

주로 GCP(Google Cloud Platform)이나 AWS(Amazon Web Service)의 EC2가 이런 개념을 활용하여 서비스를 제공하였습니다.

하지만 OS위에 OS를 또 실행시킨다는 것은 많은 리소스를 사용합니다.
Heavy한것이지용
그래서 이를 가볍게 만든 것이 Container라는 것이고, Container 기술을 쉽게 사용할 수 있도록 나온 도구가 Docker입니다.

Virtual Machine과 Container의 차이를 잘 설명해주는 그림입니다.
큰 차이가 있다면, VM은 Guest OS가 있는 반면 Container에는 OS를 또 띄우지 않는다는 점이겠습니다.

Docker

Container 기술을 쉽게 사용할 수 있도록 개발된 도구입니다.
2013년에 오픈소스로 개발됐으며, Container에 기반한 개발과 운영을 쉽고 빠르게 수행할 수 있어 많이 이용됩니다.

Docker과 비슷한 것을 예로 들자면 PC방이 있습니다.
PC방에서는 컴을 키고 이상한거를 깔아도 껐다키면 다시 원상복귀돼있죠.
PC방은 어떤 툴을 사용하는지는 모르겠지만.. Docker와 비슷하다고 생각합니다.
Docker Image로 만들어 둔 뒤, Container로 만들어 띄우면 항상 똑같은 상태의 소프트웨어를 공유할 수 있습니다.

Docker Image

컨테이너를 실행할 때 사용할 수 있는 템플릿입니다.

밀키트라고 생각하면 편할 것 같습니다.

Docker Container

Docker Image를 활용하여 실행된 인스턴스를 의미합니다.
Write가 가능하며, 따로 Volume mount를 하지 않으면 Container을 내릴 때 생성된 파일이 전부 삭제됩니다.

Docker의 활용처

  1. MySQL을 Docker로 실행할 수 있습니다.
  2. Jupyter Notebook을 Docker로 실행할 수 있습니다.
  3. 자신이 개발한 코드를 다른 사람에게 공유할 때, 의존성 문제를 피할 수 있습니다.
  4. 원격 저장소에 본인이 제작한 이미지를 공유하여 서버에서 Image를 받아 실행할 수 있습니다.
  5. 기타 등등 ..

무궁무진합니다..

Docker 설치

Docker는 공식 홈페이지 https://www.docker.com/products/docker-desktop/ 에서 다운받으실 수 있습니다.
본인의 운영체제에 맞는 Docker Desktop을 설치하시면 되겠습니다.

설치까지 끝나셨다면, Docker을 설치하고 사용할 수 있는 명령어들에 대해 알아보도록 하겠습니다.

Docker 명령어들

Docker 이미지 다운

Docker를 다운받으셨다면 Docker 이미지를 통해 container을 띄울 수 있습니다.
Docker 이미지는 Docker hub에서 다운받거나 직접 만드실 수 있습니다.
예를 들어 airflow의 이미지를 다운받고 싶다

https://hub.docker.com/search?q=airflow 에 들어가셔서 이름을 보고 apache에서 제공하는 airflow, 또는 bitnami 뭐 다양합니다.
만약 apache/airflow를 사용하고자 한다면

docker pull apache/airflow:[tag 이름] 

와 같이 명령어를 입력하여 이미지를 다운받을 수 있습니다.

docker images

를 입력하여 이미지들을 확인할 수 있습니다.


이런식으로 TAG, IMAGE ID, CREATED, SIZE 등 다양한 정보와 이미지 이름을 볼 수 있겠습니다.

Docker 실행하기

docker run "이미지 이름:TAG"

를 통해 docker을 실행할 수 있습니다.

이 때, 어떤 이미지를 container로 띄우느냐에 따라 추가적인 옵션을 넣을 수 있습니다.
예를 들어 MySQL이다

docker run --name mysql_yongari -e \
MYSQL_ROOT_PASSWORD=1234 -d -p 3306:3306 \
-v $(pwd)/data:/app/data mysql:8

인자들을 하나씩 살펴보면
--name > 컨테이너 이름: mysql_yongari
-e : 환경변수 설정 > 비밀번호: 1234 (루트 계정)
-p : 포트 매핑 > 호스트의 3306 ↔ 컨테이너의 3306
-v : 볼륨 마운트 > 호스트의 data 폴더 ↔ 컨테이너의 /app/data
MySQL 버전: 8 (image 이름)
-d : 실행 모드 > 백그라운드(Detached mode)

이외에도 인자들이 더 있을 수 있습니다.
찾아보거나 GPT에게 물어보면 되지 않을까 싶습니다.. ㅎㅎ

Container 확인하기

docker ps

위 명령어를 통해 실행된 컨테이너들을 확인할 수 있습니다.

저는 실행중인 container가 없어서 안뜨지만... 실행중인 container가 있다면 위와 같은 컬럼들을 가지는 행을 볼 수 있습니다.

docker ps -a

를 사용하면 작동을 멈춘 컨테이너도 볼 수 있습니다.
-a가 없다면 실행중인 컨테이너만 표시합니다.

Container 내부 진입하기

docker exec -it [컨테이너 이름 혹은 ID] /bin/bash

-it 인자를 통해 Docker 내부의 컨테이너에서 터미널을 실행할 수 있습니다.

-i는 stdin 입력 활성화를 의미합니다.
-t는 호스트 머신의 터미널과 컨테이너의 터미널을 연결하여 터미널 환경을 제공합니다.

즉 -it를 사용하면 상호작용 가능한 터미널을 할당하며 컨테이너 내부에서 명령어를 실행할 수 있도록 설정합니다.

Container 삭제하기

docker rm '컨테이너 이름 혹은 ID'

를 통해 멈춘 Container을 삭제할 수 있습니다.

docker rm '컨테이너 이름 혹은 ID' -f

를 사용하면 실행중인 컨테이너도 삭제할 수 있습니다.

정리를 해보면 ..

  • docker pull “이미지 이름:태그" : 필요한 이미지 다운
  • docker images :다운받은 이미지 목록 확인
  • docker run “이미지 이름:태그" : 이미지를 기반으로 컨테이너 생성 - docker ps : 실행중인 컨테이너 목록 확인
  • docker exec -it “컨테이너 이름(ID)" /bin/bash : 컨테이너에 진입 - docker stop “컨테이너 이름(ID)” : 실행중인 컨테이너를 중지
  • docker rm “컨테이너 이름(ID)” : 중지된 컨테이너 삭제

가 기본 명령어들입니다.
아유숨차
다양한 인자들도 같이 사용할 수 있는데, 저도 모르는 인자들이 많은 듯 합니다.
개발자라면 필요한건 그때그때 찾는 능력을 기르는 것이 도움이 되겠습니다.

Docker 이미지 만들기

시중에 공개된 Docker의 이미지가 아닌 본인이 만든 소스파일들을 모아 Docker 이미지를 직접 만들 수 있습니다.
프로젝트에서는 이게 사실 중요하겠죠.
Docker 이미지를 만들기 위해서는 Dockerfile을 작성해야 합니다.
config같은거라고 생각하시면 됩니다.

어떻게 생겼냐면

아유 두통이 ㅠㅠ

차근차근 알아봅시다.

FROM "이미지 이름:태그"

베이스 이미지를 선정합니다.
Python:3.8 이런 이미지가 될 수도 있고.. 다양합니다.
Docker hub에서 원하는 이미지를 찾고 입력하시면 되겠습니다.

COPY "로컬디렉토리(파일)" "컨테이너 내 디렉토리"

로컬에 있는 파일 중 어떤 파일들을 컨테이너 내에 디렉토리에 저장할 것인지 선택합니다.

예를들어 COPY . /app 라고 적었다면
현재 폴더에 있는 모든 파일들을 컨테이너 내부의 app폴더에 복사하겠다는 의미를 갖겠죠.

WORKDIR "컨테이너 내 디렉토리"

Dockerfile의 RUN, ENTRYPOINT 등의 명령어를 실행할 컨테이너 경로를 지정합니다.
RUN이나 CMD 같은 명령어들은 WORKDIR로 지정한 곳에서 실행합니다.

ENV "환경변수이름=값"

컨테이너 내부의 환경변수를 지정합니다.

파이썬 애플리케이션의 경우 일반적으로는
ENV PYTHONPATH=/app
ENV PYTHONUNVUFFERD=1
위 두 값을 지정한다고 하네요.

RUN "실행할 리눅스 명령어"

컨테이너 내부에서 실행할 명령어들을 적습니다.

pip install pip 나 pip install -r requirements.txt 등을 적을 수 있습니다.

두 개 이상의 명령어를 실행하려면
RUN pip install pip==[version] && pip install -r requirements.txt
이런식으로 &&를 사용하여 붙이면 됩니다.

앞에서 COPY와 WORKDIR이 실행되었기 때문에 requirements.txt를 가져올 수 있고, pip install -r 명령어로 실행할 수 있는겁니다.

dockerfile을 작성할 때에는 순서를 잘 고려하는 것이 중요하겠습니다.

CMD ["실행할 명령어", 인자 등등...]

이후 생성된 이 이미지를 통해 Conatiner을 만들 때, 실행할 명령어입니다.

CMD ["python", "main.py"]

이라고 작성한다면 이 이미지를 통해 컨테이너를 만들때마다 main.py가 자동으로 실행되는겁니다.
이를 통해 서버를 바로 껐다켰다 할 수 있습니다.

Build

작성한 Dockerfile을 바탕으로 이미지를 제작합니다.
docker build -t <빌드할 이미지 이름:태그 이름> "Dockerfile의 경로"
를 통해 이미지를 제작할 수 있습니다.

예를 들어

docker build -t yongari:latest .

라고 작성했다면
yongari:latest라는 이미지를 . (현재 폴더)에 있는 Dockerfile로 제작하겠다는 의미를 갖습니다.
.이 작아서 잘 안보이는데 현재폴더를 의미합니다.

정리를 해보면

  • 파이썬 환경 및 애플리케이션 코드를 작성 - Dockerfile 작성
  • FROM으로 베이스 이미지를 지정
  • COPY로 로컬 내 디렉토리 및 파일을 컨테이너 내부로 복사
  • WORKDIR로 RUN, CMD 등을 실행할 컨테이너 내 디렉토리 지정
  • RUN으로 애플리케이션 실행에 필요한 여러 리눅스 명령어들을 실행 - CMD로 이미지 실행 시 바로 실행할 명령어를 지정
  • docker build “Dockerfile이 위치한 경로” -t “이미지 이름:태그” 으로 이미지 빌드 - docker run “이미지 이름:태그”로 빌드한 이미지를 실행

이렇게 순서가 있겠군요.

이후 만든 이미지를 공유하고 싶다면 Docker hub에 가입 후 로그인한 뒤 push하여 Registry에 Docker 이미지를 공유할 수 있습니다.
Dockerhub에 올려두었다면 파일을 공유할 필요 없이 다른 사용자가 docker pull 명령어로 어디서든 받을 수 있겠죠.
아주편리합니다.

Docker Image size 최적화

Dockerfile을 통해 만들어진 이미지는 생각보다 용량이 클 수 있습니다.
ML 모델이나 Baseimage가 무겁다면 용량이 몇기가까지도 늘어날 수 있습니다.
이게 문제를 일으키는 경우가 존재합니다.
나열해보면

  1. 빌드 타임이 길어진다.
    새로운 이미지로 교체(업데이트)할 때마다 시간이 늘어남
    그만큼 서비스를 못하거나 네트워크 비용이 발생
  2. 런타임이 길어진다.
    도커 이미지를 실행할 때 시간이 오래 걸린다.
    컨테이너가 시작될 때 메모리에 로드되는 용량이 크다.
    Image Pull시 기다리는 시간이 길어진다.
  3. 호스트머신의 디스크가 부족할 수 있다.
    이미지 하나의 용량이 크면 호스트 머신의 디스크가 부족해진다.
    디스크가 부족할 경우, 클라우드를 사용하는 기업 기준 디스크의 용량을 늘리는 것은 추가비용을 발생시키기도 하기에 치명적임.

이러한 단점들을 보완하기 위해서 Image를 만들 때에도 잘 만들어야 합니다.
몇가지 전략이 있는데 적어보겠습니다.

작은 Base Image를 활용하여 빌드하기

파이썬을 기준으로 들어보면

python:3.9 > 파이썬 표준 이미지
python:3.9-slim > 데비안 이미지 기반 파이썬 이미지
python:3.9-alpine > Alpine Linux 기반 파이썬 이미지

등이 있습니다.
아는만큼 돈을 아낄 수 있겠군요.
만약 slim, alpine 이미지를 통해서도 개발한 코드가 문제 없이 작동한다면 Base Image를 바꾸는 것도 좋은 선택이 될 듯 싶습니다.

Multi Stage Build를 사용하기

하나의 파일에 여러 이미지를 빌드하고 사용합니다.
BASE 이미지를 바꾸면서 2개 이상의 Dockerfile이 있는 것처럼 빌드를 수행합니다.

위의 예시에서 Image를 만들때는

FROM python:3.9

를 통해 베이스 이미지를 가져왔었는데,
이를

FROM python:3.9 as build
~~
~~
~~
FROM python:3.9-slim as runtime
~~~
~~~

위처럼 나눠서 이미지를 제작합니다.

이런 과정을 거칠 경우, image의 크기가 1/8까지 줄어드는 효과를 볼 수 있습니다.

Container를 잘 패키징하기

.dockerignore로 필요 없는 파일들을 제거합니다.

.dockerignore이라는 파일을 작성한 뒤

model.pth
model.safetensor

와 같은 용량이 큰 모델 가중치들을 저장합니다.

모델의 가중치 등은 build 단계에서 제거하고, runtime이나 container을 시작하는 스크립트에서 다운받도록 하는것이지요.

command들의 순서를 최적화를 통해 캐싱을 최대한 이용할 수도 있습니다.
변경 가능성이 낮은 명령어들을 위로, 변경 가능성이 높은 명령어는 아래에 위치시키는 것이지요.

Docker Compose

하나의 Docker Image가 아닌 여러 Docker Image를 동시에 실행하고 싶거나 또는 각 컨테이너가 의존성이 있어 순서에 맞게 실행해야 하는 경우 활용할 수 있는 기술입니다.

Docker compose는 docker-compose.yml 파일에 특정 문법으로 작성합니다.

위 파일이 docker-compose파일입니다.

보시면 db, app이라고 변수를 정의한 뒤 각자에 맞는 설정이 작성돼 있음을 알 수 있습니다.

version은 Docker Compose의 버전을 의미합니다.
DB와 서버를 예로 들자면, DB, APP서버의 포트에 맞게 포워딩을 한 뒤, depends_on 을 통해 먼저 띄워질 이미지를 정의합니다.

위와 같이 설정하면 app container는 db container가 실행되어야만 실행됩니다.

yml파일을 작성했다면

docker-compose up

명령어를 통해 Container을 실행합니다.
이 때 필요한 이미지를 pull하거나 build하는 등의 과정도 포함됩니다.

이외의 명령어들을 정리해보면

docker-compose up -d : 백그라운드에서 실행하기(docker run -d와 동일) 
docker-compose down : 서비스 중단(컨테이너, 볼륨 등 삭제)
docker-compose logs <서비스명> : 로그 확인

등이 있습니다.

'
'
'
이로서 Docker에 대한 내용들을 많이 다룬 듯 합니다.
저도 자세히 알지 못하지만, 현업에서 일하다 보면 점점 능숙해지지 않을까요?
최근 서빙, pipeline 등에 대해 배우다 보니 이쪽에 제가 흥미가 있다는 것을 많이 알게 됩니다.
점차 배워서 백엔드도 잘 다룰 수 있는 엔지니어가 되고 싶네요.
아무튼 이번 Docker는 여기서 마칩니다.
감사합니다 !

0개의 댓글