Hướng dẫn cấu hình Anti-Affinity trong Apache Spark trên Kubernetes

Trong hệ sinh thái Apache Spark trên Kubernetes, việc tối ưu scheduling của các executor là điều cực kỳ quan trọng đối với hiệu năng, khả năng chịu lỗi (fault-tolerance) và tài nguyên cluster. Một trong những kỹ thuật phổ biến và cực kỳ hiệu quả trong DevOps là sử dụng Pod Anti-Affinity để điều khiển cách Spark scheduler phân bổ các executor.
Trong bài viết này, mình sẽ hướng dẫn cách cấu hình anti-affinity cho executor pods khi chạy Spark trên Kubernetes — cả trong tình huống bạn dùng Spark Operator hay sử dụng spark-submit
thủ công (kết hợp với Airflow hoặc các tool orchestration khác).
📌 Tại sao cần Anti-Affinity trong Spark?
Mặc định, Kubernetes scheduler có thể đặt nhiều executor trên cùng một node để tối ưu sử dụng tài nguyên. Tuy nhiên điều này mang lại những rủi ro:
Rủi ro | Tác động |
Executor trùng node | Một node chết có thể mất toàn bộ executors |
Đụng độ tài nguyên | Executors cạnh tranh CPU/RAM trên cùng node |
Không tận dụng được tính phân tán | Giảm hiệu quả job Spark phân tán |
👉 Anti-affinity giúp đảm bảo các executor chạy trên các node khác nhau, tăng khả năng phân tán, resilience, và hiệu suất.
🎯 Mục tiêu bài viết
Cấu hình anti-affinity cho Spark executor.
Áp dụng với cả Spark Operator và
spark-submit
.Giải thích chi tiết các tham số kỹ thuật.
Demo thực tế.
✅ 1. Dùng Spark Operator với SparkApplication
Giả sử bạn đã cài đặt Spark Operator trên Kubernetes, dưới đây là một ví dụ cấu hình SparkApplication
có anti-affinity cho executor:
apiVersion: sparkoperator.k8s.io/v1beta2
kind: SparkApplication
metadata:
name: spark-anti-affinity-demo
namespace: spark
spec:
type: Python
pythonVersion: "3"
mode: cluster
image: my-registry/spark-py:v3.3.0
imagePullPolicy: Always
mainApplicationFile: local:///opt/spark/work-dir/main.py
sparkVersion: "3.3.0"
driver:
cores: 1
memory: "1g"
serviceAccount: spark
executor:
instances: 3
cores: 1
memory: "1g"
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: spark-role
operator: In
values:
- executor
topologyKey: "kubernetes.io/hostname"
💡 Giải thích:
spark-role: executor
: gắn label cho executor pod.topologyKey:
kubernetes.io/hostname
: chỉ định mức phân tán theo node hostname.requiredDuringSchedulingIgnoredDuringExecution
: Kubernetes sẽ bắt buộc tránh node đã có executor khác.
✅ 2. Dùng spark-submit
với KubernetesPodOperator hoặc trực tiếp
Nếu bạn không dùng Spark Operator, bạn vẫn có thể dùng template file để chỉ định cấu hình anti-affinity cho executor.
🔧 Tạo file template executor-pod.yaml
apiVersion: v1
kind: Pod
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: spark-role
operator: In
values:
- executor
topologyKey: "kubernetes.io/hostname"
🚀 Dùng spark-submit
:
spark-submit \
--master k8s://https://kubernetes.default.svc \
--deploy-mode cluster \
--conf spark.kubernetes.container.image=my-registry/spark-py:v3.3.0 \
--conf spark.kubernetes.executor.podTemplateFile=/opt/spark/conf/executor-pod.yaml \
local:///opt/spark/work-dir/main.py
📍 Lưu ý: Pod template file phải nằm bên trong container nếu chạy bằng KubernetesPodOperator.
🎯 Khi nào nên dùng anti-affinity?
Trường hợp | Nên dùng |
Chạy job batch nặng, dài | ✅ |
Job cần hiệu suất cao, IO phân tán | ✅ |
Muốn tránh node single point of failure | ✅ |
Cluster nhỏ, ít node | ❌ (sẽ khiến pods pending) |
Testing / Dev / PoC | ❌ |
📈 Kết quả mong đợi
Executors chạy trên các node khác nhau → load balanced.
Nếu một node chết → không mất toàn bộ executor.
Log dễ theo dõi (chia đều theo node).
Giảm bottleneck CPU/memory tại một node.
🧪 DevOps Tips
Gắn
tolerations
vànodeSelector
nếu muốn executor chạy trên node pool riêng.Dùng metrics từ Prometheus hoặc Grafana để đánh giá sự phân tán.
Sử dụng anti-affinity kết hợp với resource quota để tối ưu cluster multi-tenant.
Để cấu hình Spark executors với label nhằm sử dụng anti-affinity, bạn có 2 cách phổ biến tùy theo cách triển khai Spark trên Kubernetes:
✅ 1. Dùng Spark Operator (CRD SparkApplication
)
Spark Operator tự động gán label spark-role=executor
cho các executor pod. Tuy nhiên, nếu bạn muốn gắn thêm label tùy chỉnh, bạn có thể chỉ định trong phần executor.labels
.
🔧 Ví dụ:
spec:
executor:
instances: 3
cores: 1
memory: "2g"
labels:
spark-role: executor
spark-executor-group: group-a
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: spark-executor-group
operator: In
values:
- group-a
topologyKey: "kubernetes.io/hostname"
✅ Giải thích:
executor.labels
: thêm các label cho executor pods.affinity.podAntiAffinity
: chỉ định không đặt executor cùng labelgroup-a
lên cùng node (topologyKey
).
✅ 2. Dùng spark-submit
với podTemplateFile
Khi bạn dùng spark-submit
CLI (ví dụ từ Airflow), bạn sẽ cần tạo file template pod cho executor để thêm label:
🔧 executor-pod-template.yaml:
apiVersion: v1
kind: Pod
metadata:
labels:
spark-role: executor
spark-executor-group: group-a
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: spark-executor-group
operator: In
values:
- group-a
topologyKey: "kubernetes.io/hostname"
✅ Dùng trong spark-submit
:
spark-submit \
--master k8s://https://kubernetes.default.svc \
--deploy-mode cluster \
--conf spark.kubernetes.container.image=your-repo/spark:3.3.0 \
--conf spark.kubernetes.executor.podTemplateFile=/opt/spark/conf/executor-pod-template.yaml \
local:///opt/spark/work-dir/main.py
💡 Tổng kết
Cách triển khai | Thêm label executor | Anti-affinity |
Spark Operator | executor.labels trong SparkApplication | executor.affinity |
spark-submit CLI | metadata.labels trong pod template | spec.affinity trong pod template |
🚧 Vì sao nên tách driver và executors?
Trong Spark, driver chịu trách nhiệm điều phối toàn bộ job. Nếu driver và executor nằm trên cùng một node và node đó gặp sự cố, bạn có thể mất toàn bộ tiến trình — đặc biệt nguy hiểm với các job dài.
Việc tách biệt giúp:
Giảm rủi ro SPOF (Single Point of Failure)
Tối ưu tài nguyên node (tránh CPU contention)
Dễ kiểm soát, dễ debug và scale.
✅ Cách cấu hình anti-affinity giữa driver và executor
🧠 Ý tưởng
Gắn label khác nhau cho driver và executor:
Driver:
spark-role=driver
Executor:
spark-role=executor
Sau đó cấu hình executor để tránh chạy trên node chứa driver, bằng podAntiAffinity với labelSelector spark-role=driver
.
🔧 Ví dụ cấu hình với Spark Operator
apiVersion: sparkoperator.k8s.io/v1beta2
kind: SparkApplication
metadata:
name: spark-driver-executor-anti-affinity
namespace: spark
spec:
type: Python
pythonVersion: "3"
mode: cluster
image: my-registry/spark-py:v3.3.0
mainApplicationFile: local:///opt/spark/work-dir/main.py
sparkVersion: "3.3.0"
driver:
cores: 1
memory: "1g"
serviceAccount: spark
labels:
spark-role: driver
executor:
instances: 3
cores: 1
memory: "1g"
labels:
spark-role: executor
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: spark-role
operator: In
values:
- driver
topologyKey: "kubernetes.io/hostname"
🔧 Với spark-submit
và podTemplateFile
✅ 1. Driver pod template (driver-pod-template.yaml):
apiVersion: v1
kind: Pod
metadata:
labels:
spark-role: driver
✅ 2. Executor pod template (executor-pod-template.yaml):
apiVersion: v1
kind: Pod
metadata:
labels:
spark-role: executor
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: spark-role
operator: In
values:
- driver
topologyKey: "kubernetes.io/hostname"
🧪 Chạy spark-submit
:
spark-submit \
--master k8s://https://kubernetes.default.svc \
--deploy-mode cluster \
--conf spark.kubernetes.container.image=my-repo/spark:3.3.0 \
--conf spark.kubernetes.driver.podTemplateFile=/opt/spark/driver.yaml \
--conf spark.kubernetes.executor.podTemplateFile=/opt/spark/executor.yaml \
local:///opt/spark/work-dir/main.py
🧠 Lưu ý
Mục | Chi tiết |
topologyKey | Thường là kubernetes.io/hostname để phân biệt theo node |
requiredDuring... | Bắt buộc tránh node (nếu không có node phù hợp → pod pending) |
Label phải khớp | spark-role gắn cho driver phải giống trong labelSelector của executor |
Subscribe to my newsletter
Read articles from Kilo directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
