Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 24

Running PySpark ETL on Kubernetes with Jobs

Recommended resources

Merhaba! Önceki dersimizde, CI işlem hattımızı her bir Git commit'i için benzersiz ve değişmez bir etiketle (commit SHA'sı) bir Docker imajı oluşturup container registry'mize gönderecek şekilde başarıyla güncelledik. Bu, bize tam bir izlenebilirlik sağladı. Artık registry'mizde çalıştırılmaya hazır, versiyonlanmış bir "artifact" (yapıt) bulunuyor.

Ancak bu imaj henüz hiçbir yerde çalışmıyor. Bu derste, CI (Sürekli Entegrasyon) ve CD (Sürekli Dağıtım) arasındaki köprüyü kuracağız.

Bugünkü Öğrenme Hedefimiz: Container'ize edilmiş PySpark ETL işimizi bir Kubernetes kümesinde çalıştırmak için bir Kubernetes manifest dosyası yazmak.

Bu dersi tamamladığınızda, test edilmiş, paketlenmiş ve versiyonlanmış ETL uygulamanızı bir Kubernetes ortamında nasıl çalıştıracağınızı tanımlayan YAML dosyasını oluşturabileceksiniz.

Neden Standart Bir Kubernetes Job Kullanmalıyız?

Kubernetes, farklı iş yükü türleri için çeşitli kaynaklar (resources) sunar. Deployment gibi kaynaklar, web sunucuları gibi sürekli çalışması gereken hizmetler için tasarlanmıştır. Ancak ETL işleri farklıdır: başlarlar, görevlerini (veri işleme) tamamlarlar ve sonra dururlar.

Bu "çalıştır ve tamamla" (run-to-completion) senaryosu için Kubernetes'in sunduğu ideal kaynak Job'dur. Bir Job objesi, bir veya daha fazla Pod oluşturur ve belirtilen sayıda Pod'un başarıyla tamamlanmasını sağlar. Eğer bir Pod hata verirse, Job kontrolcüsü görevin başarılı bir şekilde bitirilmesini sağlamak için Pod'u yeniden başlatabilir.

Bu konuya giriş yapmak için aşağıdaki videoyu izleyerek Job ve Deployment arasındaki temel farkları ve bir Job'un temel parametrelerini anlayabilirsiniz.

Jobs and CronJobs in Kubernetes

Pavan Elthepu tarafından hazırlanan "Jobs and CronJobs in Kubernetes" başlıklı bu video, Kubernetes Job'larının amacını ve temel çalışma prensiplerini açıklıyor.

Videonun ilk 3 dakika 32 saniyesini (00:00 - 03:32) izleyin. İzlerken şu kavramlara odaklanın: Job'ların Deployment'lardan neden farklı olduğu. backoffLimit parametresinin ne işe yaradığı (hatalı denemeleri sınırlama). completions parametresinin amacını (görevin kaç defa başarılı bir şekilde tamamlanması gerektiği).


PySpark'ı Kubernetes'te Çalıştırmanın Özel Durumu

Basit bir "hello-world" uygulamasını bir Job olarak çalıştırmak oldukça basittir. Ancak PySpark, dağıtık yapısı nedeniyle ek karmaşıklıklar getirir. spark-submit komutunu master adresi olarak Kubernetes'i (k8s://...) göstererek çalıştırdığınızda, Spark şu adımları izler:

  1. Kubernetes, spark-submit komutunu çalıştıran bir Driver Pod'u oluşturur.
  2. Bu Driver Pod, çalışmaya başladığında, Kubernetes API'si ile konuşarak iş yükünü dağıtmak için bir veya daha fazla Executor Pod talep eder.
  3. Kubernetes, bu talebe göre Executor Pod'larını oluşturur.
  4. Driver, görevleri (task) Executor'lara dağıtır ve sonuçları toplar.
  5. İş bittiğinde tüm Pod'lar sonlandırılır.

Bu süreç, aşağıdaki diyagramlarda görselleştirilmiştir.

Spark Job Submission and Execution on Kubernetes (EKS)
Bu şema, bir kullanıcının `spark-submit` ile bir iş gönderdiğinde Kubernetes'in nasıl bir Driver Pod'u başlattığını ve bu Driver'ın daha sonra Executor Pod'larını nasıl talep ettiğini adım adım göstermektedir.
Spark on Kubernetes (GKE) Cluster Architecture for PySpark ETL Jobs
Bu mimari diyagram, `spark-submit` komutunun Kubernetes API sunucusuyla nasıl etkileşime girerek Driver ve Executor Pod'larını oluşturduğunu ve bu Pod'ların container imajlarını bir registry'den nasıl çektiğini detaylandırmaktadır.

Bu mimarinin en kritik sonucu şudur: Driver Pod'unun, diğer Pod'ları (Executor'ları) oluşturma, listeleme ve silme yetkisine sahip olması gerekir. Bu yetkiler, Kubernetes'in Rol Tabanlı Erişim Kontrolü (RBAC) sistemi aracılığıyla verilir.


Çözüm: Spark Operator ve SparkApplication CRD'si

Bu süreci manuel olarak bir Job ve RBAC manifestleri ile yönetmek mümkün olsa da, topluluk tarafından benimsenen daha modern ve sağlam bir yaklaşım vardır: Spark on Kubernetes Operator.

Bir Operator, Kubernetes'in yeteneklerini belirli bir uygulama (bu durumda Spark) için genişleten özel bir kontrolcüdür. Spark Operator, SparkApplication adında yeni bir Kubernetes kaynak türü (Custom Resource Definition - CRD) sunar.

SparkApplication manifesti, standart bir Job manifesti yerine, Spark'a özgü yapılandırmaları (driver/executor kaynakları, Spark versiyonu, uygulama dosyası vb.) tanımlamanıza olanak tanıyan üst düzey bir soyutlamadır. Siz bir SparkApplication objesi oluşturduğunuzda, Spark Operator arka planda gerekli tüm alt düzey Kubernetes kaynaklarını (Driver Pod, Executor Pod'ları, Service'ler, RBAC kuralları vb.) otomatik olarak yönetir.

Bu yaklaşım, sizin işinizi büyük ölçüde basitleştirir ve PySpark işlerinizi Kubernetes üzerinde daha bildirimsel (declarative) bir şekilde tanımlamanızı sağlar.

Spark, MinIO and Kubernetes

Spark Operator ve SparkApplication CRD'si hakkında temel bir anlayış kazanmak için "Spark, MinIO and Kubernetes" başlıklı bu makaleyi inceleyelim. Makale, operatörün ne olduğunu ve nasıl kurulduğunu açıklayarak konuya iyi bir giriş yapıyor.

Makaledeki "Set Up Spark on Kubernetes" başlığından başlayarak "Deploy a Spark Application" başlığının sonuna kadar okuyun. Özellikle şu kısımlara dikkat edin: Spark Operator'ın sunduğu özellikler. Bir SparkApplication YAML manifestinin temel yapısı (basit bir Pi hesaplama örneği üzerinden gösteriliyor).


SparkApplication Manifestini Yazmak

Artık teoriye hakim olduğunuza göre, kendi projemiz için bir SparkApplication manifesti yazalım. Bu manifest, önceki derslerde oluşturduğumuz Docker imajını ve içindeki ETL betiğini kullanacaktır.

Aşağıdaki referans kaynak, bizim projemize çok benzer şekilde, bir PySpark ETL işi için hazırlanmış daha kapsamlı bir SparkApplication manifest örneği sunmaktadır.

pyspark-k8s-boilerplate/README.md at master

Şimdi, gerçek bir ETL projesi için yazılmış bir SparkApplication manifestini inceleyelim. pyspark-k8s-boilerplate projesinin README dosyası, tam da bizim ihtiyacımız olan bir örnek içeriyor.

Belgede "structure of the cloud etl yaml file" ifadesini aratın ve altındaki YAML kod bloğunu dikkatlice inceleyin. Bu örnekteki şu alanların ne anlama geldiğine odaklanın: apiVersion ve kind: Bunun bir SparkApplication olduğunu belirtir. image: Docker imajının tam yolu. mainApplicationFile: Container içindeki ana Python betiğinin yolu (local:/// önekine dikkat edin). arguments: Python betiğine geçirilecek komut satırı argümanları. driver ve executor blokları: Driver ve Executor'lar için kaynak (CPU/bellek) ve kopya (instance) sayılarını nasıl belirttiği.

Projemiz İçin Manifestin Oluşturulması

Şimdi öğrendiklerimizi birleştirerek projemiz için k8s/job.yaml adında bir dosya oluşturalım. Bu dosya, CI işlem hattımızın ürettiği Docker imajını Kubernetes'te nasıl çalıştıracağını tanımlayacaktır.

Aşağıda projemize uygun bir SparkApplication şablonu bulunmaktadır. Açıklamaları dikkatlice okuyun.




# k8s/job.yaml

apiVersion: "sparkoperator.k8s.io/v1beta2"
kind: SparkApplication
metadata:



  # Kubernetes'te işi tanımlayacak olan isim.
  name: pyspark-etl-job



  # İşin hangi namespace'de çalışacağı. Genellikle 'spark' veya 'default' gibi bir isim kullanılır.
  namespace: default
spec:
  type: Python
  pythonVersion: "3"
  mode: cluster



  # DİKKAT: Bu kısım en önemlisi. CI işlem hattımız tarafından dinamik olarak güncellenecek.
  # Şimdilik bir yer tutucu (placeholder) olarak bırakıyoruz.
  image: "YOUR_DOCKER_USERNAME/pyspark-etl:placeholder"
  imagePullPolicy: Always



  # Dockerfile'da uygulamamızı kopyaladığımız yol.
  # 'local://' öneki, dosyanın imajın içinde olduğunu belirtir.
  mainApplicationFile: local:///app/main.py



  # Uygulamamıza argüman geçmek istersek burada belirtiriz.
  # arguments:
  #   - "--input-path"
  #   - "s3a://my-bucket/input-data"
  sparkVersion: "3.5.0" # Kullandığınız Spark versiyonu ile eşleşmeli.
  restartPolicy:



    # İş başarısız olursa ne olacağını belirler.
    # 'Never': Tekrar deneme.
    # 'OnFailure': Belirtilen sayıda tekrar dene.
    type: OnFailure
    onFailureRetries: 2
    onFailureRetryInterval: 10 # saniye
  



  # Spark Driver Pod'u için yapılandırmalar
  driver:
    cores: 1
    memory: "1024m" # 1 GB
    labels:
      version: 3.5.0



    # Driver pod'unun Executor'ları yönetmek için gerekli K8s yetkilerine sahip
    # Service Account'ı. Genellikle cluster yöneticisi tarafından sağlanır.
    serviceAccount: spark




  # Spark Executor Pod'ları için yapılandırmalar
  executor:
    cores: 1



    # Kaç adet executor pod'u başlatılacağı.
    instances: 2
    memory: "2048m" # 2 GB
    labels:
      version: 3.5.0

Sıra Sizde:

  1. Projenizin kök dizininde k8s adında yeni bir klasör oluşturun.
  2. Bu klasörün içine job.yaml adında bir dosya oluşturun.
  3. Yukarıdaki YAML içeriğini bu dosyaya kopyalayın.
  4. image alanındaki YOUR_DOCKER_USERNAME kısmını kendi Docker Hub kullanıcı adınızla değiştirin. :placeholder kısmını şimdilik olduğu gibi bırakın. Gelecekteki derslerde CI işlem hattımız bu etiketi otomatik olarak güncelleyecektir.
  5. serviceAccount alanında spark yazılı bırakın. Gerçek bir kümede, Spark Operator kurulduğunda genellikle bu isimde bir servis hesabı oluşturulur.
  6. Değişikliklerinizi commit'leyip GitHub'a push edin.

Bu aşamada bu dosyayı kubectl apply ile çalıştırmayacağız. Bu dersin amacı, CD sürecinin ihtiyaç duyacağı bu kritik manifest dosyasını hazırlamaktı.


Sonuç

Tebrikler! Artık CI/CD zincirinin eksik bir halkasını daha tamamladınız. Elimizde sadece test edilmiş ve versiyonlanmış bir container imajı değil, aynı zamanda bu imajı bir Kubernetes kümesinde nasıl çalıştıracağımızı bildiren bir tarif (SparkApplication manifesti) de var.

Temel Çıkarımlar:

  • ETL gibi tamamlanıp biten görevler için Kubernetes Job kaynağı kullanılır.
  • PySpark'ın dağıtık doğası, Kubernetes üzerinde çalışırken Driver ve Executor pod'ları arasında özel bir etkileşim gerektirir.
  • Spark Operator ve SparkApplication CRD'si, bu karmaşıklığı yönetmek için standart ve bildirimsel bir yöntem sunar.
  • Bir SparkApplication manifesti; kullanılacak Docker imajını, ana uygulama dosyasını, kaynak limitlerini (CPU/bellek) ve diğer Spark'a özgü ayarları içerir.

Gelecek Dersin Önizlemesi:
Artık dağıtıma hazır bir manifest dosyamız var. Ancak bu manifesti kümeye manuel olarak uygulamak, CI/CD felsefesine aykırıdır. Bir sonraki modülümüzde GitOps dünyasına giriş yapacağız. İlk dersimizde, Git repository'mizi "tek doğru kaynak" (single source of truth) olarak kullanarak Kubernetes uygulamalarını otomatik olarak dağıtan Rancher Fleet'in arkasındaki temel kavramları (GitOps, GitRepo, Bundle) öğreneceğiz. Bu, CI/CD işlem hattımızın son halkası olan Sürekli Dağıtım'ı (CD) hayata geçirmemizi sağlayacak.