지난 포스팅에서는 Spark on YARN과 Spark on Kubernetes의 차이점을 알아보고, 이를 구성하는 세 가지 방법을 알아보았다. 세 방법을 정리하면 다음과 같다.
첫 번째 방법은 Native Kubernetes Integration을 사용하는 방법이다.
다른 용어로 Kubernetes Native Mode라고도 한다. AWS EKS, Google GKE, Azure AKS 같은 클라우드 Kubernetes 환경에서 Spark를 실행할 때 주로 많이 사용하며, 리소스 최적화, 단발성 Spark Job 실행의 경우에 자주 활용된다.
두 번째 방법은 Spark Operator를 사용하는 방법이다.
Airflow, Kubeflow, Argo Workflows 같은 워크플로우 오케스트레이션 시스템과 함께 사용할 때나 Spark를 Kubernetes Native 방식으로 완전히 자동화하려고 할 때 많이 사용한다. 반복적인 Spark Job 관리, 자동화의 경우에 자주 활용된다.
세 번째 방법은 Kubernetes에 Spark Standalone Mode를 구축하는 방법이다.
이는 실무에서 잘 사용되는 방법은 아니다. Kubernetes의 리소스 스케줄링 기능을 거의 활용할 수 없고 Spark Master/Worker를 Kubernetes에서 직접 관리해야 하므로 운영 부담이 크기 때문이다.
이번 포스팅에서는 1) Native Kubernetes Integration, 2) Spark Operator 각각을 간단하게 실행해보고 이를 정리해두려고 한다.
Kubernetes 네이티브 모드 (Native Kubernetes Mode)
Step 1. (원격) Kubernetes Cluster 접속
로컬에서 테스트해봤는데 마침 사이드 프로젝트를 하여 구축해둔 쿠버네티스 클러스터가 있어 여기에 올려보았다. 컨텍스트를 변경하고 정보를 잘 가져오는지 확인해둔다.
# 현재 쿠버네티스 컨텍스트 확인kubectl config get-contexts#>#CURRENT NAME CLUSTER AUTHINFO NAMESPACE# docker-desktop docker-desktop docker-desktop# kubernetes-admin@kubernetes kubernetes kubernetes-admin#* minikube minikube minikube default# 원격 클러스터로 변경kubectl config use-context kubernetes-admin@kubernetes#> #Switched to context "kubernetes-admin@kubernetes".# 접속상태 확인kubectl cluster-info#> #Kubernetes control plane is running at https://{remote-k8s-ip}:6443#CoreDNS is running at https://{remote-k8s-ip}:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxykubectl get nodes#>#NAME STATUS ROLES AGE VERSION#myserver01 Ready control-plane 31d v1.29.5#myserver02 Ready <none> 31d v1.29.5#myserver03 Ready <none> 31d v1.29.5
Tip
cluster-info가 가져와지지 않는다면?
만약 기존 K8s cluster 인증에 특정 IP만 허용되어 있다면 다음 명령어로 갱신해 줄 필요가 있다. 인증서 내 K8s의 private IP만 추가되어 있어 public IP를 추가해 주었다.
Kubernetes는 Spark 애플리케이션을 실행할 때 각 실행 단계를 컨테이너로 실행한다. 따라서 Spark에서 실행할 Docker 이미지가 필요하며, 이를 원격 레지스트리에 푸시해야 한다. Spark example jar들을 포함하고 있는 공식 docker 이미지를 사용할 것이므로 추가적인 빌드는 하지 않았다.
Step 3. Spark 실행 리소스 설정
Kubernetes에서 Spark Job을 실행하려면 적절한 권한(Role, RoleBinding)이 필요하다. 하단의 yaml 파일은 spark라는 ServiceAccount를 생성하고, 이를 Kubernetes의 ClusterRole(edit)과 바인딩(RoleBinding)하는 설정이다.
spark-submit 결과는 다음과 같이 잘 수행된 것을 확인할 수 있었다. submit을 하고 나서 kubectl 명령어로 pod를 가져오면 아래 응답값과 캡처 내용처럼 어떻게 수행되었는지에 대한 log들을 확인할 수 있다. 정상적으로 pi 값을 계산해낸 것으로 확인된다.
kubectl get pods#>#NAME READY STATUS RESTARTS AGE#org-apache-spark-examples-sparkpi-55ca6b9555b70d29-driver 0/1 Completed 0 3mkubectl logs -f org-apache-spark-examples-sparkpi-55ca6b9555b70d29-driver#kubectl port-forward org-apache-spark-examples-sparkpi-55ca6b9555b70d29-driver 4040:4040 -n default
Spark Operator (Kubernetes CRD 활용) 실행
이번에는 Kubernetes에서 Spark Operator를 활용하여 Spark Job을 실행하는 방법을 알아본다. Spark Operator는 kubectl apply -f 명령어로 Spark Job을 Kubernetes 리소스로 실행할 수 있도록 해준다. 실행 과정을 복기하면 다음과 같다.
동작방식
Spark Operator가 Kubernetes 클러스터에서 실행된다.
사용자는 SparkApplication이라는 Kubernetes Custom Resource Definition (CRD)를 생성한다.
Spark Operator가 SparkApplication을 감지하고, Spark Driver 및 Executor Pod를 자동으로 생성한다.
Spark Job이 끝나면 Pod가 자동 정리되며, Job 상태가 Kubernetes 리소스로 관리된다.
Step 1. Spark Operator 설치 (helm)
현재 Spark Operator의 Helm 차트는 Kubeflow 커뮤니티에서 관리하고 있으며, 새로운 Helm 저장소 URL은 https://kubeflow.github.io/spark-operator이다. 해당 helm chart를 추가하고 Kubernetes 위에 설치를 진행한다.
준비한 yaml 파일을 실행한다. 실행하면 yaml에서 정의한 SparkApplication이 생성된다. 이후 spark-operator가 이를 감지하고 Spark Driver 및 Executor Pod를 자동으로 생성한다.
# SparkApplication 생성kubectl apply -f spark-pi.yaml#>#sparkapplication.sparkoperator.k8s.io/spark-pi createdkubectl get sparkapplications#>#NAME STATUS ATTEMPTS START FINISH AGE#spark-pi SUBMITTED 1 2025-03-02T09:47:04Z <no value> 7s
Step 4. 결과 확인
수행된 결과를 확인한다. Pod를 열어 로그를 확인하면 pi값을 정상적으로 계산해낸 것을 알 수 있다.
kubectl get podskubectl logs -f spark-pi-driver#kubectl port-forward spark-pi-driver 4040:4040
정리하며
이번 포스팅에서는 Spark on Kubernetes의 주요 구성 방법인 1) Native Kubernetes Integration, 2) Spark Operator를 각각 간단하게 테스트해보았다. 실무에서 Spark on Kubernetes를 효율적으로 사용하기 위해서는 더 세세하고 많은 테스트와 공부가 필요하겠지만, 대략적인 느낌을 알 수 있어 좋았다. 추후에도 K8s에 리소스를 얹거나 사이드 프로젝트에 활용할 일이 생기면 종종 기록으로 남겨두겠다.