Spark on Kubernetes
17 Ekim 2024’de güncellendi.
Merhabalar. Bu yazımızda bir Spark uygulamasını Kubernetes cluster (Minikube) üzerinde çalıştıracağız, yaygın adıyla Spark on Kubernetes. Bildiğimiz gibi Spark 2.3 sürümüne kadar sadece 3 cluster yöneticisi vardı: Hadoop YARN, Apache Mesos ve Spark Standalone. Sürüm 2.3 ile beraber Kubernetes de bunların arasına katıldı. Şimdiye kadar Spark kullanımında hakim cluster yöneticisi YARN idi, belki hala öyledir. Peki niçin hala öyle? Kubernetes gibi bir kahraman varken neden hala YARN çok yaygın? Canlı ortamdaki sistemlerin yeniliklere reaksiyon göstermesi çok çabuk olmuyor, biraz zaman alıyor. Öncelikle bir Kubernetes ortamının kullanılmaya başlanması, yani uygulamalarda mikro servis mimarisine geçiş yapılması, tüm geliştirme, yedekleme, izleme, devops vb. süreçlerin mikro servis yaklaşımına uyması gerekiyor. Ancak bundan sonra Spark uygulamalarının da Kubernetes üzerinde koşulması mümkün olabilir. Elbette diğer uygulamalarınızı eski usül devam ettirip sadece Spark işleri için bir Kubernetes cluster kurabilirsiniz fakat sadece Spark için bunu yapar mısınız tartışılır. Yoksa diğer uygulamaların mikro servis mimarisinde çalışması Spark’ı Kubernetes’de koşmak için teknik bir ön koşul değil.
Belki belli bir süre daha konteyner teknolojilerini görmezden gelebilirsiniz fakat trend bu yönde ilerliyor ve her geçen gün daha fazla şirket Spark’ı K8s üzerinde kullanmaya başlıyor. Üstelik 2018’den beri yapılan uzun soluklu geliştirmeler sonunda sürüm 3.1 (Mart 2021) ile beraber Spark Kubernetes üzerinde canlı ortamlar için hazır (general availability/production ready) olduğunu duyurdu. Özetlemek gerekirse; önümüzdeki dönemde Spark olsun veya başka uygulamalar olsun, hatta veri tabanlarında bile, kaçınılmaz bir şekilde konteyner ortamları ile muhatap olacağız.
Bir spark uygulamasını Kubernetes üzerinde 2 şekilde başlatabiliriz.
- spark-submit
- spark operator
İlki diğer cluster yöneticilerinden de alışkın olduğumuz yöntem, ancak ikincisi Kubernetes dünyasına özgü ve daha çok tercih edilen yöntem. Operatörler özellikle statefull uygulamalarda veya veri tabanlarında insan müdahalesi gerektiren durumların önceden tespit edilerek operatör nesnesi tarafından yapılmasını sağlar. Örneğin nosql veri tabanlarının bazılarında önce master nodeları sonra slave nodeları başlatmalısınız. Operatör sizin yerinize bu gereksinimi gözetiyor. Statefull uygulamaların aksine stateless uygulamalar ise durum bilgisi tutmak gerekmediği için deployment objeleriyle rahatlıkla ölçeklenerek Kubernetes üzerinde çalıştırılabilir, yani pek bir operatör ihtiyacı yoktur. Bu yazımızdaki örnek spark operator yöntemiyle olacaktır.
Spark uygulamasını Kubernetes üzerinde çalıştırmanın faydaları:
- Konteyner teknolojisinin sunduğu genel avantajlar Spark uygulamaları için de geçerlidir. Konteynerler uygulamalarınızı daha taşınabilir hale getirir, bağımlılıkların (dependencies) paketlenmesini basitleştirir, tekrarlanabilir ve güvenilir iş akışları inşa etmeye olanak tanır. Genel DevOps yükünü azaltır ve kodunuzu daha hızlı yinelemenize olanak tanır.
- Aynı cluster üzerinde farklı sürüm Spark kullanılabilir.
- Aynı cluster dev/test/prod amacıyla kullanılabilir.
- Tam olarak izolasyon imkanı mevcuttur. Bu imkan YARN’da kısıtlıdır, genel Hadoop cluster ortam bağımlılıklarına uymak gerekir.
- Big data uygulamaları haricindeki uygulamaları da aynı cluster üzerinde çalıştırabilirsiniz. YARN ile bu pek mümkün değil.
- İlk geliştirme sonrası müteakip değişiklikler imaj içinde sadece kod katmanında olduğu için küçük bir değişikliği denemek bazen bir dakikadan bile az sürebilir. Klasik sistemlerde nokta değiştirseniz tüm kodu derleme yapıp çalıştıracağınız ortama taşımanız zaman alır.
- Kaynakları daha etkin kullanabilir ve böylelikle maliyetleri azaltabilirsiniz.
- Client kütüphanelerine bağımlı kalmadan API ile Spark uygulamasını başlatabilirsiniz.
Spark uygulamasını Kubernetes üzerinde çalıştırmanın kısıtları:
- Sürekli olarak operatör için bir pod çalışmalıdır. Ancak bu ihmal edilebilecek kadar küçük bir kaynak tüketir ve üstelik sadece operatör kullandığımızda geçerlidir.
- Dinamik kaynak kullanımı (Dynamic Resource Allocation) özelliği halen YARN’daki olgunluğa sahip değil veya biraz farklı diyelim.
- YARN kadar performanslı çalışmadığı yönünde bazı kıyaslama sonuçları var.
- Spark web arayüzüne ulaşmak için port yönlendirme veya ingress oluşturma gibi ilave işler yapmanız gerekli.
Ortam Bilgileri
Kubernetes Cluster: Minikube v1.34.0
Kubernetes sürümü: v1.31.0
İşletim Sistemi: RockyLinux 8.10
İmaj Repo: Dockerhub
Spark sürümü: 3.5.3
Varsayımlar
Minikube, helm ve kubectl hazır.
Spark Uygulaması Ne İş Yapacak?
Bu örnekte kullandığım spark uygulaması spark.range() ile bir dataframe oluşturup ekrana show ile yazdıracak. Olayı basit tutmak için spark uygulamasının harhangi bir dış bağımlılık koymadım.
Çalışacak Spark Kodu
from pyspark.sql import SparkSession, functions as F from pyspark import SparkContext, SparkConf from pyspark.sql.types import * import os, time spark = SparkSession.builder \ .appName("Spark on K8s") \ .getOrCreate() df = spark.range(1000000).withColumn("plus_10", F.col("id") + 10).withColumn("plus_20", F.col("id") + 20) print("*******************************") print(df.count()) df.printSchema() df.show(100) time.sleep(30) print("Spark is shutting down.") spark.stop()
Spark İmajı Oluşturma (Kodları baz imaja dahil etme)
Kodlarımın, Dockerfile, requirements.txt gibi dosyalarımın olduğu dizine geldim. Baz imaj olarak https://hub.docker.com/_/spark uygun bir imaj seçiniz. Ben spark:3.5.3-java17
seçtim
FROM spark:3.5.3-java17 USER root WORKDIR /app COPY requirements.txt . RUN pip3 install -r requirements.txt COPY spark_on_k8s_app.py .
requirements.txt dosyasına ben sadece ve öylesine findspark paketini koydum. Şimdi bu Dockerfile’ı kullanarak içinde uygulama kodlarının bulunduğu ikinci imajı oluşturalım. Bu kolay olacak çünkü katmanların çoğu zaten yukarıdaki baz Spark imajında burada bir katman daha ekleyip imajı mühürleyeceğiz.
docker build -t spark-k8s-app:2.0 .
Bu imajımızı Dockerhub’a göndermeden önce bir etiket verelim
docker tag spark-k8s-app:2.0 erkansirin78/spark-k8s-app-blog:2.0
Docker login ile oturum açalım ve arkasından imajımızı Dockerhub’a gönderelim.
docker login docker image push erkansirin78/spark-k8s-app-blog:2.0
Spark Operator Kurulumu
Spark uygulaması için Google Cloud tarafından özel olarak yazılmış Spark Operator’ü kuralım. Operatörün github sayfasında detaylı bilgi bulabilirsiniz. Ben helm kullanarak kurulum yapacağım.
helm repo add \ spark-operator \ https://kubeflow.github.io/spark-operator kubectl create ns spark-operator helm install \ my-spark-operator \ spark-operator/spark-operator \ --namespace spark-operator \ --set webhook.enable=true
Bazı ilave özellikleri kullanmak istiyorsak webhook’u aktif ediyoruz. Bakalım operatör kurulmuş mu?
kubectl get all -n spark-operator NAME READY STATUS RESTARTS AGE pod/my-sparkop-release-spark-operator-875cf4546-ccdf7 1/1 Running 2 26h pod/my-sparkop-release-spark-operator-webhook-init-ng5gv 0/1 Completed 0 26h NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/my-sparkop-release-spark-operator-webhook ClusterIP 10.43.18.40 <none> 443/TCP 26h NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/my-sparkop-release-spark-operator 1/1 1 1 26h NAME DESIRED CURRENT READY AGE replicaset.apps/my-sparkop-release-spark-operator-875cf4546 1 1 1 26h NAME COMPLETIONS DURATION AGE job.batch/my-sparkop-release-spark-operator-webhook-init 1/1 2m29s 26h
Kubernetes SparkApplication Objesi Yaratma
Kod yazmaktan sonra asıl ikinci önemli iş burada.
apiVersion: "sparkoperator.k8s.io/v1beta2" kind: SparkApplication metadata: name: pyspark-on-k8s namespace: default spec: type: Python pythonVersion: "3" mode: cluster image: "erkansirin78/spark-k8s-app-blog:2.0" imagePullPolicy: Always mainApplicationFile: local:///app/spark_on_k8s_app.py sparkVersion: "3.5.3" restartPolicy: type: OnFailure onFailureRetries: 3 onFailureRetryInterval: 10 onSubmissionFailureRetries: 5 onSubmissionFailureRetryInterval: 20 driver: cores: 1 coreLimit: "1200m" memory: "512m" labels: version: 3.5.3 serviceAccount: spark executor: cores: 1 instances: 2 memory: "1000m" labels: version: 3.5.3
Yukarıdaki yaml dosyasını açıklayalım:
name: pyspark-on-k8s – Spark uygulamasının adı ne olacak.
image: “erkansirin78/spark-k8s-app-blog:2.0” – Kodlarımızı içine koyup Dockerhub’a gönderdiğimiz imaj.
imagePullPolicy: Always – Kodumuzu her değiştirdiğimizde yeniden derleyip Dockerhub’a push ediyoruz ve Spark’ı Kubernetes üzerinde her çalıştırdığımızda en güncel halini dockerhub’dan alıyor. Kod çok yer kaplamadığı ve fazla yer tutan katmanlar değişmediği için her seferinde koca imajı yeniden indirmiyor.
mainApplicationFile: local:///app/spark_on_k8s_app.py – Çalışacak kodun imaj içinde nerede olduğunu söylüyoruz. Bunun yerini kodun dahil olduğu ikinci imajı oluştururken Docker file içinde ayrlamıştık.
Servis Hesapları
Hata almamak için servis hesaplarını oluşturalım.
kubectl create serviceaccount spark kubectl create clusterrolebinding spark-role --clusterrole=edit --serviceaccount=default:spark --namespace=default
İşte Spark on Kubernetes dediğimiz an
Son olarak SparkApplication objesini çalıştırmak kalıyor. İşte tam burada spark-submit’i Kubernetes Operator jargonuyla yapmış oluyoruz. Spark on Kubernetes başlasın 🙂
kubectl apply -f sparkapplication-without-vols-object.yaml
Yeni bir terminalden podları, mevcut terminalden ise logları izleyelim.
Pod watch (Yeni terminal):
kubectl get pods -w NAME READY STATUS RESTARTS AGE pyspark-on-k8s-driver 0/1 Pending 0 0s pyspark-on-k8s-driver 0/1 Pending 0 0s pyspark-on-k8s-driver 0/1 ContainerCreating 0 0s pyspark-on-k8s-driver 0/1 ContainerCreating 0 3s pyspark-on-k8s-driver 1/1 Running 0 6s spark-on-k8s-382c037b6f4053bc-exec-1 0/1 Pending 0 0s spark-on-k8s-382c037b6f4053bc-exec-1 0/1 Pending 0 0s spark-on-k8s-382c037b6f4053bc-exec-1 0/1 ContainerCreating 0 0s spark-on-k8s-382c037b6f4053bc-exec-2 0/1 Pending 0 0s spark-on-k8s-382c037b6f4053bc-exec-2 0/1 Pending 0 0s spark-on-k8s-382c037b6f4053bc-exec-2 0/1 ContainerCreating 0 0s spark-on-k8s-382c037b6f4053bc-exec-1 0/1 ContainerCreating 0 1s spark-on-k8s-382c037b6f4053bc-exec-2 0/1 ContainerCreating 0 2s spark-on-k8s-382c037b6f4053bc-exec-2 1/1 Running 0 5s spark-on-k8s-382c037b6f4053bc-exec-1 1/1 Running 0 7s
Log watch (Mevcut terminal): Son kısımları sadece kubectl logs -f pyspark-on-k8s-driver
... ... ... 1000000 root |-- id: long (nullable = false) |-- plus_10: long (nullable = false) |-- plus_20: long (nullable = false) ... ... ... +---+-------+-------+ | id|plus_10|plus_20| +---+-------+-------+ | 0| 10| 20| | 1| 11| 21| | 2| 12| 22| | 3| 13| 23| ... ...
Uygulamayı kapatmak için
kubectl delete -f sparkapplication-without-vols-object.yaml
Bir çok detay daha eklenebilir, ancak bu bir blog yazısının boyunu aşar. Artık yazıyı burada bitiriyorum. Spark’ı Kubernetes üzerinde çalıştırmanın hazırlık zahmeti var. İlk başlarda biraz saç baş yoldurabilir ve neymiş bu Spark on Kubernetes diyebilirsiniz. Ancak yeterince ısrarlı ve dikkatli bakarsanız duvarın arkasını bile görebilirsiniz 🙂
Başka bir yazıda görüşmek dileğiyle…
Kaynaklar
- https://www.datamechanics.co/blog-post/pros-and-cons-of-running-apache-spark-on-kubernetes
- Kapak Photo by frank mckenna on Unsplash
- https://www.youtube.com/watch?v=FX0F_OnsUOU&t=241s
- https://spark.apache.org/docs/latest/running-on-kubernetes.html