KServe는 LLM 모델부터 예측형 ML 모델까지 Kubernetes 환경에 쉽게 배포시켜주는 툴입니다. 그래서 머신러닝 모델을 개발하고 서비스하는 회사와 조직에서 정말 많이 사용하고 있죠.

지난 아티클에서는 KServe의 핵심을 살펴봤는데요.

이번에는 KServe를 직접 로컬 Kubernetes 환경에 배포하는 실습을 진행하겠습니다. 아마 많은 분들이 KServe가 좋은 건 아는데 써볼 수 있는 환경을 만들기 어려우셨을 거예요.

KServe 공식 문서에서 KServe를 설치하고 사용할 수 있는 다양한 방법을 다루고 있지만, 오히려 너무 선택지가 많고 그 분량도 많아서 방향을 잡기 어려울 수 있겠더라구요.

그래서 여러분들이 실습하기 쉽도록 외부 툴 의존도는 최소화하면서, 나중에 프로덕션 환경에서도 활용하기 쉽도록 안정성을 함께 챙겨가는 방법을 찾았습니다. 모두 제가 직접 테스트해가면서 알게 된 것입니다.

그럼 바로 실습에 들어가보겠습니다.


Kind 클러스터에 KServe를 올려보자

KServe를 빠르고 간편하게 사용할 수 있도록 불필요한 요소는 최대한 걷어내면서 배포해볼 건데요. 그러면서도 안정성을 함께 고려하며 진행하겠습니다. 추후 사내 플랫폼이나 프로덕션에서 활용하기 좋도록 말이죠.

실습에 앞서 우리에게 필요한 준비물은 아래와 같습니다.

  • Kubernetes 클러스터 (kubectl 포함)
  • Helm

그리고 저는 WSL2(Windows Subsystem for Linux) 환경에서 실습을 진행했습니다.

이번 가이드도 Linux, Windows, macOS 환경에 모두 동작하도록 준비했는데요.

만약 Windows를 사용 중이시라면 WSL2 환경을 적극 권장드립니다.

1. Kind 클러스터 구축

저는 Kind를 활용해서 로컬 Kubernetes 클러스터를 구축한 다음, 그 위에 KServe를 배포할 겁니다. 원한다면 minikube처럼 다른 툴로 클러스터를 구축하고 진행하셔도 좋습니다.

Kind 클러스터 구성은 이렇습니다.

  • Control Plane 1개와 Worker 노드 1개로 구성
  • 각 노드의 Kubernetes 버전은 1.32
  • extraPortMappings 설정 (Windows/macOS 로컬 환경에서 8080 포트로 Worker 노드의 80 포트로 접근 가능)
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: test-cluster
nodes:
- role: control-plane
  image: kindest/node:v1.32.0
- role: worker
  image: kindest/node:v1.32.0
  extraPortMappings:
  - containerPort: 80
    hostPort: 8080

그리고 kind create cluster --config {Kind 클러스터 설정 YAML 파일 경로} 명령어로 로컬 Kubernetes 클러스터를 구축합니다.

2. cert-manager 배포

cert-manager는 클러스터에 배포된 리소스가 더욱 안전하게 통신할 수 있도록 TLS 인증서를 생성하고 자동 갱신해주는 컴포넌트입니다. 프로덕션 환경에서 보안성을 높이는 데에 필수겠죠?

그리고 우리가 사용할 KServe의 내부 통신에 cert-manager의 CRD(Custom Resource Definition)를 통해 TLS를 사용하기 때문에, 프로덕션 환경이 아니더라도 필수로 배포해야 할 컴포넌트입니다.

Helm으로 cert-manager를 배포해보겠습니다. --set crds.enabled=true는 Helm으로 cert-manager의 CRD를 설치하겠다는 설정입니다.

helm install \
  cert-manager oci://quay.io/jetstack/charts/cert-manager \
  --version v1.19.2 \
  --namespace cert-manager \
  --create-namespace \
  --set crds.enabled=true

cert-manager 설치 완료!

kubectl -n cert-manager get po 명령어로 cert-manager가 잘 배포되었는지 확인할 수 있습니다.

아까 Helm으로 cert-manager 설치하는 명령어에서 --namespace 옵션의 값을 cert-manager로 설정하고 --create-namespace 옵션을 명시했기 때문에 기존에 없던 cert-manager 네임스페이스가 새로 생성되고 그 안에 cert-manager의 컴포넌트들이 설치된 것입니다.

3. 로드밸런서 MetalLB 배포

다음은 우리 클러스터가 사용할 로드밸런서를 설치해보겠습니다.

로컬이나 온프레미스 환경에서 사용 가능한 MetalLB를 사용해볼 건데요. 이것도 Helm으로 배포할게요. 배포할 네임스페이스는 metallb-system으로 지정합니다.

helm repo add metallb https://metallb.github.io/metallb |
helm install metallb metallb/metallb \
  --namespace metallb-system \
  --create-namespace

kubectl -n metallb-system get po 명령어를 입력하면 MetalLB 관련 컴포넌트가 정상 배포된 것을 볼 수 있습니다.

  • Controller: IP 할당이나 상태 감시 등 관리 역할
  • Speaker: 모든 클러스터 노드에 배포되며 로드밸런서로 들어온 트래픽이 어느 노드로 가야 하는지 알려주는 역할

MetalLB 설치가 완료되었다면 이 로드밸런서가 사용 가능한 네트워크 대역폭을 정해줘야 합니다.

먼저 docker 명령어로 Kind 클러스터가 사용 중인 네트워크 대역폭을 확인하겠습니다.

docker network inspect -f '{{.IPAM.Config}}' kind

네트워크 대역폭이 172.21.0.0/16이면 고정값이 172.21이고, 나머지 뒤의 두 덩어리가 변동값이므로

  • 172.21.0.0 ~ 172.21.255.255

위 범위 내에서 사용 가능합니다. 실습 환경에 따라 대역폭은 다를 수 있으니 꼭 확인해주세요.

하지만 이 범위 전체를 MetalLB가 사용하게 하지는 않고요. 이 중 끝부분 일부 50개만 할당시키겠습니다. 혹시 모를 충돌을 피해 안전하게 지정하기 위함입니다.

  • 172.21.255.200 ~ 172.21.255.250

이제 MetalLB에 사용 가능한 대역폭을 할당해서 실제로 동작하게 만들어볼게요.

그럴려면 2가지 리소스를 새로 배포해야 합니다.

  • IPAddressPool: MetalLB가 사용가능한 대역폭 지정
  • L2Advertisement: MetalLB의 Speaker들이 외부 트래픽이 어디로 가면 되는지 알릴 수 있게(광고) 해줌
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: kind-pool
  namespace: metallb-system
spec:
  addresses:
  - 172.21.255.200-172.21.255.250  # MetaLB에 할당할 범위 지정
---
# L2Advertisement 리소스 내용은 비워둠
# MetaLB에 할당된 모든 IP에 대해 광고 가능
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: kind-advertisement
  namespace: metallb-system

위 매니페스트를 kubectl apply -f 명령어로 배포한 뒤 각 리소스의 배포 상태를 확인해봅니다.

여기까지 문제 없다면 MetalLB 배포도 완료입니다!

4. Gateway API 컴포넌트 배포

KServe는 네트워크 컨트롤러로 Gateway API와 Kubernetes Ingress를 지원합니다.

네트워크 컨트롤러란 클러스터 외부에서 들어오는 트래픽을 내부 서비스로 라우팅하고 관리하는 컴포넌트를 의미합니다. 클러스터의 문지기라고 보면 되겠습니다.

이번 아티클에서는 Kubernetes 클러스터로 들어오고 나가는 트래픽을 더욱 유연하고 표준화된 방식으로 관리할 수 있는 Gateway API를 사용해보겠습니다.

Gateway API는 Kubernetes 내장 컴포넌트가 아니므로, 아래 컴포넌트를 별도로 설치해야 합니다.

  • Gateway API CRD
  • Gateway 동작용 네트워크 컨트롤러

이번 실습에서 사용할 네트워크 컨트롤러는 Envoy Gateway입니다. KServe 공식 문서에서도 Envoy Gateway를 안내하고 있으므로 사용해보겠습니다.

KServe는 Gateway API 버전 1.2.1을 사용합니다.

그래서 Envoy Gateway의 호환성 안내 문서를 보고 알맞은 Envoy Gateway 버전을 설치하는 것이 중요합니다. 호환성 안내 문서에 따라 Envoy Gateway v1.3.2로 배포하겠습니다.

아래 Helm 명령어로 Envoy Gateway를 설치합니다. 여기에 Gateway API CRD도 포함되어 있기 때문에 Gateway API 사용에 필요한 컴포넌트를 한 번에 배포할 수 있습니다.

helm install eg oci://docker.io/envoyproxy/gateway-helm \
  --version v1.3.2 \
  --namespace envoy-gateway-system \
  --create-namespace

Helm으로 Envoy Gateway 설치가 끝나면 해당 네임스페이스의 envoy-gateway Pod가 정상 동작하는지 확인해주고요.

kubectl get crd 명령어로 지금까지 클러스터에 설치된 모든 CRD를 조회해보면 Gateway API와 Envoy Gateway 관련 CRD가 설치된 것을 볼 수 있습니다.

(확인하기 편하도록 Gateway API 관련 CRD는 노란색, Envoy Gateway 관련 CRD는 주황색으로 표시했습니다.)

이제 GatewayClass를 새로 생성해봅시다. 우리가 Gateway에 어떤 네트워크 컨트롤러를 사용할지 정의하는 리소스인데요.

GatewayClass 매니페스트를 YAML 파일로 따로 저장하는 게 번거롭다면, 아래처럼 매니페스트 내용을 kubectl apply -f 명령어에 인풋(Heredoc)으로 넣어 실행해도 좋습니다. 반대로 매니페스트를 YAML 파일로 저장해서 관리하고 싶다면 EOF 사이의 내용만 저장하면 되겠죠?

kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: envoy
spec:
  controllerName: gateway.envoyproxy.io/gatewayclass-controller
EOF

그리고 kubectl get gatewayclass 명령어로 생성한 GatewayClass 리소스를 확인해보면…

Envoy 컨트롤러와 정상 연동 중이라고 출력됩니다.

이제 Gateway API 관련 작업은 끝났고, 클러스터에 KServe를 배포할 차례입니다.

5. KServe 배포

먼저 KServe의 배포 유형 3가지를 짚고 넘어가겠습니다.

  • Kubernetes의 Deployment 리소스 활용 (별도 리소스 설치 필요없음)
  • Knative 활용 (별도 리소스 설치 필요, Scale-to-Zero 지원)
  • ModelMesh (별도 리소스 설치 필요, 대규모 다중 모델 관리에 용이)

이번 아티클에서는 추가 리소스 설치가 필요없는 표준 Kubernetes Deployment 방식으로 KServe를 배포해서 사용해보겠습니다. KServe 배포와 사용에 집중하기 위해서입니다.

우리는 Helm으로 KServe를 배포할 건데요. 그럴려면 먼저 아래 명령어로 KServe의 CRD를 먼저 배포해야 합니다.

helm install kserve-crd oci://ghcr.io/kserve/charts/kserve-crd --version v0.15.0

그 다음 아래 Helm 명령어로 KServe를 배포합니다.

helm install kserve oci://ghcr.io/kserve/charts/kserve \
  --version v0.15.0 \
  --namespace kserve \
  --create-namespace \
  --set kserve.controller.deploymentMode=RawDeployment \
  --set kserve.controller.gateway.ingressGateway.enableGatewayApi=true \
  --set kserve.controller.gateway.ingressGateway.kserveGateway=kserve/kserve-ingress-gateway

Helm 명령어 뒤에 달린 --set 옵션들이 너무 복잡해보일 수도 있습니다만, 사실 간단합니다.

  • KServe를 Deployment 리소스로 배포 (KServe의 배포 유형 3가지 중 하나)
  • Gateway API를 사용도록 설정
  • KServe가 사용할 Gateway 지정 (기본 생성되는 Gateway로)

KServe 공식문서 Helm 설치 명령어에 오류가 있습니다.

KServe 공식문서에서는 kserve.controller.deploymentMode 옵션의 값을 Standard로 설정하라고 나와있습니다. 하지만 해당 옵션으로 배포한 KServe 컨트롤러 Pod는 BackOff 상태로 계속 재시작을 시도하더라고요. 왜 오류가 나지? 의아함을 느끼며 kubectl logs 명령어로 해당 Pod의 로그를 살펴봤습니다. Deployment Mode가 잘못 되었다면서, 지원하는 Mode는 Serverless, RawDeployment, ModelMesh라는 메시지가 출력되었더군요… 조사해보니 Kubernetes Deployment로 배포하는 모드의 설정값이 현재 Standard가 아닌 RawDeployment인 것을 알게 되었습니다. 그래서 명령어 수정 후 다시 배포해보니.. 정상 동작! 아마 공식 문서는 업데이트가 덜 된 것으로 보입니다.

KServe까지 설치가 완료되었네요! kserve 네임스페이스에 배포된 Pod 리스트를 확인해봅시다. (배포 완료까지 시간이 좀 걸릴 수 있습니다.)

kserve-controller-manager라는 Pod가 배포되고 정상 동작 중입니다.

다음은 KServe가 사용할 Gateway 리소스를 생성할 차례입니다. 이번 실습에서는 80 포트(HTTP)만 사용하도록 설정하겠습니다.

아래 명령어로 KServe의 Gateway를 배포해주자고요.

kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: kserve-ingress-gateway
  namespace: kserve
spec:
  gatewayClassName: envoy
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: All
  infrastructure:
    labels:
      serving.kserve.io/gateway: kserve-ingress-gateway
EOF

배포가 끝나면 kubectl -n kserve get gateway 명령어로 Gateway가 잘 생성되었는지 확인해봅시다.

KServe의 Gateway의 주소가 우리의 MetalLB에 할당된 IP 대역폭에 포함되어 있네요.

kubectl -n envoy-gateway-system get svc 명령어를 입력하면 KServe의 Gateway와 연동된 LoadBalancer도 확인할 수 있습니다.


마무리

이번 실습 가이드에서 여러분은…

  • 로컬 Kind 클러스터를 구축한 다음
  • cert-manager가 왜 필요한지 이해한 뒤 배포했고
  • MetalLB라는 로드밸런서도 배포했으며
  • Gateway API의 장점을 짚은 뒤 Envoy Gateway를 배포했고
  • 마지막으로 KServe까지 성공적으로 배포했습니다.

실습 한 번으로 MLOps에 자주 쓰이는 네트워크 및 보안 관련 컴포넌트를 단숨에 설치하고 KServe까지 배포하셨네요. 축하드립니다!

이제 MLOps하면 빠지지 않는 KServe를 직접 사용해보실 수 있게 되었습니다.

혹시 KServe로 무엇을 먼저 해야 할지 고민이신 분들을 위해, 다음 아티클에서는 KServe의 InferenceService를 직접 배포해서 테스트해보는 실습 가이드를 다뤄보려 합니다.

MLOps와 KServe에 대해 이제 막 학습을 시작하신 분이라면 다음 아티클까지 적지 않은 도움이 될 것입니다.

이번 아티클도 읽어주셔서 감사합니다.

오늘도 행복한 하루 되세요😸


함께 읽어보면 좋은 아티클