공부한 내용 & 실습들을 정리
Index를 통해서 필요한 부분으로 이동할 수 있도록 해두었으니 참조
Kubernetes 느낀점 : 끝이 없다 정말...
Kubeflow 쪽이 생각보다 정리할 것이 많아서 22/09/01 기준으로 따로 분리
Ref. Kubernetes Useful Command
Kubernetes에 대한 개념을 잡아보자
Kubernetes(a.k.a k8s)는 Container 를 쉽고 빠르게 배포 및 확장하고 관리를 자동화해줄 수 있도록 하는 Open Source Platform으로, MSA & Cloud Platformm 을 지향하고 Container로 이루어진 Application을 손쉽게 담고 관리할 수 있도록 해준다. Serverless , CI/CD , Machine Learning 등 다양한 기능들이 Kubernetes 위에서 동작 가능하다.
Kubernetes는 다양한 배포 방식들을 지원하는데, 대표적으로 나열하자면 다음과 같다.
- Deployment : New Version Application을 다양한 전략을 통해 중단 없이 배포
- StatefulSets : 실행 순서를 보장하고 Host Name 과 Volume 을 일정하게 사용할 수 있어서, 순서 혹은 데이터가 중요한 경우 사용
- DaemonSets : Log , Monitoring 등 모든 Node에 설치가 필요한 경우
- Job , CronJob : Batch 성 작업
- Desired State : 관리자가 바라는 환경으로, 얼마나 많은 Web Server가 실행되고 있으면 좋은지, 몇 번 Port로 서비스하길 원하는지 등
- Current State : 현재 상태를 Monitoring하면서 Desired State를 유지하려고 내부적으로 여러 작업들을 수행함
관리자가 Server를 배포할 때 직접적인 동작을 명령하지 않고 State 를 선언하는 방식을 사용한다. 예를 들어서 Nginx Container를 실행하는데, 80번 Port로 Open(명령, Imperative) 한다는 명령어는 80번 Port를 Open한 Nginx Container를 1개 유지(선언, Declarative)해달라는 선언으로 한다고 보면 된다.
Kubernetes에서 사용되는 Base Object로 매우 중요하다.
- Pod : Kubernetes에서 배포할 수 있는 가장 작은 단위 로, 한 개 이상의 Container, Storage, Network 속성을 가진다. Pod에 속한 Container는 Storage와 Network를 공유하고, 서로 Localhost로 접근이 가능하다. Container 하나만 사용하는 경우에도 반드시 Pod로 감싼다. Pod의 구조는 아래와 같다.
-
ReplicaSet : Pod를 여러 개(한 개 이상) 복제하여 관리하는 Object로, Pod를 생성하고 개수를 유지할 때 사용한다. 복제할 개수, 개수를 체크할 Label Selector, 생성할 Pod의 설정 값(Template) 등을 가지고 있다.
-
Service : Network 와 관련된 Object로, Pod를 외부 Network와 연결해주고, 여러 개의 Pod를 바라보는 내부 Load Balancer 를 생성할 때 사용한다. 내부 DNS 에 Service 이름을 Domain으로 등록하기 때문에 Service Discovery 역할도 수행한다.
-
Volume : Storage와 관련된 Object, Host Directory를 그대로 사용할 수도 있고, EBS 같은 Storage를 동적으로 생성하여 사용할 수도 있다.
Object Spec은 YAML 형태로 구성되며, Object 종류와 원하는 상태를 입력한다. 생성, 조회, 삭제로 관리할 수 있기 때문에 RESTful API로 쉽게 노출할 수 있고, 접근 권한 설정도 같은 개념을 적용하여 누가 어떤 Object에 요청을 할 수 있는지도 정의할 수 있다.
Kubernetes는 Application을 배포하기 위해 Desired State 를 다양한 Object에 Label 을 붙여 정의하고 API Server에 전달한다.
Kubernetes는 Master 에 API Server 와 Status Storage 를 두고 각 Node(Server) 와 Agent(Kubelet) 와 통신하는 구조를 갖는다. 기본적으로 Node는 전체 Cluster를 관리 하는 Master 와 Container가 배포 되는 Worekr 로 구성이 되는데, 모든 명령은 Master의 API Server 를 통해 호출하고 Worker는 Master와 통신하면서 필요한 작업들을 수행한다. 각각의 Node들은 IP Address를 가지며, Node들이 모여 Cluster를 구성하게 된다. 그래서 Kubernetes에서 포함 관계는 Contaiiner < Pod < Node(Master, Worker) < Cluster
라고 볼 수 있다.
하나의 Pod가 생성되는 과정은 다음과 같이 표현할 수 있다.
Kubernetes에서 중요한 역할을 수행하는 Component들에 대해서 간략하게만 서술한다.
- Ingress : 다양한 Web Application을 하나의 Load Balancer 로 서비스하기 위해 사용하는데, Proxy Server 는 Domain 과 Path 조건에 따라 등록된 Server로 요청을 전달하는데, Server가 바뀌거나 IP Address가 변경되면 매번 수정해야한다. Kubernetes에서는 이를 자동화하여 거의 그대로 사용이 가능하게 해준다.
- Namespace & Label : 하나의 Cluster를 논리적 으로 구분해서 사용 가능하게 해준다.
- RBAC(Role-Based Access Control) : 접근 권한 시스템으로, 각각의 Resource 에 대하여 User 단위로 CRUD 같은 권한을 쉽게 지정 가능하게 해준다.
-
Cloud Support : 외부 Storage를 Container 내부 Directory에 Mount하여 사용하는 것도 일반적인데, Cloud Controller 를 통해 Cloud와의 연동을 쉽게할 수 있도록 한다.
-
CRD(Custom Resource Definition) : Kubernetes가 기본적으로 제공하지 않는 기능을 기본 기능과 동일한 방식으로 적용하고 사용 가능하게 해준다. Kubeflow 에서도 여러 CRD가 존재한다.
-
Auto Scaling : CPU , Memory 사용량에 딷른 확장 및 현재 접속자 수와 같은 값들을 사용 가능하다.
- Horizontal Pod Autoscaler(HPA) : Container 개수를 조정
- Vertical Pod Autoscaler(VPA) : Container Resource 할당량을 조정
- Cluster Autoscaler(CA) : Server 개수 조정
Container 간의 Networking 제어를 할 수 있는 Plug-in 을 만들기 위한 표준으로, 공통된 Interface를 제공하기 위해 만들어졌으며, Kubernetes에서는 Pod 간의 통신을 위해서 사용한다. Kubernetes 자체적으로 kubelet
이라는 CNI Plug-in을 제공하지만, Network 기능이 매우 제한적이어서 Flannel, Calico, Weavenet, NSX 등의 3rd Party CNI Plug-in 을 사용하기도 한다.
Kubernetes 설치하는 과정을 Command Line 위주로 서술한다.
개발 환경에 따라서 차이가 있을 수 있기 때문에 잘 참고해야 한다.
또한 AWS EC2 Instance 위에서 구축을 했기 때문에 추가적인 설정이 들어갈 수도 있으니 참고해야 한다.
Kubernetes 기본 구축 환경
- 먼저 Master Node 1대 와 Worker Node 3대 의 환경을 구성하기 위한 기본 Setting을 진행한다.
# (Optional) 각 Instance에 SSH 접속을 하게 되면 Private IP 주소로 나오기 때문에 이에 대한 간단한 설정만 진행한다
sudo su
hostnamectl set-hostname <Node Name>
su ec2-user
# 기본적으로 ROOT USER가 아닐 때를 가정하고 진행한다.
# 1. yum repo check
sudo yum repolist all
sudo yum update -y
# /etc/hosts에 Cluster를 구성할 IP를 넣어준다.
sudo vi /etc/hosts
# 안에 들어갈 내용 : 완료되면 저장하고 나온다.
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
# IPv6는 사용하지 않기 때문에 주석 처리
#::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
<Master Node Private IP> master1
<Worker1 Node Private IP> worker01
<Worker2 Node Private IP> worker02
<Worker3 Node Private IP> worker03
- Docker 와 사용할 것이므로 Docker를 사전에 설치해주도록 한다. Docker 설치를 했다면
kubeadm init
과정에서 kubelet 관련 이슈를 제거하기 위해/etc/docker
경로에daemon.json
파일을 추가해주도록 한다.
# /etc/docker 경로에 daemon.json 파일 추가 -> kubeadm init 시 kubelet 관련 이슈를 제거
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"
}cd
EOF
# Docker daemon reload 및 Docker Re-start
sudo systemctl daemon-reload
sudo systemctl restart docker
sudo systemctl enable docker
- Kubernetes 설치하기 전 System Configuration 을 진행한다.
# 방화벽 Disable : firewalld 자체가 없으면 Error
sudo systemctl stop firewalld && sudo systemctl disable firewalld
# Swap off
swapoff -a && sudo sed -i '/swap/s/^/#/' /etc/fstab
# SE Linux Off
sudo setenforce 0
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
# Kubernetes Configuration Setting
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
br_netfilter
EOF
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sudo sysctl --system
# Kubernetes, Centos 우회 Repo Registration
# [주의!] 처음에 vi를 통해서 작성을 하게 되면 '$' 부분이 정상적으로 적용이 안되는 Case가 발생한다.
# 반드시 re-check해주고 수정해준다.
# [Kubernets Repo]
cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\$basearch
enabled=1
gpgcheck=0
#repo_gpgcheck=1
#gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
exclude=kubelet kubeadm kubectl
EOF
#[Centos7 Repo]
# 마찬가지로 re-check
cat <<EOF | sudo tee /etc/yum.repos.d/cent7.repo
[base]
name=CentOS-$releasever - Base
baseurl=http://mirror.centos.org/centos/7/os/$basearch/
gpgcheck=0
[updates]
name=CentOS-$releasever - Updates
baseurl=http://mirror.centos.org/centos/7/updates/$basearch/
gpgcheck=0
EOF
# 적용 후 update
sudo yum repolist all
sudo yum update -y
- System & Kubernetes Configuration까지 설정했다면, 본격적으로 Kubernetes를 사용하기 위한 Package들을 설치한다. 설치하는 Package는 다음과 같다.
- Kubeadm
- Kubelet
- Kubectl
# Kubernetes 설치를 위한 Version Check
sudo yum list ==showduplicates kubeadm --disableexcludes=kubernetes
# Version Check에서 Release Version 문제가 발생하면 수행
sudo su
echo 7Server > /etc/yum/vars/releasever
su ec2-user
# Kubelet Kubeadm Kubectl Version에 맞춰서 설치 : 여기서는 1.21.x 버전 사용
sudo yum install -y kubelet-1.21.1-0 kubeadm-1.21.1-0 kubectl-1.21.1-0 --disableexcludes=kubernetes
# Enable kubelet(Kubernetes)
# 다 끝나고 version check 통해서 설치되었는지 확인
sudo systemctl enable --now kubelet
kubelet --version
kubeadm version
kubectl version
-
정상적으로 버전이 나온다면 설치가 완료되었다는 것이므로, 다음으로는 Master Node와 Worker Node의 환경을 구성해준다.
-
Master Node (Worker Node에서는 해당 작업을 수행하지 않음)
# Master Node에서 Initialization을 할 때 Endpoint는 Master Node가 설치되어 있는 Instance의 Private IP를 넣어준다.
# 기본적으로 Kubernetes는 6443 Port를 사용한다.
# kubeadm init을 하게 되면, Token, Certification Hash Key 값이 나옴 : 모르면 아래의 Command를 참조한다.
sudo kubeadm init --control-plane-endpoint "<Master Node Private IP>:6443" --upload-certs
# (Optional) Token 값을 모를 때
kubeadm token list
# (Optional) Certification Hash 값을 모를 때
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'
# Initilization이 끝나면 Root Mode를 설정해준다.
export KUBECONFIG=/etc/kubernetes/admin.conf
# 일반 사용자를 위한 Configuation Setting
# .kube directory에 admin config file 복사
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
# config file 권한 설정
sudo chown $(id -u):$(id -g) $HOME/.kube/config
- Worker Node (Master Node에서는 해당 작업을 수행하지 않음)
# Master Node와 Connection을 위한 작업으로, Master Node Private IP를 가져옴
# Token, Certification Hash Value는 위에서 작업한 것을 토대로 가져온다.
sudo kubeadm join <Master Node Private IP>:6443 --token <Token Value> --discovery-ca-cert-hash sha256:<Certification Hash Value>
- 여기까지 작업이 다 끝났다면, Master Node가 설치되어 있는 Instance를 시원하게 Reboot 해준 뒤에, CNI 설치하고 정상적으로 Connection이 되었는지 확인해준다.
# After Reboot
curl https://docs.projectcalico.org/manifests/calico.yaml -O
# Kubernetes에 다운받은 CNI 적용
kubectl apply -f calico.yaml
# Node Connection Check
kubectl get nodes -o wide
# (Optional) kubectl 명령어를 계속 쓰게 되는데 보통 alias 설정을 k로 해준 뒤에 작업을 많이 한다. 이에 대한 Command
# .bashrc 열기 (Root 권한으로 Open)
sudo vi ~/.bashrc
# .bashrc에 alias 등록
alias k='kubectl'
# alias 적용 (kubectl -> k)
source ~/.bashrc
- 모두 정상적으로 설치되었을 때 나오는 화면 :
Kubernetes에 대한 Resource를 간단하게 안내
Kubernetes에서는 Pod 를 설정하면서 Container에 필요한 Resource를 지정할 수가 있는데, 기본적으로 CPU 와 Memory(RAM) 를 지정할 수 있다. Resource 할당은 2개의 분류로 나누어서 할 수 있는데, 다음과 같다.
- Requests : Pod를 실행시키기 위한 Minimum Resource
- Limits : Pod가 사용할 수 있는 Maximum Resource
- CPU : CPU 단위로 측정이 되는데, 기본적으로 m 을 사용하여 "Milli-core" 단위로 표현을 한다. 즉
100m
은100 milli-core
를 나타내며, 이는 1 CPU 기준 0.1에 해당한다. CPU 단위로도 표현할 수 있지만, 주로 Detail Control을 위해 milli-core 단위로 많이 표기한다. - Memory(RAM) : Memory는 Byte 단위로 측정이 되는데,
E, P, T, G, M, k
와 같은 수량 접미사를 사용할 수도 있고,Ei, Pi, Ti, Gi, Mi, Ki
와 같은 2의 거듭제곱 형태로도 사용할 수 있다.
여기서 대소문자의 구분을 명확하게 해줄 수 있도록 한다.
YAML 파일에서 어떻게 표현이 되는지 Inference CRD를 기반으로 간단하게 나타내보았다.
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
annotations:
sidecar.istio.io/inject: "false"
name: volume-test
namespace: kubeflow-user-example-com
spec:
predictor:
sklearn:
protocolVersion: v2
resources:
limits:
cpu: 750m
memory: 1Gi
requests:
cpu: 500m
memory: 2Gi
storageUri: pvc://test-volume/model/model.joblib
- Overcommit : Node 내의 Container 모든 Resource의 Limit 합계가 Node 전체 Resource의 양을 초과한 상태로, Kubernetes는 Overcommit을 허용하는데, 보통 Node가 Scale up 되어지거나, Pod가 삭제되고 다시 생성된다. 이때 Pod가 삭제되는 순서는 Resource 요청을 가장 많이 초과한 Pod부터 삭제된다.
NVIDIA GPU
Kubeflow 자체가 MLOps 를 위한 것이기 때문에 대부분 GPU를 사용하게 될 것이다. 그래서 GPU를 사용하는 방법에 대해서 간단하게 서술하려고 한다. 흐름은 Kubernetes - GPU 스케줄링 을 참조했으며, AWS EC2 Instance 중에서 G4dn.xlarge 을 기반으로 작업했다. (사실 돈 나가는 것이 무서워서 G4dn 했음...) G4dn은 NVIDIA GPU Tesla T4 1개를 사용한다.
GPU를 할당하고 싶다면 Computing Resource에 GPU가 있어야 하며, 해당 Computer를 Cluster에 구성할 수 있어야 한다.
NVIDIA GPU Resource를 사용하기 위해서 우선 NVIDIA GPU Driver 를 설치해야한다. GPU Driver 검색 에 가서 맞는 버전을 찾아서 다운받은 다음에 설치를 진행한다.
Driver 설치 과정에서 다양한 오류들이 발생할 수도 있는데, (Nouveau 충돌 이슈, Kernerl Install 이슈 등...) 이 과정들은 구글링을 통해서 해결하는 것이 더 나을 것 같다. Driver 설치 후 NVIDIA Docker 2.0 를 설치하는데, 과정은 다음과 같다.
- Repository 및 GPG Key 설정
distribution=$(. /etc/os-release;echo $ID$VERSION_ID) \
&& curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
&& curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
- NVIDIA Docker 2.0 Install
sudo yum update
sudo yum install -y nvidia-docker2
- NVIDIA Docker 2.0 설치가 정상적으로 끝나면 Docker의
Daemon.json
파일을 수정
# sudo vi /etc/docker/daemon.json
{
"default-runtime": "nvidia",
"runtimes": {
"nvidia": {
"path": "/usr/bin/nvidia-container-runtime",
"runtimeArgs": []
}
}
}
sudo pkill -SIGHUP dockerd
# Restart Docker
sudo systemctl restart docker
작업을 마친 후 nvidia-smi
를 Terminal에 입력하면 다음과 같은 NVIDIA GPU Spec이 나온다.
이후 해당 Computer를 Node로 붙인 후 Kubernetes에서 NVIDIA GPU를 사용할 수 있게 Master Node에서 Kubernetes NVIDIA Device Plugin 을 설치해준다.
kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/1.0.0-beta4/nvidia-device-plugin.yml
GPU를 사용할 수 있도록 설정을 해주었으니, 이를 Pod에 할당해주면 된다.
Pod 혹은 CRD에 할당하는 방법은 다음과 같이 Resource 선언 부분에서 Requests 및 Limits 에 정의해주면 되는데, 주의할 점은 Limit와 Request의 GPU 수를 반드시 동일하게 맞춰주도록 한다.
# GPU 스케줄링을 가져옴
apiVersion: v1
kind: Pod
metadata:
name: cuda-vector-add
spec:
restartPolicy: OnFailure
containers:
- name: cuda-vector-add
# https://github.com/kubernetes/kubernetes/blob/v1.7.11/test/images/nvidia-cuda/Dockerfile
image: "k8s.gcr.io/cuda-vector-add:v0.1"
resources:
limits:
nvidia.com/gpu: 1
nodeSelector:
accelerator: nvidia-tesla-p100 # 또는 nvidia-tesla-k80 등.
여기서 Node Selector 에 대한 설명을 간단하게 하자면 해당 GPU Node에 Label (accelerator)을 달고, value를 본인이 구분할 수 있도록 GPU Model명을 달아주면, 추후에 다양한 GPU Model이 있을 때 Node Selector를 통해서 원하는 GPU Model을 할당할 수 있다. Kubeflow의 Notebook, Inference 등도 위와 같이 정의해주면 된다.
참고로 Kubeflow Notebook의 생성 화면에서 GPU를 할당할 때 Plugin 설치가 되어있지 않으면 GPU Vendor가 없다고 나오며, GPU Node를 할당하지 않았거나 설정 과정에서 오류가 있어서 정상적으로 할당이 불가능하면 Pod가 생성되지 않는다. 가급적이면 Sample Pod로 올려보고 사용하는 것이 좋지 않을까 싶다.
또한, AWS 및 타 Cloud Service 회사에서 GPU Resource를 이용하게 되면 비용이 꽤 많이 부가된다. 그래서 습관적으로 On/Off 할 수 있는 습관을 들이면 좋을 것 같다.
Kubernetes를 사용하면서 알아두면 유용한 Command를 기록해둔다.
- Pod 재시작 :
kubectl get pod -n ${NAMESPACE} ${POD_NAME} -o yaml | kubectl replace --force -f-
- Pod Log 조회 :
kubectl logs -c ${CONTAINER_NAME} -n ${NAMESPACE} ${POD_NAME}
- 특정 Service Type으로 Pod 노출 :
kubectl expose pod -n ${NAMESPACE} {POD_NAME} --type=${SERVICE_TYPE}
(NodePort, Cluster IP, LoadBalancer) - Inference Service 환경설정 조회 :
kubectl edit(describe) configmap -n ${NAMESPACE} inferenceservice-config
- 특정 Pod Shell Script 접속 :
kubectl exec --stdin --tty -n ${NAMESPACE} ${POD_NAME} -- /bin/bash
- Kubernetes Defualt Namespace 설정 :
kubectl config set-context --current --namespace=${NAMESPACE}
- Persistant Volume Claim(PVC) State가 'Terminating' 상태로 남아있을 때 삭제하는 방법 :
kubectl patch pvc -n ${NAMESPACE} ${PVC_NAME} -p '{"metadata": {"finalizers": null}}'
- Kubernetes Node/Pod 상태 체크 :
kubectl top node(pod) ${NODE_NAME}(-n ${NAMESPACE} ${POD_NAME})