Engineering
Glossvation Engineering
クラウド

Spark on Kubernetes の採用判断 — YARN / EMR との棲み分けと運用設計

Spark を Kubernetes で実行するときの構成要素 (Spark Operator / spark-submit)、既存の YARN / EMR との違い、リソース管理・ログ・メトリクスの運用設計、避けるべき罠。

Glossvation ·

Spark を Kubernetes で動かす意義

Apache Spark は大規模データ処理の定番フレームワーク。従来は Hadoop YARN や Mesos の上で動かすのが主流でしたが、Spark 2.3+ (2018 年) で Kubernetes が公式対応し、2.4+ で本番利用に耐える品質に達しました。

Kubernetes 上で動かすメリット:

  • クラスタマネージャが Kubernetes に一元化 (別途 YARN 運用不要)
  • コンテナイメージで依存関係を固定化、環境差分 0
  • 既存の K8s エコシステム (Prometheus / Istio / ArgoCD) と統合
  • クラウドネイティブ運用 (GKE / EKS / AKS のオートスケール活用)

デメリット:

  • Kubernetes の学習コスト・運用負荷
  • YARN と比較してエコシステムの歴史が浅い (Jupyter 統合等は YARN 側が先行していた)

他の実行環境との比較 (2024+)

Spark on KubernetesSpark on YARNAWS EMRDatabricks
クラスタ管理K8sHadoop YARNマネージドフルマネージド
インフラ柔軟性高 (任意クラウド)オンプレ / クラウドAWS のみAWS/Azure/GCP
運用コスト中〜高 (自前)高 (YARN 専門知識)低 (マネージド)低 (フル)
コスト使用分だけ常時クラスタOn-demandプレミアム
MLOps 統合自前 (MLflow 等)自前SageMaker 連携標準搭載

選び方:

  • 既に K8s 基盤がある企業 → Spark on K8s で統合
  • データエンジニアだけで素早く始めたい → EMR か Databricks
  • 大規模データサイエンス組織 → Databricks
  • オンプレ要件 → Spark on K8s (自前) か YARN

デプロイ方式

A. spark-submit 直接

spark-submit \
  --master k8s://https://<kubernetes-api>:443 \
  --deploy-mode cluster \
  --name my-job \
  --class com.example.MyJob \
  --conf spark.kubernetes.namespace=spark \
  --conf spark.kubernetes.container.image=myrepo/spark:3.5.0 \
  --conf spark.executor.instances=5 \
  local:///opt/spark/examples/jars/my-app.jar

Spark 公式がサポートする素の方式。Driver / Executor が Pod として起動。

B. Spark Operator (推奨)

GoogleCloudPlatform/spark-on-k8s-operator で、Spark ジョブを K8s CRD として宣言。

apiVersion: sparkoperator.k8s.io/v1beta2
kind: SparkApplication
metadata:
  name: my-job
  namespace: spark
spec:
  type: Scala
  mode: cluster
  image: "myrepo/spark:3.5.0"
  mainClass: com.example.MyJob
  mainApplicationFile: "local:///opt/spark/examples/jars/my-app.jar"
  sparkVersion: "3.5.0"
  driver:
    cores: 1
    memory: "2g"
    serviceAccount: spark
  executor:
    cores: 2
    instances: 5
    memory: "4g"

Argo CD や Flux で GitOps 化しやすく、本番運用の定番構成。

運用設計のポイント

1. Executor の Dynamic Allocation

Spark 3.0+ は Dynamic Allocation on K8s をサポート。負荷に応じて Executor Pod 数を自動調整。

spark.dynamicAllocation.enabled=true
spark.dynamicAllocation.minExecutors=2
spark.dynamicAllocation.maxExecutors=50
spark.dynamicAllocation.executorIdleTimeout=60s

バッチジョブはコスト削減に直結。

2. ノード Taint / Tolerations でワークロード分離

Spark Executor を汎用ノードから隔離し、Spark 専用ノードプールに集約:

executor:
  tolerations:
    - key: dedicated
      operator: Equal
      value: spark
      effect: NoSchedule
  nodeSelector:
    workload: spark

Spark ジョブで他サービスを巻き込まない。

3. ログ集約

Driver / Executor Pod の stdout は K8s ノードに書かれる。Fluentd / Fluent Bit で ELK / Loki に集約。Spark UI (spark.history.server) は別途 Spark History Server を立てる。

4. メトリクス

Spark の Prometheus Sink を有効化:

spark.metrics.conf.*.sink.prometheusServlet.class=org.apache.spark.metrics.sink.PrometheusServlet
spark.metrics.conf.*.sink.prometheusServlet.path=/metrics/prometheus

Pod の /metrics/prometheus エンドポイントを Prometheus でスクレイプ。

5. ストレージ

  • HDFS は K8s と相性が悪い → S3 / GCS / Azure Blob を直接利用
  • シャッフルファイル は Executor の emptyDir か、大規模なら専用 Shuffle Service

落とし穴

1. ノードリソースが足りず Pod Pending

Spark は要求リソース (CPU / メモリ) を Pod 定義に反映。ノードが足りないと Pending 状態で止まる。Cluster Autoscaler / Karpenter でノード自動追加設定を。

2. OOM Killed

Executor の memoryOverhead を見落とすとコンテナ OOM で kill される。JVM ヒープ + オフヒープ + K8s オーバーヘッドを考慮:

executor.memory = 4g
executor.memoryOverhead = 1g (JVM ヒープ以外の領域)
→ Pod requests.memory = 5g

3. Spark History Server のストレージ爆発

ジョブログ (event log) が S3 に貯まり続ける。ライフサイクルポリシーで古いものを自動削除。

4. Image Pull 問題

毎 Executor 起動時にコンテナイメージを pull すると遅い。ノード上にキャッシュ済みイメージを配布、または軽量イメージを用意。

5. Service Account / IAM

Executor Pod は KUBE API に触る必要がある。IRSA (EKS) や Workload Identity (GKE) で Pod にクラウド IAM を割り当て、S3 等への権限を安全に付与。

まとめ

  • Spark on K8s は 既存 K8s 基盤がある企業に強い
  • Spark Operator + Dynamic Allocation + ノード分離 が運用の標準
  • マネージド (EMR / Databricks) と比較して 運用コスト vs コントロール のトレードオフ
  • ログ・メトリクス・ストレージの設計を初期に決めないと後で苦労する

関連記事