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:
- Kubernetes,
spark-submitkomutunu çalıştıran bir Driver Pod'u oluşturur. - 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.
- Kubernetes, bu talebe göre Executor Pod'larını oluşturur.
- Driver, görevleri (task) Executor'lara dağıtır ve sonuçları toplar.
- İş bittiğinde tüm Pod'lar sonlandırılır.
Bu süreç, aşağıdaki diyagramlarda görselleştirilmiştir.

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 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:
- Projenizin kök dizininde
k8sadında yeni bir klasör oluşturun. - Bu klasörün içine
job.yamladında bir dosya oluşturun. - Yukarıdaki YAML içeriğini bu dosyaya kopyalayın.
imagealanındakiYOUR_DOCKER_USERNAMEkısmını kendi Docker Hub kullanıcı adınızla değiştirin.:placeholderkısmını şimdilik olduğu gibi bırakın. Gelecekteki derslerde CI işlem hattımız bu etiketi otomatik olarak güncelleyecektir.serviceAccountalanındasparkyazılı bırakın. Gerçek bir kümede, Spark Operator kurulduğunda genellikle bu isimde bir servis hesabı oluşturulur.- 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
Jobkaynağı 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
SparkApplicationCRD'si, bu karmaşıklığı yönetmek için standart ve bildirimsel bir yöntem sunar. - Bir
SparkApplicationmanifesti; 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.