Запуск задач Spark в Kubernetes с любого хоста с использованием Helm

В данной статье показано, как установить оператор Spark в кластере Kubernetes и как запускать приложения Spark в этом кластере с любого хоста с помощью Helm и kubectl.

Требования

Для развертывания оператора Spark в Kubernetes необходимо:

  • Кластер Kubernetes (версии 1.32 или более поздней) с настроенным доступом через kubectl.

  • Helm (версия 3.8.0 или выше) — пакетный менеджер для быстрого развертывания Docker-образов в Kubernetes.

  • Артефакты Spark, включая Docker-образы и Helm-чарты (chart), предварительно загруженные в ваш приватный OCI-реестр. Эти артефакты доступны в offline-пакетах, которые можно запросить у службы поддержки Arenadata. Для деплоя Spark в Kubernetes необходимо извлечь следующие образы:

    • hub.arenadata.io/adc-enterprise/spark3:<version>

    • hub.arenadata.io/adc-enterprise/spark4:<version>-java17

    • hub.arenadata.io/adc-enterprise/spark4:<version>-java21

    Также необходимо извлечь следующие Helm-чарты и загрузить их в ваш приватный реестр:

    • hub.arenadata.io/ng/charts/spark-apps:<version>

    • hub.arenadata.io/ng/charts/spark-operator:<version>

Процедура развертывания

Ниже приведены шаги по установке компонентов Spark и запуску Spark-приложения. Настройка внешнего доступа, Ingress-контроллеров, балансировщиков нагрузки, DNS и облачных аннотаций должна быть выполнена с учетом особенностей вашей Kubernetes-инфраструктуры.

Шаг 1. Установка оператора Spark

Установите оператор Spark для Kubernetes, который отвечает за управление жизненным циклом Spark-приложений в кластере Kubernetes.

  1. Создайте файл Helm values spark-operator-values.yaml:

    spark-operator-values.yaml
    # Default values for spark-operator.
    # This is a YAML-formatted file.
    # Declare variables to be passed into your templates.
    
    # This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/
    replicas: 1
    payloadNamespaces: (1)
      # Managed namespaces for Spark payload resources.
      names:
        - spark-application-min
      # Explicit opt-in for cluster-wide RBAC when payloadNamespaces.names is empty.
      # When false, chart rendering fails until namespaces are specified.
      allowClusterRole: false
      deleteProtection: false
      avoidCreation: false
    
    # This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/
    image:
      registry: "<registry>" (2)
      repository: "<image>" (3)
      # This sets the pull policy for images.
      pullPolicy: Always
      # Overrides the image tag whose default is the chart appVersion.
      tag: "<version>" (4)
      # This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
      pullSecret: (5)
        name: ""
        ## List of secrets to create for image pulling in all product namespaces
        credentials: {}
    #      registry: private-docker-registry
    #      username: user
    #      password: pass
    
    # This is to override the chart name.
    nameOverride: ""
    fullnameOverride: ""
    
    # This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/
    serviceAccount:
      # Automatically mount a ServiceAccount's API credentials?
      automount: true
      # Annotations to add to the service account
      annotations: {}
      # The name of the service account to use.
      # If not set and create is true, a name is generated using the fullname template
      name: ""
    
    # This is for setting Kubernetes Annotations to a Pod.
    # For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
    podAnnotations: {}
    # This is for setting Kubernetes Labels to a Pod.
    # For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
    podLabels: {}
    
    podSecurityContext: {}
      # fsGroup: 2000
    
    securityContext:
      readOnlyRootFilesystem: true
      privileged: false
      allowPrivilegeEscalation: false
      runAsNonRoot: true
      runAsUser: 65532
      capabilities:
        drop:
          - ALL
      seccompProfile:
        type: RuntimeDefault
    
    # This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/
    service:
      # This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types
      type: ClusterIP
      # This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports
      port: 8443
    
    resources: {}
      # We usually recommend not to specify default resources and to leave this as a conscious
      # choice for the user. This also increases chances charts run on environments with little
      # resources, such as Minikube. If you do want to specify resources, uncomment the following
      # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
      # limits:
      #   cpu: 100m
      #   memory: 128Mi
      # requests:
      #   cpu: 100m
      #   memory: 128Mi
    
    nodeSelector: {}
    
    tolerations: []
    
    affinity: {}
    
    terminationGracePeriodSeconds: 10
    
    metrics:
      # Enable to protect the metrics endpoint with authn/authz. Requires ClusterRole.
      # See https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.1/pkg/metrics/filters#WithAuthenticationAndAuthorization
      auth: false
    1 Список пространств имен (namespace), в которых оператор управляет ресурсами.
    2 Адрес приватного OCI-хранилища, из которого будут загружены образы.
    3 Имя репозитория в приватном хранилище.
    4 Версия образа.
    5 Учетные данные для доступа к вашему Docker-реестру.
  2. Установите оператор Spark:

    $ helm upgrade --install spark-operator oci://<registry-address>/ng/charts/spark-operator:<version> -f spark-operator-values.yaml --namespace spark-operator-min --create-namespace

    где <registry-address> — это адрес вашего OCI-хранилища с загруженными Helm-чартами для компонентов Spark.

    Пример вывода:

    Release "spark-operator" does not exist. Installing it now.
    Pulled: hub.adsw.io/ng/charts/spark-operator:1.41.0
    Digest: sha256:cb65eec82abea847af2f4022cd8681e6805f26cab9163bf4b596df8fe4223812
    NAME: spark-operator
    LAST DEPLOYED: Fri Aug 28 08:55:22 2026
    NAMESPACE: spark-operator-min
    STATUS: deployed
    REVISION: 1
    DESCRIPTION: Install complete
    TEST SUITE: None
    NOTES:
  3. Проверьте установку оператора Spark с помощью команды:

    $ kubectl get pods -n spark-operator-min

    Вывод:

    NAME                              READY   STATUS    RESTARTS   AGE
    spark-operator-5df74c7458-74zlb   1/1     Running   0          2m17s

Шаг 2. Развертывание приложения Spark

После установки оператора Spark необходимо развернуть тестовое приложение Spark с помощью Helm-чарта.

  1. Создайте файл spark-submit-values.yaml:

    spark-submit-values.yaml
    image:
      registry: "<registry>" (1)
      repository: "<image>" (2)
      tag: "<tag>"
      ## Specify a pullPolicy
      ## ref: https://kubernetes.io/docs/concepts/containers/images/#pre-pulled-images
      ##
      pullPolicy: "Always"
      ## Existing secret or secret to create to use for image pulling, they must exist in all product namespaces
      ##
      pullSecret: (3)
        name: ""
        credentials: {}
    #      registry: private-docker-registry
    #      username: user
    #      password: pass
    
    #ServiceAccount name for Spark driver/executor pods
    serviceAccountName: "spark-app"
    
    #Application
    #mainApplicationFile: path to the main app file (hdfs://, local://, etc.)
    mainApplicationFile: "local:///opt/spark/examples/jars/spark-examples_2.13-3.5.4.4-4.3.0-2.jar" (4)
    
    #Restart policy on failure: Never or OnFailure
    #When set to OnFailure, maxRetries must be specified
    restartPolicy: ""
    
    #Maximum number of Job restart attempts on failure
    #Required when restartPolicy is OnFailure
    #maxRetries:
    
    #mainClass: set only for JVM apps (e.g. SparkConnectServer)
    mainClass: "org.apache.spark.examples.SparkPi"
    
    
    #Hadoop configurations
    hadoopConfigsSecretName: ""
    
    #Spark configurations. When set, this client-managed Secret is the complete
    #Spark configuration source. If Ranger is enabled, include the ranger-spark-*.xml
    #files in this Secret; the chart does not create a separate Ranger config Secret.
    sparkConfigsSecretName: ""
    
    #Kerberos (keytab mode), disabled by default.
    #The referenced Secret must already exist and carry two keys: keytab + krb5.conf.
    #Ticket-cache mode is CLI-only and is not exposed here.
    #Extra confs (e.g. spark.kerberos.access.hadoopFileSystems) go under sparkConf.
    kerberos: {}
    #  principal: user@RU-CENTRAL1.INTERNAL
    #  keytab:
    #    secretName: spark-keytab
    
    # Arguments passed to the main application class after the main file
    args:
      - "10"
    
    #Spark configuration
    #Key/value map rendered into spec.sparkConf
    sparkConf: {}
    
    #SSL / truststore settings. The referenced Secret must already exist;
    #the operator mounts the stores at /etc/ssl.
    #Enabled implicitly when secretName is set.
    ssl: {}
    #  secretName: "ca-store"
    #  trustStoreKey: "truststore.jks"
    #  keyStoreKey: "keystore.jks"
    
    #Ranger authorization (Kyuubi Spark Authz plugin), disabled by default.
    #When enabled without sparkConfigsSecretName, the chart renders the
    #ranger-spark-*.xml config Secret. The spark-apps.sparkConf helper always appends
    #the Ranger extension to spark.sql.extensions.
    #The audit JAAS block is written only when the kerberos block is set (keytab mode);
    #the policymgr-ssl truststore is rendered only when ssl.enabled.
    ranger:
      enabled: false
      # ranger.plugin.spark.policy.rest.url
      policyRestURL: ""
      # ranger.plugin.spark.service.name
      serviceName: ""
      # xasecure.audit.destination.solr.zookeepers
      solrZookeepers: ""
    
    job:
      ## @param replicas set number of job replicas
      ##
      replicas: 1
    
      ## When false, the spark-submit Job pod is not automatically deleted after completion (useful for log inspection)
      ##
      #deleteOnTermination: false
    
      ## Additional configuration that you want to be added to job-config
      args: {}
      #  "task.max-worker-threads": 8
    
      ## Annotations for job pods
      annotations: {}
    
      ## Set container requests and limits for resource like CPU or memory (essential for production workloads)
      ##
      resources: {}
        #limits:
        #  cpu: "2"
        #  memory: "8Gi"
        #requests:
        #  cpu: "2"
        #  memory: "8Gi"
    
      ## Request additional PVC for pod
      ##
      persistentVolume: {}
      #  mountPath: "/data/spark"
      #  volumeClaimTemplates:
      #    - metadata:
      #        name: data
      #      spec:
      #        accessModes: ["ReadWriteOnce"]
      #        resources:
      #          requests:
      #            storage: 10Gi
      #        storageClassName: default
    
      ## nodeAffinity: Object defining constraints to place pods on a specific set of Nodes
      ##
      nodeSelector: {}
    
      topologySpreadConstraints: []
    
      ## Allow a Pod to be scheduled onto nodes that have taints.
      ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
      ##
      tolerations: []
       # - key: "example-key"
       #   operator: "Exists"
       #   effect: "NoSchedule"
    
      ## affinity: Object defining soft rules to place pods on a specific set of Nodes or didn't place pod to nodes due to some conditions
      ##
      affinity: {}
      #  nodeAffinity:
      #      requiredDuringSchedulingIgnoredDuringExecution:
      #        nodeSelectorTerms:
      #        - matchExpressions:
      #          - key: topology.kubernetes.io/zone
      #            operator: In
      #            values:
      #            - antarctica-east1
      #            - antarctica-west1
      #      preferredDuringSchedulingIgnoredDuringExecution:
      #      - weight: 1
      #        preference:
      #          matchExpressions:
      #          - key: another-node-label-key
      #            operator: In
      #            values:
      #            - another-node-label-value
    
      startupProbe: {}
      #  type: httpGet
      #  port: 8080
      #  path: /v1/info
      #  scheme: HTTP
      #  initialDelaySeconds: 20
      #  periodSeconds: 5
      #  timeoutSeconds: 3
      #  successThreshold: 1
      #  failureThreshold: 2
    
      livenessProbe: {}
      #  type: httpGet
      #  port: 8080
      #  path: /v1/info
      #  scheme: HTTP
      #  initialDelaySeconds: 20
      #  periodSeconds: 5
      #  timeoutSeconds: 3
      #  successThreshold: 1
      #  failureThreshold: 2
    
      readinessProbe: {}
      #  type: exec
      #  command: test -f /opt/spark/etc/truststore/custom-truststore.jks
      #  initialDelaySeconds: 20
      #  periodSeconds: 5
      #  timeoutSeconds: 3
      #  successThreshold: 1
      #  failureThreshold: 2
    
      ## Mount additional secrets into the pod
      ##
      mountSecrets: []
      #  - secretName: my-secret
      #    mountPath: /etc/spark/my-secret
    
    driver:
      ## @param replicas set number of driver replicas
      ##
      replicas: 1
    
      ## Additional configuration that you want to be added to driver-config
      args: {}
      #  "task.max-worker-threads": 8
    
      ## Annotations for driver pods
      annotations: {}
    
      ## Set container requests and limits for resource like CPU or memory (essential for production workloads)
      ##
      resources: {}
        #limits:
        #  cpu: "2"
        #  memory: "8Gi"
        #requests:
        #  cpu: "2"
        #  memory: "8Gi"
    
      ## Request additional PVC for pod
      ##
      persistentVolume: {}
      #  mountPath: "/data/spark"
      #  volumeClaimTemplates:
      #    - metadata:
      #        name: data
      #      spec:
      #        accessModes: ["ReadWriteOnce"]
      #        resources:
      #          requests:
      #            storage: 10Gi
      #        storageClassName: default
    
      ## nodeAffinity: Object defining constraints to place pods on a specific set of Nodes
      ##
      nodeSelector: {}
    
      topologySpreadConstraints: []
    
      ## Allow a Pod to be scheduled onto nodes that have taints.
      ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
      ##
      tolerations: []
       # - key: "example-key"
       #   operator: "Exists"
       #   effect: "NoSchedule"
    
      ## affinity: Object defining soft rules to place pods on a specific set of Nodes or didn't place pod to nodes due to some conditions
      ##
      affinity: {}
      #  nodeAffinity:
      #      requiredDuringSchedulingIgnoredDuringExecution:
      #        nodeSelectorTerms:
      #        - matchExpressions:
      #          - key: topology.kubernetes.io/zone
      #            operator: In
      #            values:
      #            - antarctica-east1
      #            - antarctica-west1
      #      preferredDuringSchedulingIgnoredDuringExecution:
      #      - weight: 1
      #        preference:
      #          matchExpressions:
      #          - key: another-node-label-key
      #            operator: In
      #            values:
      #            - another-node-label-value
    
      startupProbe: {}
      #  type: httpGet
      #  port: 8080
      #  path: /v1/info
      #  scheme: HTTP
      #  initialDelaySeconds: 20
      #  periodSeconds: 5
      #  timeoutSeconds: 3
      #  successThreshold: 1
      #  failureThreshold: 2
    
      livenessProbe: {}
      #  type: httpGet
      #  port: 8080
      #  path: /v1/info
      #  scheme: HTTP
      #  initialDelaySeconds: 20
      #  periodSeconds: 5
      #  timeoutSeconds: 3
      #  successThreshold: 1
      #  failureThreshold: 2
    
      readinessProbe: {}
      #  type: exec
      #  command: test -f /opt/spark/etc/truststore/custom-truststore.jks
      #  initialDelaySeconds: 20
      #  periodSeconds: 5
      #  timeoutSeconds: 3
      #  successThreshold: 1
      #  failureThreshold: 2
    
      ## Mount additional secrets into the pod
      ##
      mountSecrets: []
      #  - secretName: my-secret
      #    mountPath: /etc/spark/my-secret
    
    executor:
      ## @param replicas set number of executor replicas
      ##
      replicas: 1
    
      ## Additional configuration that you want to be added to executor-config
      args: {}
      #  "task.max-worker-threads": 8
    
      ## Annotations for executor pods
      annotations: {}
    
      ## Set container requests and limits for resource like CPU or memory (essential for production workloads)
      ##
      resources: {}
        #limits:
        #  cpu: "2"
        #  memory: "8Gi"
        #requests:
        #  cpu: "2"
        #  memory: "8Gi"
    
      ## Request additional PVC for pod
      ##
      persistentVolume: {}
      #  mountPath: "/data/spark"
      #  volumeClaimTemplates:
      #    - metadata:
      #        name: data
      #      spec:
      #        accessModes: ["ReadWriteOnce"]
      #        resources:
      #          requests:
      #            storage: 10Gi
      #        storageClassName: default
    
      ## nodeAffinity: Object defining constraints to place pods on a specific set of Nodes
      ##
      nodeSelector: {}
    
      topologySpreadConstraints: []
    
      ## Allow a Pod to be scheduled onto nodes that have taints.
      ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
      ##
      tolerations: []
       # - key: "example-key"
       #   operator: "Exists"
       #   effect: "NoSchedule"
    
      ## affinity: Object defining soft rules to place pods on a specific set of Nodes or didn't place pod to nodes due to some conditions
      ##
      affinity: {}
      #  nodeAffinity:
      #      requiredDuringSchedulingIgnoredDuringExecution:
      #        nodeSelectorTerms:
      #        - matchExpressions:
      #          - key: topology.kubernetes.io/zone
      #            operator: In
      #            values:
      #            - antarctica-east1
      #            - antarctica-west1
      #      preferredDuringSchedulingIgnoredDuringExecution:
      #      - weight: 1
      #        preference:
      #          matchExpressions:
      #          - key: another-node-label-key
      #            operator: In
      #            values:
      #            - another-node-label-value
    
      startupProbe: {}
      #  type: httpGet
      #  port: 8080
      #  path: /v1/info
      #  scheme: HTTP
      #  initialDelaySeconds: 20
      #  periodSeconds: 5
      #  timeoutSeconds: 3
      #  successThreshold: 1
      #  failureThreshold: 2
    
      livenessProbe: {}
      #  type: httpGet
      #  port: 8080
      #  path: /v1/info
      #  scheme: HTTP
      #  initialDelaySeconds: 20
      #  periodSeconds: 5
      #  timeoutSeconds: 3
      #  successThreshold: 1
      #  failureThreshold: 2
    
      readinessProbe: {}
      #  type: exec
      #  command: test -f /opt/spark/etc/truststore/custom-truststore.jks
      #  initialDelaySeconds: 20
      #  periodSeconds: 5
      #  timeoutSeconds: 3
      #  successThreshold: 1
      #  failureThreshold: 2
    
      ## Mount additional secrets into the pod
      ##
      mountSecrets: []
      #  - secretName: my-secret
      #    mountPath: /etc/spark/my-secret
    
    
    #Optional: inline Secret for properties-file pattern
    propertiesFile:
      enabled: false
      # Raw content rendered into the Secret's stringData
      content: ""
    
    #RBAC
    rbac:
      # Set to false if the ServiceAccount/Role/RoleBinding already exist
      create: true
      rules:
        - apiGroups:
            - ""
          resources:
            - pods
            - configmaps
            - persistentvolumeclaims
            - services
            - secrets
          verbs:
            - get
            - list
            - watch
            - create
            - update
            - patch
            - delete
            - deletecollection
        - apiGroups:
            - networking.k8s.io
          verbs:
            - get
            - list
            - watch
            - create
            - update
            - patch
            - delete
          resources:
            - networkpolicies
    1 Адрес приватного OCI-хранилища, из которого будут загружены образы.
    2 Имя репозитория в приватном хранилище.
    3 Учетные данные для доступа к вашему приватному Docker-реестру.
    4 Путь к JAR-файлу приложения Spark. Схема local:// указывает, что JAR расположен в образе контейнера Spark.
  2. Разверните приложение Spark с помощью Helm:

    $ helm upgrade --install spark-application oci://<registry-address>/ng/charts/spark-apps:<version> -f spark-submit-values.yaml --namespace spark-application-min --create-namespace

    Вывод:

    Release "spark-application" does not exist. Installing it now.
    Pulled: hub.adsw.io/ng/charts/spark-apps:1.41.0
    Digest: sha256:73debb0c68945951ec0e0f0ef90ff6f3182184581cfb360157c2b5da47e11cf1
    NAME: spark-application
    LAST DEPLOYED: Fri Aug 28 09:16:19 2026
    NAMESPACE: spark-application-min
    STATUS: deployed
    REVISION: 1
    DESCRIPTION: Install complete
    TEST SUITE: None

Шаг 3. Запуск приложения Spark с помощью kubectl

Перед запуском Spark-приложения с помощью kubectl необходимо настроить управление доступом на основе ролей (Role-Based Access Control, RBAC) для приложения Spark. Следующий манифест содержит определения ресурсов ServiceAccount, Role и RoleBinding, которые используются для настройки RBAC для приложения Spark.

  1. Создайте файл rbac.yaml:

    rbac.yaml
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: spark-minimal
      namespace: spark-kbctl
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: spark-minimal-role
      namespace: spark-kbctl
    rules:
      - apiGroups:
          - ""
        resources:
          - pods
          - configmaps
          - persistentvolumeclaims
          - services
          - secrets
        verbs:
          - get
          - list
          - watch
          - create
          - update
          - patch
          - delete
          - deletecollection
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: spark-minimal-role-binding
      namespace: spark-kbctl
    subjects:
      - kind: ServiceAccount
        name: spark-minimal
        namespace: spark-kbctl
    roleRef:
      kind: Role
      name: spark-minimal-role
      apiGroup: rbac.authorization.k8s.io
  2. Создайте неймспейс и примените конфигурацию:

    $ kubectl create namespace spark-kbctl
    $ kubectl apply -f rbac.yaml

    Вывод:

    serviceaccount/spark-minimal created
    role.rbac.authorization.k8s.io/spark-minimal-role created
    rolebinding.rbac.authorization.k8s.io/spark-minimal-role-binding created
  3. Создайте файл spark-app.yaml:

    spark-app.yaml
    apiVersion: spark.arenadata.io/v1alpha1
    kind: SparkApplication
    metadata:
      labels:
        app.kubernetes.io/name: spark-operator
        app.kubernetes.io/managed-by: kustomize
      name: spark-pi-java
      namespace: spark-kbctl
    spec:
      mainApplicationFile: "local:///opt/spark/examples/jars/spark-examples_2.13-3.5.4.4-4.3.0-2.jar"
      mainClass: "org.apache.spark.examples.SparkPi"
      args:
        - "1000"
      serviceAccountName: spark-minimal
      sparkConf:
        "spark.kubernetes.authenticate.driver.serviceAccountName": "spark-minimal"
        "spark.kubernetes.namespace": "spark-kbctl"
      job:
        spec:
          image: &image hub.adsw.io/adh-enterprise/spark3-docker:3.5.4.4-adh-4.3.0-x86_64
      driver:
        spec:
          image: *image
      executor:
        replicas: 2
        spec:
          image: *image
  4. Запустите приложение Spark с помощью kubectl:

    $ kubectl apply -f spark-app.yaml

    Вывод:

    sparkapplication.spark.arenadata.io/spark-pi-java created
  5. Чтобы убедиться, что приложение отработало успешно, откройте логи Spark-драйвера в поде драйвера:

    $ kubectl logs -n spark-application-min spark-appplication-spark-apps-<ID>-driver

    В логах отражены основные этапы работы приложения, включая результат выполнения задачи Spark — вычисленное значение числа Пи.

    Pi is roughly 3.1402951402951405
Нашли ошибку? Выделите текст и нажмите Ctrl+Enter чтобы сообщить о ней