개발할 때에 서비스 운영에 사용하는 서버에 직접 들어가는 일은 드물다.
보통 다 Local에서 개발하고 완료되면 Staging 그리고 이후에 Production서버에 배포한다.
근데 이때! 개발을 진행한 Local 환경과 Production 서버 환경이 다른경우...
라이브러리, 파이썬 등이 미묘하게 다를 수 있어서
배포할 때 개발할 때와 다르게 작동하지 않는 부분들이 있을 수 있다.
그래서 이걸 매번 환경 맞춰주고 이러기는 너무 힘드니까 나온 것이 바로 가상화
VMContainer가 등장Docker => Container 기술을 잘 사용할 수 있도록 나온 도구가 바로 Docker 
출처: https://collabnix.com/docker-vs-virtual-machine-vm-key-differences-you-should-know/
Docker Image로 만들어두고 재푸팅하면 Docker Image의 상태로 재실행
Docker Image
컨테니어를 실행할 때 사용할 수 있는 "템플릿"
Docker Container
Docker Image를 활용하여 실행된 인스턴스
=> 다른 사람이 만든 소프트웨어를 환경 제약 없이 바로 가져와서 사용 가능하다.
다른 사람이 만든 소프트웨어: Docker Image
OS, 설정을 포함한 실행 환경
Host OS가 무엇이든지 Linux, Window, Mac 어디에서나 동일하게 실행 가능
자신만의 이미지를 만들면 다른 사람에게 공유할 수 있으며
원격저장소 ? => Container Regeistry
- 회사에서 서비스를 배포할 때는 원격 저장소에 이미지를 업로드하고, 서버에서 받아서 실행하는 식으로 진행
docker를 공식 홈페이지에서 다운 받고
터미널에서
docekr
쳐서 확인하고
이후 docker pull mysql:8 같이 이미지 다운받아서
docker images로 제대로 다운받아 졌는지 재확인
다운 받은 docekr image를 기반으로 컨테이너를 띄워야겠다?
docker run "이미지 이름:태그"
docker run --name mysql-tutorial -e MYSQL_ROOT_PASSWORD=1234 -d -p 3306:3306 mysql:8
docker run--name mysql-tutorialmysql-tutorial이라는 이름으로 부를 수 있도록 설정함 (이거 없으면 임의로 Docker가 이름 부여함-e MYSQL_ROOT_PASSWORD=1234MYSQL_ROOT_PASSWORD는 MySQL 공식 이미지에서 사용하는 표준 환경변수로써 루트 비밀번호를 설정하기 위해 반드시 지정해야 함-d-p 3306:3306MySQL 서버의 기본 포트가 3306이므로, 외부에서 이 컨테이너에 접근할 때 호스트의 3306번 포트를 사용
mysql:8mysql이라는 이름의 이미지 중 버전 8을 기반으로 컨테이너를 생성 (:8은 버전을 의미하며 MySQL 버전을 8로 명시적으로 지정한 것)docker ps 명령어로 확인컨테이너 안으로 진입까지 해보자!
docker exec -it "컨테이너 이름(혹은ID)"/bin/bashCompute Engine에서 SSH와 접속하는 것과 유사하다.
구체적으로 MySQL 프로세스로 들어가면 MySQL 쉘 화면이 보인다.
mysql -u root -p
작동을 멈춘 컨테이너는 docker ps -a명령어로만 확인할 수 있음
docker ps는 실행중인 컨테이너만 보여줌
컨테이너는 멈춘 컨테이너만 삭제할 수 있고
이는 docker rm "컨테이너 이름(ID)"로 삭제 가능
이외에도
docker stop "컨테이너 이름(ID)": 실행중인 컨테이너 중지
docker rm "컨테이너 이름(ID)": 중지된 컨테이너 삭제
와 같은 기본 명령어가 있음
Container안에 있는 파일의 경우 보통 컨테이너를 삭제하면 자동으로 사라진다. 즉, HOST와 Container 간에 파일 공유가 되지 않는다는 것
만약 파일을 유지하고 싶다면 Host와 Container의 저장소를 공유하여야 한다.
Volumne Mount를 진행하면 Host와 Container의 폴더가 공유되는데,
-v옵션을 사용하며, `-p(Port)처럼 사용한다.
-v Host_Folder:Container_Folder
ex)
docekr run it -p 8888:8888 -v /some/host/folder/for/work:/home/jovyan/workspacejupyter/minimal-notebook
간단한 PyTorch example 코드를 실행하는 Docker Image를 생성해보자
먼저 폴더를 하나 만들고 여기에 poetry 세팅과 torch 관련 패키지 설치
Dockerfile 파일을 만들어서 작성한다.
아래는 예시
# 1. 베이스 이미지로 공식 PyTorch 이미지 사용
# PyTorch가 사전 설치된 Python 환경을 제공
FROM pytorch/pytorch:1.12.1-cuda11.3-cudnn8-runtime
# 2. 작업 디렉토리 설정
# 컨테이너 내에서 작업할 디렉토리를 /app으로 설정
WORKDIR /app
# 3. 요구 사항 파일 복사
# 호스트 머신에서 컨테이너로 requirements.txt 파일을 복사
COPY requirements.txt .
# 4. Python 패키지 설치
# requirements.txt에 명시된 모든 Python 패키지를 설치
RUN pip install --no-cache-dir -r requirements.txt
# 5. 애플리케이션 코드 복사
# 현재 디렉토리의 모든 파일을 컨테이너의 /app 디렉토리로 복사
COPY . .
# 6. 포트 노출 (필요한 경우)
# 컨테이너가 외부와 통신할 포트를 지정 (예: 8080)
EXPOSE 8080
# 7. 기본 실행 명령 설정
# 컨테이너 시작 시 실행할 기본 명령어를 설정 (예: main.py 실행)
CMD ["python", "main.py"]
베이스 이미지 선택:
FROM pytorch/pytorch:1.12.1-cuda11.3-cudnn8-runtime는 PyTorch가 사전 설치된 Docker 이미지를 사용한다.작업 디렉토리 설정:
WORKDIR /app은 컨테이너 내에서 /app 디렉토리를 작업 디렉토리로 설정요구 사항 파일 복사:
COPY requirements.txt .는 호스트 머신의 requirements.txt 파일을 컨테이너의 현재 작업 디렉토리 (/app)로 복사한다.Python 패키지 설치:
RUN pip install --no-cache-dir -r requirements.txt는 requirements.txt에 명시된 모든 Python 패키지를 설치한다.--no-cache-dir 옵션은 캐시를 사용하지 않아 이미지 크기를 줄이는 데 도움을 줌애플리케이션 코드 복사:
COPY . .는 현재 디렉토리의 모든 파일을 컨테이너의 /app 디렉토리로 복사.포트 노출:
EXPOSE 8080은 컨테이너가 외부와 통신할 포트를 지정CMD ["python", "main.py"]는 컨테이너 시작 시 실행할 기본 명령어를 설정main.py라는 Python 스크립트를 실행하도록 설정Dockerfile을 작성한 후, 다음 명령어를 사용하여 이미지를 빌드하고 실행
docker build -t my-pytorch-app .
이게 의미하는 바는
.은 현재 폴더에 Dockerfile이 있음을 의미-t "이미지이름:태그" 옵션으로 이미지 이름과 태그 지정할 수 있음docker run --rm my-pytorch-app
doker run "이미지 이름:태그"를 하는 것으로
방금 만든 이미지를 실행하는 명령어
태그가 latest인 경우에는 생략이 가능하다.
이외에도 Dockerfile에서 사용하는 것이 있는데
- EXPOSE: 컨테이너 외부에 노출할 포트 지정
- ENTRYPOINT: 이미지를 컨테이너로 띄울 때 항상 실행하는 커맨드
그럼 CMD랑 ENTRYPOINT 둘 다 도커 이미지 시작할 때
app.py실행에 쓰일 수 있는거 아닌가요?예 맞습니다. 다만
CMD의 경우에는 실행 시점에 오버라이딩할 수 있으나
ENTRYPOINT의 경우 오버라이딩이 어렵다. 그런데, 그렇기에 보안적인 면에서 더 안전하다.오버라이딩이 뭔가용?
오버라이딩이란 명령어에 추가 인자를 제공하였을 때에 해당 명령어가 덧 씌워지는 것을 말함. 즉, 말 그대로대체하는 것
이제 우리가 만든 이미지를 인터넷에 업로드 해서 배포해보자..,.
이를 위해 이미지 저장소인 Container Registry에 Docker Image Push ...
근데 Container Registry 는 다양하다
도커 허브도 있고
GCP, AWS도 있다.
GCP 쓰면 GCR 쓰는거고
AWS 쓰면 ECR 쓴다.
내 계정ID/이미지 이름 형태여야 함Build => Tag => Push머신러닝 모델이 들어간 이미지는 좀 사이즈가 크다...
이거 어떻게 최적화 할지 좀 생각해 볼만 하다. 왜냐하면 다음 문제가 있기 때문
빌드 타임: 이미지 빌드하는 시점에서의 문제
아니 이거 어떻게 기다려요??
새로운 이미지로 교체하기 위해 기다려야 하는 시간이 늘어나서 신속한 대응이 어려움
비용
네트워크 전송하는 절대량이 많아짐으로써 비용이 발생
이것도 다 돈임! 디스크 용량, 빌드 시간 ,, 네트워크,,, 등등...
런타임: 도커 이미지 실행 시점의 문제
Image pull 땡길때 기다리는 시간 엄청 길어짐Host 머신 디스크
비용의 문제가 됨.파이썬 표준이미지만 보더라도
파이썬 표준인 python:3.9가 있는가하면
파이썬 슬림인 python:3.9-slim이 있다.
슬림한 데비안 이미지 기반인데, Production 환경에 적합하다.
이 외에도 파이썬 알파인 이미지도 있는데, 이렇게 BaseModel을 고름에 있어서 가벼운 걸로 내 프로젝트에 맞춰 땡기는 것이 중요하다.
Multi Stage Build 도커 이미지를 효율적으로 작성하고 최적화하기 위한 방법, 컨테이너 이미지를 만들면서 빌드엔 필요하지만 최종 컨테이너 이미지엔 필요 없는 내용을 제외하며 이미지를 생성하는 방법이다.COPY --from 옵션을 통해서 실행 이미지로 전달할 수 있음
예시
Single Stage vs Multi Stage
Single Stage Build (비효율적인 방식)
# Base 이미지로 Python 3.9 사용
FROM python:3.9
# 작업 디렉토리 설정
WORKDIR /app
# requirements.txt 파일 복사
COPY ./requirements.txt /app/requirements.txt
# 패키지 설치 - 캐시 미사용, 용량 증가 요인
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
# 애플리케이션 파일 복사
COPY ./simple_webserver.py /app/simple_webserver.py
# 실행 명령 설정
CMD ["python", "simple_webserver.py"]
Multi Stage Build (최적화된 방식)
# Stage 1: 빌드 환경 구성
FROM python:3.9 as build
WORKDIR /app
COPY ./requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
# Stage 2: 실행 환경 구성
FROM python:3.9 as runtime
WORKDIR /app
# build 스테이지에서 필요한 파일만 복사
# /root/.local: pip install로 설치된 패키지 위치
COPY --from=build /root/.local /root/.local
# 애플리케이션 파일만 복사
COPY ./simple_webserver.py /app/simple_webserver.py
# PATH 환경변수 설정 - pip 설치 경로 추가
ENV PATH=/root/.local:$PATH
# 실행 명령 설정
CMD ["python", "simple_webserver.py"]
이거 잘 활용하면 1/10 으로 줄일 수도 있음!
.dockerignore로 필요없는 파일들 제거.pt, .pth 파일과 같은 큰 사이즈 asset들은 빌드에서 포함하지 않고, 빌드 타임 혹은 컨테이너 시작하는 스크립트에서 다운로드Dockerfile 안에서 commnad들의 순서 최적화를 통해 캐싱을 최대한 이용이거 왜 쓰나요?
1. 구체적인 경우로 따지면
에어플로우는 항상 웹서버와 스케줄러를 같이 띄워야 하는데 도커 이미지에서 한번에 띄우는 거 말고 각각을 따로 띄워야 할 때도 있음
그때 사용하는게 바로 Docker Image2. 혹은 의존성 문제가 있을 수 있음
A먼저 띄우고 나중에 B Container를 띄워야 하는 경우가 있을 수 있음.
예를 들면 A는 데이터베이스고, B는 웹서비스인 경우가 이에 해당함3.
docker run옵션 관리 및Volumn Mount관리에 유용
docker run할 때 옵션이 너무 다양하고,Volumn Mount를 하지 않았다면 데이터가 모두 날아갈 판...
이럴 때 유용하게 사용하는 것이 Docker Compose이다.
docker-compose.yml 파일에 작성하여 운용한다고 생각하면 된다.MySQL과 애플리케이션 서버를 연동하는 docker-compose.yml 파일의 기본 구조 예시이다.
# Docker Compose 파일의 버전 지정 (최신 버전 중 하나)
version: '3'
# 서비스(컨테이너) 정의 섹션
services:
# MySQL 데이터베이스 서비스 설정
db:
# MySQL 8.0 공식 이미지 사용
image: mysql:8.0
# 컨테이너에 custom 이름 부여 (기본 이름 대신 사용)
container_name: mysql_db
# 데이터베이스 초기 설정을 위한 환경 변수들
environment:
# MySQL root 계정의 비밀번호
MYSQL_ROOT_PASSWORD: rootpassword
# 애플리케이션에서 사용할 데이터베이스 이름
MYSQL_DATABASE: myapp_db
# 일반 사용자 계정 이름
MYSQL_USER: user
# 일반 사용자 계정 비밀번호
MYSQL_PASSWORD: userpassword
# 호스트와 컨테이너 간 포트 매핑 (외부에서 MySQL 접근 가능)
ports:
- "3306:3306"
# 데이터 영구 보존을 위한 볼륨 마운트
volumes:
- db_data:/var/lib/mysql
# 컨테이너 간 네트워크 연결 설정
networks:
- app-network
# 스프링 부트 애플리케이션 서비스 설정
app:
# 로컬 Dockerfile을 사용하여 이미지 빌드
build:
# 빌드 컨텍스트 (Dockerfile이 위치한 디렉토리)
context: .
# 사용할 Dockerfile 지정
dockerfile: Dockerfile
# 애플리케이션 컨테이너에 custom 이름 부여
container_name: myapp
# DB 서비스가 완전히 시작된 후에 이 서비스 시작
depends_on:
- db
# 호스트와 컨테이너 간 포트 매핑 (애플리케이션 접근)
ports:
- "8080:8080"
# 스프링 부트 데이터베이스 연결 설정 환경 변수
environment:
# MySQL 서비스와 연결되는 JDBC URL
# 'db'는 서비스 이름을 참조 (Docker 내부 네트워크)
SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/myapp_db
# 데이터베이스 사용자 이름
SPRING_DATASOURCE_USERNAME: user
# 데이터베이스 사용자 비밀번호
SPRING_DATASOURCE_PASSWORD: userpassword
# 애플리케이션을 데이터베이스와 같은 네트워크에 연결
networks:
- app-network
# 영구 데이터 저장을 위한 볼륨 정의
volumes:
db_data:
# 컨테이너 간 통신을 위한 네트워크 정의
networks:
# 브릿지 네트워크 드라이버 사용 (컨테이너 간 통신)
app-network:
driver: bridge
데이터베이스 서비스 (db)
애플리케이션 서비스 (app)
docker-compose up -d 명령어로 실행 # background옵션docker-compose ps 명령어로 상태 확인docker-compose logs 명령어로 로그 확인docker-compose down 서비스 중단(컨테이너, 볼륨 등 삭제)참고로
docker-compose.yml파일을 수정하고docker-compose up을 하게되면 컨테이너를 재생성하고 서비스를 재시작함.
docker-compose up이 완료되면docker ps명령어나,docker-compose ps명령어로 현재 실행되고 있는 컨테이너를 확인할 수 있다.
이외에도
- secrets
보안이 필요한 데이터 전달- configs
컨테이너에 사용할 config 파일- command
컨테이너가 시작될 때 실행할 명령 지정등의 기능이 있다.
Step 1: MySQL Docker 이미지 다운로드
docker pull mysql:8.0
Step 2: 컨테이너 실행
docker run --name mysql-container -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=testdb -e MYSQL_USER=testuser -e MYSQL_PASSWORD=testpass -p 3306:3306 -v mysql_data:/var/lib/mysql -d mysql:8.0
MYSQL_ROOT_PASSWORD, MYSQL_DATABASE 등 환경 변수 설정 필수.-v mysql_data:/var/lib/mysql).에러 핸들링:
-p 3307:3306).Step 1: Jupyter Lab Docker 이미지 다운로드
docker pull jupyter/base-notebook
Step 2: 컨테이너 실행
docker run --name jupyter-lab -p 8888:8888 -v $(pwd):/home/jovyan/work -d jupyter/base-notebook
/home/jovyan/work로 마운트해 데이터 공유.에러 핸들링:
chmod 활용).Step 1: docker-compose.yml 작성
version: "3.9"
services:
jupyter:
image: jupyter/base-notebook
ports:
- "8888:8888"
volumes:
- ./workspace:/home/jovyan/work
Step 2: Compose 실행
docker-compose up -d
에러 핸들링:
docker-compose.yml 파일 경로 확인. Step 1: docker-compose.yml 작성
version: "3.9"
services:
airflow:
image: apache/airflow:2.6.1
environment:
- AIRFLOW__CORE__LOAD_EXAMPLES=False
volumes:
- ./dags:/opt/airflow/dags
ports:
- "8080:8080"
Step 2: 초기화 및 실행
docker-compose up airflow-init
docker-compose up -d
에러 핸들링:
dags 폴더 위치 확인 및 DAG 파일 포맷 점검.Step 1: docker-compose.yml 작성
version: "3.9"
services:
fastapi:
build: .
ports:
- "8000:8000"
db:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=fastapi_db
ports:
- "3306:3306"
Step 2: Docker Compose 실행
docker-compose up -d
에러 핸들링:
DB_URL 환경 변수 확인. docker logs [컨테이너명] 명령으로 원인 파악.