이미지 출처는 링크 or 아이펠 교육 자료입니다.

2-1. Docker 소개 및 설치


학습 목표

  • Docker 기술에 대한 이해, 기술 장단점 살펴보기
  • 웹 서버를 통한 Docker 이용 및 실습
  • 도커 스웜, 도커 컴포즈 이해 및 실습
  • 쿠버네티스, Kubeflow 학습

개발 환경 vs 서비스 환경

  • 개발 환경

    • macOS 13.3.1
    • FastAPI 0.106.0
    • CUDA 11.7.1
    • Python 3.9
    • openssl 0.9.5a
  • 서비스 환경

    • Ubuntu 20.04
    • FastAPI 0.108.0
    • CUDA 12.3.1
    • Python 3.11
    • openssl 1.0.2n

보통은 이러한 차이로 인해 배포가 쉽지 않을 것(최악의 경우, 서비스 데스 발생)

  • 라이브러리 충돌
  • 버전 충돌
  • 데이터베이스 등의 충돌...

전통적 서비스 배포 방식의 문제점

  • 개발한 서비스를 SFTP, SSH를 통해 FastAPI로 배포
    • 새로운 서버를 올리면, 기존 서버가 죽을 수도 있음
    • 그 새로운 서버마저 죽을 수 있는 것
    • 새로운 서버가 정상적으로 잘 작동해도, 기존 서버를 이용하던 사용자들의 서비스가 갑자기 중단되는 문제가 발생할 수 있음

Docker

  • OS 수준의 가상화 PaaS
  • 컨테이너로 여러 프로그램을 자원 측면에서 효율적으로 운영하게 됨

Docker 구조

  • 현재, 대부분의 컴퓨팅 환경은 멀티 코어로 구성되어 있음

Virtual Machine

  • 서버 박스에 여러 가지 서버가 추상적으로 가상화되어 돌아가면 -> 코어를 효율적으로 사용할 수 있음(ex. 버추얼 머신)
  • 독립적인 운영
    • 서비스 A와 B를 동시에 가동할 때, 서비스 B를 위해 설정 변경 시 A가 영향받는 경우가 생김
    • 샌드박스 구조 -> 별도의 가상화 서버를 사용하기 때문에 해킹 우려, 보안 이슈 감소
  • 서버 박스의 하드웨어 시스템을 효율적으로 사용

단점

  • 서버 시스템에 또다른 오퍼레이션 시스템을 올리게 되다보니, 용량 이슈 발생
    • 오퍼레이팅 시스템을 RAM 위에 올리고 구동을 해야 하기 때문에 막대한 자원 사용량이 요구됨
    • 오퍼레이팅 시스템 내부의 커널, 스케줄러 등이 독립적으로 모두 동작해야 하기 때문에 CPU 오버헤드 발생

Docker

  • 앞선 Virtual Machine의 단점들을 예방해줄 수 있는, 더 경량화된 Virtual Machine으로 봐도 무방
  • 서버 박스 위에, Docker Engine이 올라가고 Guest OS가 올라가는 형태
    • Host OS와 다른 Guest OS가 올라가는 것은 맞음
    • 단, Guest OS에 있는 커널들은 Host OS에서 공유받아 생성되는 형태이기 때문에, OS 내부의 공통적 동작들은 Host OS로붗터 공유받아 사용하는 형태로 문제가 적어짐


2-2. 컨테이너 환경 구성 및 이미지 활용 사례 학습


도커 설치

기존 설치 완료 -> 실습 바로 진행

$ docker help


$ docker ps -a


$ docker images


docker command line 이해하기_이미지 다운로드

  • 이미지 받아오기

    • 띄워야 하는 환경의 형상 담당(디스크의 개념)
  • 컨테이너 생성

    • 실제로 동작하는 프로세스(docker run을 이용하면 컨테이너 생성됨)
# hello-word 이미지 pull
$ docker pull hello-world

# 다운 확인
$ docker images | grep hello-world


  • hello-world 출처: Docker Hub
    • 이 Docker Hub가 오피셜한 저장소라고 보면 됨

docker pull {url/image:tag}

  • url : 저장소(설정하지 않을 경우, Docker Hub에서 다운)
  • image : 이름
  • tag : 지정 안할 경우 latest로 자동 지정

# hello-world 이미지 제거
$ docker rmi hello-world

  • 잘 삭제되었는지 확인
    • 현재 생성했던 hello-world:latest가 사라졌음을 알 수 있음

docker command line 이해하기_컨테이너 사용

  • docker run
    • 해당 이미지가 없을 경우, pull 동작을 내부적으로 수행함
# hello-world pull & run
$ docker run -dit --name test hello-world

  • 실행된 후, 잘 종료되었음을 확인할 수 있음
# 현재 도커의 컨테이너 리스트 확인(hello-world)
docker ps -a | grep hello-world


  • 컨테이너가 띄워져 있는 상태에서 docker rmi 실행 시, 동작 안함

컨테이너 삭제 후 ➡️ 이미지 삭제 가능!

  • rm의 기본 동작: 잘 삭제 된 경우, 해당 값을 repeat해줌
$ docker rm {컨데이너 아이디}


  • 랜덤으로 지정되는 ID 값으로 프로세스 관리하기에는 어려움 있음
    • --name 옵션 사용
$ docker run --name test hello-world

  • 삭제 시, 지정했던 이름으로 삭제가 가능해짐
$ docker rm test

같은 이름을 부여하게 되면, 나중에 받으려는 이미지의 경우, 에러가 발생할 것


  • 출력 로그 확인
$ docker logs {name or ID}

  • -dit
    • detached : 새로 생성한 컨테이너를 백그라운드 실행 & 생성한 Container ID 출력
    • interactive : 호스트 stdin - 컨테이너 stdin 연결
      • 사용자가 입력한 표준 입력 ➡️ 컨테이너가 처리
    • tty
      - 사용자 터미널 tty 드라이버를 컨테이너에 연결
      - -i 옵션과 함꼐 사용
      - 터미널 출력 색상, REDACTED 출력 등 처리 요소 조정 가능

참고: - 와 --
- : 단어 가장 처음 글자 하나씩 사용 가능
-- : 단어 단위 옵션(fully)

  • 추가 실습
$ docker run -dit --name nginx -p 8000:80 nginx

  • localhost:8000 접속

  • logs 옵션 -f 사용

    • 팔로우됨
  • 프로세스 계속 진행 중인 상태

    • 컨테이너 관리 잘 해줘야 함!
$ docker stop nginx
$ docker rm nginx # 강제 삭제 시 docker rm -f nginx

  • 포트 포워드 명령 없이 바로 실행

    • 실행은 잘 되나, 8000번 포트로 접근 시 접근이 되지 않음
    • 서버 안쪽에서 80번 포트로 떠 있는 형태 -> 외부(Host OS)에서는 브릿지 없이 접근 불가
  • 내부에서 접근


  • 모든 자원 삭제
$ docker rm -f nginx
$ docker rmi nginx


Docker Nginx 컨테이너 구성

  • 포트 바인딩 : 내부 컨테이너와 외부의 호스트 간 브릿지


2-3. Docker 이미지와 레지스트리


깃허브 레포지터리 클론

1. tmp 폴더에서 진행

$ cd /tmp
$ git clone https://github.com/KennethanCeyer/mlops-quicklab.git

  • 잘 가져왔는지 확인
$ ll | grep mlops-quicklab

  • mlops-quicklab 폴더로 이동(확인)
$ cd mlops-quicklab
$ ll

  • 폴더 구조 확인
    • dockers 폴더 안의 예제 활용 예정
$ tree -L 2 .


  • 🗂️ dockers


2. docker bulid

  • basic 폴더로 이동
$ cd basic

  • Dockerfile
$ cat Dockerfile

  • Dockerfile 구조 파악
    • FROM, WORKDIR...: 예약어(동작 담당)
    • FROM
      • DockerHub의 python 태그 3.11 image download
    • WORKDIR
      • 기본 폴더 지정(없다면 생성해서 기본 폴더로 잡는 형태)
    • COPY
      • 컨테이너 내부에 없는 파일을 Host에서 찾아서 컨테이너 안으로 넣어줌 {컨테이너} {Host}
    • RUN apt-get update
      • 데비안 계열 리눅스가 사용되기 때문에 apt-get 사용하여 install 가능
    • RUN pip install ...
      • 패키지 설치
    • CMD ...
      • "프로그램 이름" : 프로그램 종료 시점 == 컨테이너 종료 시점
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"]

RUN : 이미지를 만드는 타이밍에 동작
CMD : 이미지를 실행시키는 타이밍에 동작


  • 📝 app/main.py 실행해보기
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

  • requirement.txt 설치
$ pip3 install -r requirements.txt # 로컬에 설치됨

  • uvicorn으로 실행
$ uvicorn app.main:app --host 0.0.0.0 --port 8000


  • docker bulid
    • . : 현재 폴더 위치를 의미
    • -t : tag
$ docker build . -t fastapi-app:latest #latest는 생략 가능

  • 이미지 확인

docker login 옵션이 뜨는 경우 -> 로그인 진행 후 사용


3. docker run

  • foreground에서 실행
$ docker run --name fastapi-app -p 8000:80 fastapi-app
  • 0.0.0.0:8000

컨테이너 한번에 종료

$ docker rm -f $(docker ps -qa)

이미지 한번에 삭제

$ docker rmi $(docker images -a)

필자 노트북의 경우, 기존에 사용하던 삭제하면 안되는 컨테이너가 있어 컨테이너 ID 지정하여 특정 컨테이너만 삭제함.


Docker Bulid 과정

  • 도커 파일 ➡️ 도커 이미지 ➡️ 도커 레지스트리 내 도커 이미지(docker push <IMAGE>:<TAG>)
    ➡️ 호스트 도커 실행환경 컨테이너(docker run <IMAGE>:<TAG>)

Docker 주요 명령어

명령어설명
FROMbase image 지정(모든 Dockerfile은 FROM으로 시작)
ENV환경 변수
USER명령어 실행 사용자 계정 설정
COPY파일 or 디렉터리를 이미지에 복사하는 명령어
WORKDIR작업 디렉터리 설정
RUN컨테이너 내 명령어 실행(패키지 설치 등)
CMD컨테이너 시작 시 실행 명령어
ARGDockerfile에 쓰는 변수 정의(build할 때 끌어옴)


2-4. Docker 이미지 구조와 최적화


Docker image 구조

  • Docker Bulid 시, 위의 명령어부터 실행되면서 실행 라인이 레이어 형태로 쌓이고 캐시 처리가 되는 것
  • Docker Build 시 임의로 중단해도 다시 Build를 하면, 실행이 끊긴 부분부터 다시 빌드하는 특징을 가지고 있음!
    • 마지막까지 빌드되었던 스냅샷까지는 캐시가 있기 때문에 그 이후부터 다시 시작하는 것이 가능한 것
    • 매번 모두 다시 실행하는 것이 아님 ➡️ 단, Dockerfile 변경점 발생 시 그 지점부터 다시 실행됨!!
  • 파일 변경이 빈번한 커맨드가 위에 있다 ➡️ 비효율성이 아주 커진다는 의미이기도 함
  • 커맨드 개수가 많아지는 것 또한 오버헤드를 만드는 것임..
    • 즉, 최대한 1개의 커맨드로 표현될 수 있도록 잘 줄여줘야 함

Docker 최적화: Dive

$ brew install dive # 설치

  • 이미지 찾기
$ dive fastapi-app


기본: 4분 26초 소요

[소스 코드] ➡️ [FROM xxx: 24초] ➡️ [Add Codes: 1초] ➡️ [Install Deps: 4분] ➡️ [CMD xxx: 1초] ➡️ Docker Image

소스코드 업데이트 시마다, 4분이 넘는 시간동안 다시 기다려야한다는 것이 매우 비효율적인 것


[캐싱] 4분 2초(-9%)

[소스 코드] ➡️ [FROM xxx: 캐싱] ➡️ [Add Codes: 1초] ➡️ [Install Deps: 4분] ➡️ [CMD xxx: 1초] ➡️ Docker Image


[캐싱] 34초(-87%)

  • 베이스와 서비스로 나눠, 변경점이 잦은 부분을 서비스로 올려 빠르게 캐싱

자주 쓰는 Docker Image 바로 사용할 수 있게 준비하기

$ cd optimization
$ docker build -f Dockerfile.base -t fastapi-app:base .

  • Dockerfile의 FROM절의 로컬 환경의 fastapi-app:base로 변경

  • 빌드 ➡️ 속도가 빨라짐을 체감할 수 있음

docker build. -t fastapi-app:latest


  • 소스코드 변경을 통해, 변경점 반영되었는지 확인
$ vi app/main.py # Hello word 출력 부분 수정 후 저장
$ docker build -t fastapi-app:latest .
$ docker run -dit --name fastapi-app -p 8000:80 fastapi-app:lastest


  • DockerHub에 Repository 생성하여 원격 실행 테스트(base로 배포 예정)

  • 유저 이름이 앞에 있어야, 해당 Docker Repository를 사용하는 것

    • tag를 이용해 수정
    • 동일한 타겟으로 잘 잡히는 걸 확인할 수 있음(파일 복사 아님, 바로가기가 추가된 것)
$ docker tag fastapi-app:base hayannn/fastapi-app:base

  • push
$ docker push hayannn/fastapi-app:base

로그인이 안되어 있는 경우, 에러 발생하니 로그인 후 재실행하면 됨



2-5. 더 나아가기: 부하분산


트래픽 증가에 대한 대응

  • 사용자가 늘어나면, 사용자 요청 수도 늘어나는 양상을 보임

RPS

  • Request per Seconds
    • Requests / Seconds
    • 초당 요청 수
    • 서버 애플리케이션 성능 측정 지표
    • 시스템 처리 능력 및 안정성 평가
    • 트래픽이 몰린 상황에서의 서비스 응답성을 파악

Scale-up, Scale-out

다음 내용 참고: CH1. 사용자 수에 따른 규모 확장성_💭 수직적 규모 확장 vs 수평적 규모 확장

  • Scale-up

    • CPU 코어 증가 등의 방식으로 더 좋은 CPU로 대체
    • CPU 교체 과정에서 서버의 down time이 불가피하게 발생
    • 단일 요청에 대해서는 응답이 효과적이나, 사용자 증가 시 단일 머신 한계로 인해 효과적인 요청 처리가 어려움
  • ⭐️ Scale-out ⭐️

    • 동일 CPU를 여러 개로 늘린 뒤, 하나의 클러스터처럼 동작하도록 함
    • 단일 요청 자체가 빨라지지는 않지만, 수많은 요청이 왔을 때의 그 요청이 잘 분배되는 효과가 있는 것

Load Balancing

다음 내용 참고: CH1. 사용자 수에 따른 규모 확장성_💭 수직적 규모 확장 vs 수평적 규모 확장



2-5. 예제 실습: 도커 스웜


Docker Swarm

  • 도커에 내장되어, Docker 이외의 별다른 설치 작업 불필요
  • 도커 오케스트레이션 관점 제어
  • 호스트 도커가 여러 개일 때, 각 워커 제어가 가능한 클러스터링 지원

실습

현재 디렉터리 : /tmp/mlops-quicklab/dockers/swarm

  • main.py
    • is_prime : 계산량이 많이 필요한 함수를 서비스에서 돌린다고 가정하기 위해 만듦
    • find_primes : 모든 소수를 찾는 로직
$ python3 app/main.py

  • 0.0.0.0:8000/docs로 접속

ModuleNotFoundError: No module named 'pydantic_settings'

  • 해결
$ pip3 install -r requirements.txt

  • locustfile.py
    • locust: 부하분산 테스팅 도구
from locust import HttpUser, between, task
import random


class WebsiteUser(HttpUser):
    host = "http://localhost:8000"
    wait_time = between(1, 5)

    @task
    def get_primes(self):
        end = random.randint(1000, 50000)
        start = random.randint(1, end // 2)
        self.client.get(f"/primes?start={start}&end={end}")
  • 설치 및 실행
$ pip install locust
$ locust -f locustfile.py

  • 모두 에러가 나기 시작: 서버가 꺼져있기 때문
  • 서버 실행 & locust 재구동

  • 실패가 발생하지 않고, RPS가 15 ~ 18 선을 유지

  • 유저 200

  • 유저 1,000 ➡️ failures 발생 시작

  • 유저 2000


로드밸런서 적용

  • DockerHub : kennethan/fastapi-app-prime
$ docker run -dit --name fastapi-app -p 8000:8000 kennethan/fastapi-app-prime
$ docker ps -a | grep fastapi-app

  • Google Cloud 이용

    이전에 사용하던 계정이 있다면, 새 계정으로 진행할 것

  • compute engine 검색 및 사용 클릭

  • Create instance 클릭

  • 설정

    • 이름 : docker-swam-worker1
      • 2, 3까지 만들어줘야 함
    • 리전 : asia-northeast3(서울)
    • E2
    • 머신 유형 : e2-micro
    • OS와 스토리지에서 변경 클릭 -> 운영체제 ubuntu, 크기 40으로 변경
    • 네트워킹에서 http, https 방화벽 모두 허용
  • 인스턴스 생성 완료



여기까지 진행 후, 창을 모두 닫고 다시 SSH 창 열기

  • docker 실행(3가지 모두 반복 수행)
$ docker run -dit --name fastapi -p 80:8000 kennethan/fastapi-app-prime
$ docker ps -a
  • docker-swam-worker1
  • docker-swam-worker2
  • docker-swam-worker3


  • 이렇게 설정한 IP로 접근하여, 부하분산 테스트 해보기
    • Host 주소를 IP 주소로 변경

  • docker swarm 사용
    • docker-swam-worker1에 이 명령어를 실행한 뒤, 나오는 커맨드 라인 복사해서 ➡️ docker-swam-worker2, 3에 모두 붙여넣기
      $ docker swarm init
      $ docker swarm join --token {토큰내용}

  • docker swarm 안에 있는 모든 서비스 상태 확인
    • docker service ls


  • docker node ls : 노드 내용 확인
    • 이렇게 3가지 노드가 1개의 클러스터를 이루고 있는 것

  • docker-compose.yml을 이용한 deploy

    • 포트 80으로 변경
  • 내용 copy

    • cat docker-compose.yml | pbcopy
  • 1번 SSL 콘솔

$ cd /tmp
$ vi docker-compose.yml

  • 배포
$ docker stack deploy -c docker-compose.yml fastapi
$ docker service ls

  • 1,2, 3번에 나눠서 뜬 걸 확인할 수 있음

    • 1 -> 1개

    • 2 -> 2개

    • 3 -> 1개


  • 도커 컨테이너 정리: docker rm -f $(docker ps -qa)
    • 1, 2, 3 모두 실행 후, docker service ls로 서비스 확인
      • 아직, 서비스가 중단되지 않았기 때문에 컨테이너를 다시 띄워주는 기능이 있음!

  • 이후, Locust로 테스팅 & FastAPI를 사용해보면, 속도가 훨씬 향상되었음을 알 수 있음

로드밸런싱 구성

  • External LB : 물리적 구성(하드웨어 로드밸런서)이기 때문에 부하 적음


2-7. 예제 실습: 도커 컴포즈


Docker Compose

  • 도커 컨테이너 관리 목적
  • 개발, 테스트 목적의 단일 호스트 -> 여러 복합 컨테이너 구성 & 관리에 사용

Docker Compose Install


예제: FastAPI + MySQL 서비스 구성

디렉터리 : /tmp/mlops-quicklab/dockers/compose

  • 실행
$ docker compose up


  • 유저 생성 및 확인



  • 종료

  • docker compose 내리기
$ docker compose down --volumes

Volume?

  • "컨테이너가 죽더라도, DB에 있는 데이터 등의 민감한 데이터들이 날아가지 않아야 하며" + "여러 컨테이너가 데이터를 공유해야하기 때문"에 Host에 내용을 저장하고, 그걸 링크로 불러오는 것

  • 백그라운드에서 실행
$ docker compose up -d

  • 실행 중인 컨테이너 삭제 & 재업로드
    • 삭제하고, 재업로드를 하더라도 변경점만을 빠르게 반영함을 알 수 있음

컨테이너의 실행 여부와 관련없이, docker compose 동작 가능



2-8. 더 나아가기: Kubernetes


Kubernetes

  • 컨테이너화된 애플리케이션 자동 배포, 스케일링, 오케스트레이션 담당
  • 컨테이너 기반 애플리케이션의 배치 자동화, 스케일링 및 운영 관리 기능까지 제공함
  • 노드 그룹 클러스터링(고가용성, 확장성)
  • 로드 밸런싱, 서비스 디스커버리, 롤링 업데이트 등
  • 대규모 커뮤니티, 클라우드 및 온프레미스 환경까지 지원

minikube에서의 Kubernetes 구성


minikube

curl -LO https://github.com/kubernetes/minikube/releases/latest/download/minikube-darwin-arm64
sudo install minikube-darwin-arm64 /usr/local/bin/minikube


  • start
$ minikube start --cpus 4 --memory 7844 --disk-size=40g

  • 설치 완료 후 실행 확인


  • deploy
    • kubectl 이용
$ kubectl create deployment hello-minikube --image=kicbase/echo-server:1.0
$ kubectl expose deployment hello-minikube --type=NodePort --port=8080
$ 

  • 서비스 확인
$ kubectl get services


  • 포트 바인딩
$ kubectl get services hello-minikube
$ minikube service hello-minikube
$ kubectl port-forward service/hello-minikube 7080:8080


  • 기존 서비스 삭제
$ kubectl delete service hello-minikube
$ kubectl delete deployment hello-minikube


쿠버네티스 기반 MLOps 구성: Kuberflow

  • 오픈 소스 플랫폼
  • ML 워크플로우 간소화, 쿠버네티스 상의 배포에 도움
  • 쿠버네티스 위에서 ML pipeline 구축, 관리, 배포까지를 할 수 있게 해줌
  • ML 프레임워크 사용 가능: Tensorflow, PyTorch 등
  • Jupyter, Training, Serving, Piplines, Katib 포함
  • 분산 훈련 & 하이퍼파라미터 최적화까지 지원해줌

Kubeflow 아키텍쳐 및 구조


Kubeflow를 도입하는 이유는?

  • 1개의 환경에서 불오나전한 설정을 하게 되어 설정 충돌이 발생될 수 있음
    • 협업 및 멀티테넌시 지원으로 독립적 환경을 구동하게 됨
  • 메뉴얼 제어 기반(수동적 학습, 서빙 프로세스)으로 사용
    • 프로세스의 표준화, 자동화로 해결
  • 클라우드(or 로컬 환경)에 의존적인 환경
    • 쿠버네티스 오케스트레이션 위에 구동을 하니, 이식성이 높아짐
  • 자원 확충, AutoScaling 미지원 문제
    • 자원 확충을 유연하게 할 수 있으며, 스케줄러를 통해 Auto Scaling 가능!
profile
언젠가 내 코드로 세상에 기여할 수 있도록, Data Science&BE 개발 기록 노트☘️

0개의 댓글

관련 채용 정보