Home Spark on Kubernetes 사용해보기
Post
Cancel

Spark on Kubernetes 사용해보기


들어가며

지난 포스팅 에서는 spark on yarnspark 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 방식으로 완전히 자동화하려고 할 때 많이 사용합니다. kubectl apply -f spark-application.yaml 방식으로 Spark Job을 Kubernetes의 리소스 형태로 관리 가능하고 운영 및 유지보수가 쉽다는 장점이 있습니다. 반복적인 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 접속

저는 로컬에서 테스트해봤는데 마침 사이드 프로젝트를 하여 구축해둔 쿠버네티스 클러스터가 있어 여기에 올려보았습니다. 컨텍스트를 변경하고 정보를 잘 가지고 오는지 확인해둡니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 현재 쿠버네티스 컨텍스트 확인
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/proxy

kubectl 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

cluster-info가 가져와지지 않는다면? 만약 기존 k8s cluster 인증에 특정IP 만 허용되어 있다면 다음 명령어로 갱신해 줄 필요가 있습니다. 저는 인증서 내 k8s의 private ip 만 추가되어 있어 public ip를 추가해 주었습니다.

1
2
3
4
sudo mv /etc/kubernetes/pki/apiserver.crt /etc/kubernetes/pki/apiserver.crt.bak
sudo mv /etc/kubernetes/pki/apiserver.key /etc/kubernetes/pki/apiserver.key.bak
sudo kubeadm init phase certs apiserver --apiserver-cert-extra-sans={remote-k8s-ip(public)}
sudo systemctl restart kubelet

(spark 세팅)

로컬에 spark 가 설치되었다면 따로 세팅할 부분은 없습니다. 아래는 제가 실습한 로컬PC의 버전 정보입니다.

1
2
3
#spark version: 3.5.4 
#Scala version: 2.12.18
#java version: OpenJDK 11.0.25

Step 2. Spark 용 Docker 이미지 준비

Kubernetes는 Spark 애플리케이션을 실행할 때 각 실행 단계를 컨테이너로 실행합니다. 따라서 Spark 에서 실행할 Docker 이미지가 필요하며, 이를 원격 레지스트리에 푸시해야 합니다. 저는 Spark example jar 들을 포함하고 있는 공식 docker 이미지를 사용할 것이므로 추가적인 빌드는 하지 않았습니다.

Step 3. Spark 실행 리소스 설정

Kubernetes에서 Spark Job을 실행하려면 적절한 권한(Role, RoleBinding) 이 필요합니다. 하단의 yaml 파일은 spark라는 ServiceAccount 를 생성하고, 이를 Kubernetes의 ClusterRole(edit)바인딩(RoleBinding) 하는 설정입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: ServiceAccount
metadata:
  name: spark
  namespace: default

---

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: spark-role-binding
  namespace: default
subjects:
  - kind: ServiceAccount
    name: spark
    namespace: default
roleRef:
  kind: ClusterRole
  name: edit
  apiGroup: rbac.authorization.k8s.io
1
2
3
4
5
6
7
8
9
10
# Service account 는 바로 생성해 둡니다.
kubectl apply -f spark-serviceaccount.yaml
#>
#serviceaccount/spark created
#rolebinding.rbac.authorization.k8s.io/spark-role-binding created

# pods 생성이 가능한지도 체크해 둡니다.
kubectl auth can-i create pods --as=system:serviceaccount:default:spark -n default
#>
#yes

Step 4. spark-submit 실행

이제 준비가 다 되었으므로 spark-submit 을 통해 Job을 제출합니다. 이때 사용된 KUBECONFIG 는 여러대의 cluster 가 로컬에 등록되었을경우 해당 config 를 알맞게 적용하기 위한 것으로 각자 세팅에 맞게 사용하시면 됩니다.

1
2
3
4
5
6
7
8
9
KUBECONFIG=/Users/{username}/.kube/kubeconfig-remote-onpremise spark-submit \
  --master k8s://https://210.94.87.94:6443 \
  --deploy-mode cluster \
  --conf spark.kubernetes.container.image=apache/spark:3.5.4 \
  --conf spark.kubernetes.authenticate.driver.serviceAccountName=spark \
  --conf spark.executor.instances=2 \
  --conf spark.kubernetes.namespace=default \
  --class org.apache.spark.examples.SparkPi \
  local:///opt/spark/examples/jars/spark-examples_2.12-3.5.4.jar 100

Step 5. 결과 확인

spark-submit 결과는 다음과 같이 잘 수행된 것을 확인 할 수 있었습니다. submit을 하고 나서 kubectl 명령어로 pod를 가지고 오면 아래 응답값과 캡쳐 내용처럼 어떻게 수행되었는지에 대한 log들을 확인할 수 있습니다. 정상적으로 pi 값을 계산해낸것으로 확인됩니다.

Image 1 Image 2
1
2
3
4
5
6
kubectl get pods
#>
#NAME                                                        READY   STATUS        RESTARTS   AGE
#org-apache-spark-examples-sparkpi-55ca6b9555b70d29-driver   0/1     Completed     0          3m
kubectl 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 Operatorkubectl apply -f 명령어로 Spark Job을 Kubernetes 리소스로 실행할 수 있도록 해줍니다. 실행 과정을 복기하면 다음과 같습니다.

동작방식

  1. Spark Operator가 Kubernetes 클러스터에서 실행됨.
  2. 사용자는 SparkApplication이라는 Kubernetes Custom Resource Definition (CRD) 를 생성.
  3. Spark Operator가 SparkApplication을 감지하고, Spark Driver 및 Executor Pod를 자동으로 생성.
  4. 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 위에 설치를 진행합니다.

1
2
3
4
5
6
7
8
9
10
# kubeflow spark-operator in helm
helm repo add spark-operator https://kubeflow.github.io/spark-operator
helm repo update
helm install spark-operator spark-operator/spark-operator --namespace spark-operator --create-namespace

kubectl get pods -n spark-operator
#>
#NAME                                         READY   STATUS    RESTARTS   AGE
#spark-operator-controller-7b674d78dd-lz4mm   1/1     Running   0          2m2s
#spark-operator-webhook-c88fd9895-scg82       1/1     Running   0          2m2s

Step 2. yaml 작성

Spark Operator는 SparkApplication 이라는 Kubernetes CRD(Custom Resource) 를 통해 Spark Job을 실행합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: sparkoperator.k8s.io/v1beta2 
kind: SparkApplication # Kubernetes CRD(Custom Resource)
metadata:
  name: spark-pi
  namespace: default
spec:
  type: Scala
  mode: cluster
  image: apache/spark:3.5.4
  imagePullPolicy: Always
  mainClass: org.apache.spark.examples.SparkPi
  mainApplicationFile: local:///opt/spark/examples/jars/spark-examples_2.12-3.5.4.jar
  sparkVersion: "3.5.4"
  restartPolicy:
    type: Never
  driver:
    cores: 1
    memory: "512m"
    serviceAccount: spark # 아까 생성한 k8s 실행 계정
  executor:
    cores: 1
    instances: 2
    memory: "512m"

Step 3. yaml 실행

준비한 yaml 파일을 실행합니다. 실행하면 yaml 에서 정의한 SparkApplication 이 생성됩니다. 이 이후에 spark-operator 가 이를 감지하고 Spark Driver 및 Executor Pod를 자동으로 생성합니다.

1
2
3
4
5
6
7
8
9
# SparkApplication 생성
kubectl apply -f spark-pi.yaml
#>
#sparkapplication.sparkoperator.k8s.io/spark-pi created

kubectl get sparkapplications
#>
#NAME       STATUS      ATTEMPTS   START                  FINISH       AGE
#spark-pi   SUBMITTED   1          2025-03-02T09:47:04Z   <no value>   7s

Step 4. 결과 확인

수행된 결과를 확인합니다. pod 를 열어 로그를 확인하면 pi값을 정상적으로 계산해낸 것을 알 수 있습니다.

Image 1 Image 2
1
2
3
kubectl get pods
kubectl 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 에 리소스를 얹거나 사이드 프로젝트에 활용할 일이 생기면 종종 기록으로 남겨둬야겠습니다. 👋


참고문헌

This post is licensed under CC BY 4.0 by the author.