cuda toolkit 12.1 지원

pytorch 2버전이상부터는 cuda toolkit 12.1으로 사용하는게 pip 이나 설치가 편하기에, toolkit 12.1을 설치하려고함.

sudo ubuntu-drivers autoinstall

 

cuda toolkit 12.1 install

ubuntu 공식 도큐먼트(URL)을 따라서 설치하려고함. 

 

Trouble shooting 1: package has invalid Support PBheader, cannot determine support level

ubuntu 그레픽드라이버 유틸리티인 `ubuntu-drivers list`로 현재 설치가능한 드라이버를 찾다보면 위와 같은 경고를 볼 수 있는 습니다. 아래와 같이 해결했습니다 [URL]. 

$ sudo apt update
$ sudo apt upgrade

 

Trouble shooting 2: but it is not going to be installed

 

sudo apt remove nvidia*
sudo apt autoremove
반응형

API 요청을 관리하는 데에는 여러 이유가 있습니다. 그 중에서도 토큰 버킷을 사용하는 이유는 다음과 같습니다:

  1. 트래픽 제어: 토큰 버킷을 사용하면 API로 들어오는 트래픽을 제어할 수 있습니다. 이를 통해 서버에 과도한 부하가 걸리는 것을 방지하고, 서비스의 안정성을 유지할 수 있습니다.
  2. 사용량 제한: 토큰 버킷을 이용하면 API를 사용하는 클라이언트의 요청 수를 제한할 수 있습니다. 이는 과도한 사용량으로 인한 서버 과부하를 방지하고, 공정한 서비스 이용을 보장합니다.

 

TokenBucket 만들기

토큰 버킷 패턴은 고정된 속도로 토큰을 생성하고, 요청이 들어올 때마다 토큰을 소비하여 일정량의 토큰을 가지고 있는지 확인하여 처리하는 방식입니다. 알고리즘은 아래와 같습니다.

  1. TokenBucket은 버켓(바구니)가 있다고 가정합니다.
  2. 요청이 들어오면, 버켓에서 1개를 소비해서 그 다음의 로직을 진행시킵니다. 요청이 들어왔을 때 버킷에 토큰이 없으면 drop합니다.
  3. 고정된 속도(r)로 버킷에 토큰채웁니다. 

https://www.researchgate.net/figure/Diagram-representing-token-bucket-algorithm_fig1_351452059

 

TokenBucket 을 middle ware 에 추가

- FastAPI의 starlette을 베이스로 작성되어있어서, 이 미들웨어를 서브클레싱해서 손쉽게 token bucket을 사용할 수 있습니다.

- starlette.middleware.base.BaseHTTPMiddleware을 초기화시 app을 받게되어있고 이 베이스클레스는 dispatch라는 함수를 구현하게어 있습니다.

class BaseHTTPMiddleware:
    ...
    
    async def dispatch(
        self, request: Request, call_next: RequestResponseEndpoint
    ) -> Response:
        raise NotImplementedError()  # pragma: no cover

 

dispatch을 함수를 아래와 같이 구현해서 bucket.consume_token()이라는 함수가 bool으로 false or True을 구현하게 함에따라, 예외를 일으킬것인지 다음 로직을 실행시킬것인지를 결정하게 작성합니다.

 

위의 구현을 위해 아래와 같이 tokenbucket을 정의합니다.

초기화시, 버킷의 토큰 최대 저장량(max_len)과 토큰을 채우는 시간(r, refill_rate)을 지정하여 아래와 같이 작성합니다.

 

실행

- 다음과 같이 python3으로 fastAPI을 구동시킵니다.

(misc) (base) heon@gpusvr03:~/repositories/misc/tokenbucket_ex$ python3 app.py 
INFO:     Started server process [4121818]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:30000 (Press CTRL+C to quit)

 

그 후 요청 15개를 보내봅니다. 아래와 같이 10개를 토큰을 다 소비한경우 5개의 요청이 예외처리됩니다.

>>> import requests
>>> requests.get("http://0.0.0.0:30000/")
<Response [200]>
>>> for _ in range(15):
...     requests.get("http://0.0.0.0:30000/")
... 
<Response [200]>
<Response [200]>
<Response [200]>
<Response [200]>
<Response [200]>
<Response [200]>
<Response [200]>
<Response [200]>
<Response [200]>
<Response [200]>
<Response [500]> # 토큰 10개 소비되어 예외처리
<Response [500]>
<Response [500]>
<Response [500]>
<Response [500]>

 

반응형

"-e" 옵션은 설치한 경우 파이썬 패키지를 담는 "site-packages" 디렉토리에 설치되는 것은 동일하나, 심볼릭 링크로 설치가되어 원본소스코드가 변경된 후, 별도의 pip install 명령어 없이, import시 바로 변경사항이 반영되는 옵션

 

pip install -e 옵션

종종 github에서 `pip install -e .`로 "-e"옵션을 함께 전달하여 파이썬 패키지를 설치하라는 리포지토리가 있습니다

 

예시로 아래와 같은 패키지 디자인의 경로가 있다고 가정합니다. 

(camelyon) @gpusvr03:~/repositories/camelyon$ tree -L 1
.
├── camelyon
├── data
├── LICENSE
├── logs
├── notebooks
├── README.md
├── setup.py
├── tiling_version16.py
├── tiling_version17.py
└── utils.py

이 패키지의 setup.py에는 다음과 같이 설치 명령어가 있습니다. 이 setup.py을 이용하여 "$pip install ."을 해보겠습니다.

아래와 같이 잘 설치됨을 알 수 있습니다. 패키지 설치장소는 가상환경인 "/home/heon/anaconda3/envs/camelyon"에 설치되었습니다. 

 

 

설치한 패키지를 들어가보면, setup.py에서 지정한 p.y파일들이 들어가있습니다.

 

이 패키지를 지우고, pip install -e 옵션으로 설치해보겠습니다. 패키지가 설치된 장소를 들어가보면, *.egg-link만 있습니다.

이 링크의 내용물은 경로만 담겨져 있습니다.

 

*egg.link 파일은 내부에 파일하나나 디렉토리만 담고있습니다. 심볼릭링크를 지원하기위한 파일입니다. 즉, 설치는 했지만 바라보는 소스코드는 원본을 바라보고 있어서, 디렉토리의 변경사항을 바로 추적할 수 있습니다.

https://wiki.python.org/moin/PythonPackagingTerminology

반응형
import mlflow
from setting import parent_run_id

mlflow.set_tracking_uri(TRACKING_URI)

child_run_ids = list()
for run in client.search_runs(experiment_ids=[6]):

    if "mlflow.parentRunId" not in run.data.tags:
        continue

    if run.data.tags["mlflow.parentRunId"] != parent_run_id:
        continue

    child_run_ids.append(run.info.run_id)
반응형

이 포스팅은 GitHub Actions를 사용하여 이미지를 생성하고, Amazon Elastic Container Registry (ECR)에 업로드한 후, EC2 인스턴스에 배포하는 단계를 설명합니다.

이 과정은 크게 아래와 같이 이루어집니다.

  1. 개발자가 trigger: 개발자가 github에 코드를 commit / push 등 엑션을 하하고, 리포지토리에서 action trigger가 실행
  2. Action에서 이미지 빌딩 및 푸시: github action에서 이미지 빌드: Dockerfile을 이용하여 이미지를 만들고, 만든 이미지를 ECR에 PUSH
  3. Actiond에서 SSH 접속 후 Pull 및 Run: Action에서 EC2인스턴스에 SSH로 접속하여, ECR에 올려져있는 이미지를 PULL 및 RUN

GitHub Actions를 사용하여 이미지를 생성하고, Amazon Elastic Container Registry (ECR)에 업로드한 후, EC2 인스턴스에 배포하는 단계

 

이 과정을 위해, 사전 요구사항 및 단계는 다음과 같이 나뉩니다:

사전 요구사항(Preliquesite)

  1. Dockerfile 만들기
  2. IAM 생성 및 권한 설정
  3. Github secrets 설정하기

Github Action 설정

  1. Action yaml파일 설정하기
  2. AWS 자격증명: AWS 액세스 키와 시크릿 키, 그리고 지정한 AWS 리전을 사용하여 AWS 자격증명을 설정합니다.
  3. ECR에 로그인: Docker를 ECR에 푸시하기 전에 ECR에 로그인을 수행
  4. Docker 이미지 빌드:   Docker 이미지를 빌드
  5. Docker 이미지 ECR로 푸시: 이미지를 ECR로 푸시하기 위해 해당 이미지를 태깅하고, ECR에 로그인한 뒤 이미지를 푸시합니다.
  6. EC2 인스턴스로 SSH 접속:
    5.1암호화된 PEM 파일을 사용하여 EC2 인스턴스로 SSH 접속합니다.
    SSH로 EC2 인스턴스에 접속한 후, ECR에서 이미지를 풀고 해당 이미지를 실행합니다.

 

 

사전 요구사항


1. Dockerifle 생성: ECR에 올려서 PUSH/PULL할 것이기 때문에, 간단한 Dockerfile을 하나 생성합니다.

//Dockerfile
# 베이스 이미지로 Python 3.8을 사용합니다.
FROM python:3.8

# 작업 디렉토리를 /app으로 설정합니다.
WORKDIR app

# 현재 디렉토리의 모든 파일을 /app 디렉토리로 복사합니다.
COPY . .

# Python 종속성을 설치합니다.b
RUN pip install --no-cache-dir -r requirements.txt
RUN pip install uvicorn

# 앱을 실행합니다.
CMD ["uvicorn", "app:app", "--reload"]

아래와 같이 app.py (FastAPI의 앱)을 간단히 생성합니다.

//app.py
from fastapi import FastAPI

app = FastAPI()


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

requirements.txt도 하나 생성합니다.

//requirement.txt
fastapi

 

 

2. IAM 생성, 키 생성 및 권한 부여: IAM 생성및 EC2, ECR 관련 권한부여

AWS Identity and Access Management (IAM)은 Amazon Web Services (AWS)에서 제공하는 서비스로, AWS 리소스에 대한 액세스를 관리하고 보안을 강화하는 데 사용됩니다. IAM을 사용하여 AWS 계정에 사용자, 그룹 및 역할을 생성하고, 이러한 엔터티에 대한 권한을 정의하여 AWS 리소스에 대한 액세스를 제어할 수 있습니다. 본 과정에서는 github action으로 AWS 리소스(ECR, EC2)을 제어 하기 위함입니다.

권한은 아래와 같이, "AdministratorAccess" 와 "AmazonEC2ContainerRegistryFullAccess"을 할당했습니다.

 

3. Github secrets 설정: 리포지토리 내, Settings-Security 내 Secrets and variable - Actions 등록

Action secrets는 GitHub Actions 워크플로우에서 사용되는 민감한 정보를 안전하게 저장하고 관리하기 위한 기능입니다. Secrets는 암호화되어 안전한 저장소에 저장되며, 워크플로우 실행 시에만 필요한 환경 변수로 사용할 수 있습니다. Action에서 사용하게되는 변수들을 repository에서 안전하게 저장하고, action에서 사용하는 yaml파일에서 접근이 가능합니다.

다음과 같이 등록이 필요합니다.

  • AWS_ACCESS_KEY_ID: AWS(Amazon Web Services) 계정에 액세스하는 데 사용되는 액세스 키의 ID 부분입니다. AWS API에 액세스할 때 사용
  • AWS_SECRET_ACCESS_KEY: AWS(Amazon Web Services) 계정에 액세스하는 데 사용되는 액세스 키의 시크릿 키 부분입니다. AWS API에 액세스할 때 사용
  • AWS_REGION: AWS 리소스가 위치한 지역(region)을 지정하는 변수. 참고로 ap-northeast-2=아시아 태평양(서울)입니다.
  • REPO_NAME: ECR 리포지토리에 접근하는 데 사용되는 계정 ID 또는 별칭입니다. ECR에 도커 이미지를 푸시하거나 불러올 때 사용
  • EC2_PRIVATE_KEY: AWS EC2 인스턴스에 SSH로 접속할 때 사용되는 개인 키(private key). EC2의 pem키의 내용을 그대로 copy & paste하시면 됩니다. "--benign", "--end"을 포함하여 plain text로 복붙하시면됩니다!

github actions secret이 등록된 화면

 

4. ECR 생성하기: dockerhub을 이용하셨다면, 같은 기능을 하는 AWS의 dockerhub라고 보셔도 무관합니다.

AWS 검색화면에서 ECR을 검색하여 다음과 같이, 리포지토리(repository)을 생성합니다. 아래와 같이 private으로 설정할 수 있습니다. 그리고, Repository name에보면, "숫자".dkr.ecr.ap-northeast-2.amazonaws.com 으로 나열되는데 "숫자"에 해당하는 부분이 aws_account_id이고, ap-northeast-2가 region입니다[1]. aws_account_id도 secret_key로 본 포스팅에서는 등록하였습니다.

ECR (리포지토리) 생성과정 화면
ECR 생성후 리포지토리 화면

[1] https://docs.aws.amazon.com/AmazonECR/latest/userguide/Registries.html#registry_auth

 

 

Action 설정하기


1. Action yaml파일 생성: github/workflows 디렉토리에 새 워크플로우 파일을 생성합니다. 예를 들어, deploy.yml 파일을 만들 수 있습니다. 아래와 같이 "set up a workflow yourself"을 눌러 workflow(yaml파일)을 처음부터 작성할 수 있습니다.

Actions 클릭 후 화면
"set up a workflow yourself" 을 클릭 후 화면

 

그리고, 아래와 같이 yaml파일의 초반부를 작성합니다.

name: Deploy to EC2 with ECR
on:
  push:
    branches:
      - main   # 배포할 브랜치를 선택합니다. 원하는 브랜치로 변경 가능

env:
  EC2_USER: ubuntu
  EC2_HOST: <EC2의 IPv4 공개IP>
  
jobs:
  deploy:
  runs-on: ubuntu-latest

  steps:
  - name: Checkout repository
    uses: actions/checkout@v2

 

AWS 자격증명: AWS 액세스 키와 시크릿 키, 그리고 지정한 AWS 리전을 사용하여 AWS 자격증명을 설정합니다.

    - name: Configure AWS Credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ${{ secrets.AWS_REGION }}

 

ECR에 로그인: Docker를 ECR에 푸시하기 전에 ECR에 로그인을 수행

    - name: Login to ECR
      id: ecr
      uses: jwalton/gh-ecr-login@v1
      with:
        access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        region: ${{ secrets.AWS_REGION }}

 

Docker 이미지 빌드:   Docker 이미지를 빌드

    - name: Build image
      run: |
        docker build --tag my-image:latest .

 

Docker 이미지 ECR로 푸시: 이미지를 ECR로 푸시하기 위해 해당 이미지를 태깅하고, ECR에 로그인한 뒤 이미지를 푸시합니다. 위의 단계에서 만든 "my-image:latest"의 이미지를 ECR로 푸시하기위해서, ECR에 로그인하고, ECR리포지토리에 맞게 테그명을 변경합니다. 만일 ECR의 URL가 "000000000.dkr.ecr.ap-northeast-2.amazonaws.com/my-image" 였다면, 이미지도 재 태그하여 "000000000.dkr.ecr.ap-northeast-2.amazonaws.com/my-image:<버전테그>" 형식어야합니다. 즉 "/????"의 이름도 같아야합니다.

    - name : Push to ECR
      env:
        AWS_REGION: ${{ secrets.AWS_REGION }}
        AWS_ACCOUNT: ${{ secrets.AWS_ECR_ACCOUNT }}
        IMAGE_TAG: ${{ github.sha }}
      run: |
        aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com
        docker tag my-image:$IMAGE_TAG $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/my-image:$IMAGE_TAG
        docker push $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/my-image:$IMAGE_TAG

 

EC2 인스턴스로 SSH 접속: EC2 인스턴스에 접속하기위해서 ".pem"파일의 내용을 sercrets 변수로부터 stdout을 "key.pem"을 작성을 합니다. 이를 이용하여 ssh -i <pem파일경로> 로 접속합니다. 그리고, SSH에 접속후에 ECR로부터 이미지를 pull 받고, 이미 띄워져있는 컨테이너가있다면 중단, 삭제하고, 새로운 이미지를 컨테이너로 구동합니다. 그리고 마지막으로 pem키를 삭제합니다.

    - name: SSH into EC2 instance and deploy
      env:
        AWS_REGION: ${{ env.AWS_REGION }}
        AWS_ACCOUNT: ${{ secrets.AWS_ECR_ACCOUNT }}
        EC2_USER:  ${{ env.EC2_USER }}
        EC2_HOST:  ${{ env.EC2_HOST }}
        IMAGE_TAG: ${{ github.sha }}
      run: |
        # Create PEM file from encrypted secret
        echo "${{ secrets.EC2_PRIVATE_KEY }}" > key.pem
        chmod 400 key.pem

        # SSH into the EC2 instance and perform deployment
        ssh -o "StrictHostKeyChecking no" -i key.pem $EC2_USER@$EC2_HOST \
          "sudo docker login -u AWS -p \"$(aws ecr get-login-password --region $AWS_REGION)\" $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com \
          && sudo docker pull $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/my-image:$IMAGE_TAG \
          && sudo docker stop my-container || true \
          && sudo docker rm my-container || true \
          && sudo docker run -d --name my-container -p 8000:8000 $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/my-image:$IMAGE_TAG" 

        # Remove pem
        rm key.pem

 

여기까지 작성했으면, 이제 trigger을 이용하여 action을 실행해볼 차례입니다 .임의의 파일을 수정하여 main에 push하여 tigger을 동작시키면 action화면에 아래와 같이 모든 job 의 실행이 보이게 됩니다.

 

전체 코드는 아래와 같습니다.

name: Deploy to EC2 with ECR
on:
  push:
    branches:
      - main 

  EC2_USER: ubuntu
  EC2_HOST: <IP>
  
jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout repository
      uses: actions/checkout@v2

    - name: Configure AWS Credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ${{ secrets.AWS_REGION }}

    - name: Login to ECR
      id: ecr
      uses: jwalton/gh-ecr-login@v1
      with:
        access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        region: ${{ secrets.AWS_REGION }}

    - name: Build image
      run: |
        docker build --tag my-image:$IMAGE_TAG .

    - name : Push to ECR
      env:
        AWS_REGION: ${{ secrets.AWS_REGION }}
        AWS_ACCOUNT: ${{ secrets.AWS_ECR_ACCOUNT }}
        IMAGE_TAG: ${{ github.sha }}
      run: |
        aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com
        docker tag my-image:$IMAGE_TAG $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/my-image:$IMAGE_TAG
        docker push $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/my-image:$IMAGE_TAG

    - name: SSH into EC2 instance and deploy
      env:
        AWS_REGION: ${{ env.AWS_REGION }}
        AWS_ACCOUNT: ${{ secrets.AWS_ECR_ACCOUNT }}
        EC2_USER:  ${{ env.EC2_USER }}
        EC2_HOST:  ${{ env.EC2_HOST }}
        IMAGE_TAG: ${{ github.sha }}
      run: |
        # Create PEM file from encrypted secret
        echo "${{ secrets.EC2_PRIVATE_KEY }}" > key.pem
        chmod 400 key.pem

        # SSH into the EC2 instance and perform deployment
        ssh -o "StrictHostKeyChecking no" -i key.pem $EC2_USER@$EC2_HOST \
          "sudo docker login -u AWS -p \"$(aws ecr get-login-password --region $AWS_REGION)\" $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com \
          && sudo docker pull $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/my-image:$IMAGE_TAG \
          && sudo docker stop my-container || true \
          && sudo docker rm my-container || true \
          && sudo docker run -d --name my-container -p 8000:8000 $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/my-image:$IMAGE_TAG" 

        # Remove pem
        rm key.pem
반응형

요약


master node에 쿠버네티스를 세팅하고, 각 노드랑 연결해보려고합니다. 쿠버네티스로 클러스터를 구성하는 것은 1) control-plane을 설치해야합니다(단일 control-plane또는 복수의 control-plane도 가능), 2) 또한, 각 워커노드들을 등로갛여 파드들의 네트웍이 클러스터내에서 잘 통신되게 하는 것입니다.

 

시작전에 앞서


kubeadm을 이용해서 쿠버네티스를 세팅해보려고합니다. kubeadm으로 쿠버네티스를 세팅하는 것이 쿠버네티스 공식홈페이지에서도 "best practice"라고 설명되어있습니다. kubeadm은 쿠버네티스를 간단히 설치해서 사용해보기에 용이합니다. 

 

 

사용 전 확인사항


쿠버네티스를 사용하기전에 장치에 아래의 스펙요구사항을 만족하는지 확인해야합니다.

  • A compatible Linux host. The Kubernetes project provides generic instructions for Linux distributions based on Debian and Red Hat, and those distributions without a package manager.
  • 2 GB or more of RAM per machine (any less will leave little room for your apps).
  • 2 CPUs or more.
  • Full network connectivity between all machines in the cluster (public or private network is fine).
  • Unique hostname, MAC address, and product_uuid for every node. See here for more details.
  • Certain ports are open on your machines. See here for more details.
  • Swap disabled. You MUST disable swap in order for the kubelet to work properly

마지막의 swap을 끄라고 되어있는데 이는 "kubelet"이라는 데몬이 정상적으로 돌게하기위함이라고 합니다. "kubelet"은 파드에서 컨테이너들이 "선언적으로 정의한"(=내가 세팅한 그대로 현 상태를 유지해주는 것)대로 실행되게끔 해주는 데몬입니다. 

 

본 포스팅에서는 카카오클라우드를 이용해서 private network로 구성된 클러스터를 구성해볼 예정입니다. 사설 IP로 구성된 노드들은 아래와 같습니다.

MAC address가 유요한지 product uuid가 각 노드에서 유니크한지 확인합니다. 아래의 명령어를 이용해서, 각 노드에서 MAC adresss, product_uuid가 유일한지 확인해봅니다. 일단은 마스터노드만 확인해봅니다.

ubuntu@k8s-master:~$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback [.........]
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether [..........]
    
    
ubuntu@k8s-master:~$ sudo cat /sys/class/dmi/id/product_uuid
[...........]

 

K8S을 설치하기전에 필수로 설치해야할 것이 docer가 있습니다. Docker은 다음의 링크로 설치하시길바랍니다 [1]

 

kubeadm, kubelet, kubectl을 설치할 것입니다. 각 기능은 아래와같습니다.

  • kubeadm: 클러스터를 구축하고 실해나는 과정을 의미합니다 (=cluster bootstrap). control-plane, worker nodes을 구성하고 각 노드들끼리 올바른 정보들을 가지고있는지, 통신이 한지를 확인합니다. [2] 
  • kubelet: 선언적으로 정의한 대로 파드를 시작하거나, 컨테이너를 시작해주는 기능을 합니다.
  • kubectl: 쿠버네티스를 운용하는 사용자와 인터페이스하는 기능을 합니다.

 

구글 공개키를 다운로드합니다.

sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg

apt 리포지토리에 쿠버네티스를 등록합니다.

echo "deb [signed-by=/etc/apt/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list

apt 패키지를 업데이트하고, kubelet, kubeadm, kubectl을 설치합니다. 아래의 apt-mark hold은 다른 패키지들이 ㅅ ㅓㄹ치되면서 자동으로 해당패키지들은 업그레이드 되거나, 제거되지 않게 하기 위함입니다.

sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl # 패키지 버전/삭제/업데이트 고정

 

cgroup 드라이버를 systemd로 일치

sudo mkdir /etc/docker
 
cat <<EOF | sudo tee /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
EOF
  
sudo systemctl enable docker
sudo systemctl daemon-reload
sudo systemctl restart docker
 
 
kubeadm reset

 

(option) kubeadm init이 문제가 있는 경우 해결방법

sudo rm /etc/containerd/config.toml
sudo systemctl restart containerd
sudo kubeadm init

 

kubeadm init을 하고나면, 아래와 같이 join하기 위한 토큰도 나오고, 일반사용자도 클러스터를 등록하기위해서 사용하라는 명령어도 나옵니다. 

kubeadm init --apiserver-advertise-address 172.16.2.231  # Master의 Host을 CIDR로 사용합니다.
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:


# 하단의 명령어는 적을 수 있다면 잘 기록하시길 바랍니다.
kubeadm join 172.16.2.231:6443 --token y24p4r.7itfeqy96rkjof8q \
        --discovery-token-ca-cert-hash sha256:5e9e43eadba4eb7858033de6fbb4d99cc811f7d49d96f3637435370463c087e

 

클러스터의 실행을 위해서 위의 나온 메시지대로 $HOME/.kube/config 파일을 설정합니다. (일반사용자로 전환후)

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

 

아래의 명령어로 쿠버네티스(kubelet, kubeadm, kubectl)이 설치된 노드에서 클러스터로 참여할 수 있게됩니다. 그리고, 해당 노드는 워커노드가 됩니다. 이 명령어를 잃버어렸다고해서 참여못하는 것은 아닙니다. 

# kubeadm join <kubernetst cluster API:PORT> --token [token] --discovery-token-ca-cert-hash sha256 <hash>

kubeadm join 172.16.2.231:6443 --token y24p4r.7itfeqy96rkjof8q \
        --discovery-token-ca-cert-hash sha256:5e9e43eadba4eb7858033de6fbb4d99cc811f7d49d96f3637435370463c087e

 

token을 잃어버렸을 경우 및 해시를 잃어버린경우

// 명렁어로 토큰 출력
# kubeadm token list 

// 아래의 명령어로 해시 출력
# openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'

 

 

control-plane에서만 deployment을 실행하면 파드가 생성되지않음을 확인할 수 있습니다. deployment에는 replicas 수를 3으로 지정해놓았는데요. 할당할 worker노드가 없기 때문에 pending으로 나온 것으로 판단됩니다.  실제로 하나의 파드의 상태를 추적해보면 아래와 같습니다.

root@k8s-master:/home/ubuntu# kubectl apply -f controllers-nginx-deployment-yaml
deployment.apps/nginx-deployment created
# watch "kubectl get pods"
Every 2.0s: kubectl get pods                                                                        k8s-master: Sun Jan  8 09:00:15 2023

NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-85996f8dbd-98xlx   0/1     Pending   0          118s
nginx-deployment-85996f8dbd-d9bhg   0/1     Pending   0          118s
nginx-deployment-85996f8dbd-kjk8t   0/1     Pending   0          118s
root@k8s-master:/home/ubuntu# kubectl describe pod nginx-deployment-85996f8dbd-98xlx
Name:             nginx-deployment-85996f8dbd-98xlx
Namespace:        default
Priority:         0
Service Account:  default
Node:             <none>
Labels:           app=nginx
                  pod-template-hash=85996f8dbd
Annotations:      <none>
Status:           Pending
IP:               
IPs:              <none>
Controlled By:    ReplicaSet/nginx-deployment-85996f8dbd
Containers:
  nginx:
    Image:        nginx:1.14.2
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-h2w8s (ro)
Conditions:
  Type           Status
  PodScheduled   False 
Volumes:
  kube-api-access-h2w8s:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  49s   default-scheduler  0/1 nodes are available: 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }. preemption: 0/1 nodes are available: 1 Preemption is not helpful for scheduling..

 

 

Worker노드를 할당해보겠습니다.

1. 마스터노드에서 join command을 stdout합니다.

root@k8s-master:/home/ubuntu# kubeadm token create --print-join-command
kubeadm join 172.16.2.231:6443 --token 8yxfot.ktr4irill5hqw6h7 --discovery-token-ca-cert-hash sha256:bd91d60981b5774add11106cd42e1f8d6db18b241b5fd529b0d638e71

2. Worker Node에서 해당 명령어를 copy&paste 하여 사용합니다.

root@k8s-test:/home/ubuntu# kubeadm join 172.16.2.231:6443 --token 8yxfot.ktr4irill5hqw6h7 --discovery-token-ca-cert-hash sha256:bd91d60981b5774add11106cd42e1f8d6db18b241b5fd529b0d638e713522494
[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

 

control-plane (master node)로 돌아가, 파드가 정상적으로 실행되는지 확인합니다. nginx 서버 3개가 정상적으로 돌아가는 것을 확인할 수 있었습니다.

root@k8s-master:/home/ubuntu# kubectl get pods --all-namespaces
NAMESPACE     NAME                                      READY   STATUS    RESTARTS   AGE
default       nginx-deployment-85996f8dbd-98xlx         1/1     Running   0          30m
default       nginx-deployment-85996f8dbd-d9bhg         1/1     Running   0          30m
default       nginx-deployment-85996f8dbd-kjk8t         1/1     Running   0          30m
kube-system   calico-kube-controllers-7bdbfc669-5qmkf   1/1     Running   0          4h49m
kube-system   calico-node-4ss8x                         0/1     Running   0          86s
kube-system   calico-node-ns4ct                         0/1     Running   0          4h49m
kube-system   coredns-787d4945fb-lgxsp                  1/1     Running   0          5h8m
kube-system   coredns-787d4945fb-xs85r                  1/1     Running   0          5h8m
kube-system   etcd-k8s-master                           1/1     Running   3          5h8m
kube-system   kube-apiserver-k8s-master                 1/1     Running   3          5h8m
kube-system   kube-controller-manager-k8s-master        1/1     Running   3          5h8m
kube-system   kube-proxy-4rkz6                          1/1     Running   0          5h8m
kube-system   kube-proxy-pzbmc                          1/1     Running   0          86s
kube-system   kube-scheduler-k8s-master                 1/1     Running   3          5h8m

 

 

아래와 같이, NodePort와 deployment을 각각 실행합니다. deployment와 nodeport의 manifest(yaml)은 아래와 같습니다.

# nodeport.yaml
apiVersion: v1
kind: Service
metadata:
  name: nodeport-svc
spec:
  type: NodePort
  clusterIP: 10.100.100.200
  selector:
    app: my-nginx  # deployment에 의해서 생성되는 라벨의 이름과 동일해야합니다.
  ports:
  - protocol: TCP
    port: 80         # 쿠버네티스 서비스의 포트
    targetPort: 80   # POD로 전달될 포트
    nodePort: 30052  # 각 노드에 뚫릴 포트입니다. 외부와 통신합니다.

 

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      app: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports :
        - containerPort: 80

 

// 클러스터에서 실행해봅시다.
# kubectl apply -f nodeport.yaml
# kubectl apply -f deployment.yaml

 

리소스들이 제대로 생성되었는지 확인합니다.

// 리소스들이 잘 실행되는지 확인
root@k8s-master:/home/ubuntu/test# kubectl get pods,deploy,svc,endpoints

NAME                            READY   STATUS    RESTARTS   AGE
pod/my-nginx-7c777db54b-26rcz   1/1     Running   0          34m
pod/my-nginx-7c777db54b-4l2wc   1/1     Running   0          34m
pod/my-nginx-7c777db54b-4wtdn   1/1     Running   0          34m
pod/my-nginx-7c777db54b-8l2vg   1/1     Running   0          34m
pod/my-nginx-7c777db54b-dmtjd   1/1     Running   0          34m
pod/my-nginx-7c777db54b-fphk9   1/1     Running   0          34m
pod/my-nginx-7c777db54b-rcql5   1/1     Running   0          34m
pod/my-nginx-7c777db54b-s658f   1/1     Running   0          34m
pod/my-nginx-7c777db54b-tq5t6   1/1     Running   0          34m
pod/my-nginx-7c777db54b-tqd4q   1/1     Running   0          34m

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/my-nginx   10/10   10           10          34m

NAME                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
service/kubernetes     ClusterIP   10.96.0.1        <none>        443/TCP        34m
service/nodeport-svc   NodePort    10.100.100.200   <none>        80:30052/TCP   34m

NAME                     ENDPOINTS                                                           AGE
endpoints/kubernetes     172.16.2.231:6443                                                   34m
endpoints/nodeport-svc   172.18.137.166:80,172.18.137.167:80,172.18.137.168:80 + 7 more...   34m

 

각 node로 들어가서 listening 중인지 확인합니다. 쿠버네티스 최신버전들은 netstat으로 확인이 불가능하고, iptables로만 확인이 가능합니다.

ubuntu@k8s-master: ssh k8s-test

ubuntu@k8s-master: su

root@k8s-test:/home/ubuntu# iptables -t nat -L KUBE-NODEPORTS | column -t
Chain                      KUBE-NODEPORTS  (1   references)
target                     prot            opt  source       destination
KUBE-EXT-FDIGUWRAAKMFZSYO  tcp             --   anywhere     anywhere     /*  default/nodeport-svc  */  tcp  dpt:30052

 

30052로 제대로 포트로 요청을 보내서 응답이 오는지 확인해봅니다.

root@k8s-test:/home/ubuntu# curl localhost:30052
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

 

localhost말고, 외부에서 해당 노드로도 요청을 보내봅니다.

// 마스터노드에서 노드1의 사설IP:30052포트로 요청
ubuntu@k8s-master:~$ curl 172.16.2.253:30052
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

 

[1] https://confluence.curvc.com/pages/releaseview.action?pageId=98048155#:~:text=kubelet%2C%20kubeadm%2C%20kubectl%20%EC%84%A4%EC%B9%98%20(master%2C%20node),-%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4&text=%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4%EB%A5%BC%20%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0%20%EC%9C%84%ED%95%B4%20Kubernetes%20%EC%A0%80%EC%9E%A5%EC%86%8C%20%EC%B6%94%EA%B0%80%ED%95%9C%EB%8B%A4.&text=%EC%A0%80%EC%9E%A5%EC%86%8C%20%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8%20%ED%9B%84%20kubelet%2C%20kubeadm,%EB%A5%BC%20%EC%88%9C%EC%B0%A8%EC%A0%81%EC%9C%BC%EB%A1%9C%20%EC%A7%84%ED%96%89%ED%95%9C%EB%8B%A4.&text=%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4%EB%A5%BC%20%EC%84%9C%EB%B9%84%EC%8A%A4%20%EB%93%B1%EB%A1%9D%20%EB%B0%8F%20%EC%9E%AC%EC%8B%9C%EC%9E%91%EC%9D%84%20%EC%88%98%ED%96%89%ED%95%9C%EB%8B%A4.

[2] https://www.techtarget.com/searchitoperations/tip/Learn-how-to-bootstrap-Kubernetes-clusters-with-kubeadm

반응형

딥러닝을 이용한 서비스를 배포할 목적이 있으면, 운영체제 등에서 독립적으로 S/W가 돌아갈 수 있게끔 배포하는 것이 중요하다. 이 때 주로쓰이는 것이 컨테이너 기술인데, 컨테이너 기술을 이용해서, 딥러닝의 GPU가속을 사용해보자.

 

단계는 아래와 같다


  1. NVIDA driver (그레픽 드라이버) 와 docker은 설치
  2. CUDA 이미지 풀하기
  3. GPU 지원 컨테이너 만들기
    1. 방법 1: Native GPU support
    2. 방법 2: Nivida container Runtime을 이용하기
    3. 방법 3: Nivida GPus 을 위한 도커엔진 유틸리티 이용하기
  4. 확인: GPU 지원 컨테이너 내 접속 후 cuda확인

 

0. NVIDIA driver 확인


이미 설치된 경우, 다음의 코드로 CUDA Version을 확인한다.아래와 같이 CUDA Version이 11.7임을 확인할 수 있다. 

$nvidia-smi

 

 

2. 도커허브에서 CUDA 이미지 가져오기


docker hub에 cuda을 검색하면, 아래의 그림과 같이 여러 이미지들이 검색되어 나온다.

그 중에, nvidia/cuda을 설치해본다. (링크: https://hub.docker.com/r/nvidia/cuda)

 

nvidia/cuda 리포지토리에 들어가면 Tags가 나오는데, CUDA 버전에 맞는 tag을 찾아본다.

 

아래와 같이 CUDA 버전에 맞는 이미지를 찾는다.

nvidia-smi 명령어에서 cuda 버전이 11.7이었으므로 11.7을 검색해서 runtime, base, devel, 버전 및 원하는 컨테이너 OS환경에 맞춰 설치한다.

 

필자는 nvidia-11.7.1용 런타임-ubunut22.04을 pull했다.

 

제대로 잘 pulling되었는지 아래와 같이 확인해본다.

 

 

GPU 지원 컨테이너 사용: 방법1: Native GPU 지원


아래와 같이 docker 및 docker환경에서 GPU을 돌리기위한 데몬(nvidia-container-toolkit)을 설치한다

// docker, nvidia-conatiner-toolkit 설치
$ sudo apt-get install -y docker nvidia-container-toolkit

 

모든그레픽카드을 사용할 수 있도록 GPU세팅을 하는 것은 아래와 같다.

docker run --gpus all [이미지명:테그명]

 

필자는 background option으로 (-d), ssh로 접속해서 인터렉션할것이기 떄문에(-it)아래와 같이 옵션을 주어 컨테이너를 실행한다.

컨테이너명이 "2c2e80a61f240a689d1d6d13a94c886fdab9508fc6226e1889ac5cbb623a5528"로 반환되어 컨테이너가 실행중임을 알려준다.

 

컨테이너가 잘 생성되었는지, docker ps명령어로 확인해본다. 33초전에 실행됨을 확인할 수 있다.

이 컨테이너로 직접 들어가보자

 

대화창이 나온다.

 

nvidia-smi명령어로 host graphic card을 사용할수 있는지 확인해본다.

 

반응형

0. PUSH할 원격저장소를 만든다. docker hub (https://hub.docker.com/) 에서 로그인 후, 원격저장소를 생성한다. github저장소처럼 "ID/원격저장소명"으로 주소가 만들어진다.

 

 

1. 도커 로그인

$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: [YourID]
Password: ******

Login Succeeded

 

 

2. Docker hub에 올릴 이미지를 찾는다. 여기서 필자는 REPOSITOY에 simple-nlp-app, TAG은 v1을 올릴 것이다. 그렇지만, 도커허브에 올릴 때는 ID/repository명  = docker image ls에서의 REPOSITORY 명이 동일해야한다. 따라서, 한번 변경이 필요하다. 

$ docker image ls
REPOSITORY                                                TAG                                                                          IMAGE ID       CREATED         SIZE  
simple-nlp-app                                            v1                                                                           7108c8dbe574   6 minutes ago   2.73GB
simple-nlp-app                                            v0                                                                           c82b9d4786fe   9 minutes ago   2.73GB
simple-nlp-app                                            latest                                                                       798c7cf60156   2 days ago      2.72GB
nginx                                                     latest                                                                       ac8efec875ce   6 days ago      142MB 
alpine/git                                                latest                                                                       22d84a66cda4   3 weeks ago     43.6MB
hubproxy.docker.internal:5000/docker/desktop-kubernetes   kubernetes-v1.25.2-cni-v1.1.1-critools-v1.24.2-cri-dockerd-v0.2.5-1-debian   09d7e1dbc2c4   2 months ago    363MB 
k8s.gcr.io/kube-apiserver                                 v1.25.2                                                                      97801f839490   2 months ago    128MB 
k8s.gcr.io/kube-controller-manager                        v1.25.2                                                                      dbfceb93c69b   2 months ago    117MB 
k8s.gcr.io/kube-scheduler                                 v1.25.2                                                                      ca0ea1ee3cfd   2 months ago    50.6MB
k8s.gcr.io/kube-proxy                                     v1.25.2                                                                      1c7d8c51823b   2 months ago    61.7MB
k8s.gcr.io/pause                                          3.8                                                                          4873874c08ef   5 months ago    711kB 
k8s.gcr.io/etcd                                           3.5.4-0                                                                      a8a176a5d5d6   6 months ago    300MB 
k8s.gcr.io/coredns                                        v1.9.3                                                                       5185b96f0bec   6 months ago    48.8MB
docker/desktop-vpnkit-controller                          v2.0                                                                         8c2c38aa676e   19 months ago   21MB  
docker/desktop-storage-provisioner                        v2.0                                                                         99f89471f470   19 months ago   41.9MB
nginx                                                     1.14.2                                                                       295c7be07902   3 years ago     109MB 
dattarajrao/simple-app                                    latest                                                                       3f4a466b587c   4 years ago     132M

 

3. 두 가지 방법이 있는데, 첫 번째는 docker image tag을 이용하는 방법.

  • docker image tag SOURCE_IMAGE[:TAG] TARGET[:TAG]  . 이 명령어는 원본 이미지에서 타겟이이지로 리포지토리 명을 다시 테그를 달 수 있게한다.
  • docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]] : 이 명령어는 실행중인 컨테이너를 다시 이미지화하는 방법이다.

아래의 예시는 docker image tag을 이용한 방법이다. tag을 이용할 때, 미리 생성한 원격저장소의 이름과 동일하게 해준다. 그렇지 않으면 아래와 같이 에러가 뜬다.

$ docker push 4pymgalion/simple-nlp-app:v1
The push refers to repository [docker.io/4pymgalion/simple-nlp-app]
An image does not exist locally with the tag

 

따라서, 아래와 같이 원격저장소의 ID/저장소명과 같이 도커 이미지를 테그를 다시 달아준다. 그러면 추가로 "4pygmalion/simple-nlp-app"라는 리포지토리 이름을 가진 이미지가 생성된다.

$ docker tag simple-nlp-app:v1 4pygmalion/simple-nlp-app:latest
$ docker images
REPOSITORY                                                TAG                                                                          IMAGE ID       CREATED          SIZE
4pygmalion/simple-nlp-app                                 latest                                                                       7108c8dbe574   14 minutes ago   2.73GB
simple-nlp-app                                            v1                                                                           7108c8dbe574   14 minutes ago   2.73GB
nginx                                                     latest                                                                       ac8efec875ce   6 days ago       142MB 
alpine/git                                                latest                                                                       22d84a66cda4   3 weeks ago      43.6MB
hubproxy.docker.internal:5000/docker/desktop-kubernetes   kubernetes-v1.25.2-cni-v1.1.1-critools-v1.24.2-cri-dockerd-v0.2.5-1-debian   09d7e1dbc2c4   2 months ago     363MB 
k8s.gcr.io/kube-apiserver                                 v1.25.2                                                                      97801f839490   2 months ago     128MB 
k8s.gcr.io/kube-scheduler                                 v1.25.2                                                                      ca0ea1ee3cfd   2 months ago     50.6MB

 

4. 푸시: Docker push 도커이미지/리포지토리명

$ docker push 4pygmalion/simple-nlp-app:latest
The push refers to repository [docker.io/4pygmalion/simple-nlp-app]
e56c84262ba7: Pushing [>                                                  ]  35.33kB/2.22MB
cec010fb56f8: Pushing [>                                                  ]  544.8kB/2.262GB
5af9244c08c5: Pushing [==================================================>]  5.632kB
fac360d91a98: Preparing
c43e4ddc5992: Preparing
e5896e4ce6c3: Waiting
7c40600bc52d: Waiting
d326469892d9: Waiting
b5ebffba54d3: Waiting

 

반응형

 

요약


쿠버네티스를 이용하면,파드를 통해 클라이언트들이 어플리케이션을 호출할 수 있다. 그러나, 클라이언트 입장에서는 각각 파드의 주소가 존재하는하고, 클라이언트는 어느 파드로 갈지 명시하지 않았는데도, 쿠버네티스가 알아서 각각의 파드가 처리하게 만들어준다. 즉, 네트워킹이 쿠버네티스에 의해서, 알아서 처리되는데, 이 네트워크 서비스를 제공할 수 있는 자원이 "서비스(Service)"이다. 즉, 파드로 실행중인 앱을 네트워크서비스로 노출하게 해주는 자원이다.

 

 

서비스를 이용하기위해 각 파드, 노드가 만족해야할 사항[1]


  • 각 파드는 노드상의 모든 파드와 통신할 수 있어야함
  • 노드상의 에이전트(데몬, kubelet)은 해당 노드의 모든 파드와 통신할 수 있어야함.

 

 

어플리케이션을 네트워크 서비스로 외부에 노출하기


아래와 같이, 디플로이먼트(deployment)와 서비스(Service)를 명시한 yaml파일을 각각 만든다. 

// nginx-svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    run: my-nginx
# run-my-nginx.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80

 

그리고, 아래와 같이 클러스터 내부에서는  서비스가 가능하고, 각 클러스터의 IP을 확인할 수 있다.

$ kubectl get services
NAME              TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes        ClusterIP   10.96.0.1        <none>        443/TCP        28h
my-nginx          ClusterIP   10.108.171.235   <none>        80/TCP         26h

해당 클러스터의 내부

 

 

Trouble shooting: Cluster IP에 접속이 안될 때 -> Node Port로 외부 클라이언트가 접속할 수 있도록 변경 [2]


서비스하는 클러스터의 IP는 가상의 IP이기 때문에, 클러스터 내부에서 내부로만 서비스할 수 있다. 즉 위의 예시에서처럼 클러스터 IP가 10.108.171.235면, 이 클러스터로 들어가야만 "10.108.171.235:80"의 서비스를 요청하고 받을 수 있는 것이다. 이것을 해결하기, "Cluster IP"을 "Node Port"로 변경해주어야 한다.

Node Port은 외부 클라이언트가 노드로 접속하기 위한 포트를 의미한다. 이 노드 포트을 변경하려면 아래와 같이 kubectl expose을 이용한다.

$ kubectl expose deployment hello-world --type=LoadBalancer --name=example-service

 

expose을 하면 TYPE이 NodePort인 서비스가 하나 생성이된다.  example-serivce라는 이름을 가진 서비스의 TYPE이 NodePort인 것을 잘보자. 이 클러스터의 가상 IP은 10.101.151.252이다. 이것은 여전히 가상IP이기 때문에 서비스를 요청할 수 없다. 다만, 이 노드의 원래주소(localhost, 또는 privateIP, publicIP)로 들어갈때, 30384포트로 요청을하면 알아서 80번으로 바꿔준다. 즉 포트포워딩으로 30384을 80으로 바꿔서 10.101.151.252로 요청해볼 수 있다.

$ kubectl get services
NAME              TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
example-service   NodePort    10.101.151.252   <none>        80:30384/TCP   26h
kubernetes        ClusterIP   10.96.0.1        <none>        443/TCP        28h

$ curl localhost:30384
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

 

 

[1] https://kubernetes.io/ko/docs/concepts/services-networking/

 

서비스, 로드밸런싱, 네트워킹

쿠버네티스의 네트워킹에 대한 개념과 리소스에 대해 설명한다.

kubernetes.io

[2] https://stackoverflow.com/questions/49297226/kubernetes-cluster-ip-service-not-working-as-expected

반응형

표지: Engineering MLOps: Rapidly build, test, and manage production-ready machine learning life cycles at scale

 

해당 책은 아래와 같은 구성으로 이뤄져 있다. 해당 포스팅은 Section 1을 다룬다.

3
27
51
79
103
Deploying Machine Learning Models at Scale
133
Key Principles for Deploying Your ML System
135
Building Robust CICD Pipelines
165
APIs and Microservice Management 191
Testing and Securing Your ML Solution
213
Essentials of Production Release
229
Monitoring Machine Learning Models in Production
253
Key Principles for Monitoring Your ML System
255
Model Serving and Monitoring
275
Governing the ML System for Continual Learning
311
About Packt
339
Other Books You May Enjoy
340
Index
343
 
 

 

 

Fundamentals of an MLOps workflow


전통적인 소프트웨어 개발방식MLOps와의 차이: 데이터와 코드를 둘다 다뤄야하는 ML문제

ML을 이용한 서비스가 점차적으로 확대될 것이라는 것에 이 챕터의 내용이 강조되고 있다. 여태까지 소프트웨어의 발전은 보통 "개발업무(development)"라는 것으로 취급되어서, Waterfall 방식, Agile 방식, DevOps방식으로 쭉 진행되어왔다. 소프트웨어의 개발이 더 큰 개념이기 때문에, 열거한 3가지 방식중에 최근 방식인 Agile/DevOps로 해결하면 되는것이 아닌가? 생각을 하게된다. 하지만, 현업에서는 많은 실패사례들이 존재한다. 왜 그럴까? 이 책에서는 ML을 이용한 서비스가 실패하는 이유를 말하는데, 전통적인 소프트웨어 개발과의 차이점을 강조한다.

Machine Learning = data  + code

 

DevOps라고하면, 지속적으로 코드를 개발하고, 업데이트하고, 사용자가 일부 개발과정을 포함해서 지속적으로 개발/도입/운영할 수 있는 개발방식으로 여겨지는데, MLOps도 이와 같이 했을 경우는 코드는 업데이트되지만, 데이터는 업데이트 안된다는 것에 단점이 있다. 따라서, 데이터와 코드가 지속적으로 함께 관리되어야함을 강조한다. 예) 모델은 최신모델인데 과거데이터를 쓰거나, 현재 데이터만을 이용해서 향후에 서비스 방식이 바뀌면 재도입이 어려운 것들에 대한 문제가 이해 해당된다. 이러한 단점을 줄이기 위해서 MLOps은 체계적인 방식으로 "data"와 "code(model)"을 같이 운영하는 것을 목표로한다. 이를 위해서, 모델의 개발, 도입(depolyment), 모니터링등알 함께 지속적인 방식으로 도입하는 것을 의미한다.

 

 

MLOps의 워크플로우: 어떤방식으로 진행이되나?


Figure 2. https://static.packt-cdn.com/products/9781800562882/graphics/image/B16572_01_010.jpg

 

Figure 2은 MLOps의 워크플로우의 개념도이다. 두 형태로 나뉘는데, 상위(Build, Delopy, Monitor)은 주로 MLOps pipeline이라고 불린다.한편 하위는 Driver(드라이버)라고 불린다.

  • MLOps pipeline: 모델 개발, 적용, 적용후 모니터링에 대한 업무의 개요에 해당된다.
  • Driver: MLOps pipeline을 하기위해서 실제로 행해야하는 하위 테스크, 구성되어야하는 인프라 등을 일컫는다.

MLOps업무를 해본사람이면 대부분 이해할 수 있는 개념이다. 참고로, model packaging, model registering, application testing 에 관한 내용만 추가로 아래와 같이 기술해 놓았다.

  • Model packaging: 모델을 직렬화하고, 독립적인 소프트웨어로 작동할 수 있게끔 컨테이너화하는 단계를 의미한다. 주로 ONNX file로 모델을 직렬화한다. ML모델을 Training/Testing으로 나누고 실제 잘 개발되었다고하자. 더 이상 이 모델은 현시점에서 바뀔일이 없다. 따라서, Pickling을 하든, ONNX로 변경하든 직렬화해놓고, 메모리에 로드하여 바로 쓸 수 있는 단계로 만드는 일에 해당된다. 그리고, 이 직렬화한 모델을 독립적인 소프트웨어로 쓸 수 있도록, Docker등으로 필요한 데이터, 종속코드 등을 하나로 묶어 만들어놓는 것을 의미한다.
  • Model registering: 모델레지스터링은 직렬화+패키징된 모델을 DB등과 같은 레지스트리에 등록/저장해놓는 것을 의미한다. 이렇게 해놓으면, 언제든지 해당 모델이 필요할 때, 다운로드 받아서 쓸 수 있다.
  • Application testing: 동의어로 Pre-production testing이라고 생각하면된다. 모델을 개발해놓고, 실제 운영에 쓰기전에 진짜 쓸만한것인지를 Extra validation하는 것과 같다. API나 쿠버네티스, 컨테이너로 띄워놓고 훈련용이 아닌, 추가적인 테스트용도로 데이터를 보내보고, 모델이 기대하는 것과 잘 동작하는지를 의미한다. Application testing의 결과는 도메인 전문가가 직접 결과를 분석하기도 한다(실제로 잘되었는지 확인해야하니까..), 도메인 전문가가 쓸만하다라고 판단해준 이후에나 실제 개발환경에 넣고, 추론할 수 있는 대기상태로 만들어두는 것이다.

 

MLOps 파이프라인의 예시: 희귀질환 환자의 유전변이추천시스템을 개발했다고 하자.

  1. 이 개발할 때 썼던 데이터는 2021년 이전의 데이터였다. 그렇게 만들어낸 Top 5 recall이 96.3%였다. 여기까지가 Model build이다.
  2. 이 모델을 바로 쓸 수 있도록, ONNX으로 직렬화하고 docker image로 만들어 내었다(model packaging).
  3. 이렇게 모델패키지한 것을 도커 이미지 리포지토리에 올려놓았다(model registering, 완벽한의미의 regstering은 아님).
  4. 그리고 나서, 임상유전학자들이 판독에 쓰기직전에 추가적으로 2022년 이후의 데이터를 모아놓고, 추천시스템 API을 띄워놓은 상태에서 모아놓은 데이터를 추론해보았다. 2022년 이후의 데이터를 추론했을 때의 결과가 20개중에, top 3가 95%이상이었다. 이 결과를 임상유전학자에게 다시 보냈다(Application testing).
  5. 그 후에 model production에 쓸 수 있도록 운영 서버로부터 데이터수 있도록 Restful API을 오픈했다. 

 

반응형

+ Recent posts