Welcome to Jaehai.Opslog
안녕하세요! 넓은 스펙트럼을 바탕으로 전체적인 그림을 보는 Ops Engineer 이재하입니다.
이번 글에서는 제가 KServe Contributor가 된 기념으로 썰(?)을 풀어볼까 합니다.
어떻게 발견했나요?
현업에서 Multi Cluster 환경에서 API 하나로 KServe InferenceService로 배포하는 솔루션을 개발하는 일을 하고 있었습니다. 1개의 GPU에 여러가지 모델을 Serving 해야하기 때문에 TrainedModel을 섞어서 사용하고 있었고, KServe의 TrainedModel StorageURI는 HTTP, HTTPS, S3, GS 만 지원하였습니다.
저희는 클라우드 환경에서는 GKE를 사용하고 있었기 때문에 GS를 Model Storage를 사용하였고, GKE 및 로컬 클러스터에 Kubeflow와 KServe를 설치하며 모든 것이 순조(?)로웠습니다.
TrainedModel StorageURI에 gs://우리버킷/우리모델 을 기입하여 TrainedModel.yaml을 apply 한 순간
KServe Agent가 OOM 에러를 내뱉었습니다.
무엇이 문제인가요?
자 그럼 이 KServe Agent가 무엇을 하는지 알아봐야겠죠? KServe Agent의 Log를 까보았더니 이 친구가 Google Cloud Storage에서 Model을 다운로드하는 것을 알았습니다. “Downloading 우리버킷의우리모델 to model dir /mnt/models”의 로그를 마지막으로 pod가 계속 재시작되는 문제가 발생했습니다. 하루는 계속 두면 어떻게 될까하는 생각으로 하루정도 관찰한 결과 4000번 이상의 재부팅과 agent pod가 Restart 되는 과정에서는 istio-proxy에 요청을 보낼 수 없었습니다.
어떻게 해결했나요?
저는 먼저 Golang을 한번도 사용해보지 않았고, 어떤 형식으로 되어있는지도 몰랐습니다. (이렇게 밑밥 한번)
모든 개발자분들이 공감해주셔야합니다. 가장 쉬운 디버깅 방법으로는 print문을 남발하는 것입니다.
네, 모든 함수에 print를 찍어보았고 마침내 저는 에러가 나는 함수를 찾을 수 있었습니다.
func (g *GCSObjectDownloader) DownloadFile(client stiface.Client, attrs *gstorage.ObjectAttrs, file *os.File) error {
reader, err := client.Bucket(attrs.Bucket).Object(attrs.Name).NewReader(g.Context)
if err != nil {
return fmt.Errorf("failed to create reader for object(%s) in bucket(%s): %w",
attrs.Name,
attrs.Bucket,
err,
)
}
defer func(reader stiface.Reader) {
closeErr := reader.Close()
if closeErr != nil {
log.Error(closeErr, "failed to close reader")
}
}(reader)
data, err := io.ReadAll(reader)
if err != nil {
return fmt.Errorf("failed to read object(%s) in bucket(%s): %w",
attrs.Name,
attrs.Bucket,
err,
)
}
return g.WriteToFile(data, attrs, file)
}