Настройка Kerberos и SSL для Spark в Kubernetes с помощью Helm
В данной статье описана активация SSL и Kerberos для оператора Spark в кластере Kubernetes с помощью Helm и kubectl.
|
ПРИМЕЧАНИЕ
Технически Kerberos и SSL можно настроить для Spark в Kubernetes независимо друг от друга.
Однако рекомендуется включать SSL вместе с Kerberos, чтобы обеспечить взаимодействие Spark с кластером ADH по зашифрованному каналу.
|
Требования
-
Кластер 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
-
hub.arenadata.io/ng/charts/spark-apps:<version>
-
hub.arenadata.io/ng/charts/spark-operator:<version>
-
-
Если Spark в Kubernetes задействует сервисы ADH (например, HDFS/Ozone, Hive Metastore и другие), необходим установленный кластер ADH. Кластер ADH должен быть керберизирован и иметь включенный SSL. Версия кластера должна соответствовать версии ADH Cloud.
Процедура развертывания
Ниже приведены шаги по установке компонентов Spark и запуску Spark-приложения. Настройка внешнего доступа, Ingress-контроллеров, балансировщиков нагрузки, DNS и облачных аннотаций должна быть выполнена с учетом особенностей вашей Kubernetes-инфраструктуры.
Шаг 1. Установка оператора Spark
-
Создайте файл 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-helm # 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: "<tag>" (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: name: "" ## List of secrets to create for image pulling in all product namespaces credentials: {} (5) # 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: false1 Список пространств имен (namespace), в которых оператор управляет ресурсами. 2 Адрес приватного OCI-хранилища, из которого будут загружены образы. 3 Имя репозитория в приватном хранилище. 4 Версия образа. 5 Учетные данные для доступа к вашему Docker-реестру. -
Установите оператор 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: Tue Sep 15 09:38:55 2026 NAMESPACE: spark-operator-min STATUS: deployed REVISION: 1 DESCRIPTION: Install complete TEST SUITE: None NOTES:
-
Проверьте установку оператора Spark с помощью следующей команды:
$ kubectl get pods -n spark-operator-minВывод:
NAME READY STATUS RESTARTS AGE spark-operator-7f8bcf45cf-kbv9m 1/1 Running 0 5m27s
Шаг 2. Настройка разрешений для ServiceAccount
-
Создайте файл sa.yaml:
sa.yaml--- apiVersion: v1 kind: ServiceAccount metadata: name: spark-helm namespace: spark-helm --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: spark-helm namespace: spark-helm rules: - apiGroups: - "" resources: - pods - configmaps - persistentvolumeclaims - services - secrets verbs: - get - list - watch - create - update - patch - delete - deletecollection - apiGroups: - networking.k8s.io resources: - networkpolicies verbs: - get - list - watch - create - update - patch - delete - apiGroups: - events.k8s.io resources: - events verbs: - create - patch - update --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: spark-helm namespace: spark-helm roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: spark-helm subjects: - kind: ServiceAccount name: spark-helm namespace: spark-helm -
Примените конфигурацию ServiceAccount:
$ kubectl apply -f sa.yamlПример вывода:
serviceaccount/spark-helm created role.rbac.authorization.k8s.io/spark-helm created rolebinding.rbac.authorization.k8s.io/spark-helm created
Шаг 3. Создание секретов
-
Создайте секрет Kubernetes для truststore-хранилища. Truststore-секрет необходим, чтобы Spark в Kubernetes мог подключаться к кластеру ADH и его сервисам, защищенным с помощью SSL. Секрет необходимо создать из truststore-файла, который использовался при включении SSL в кластере ADH. Truststore-файл должен содержать сертификаты, необходимые для доступа к сервисам ADH (Ozone/HDFS, Hive Metastore).
$ kubectl create secret generic custom-ssl-secret --namespace spark-helm --from-file=truststore.jks=truststore.jks -
Создайте секрет для keytab, используя оператор Kerberos (установите оператор, если еще не установлен). Keytab-cекрет необходим для аутентификации Spark в Kubernetes при подключении к керберизированному кластеру ADH. Создайте файл keytab.yaml:
keytab.yamlapiVersion: krb5.arenadata.io/v1alpha1 kind: Keytab metadata: name: keytab-secret namespace: spark-helm spec: items: - realm: RU-CENTRAL1.INTERNAL labelSelector: env: prod principals: - spark/spark-ozone.ru-central1.internal rotation: interval: 720h checkInterval: 1hПримените конфигурацию:
$ kubectl apply -f keytab.yamlРезультат:
keytab.krb5.arenadata.io/keytab-secret created
-
Создайте секреты из файлов core-site.xml и hive-site.xml, чтобы Spark мог подключаться к HDFS/Ozone и Hive Metastore в кластере ADH:
core-site.xml<configuration> <property> <name>dfs.client.failover.proxy.provider.adh</name> <value>org.apache.hadoop.hdfs.server.namenode.ha.ObserverReadProxyProvider</value> </property> <property> <name>dfs.ha.namenodes.adh</name> <value>nn_ka-adh-1,nn_ka-adh-2</value> </property> <property> <name>dfs.namenode.rpc-address.adh.nn_ka-adh-1</name> <value>ka-adh-1.ru-central1.internal:8020</value> </property> <property> <name>dfs.namenode.rpc-address.adh.nn_ka-adh-2</name> <value>ka-adh-2.ru-central1.internal:8020</value> </property> <property> <name>dfs.nameservices</name> <value>adh</value> </property> <property> <name>fs.defaultFS</name> <value>ofs://adho</value> </property> <property> <name>hadoop.security.authentication</name> <value>kerberos</value> </property> <property> <name>ozone.om.address.adho.om_ka-adh-1</name> <value>ka-adh-1.ru-central1.internal:9862</value> </property> <property> <name>ozone.om.address.adho.om_ka-adh-2</name> <value>ka-adh-2.ru-central1.internal:9862</value> </property> <property> <name>ozone.om.address.adho.om_ka-adh-3</name> <value>ka-adh-3.ru-central1.internal:9862</value> </property> <property> <name>ozone.om.kerberos.principal</name> <value>om/_HOST@RU-CENTRAL1.INTERNAL</value> </property> <property> <name>ozone.om.nodes.demo</name> <value>om_ka-adh-1,om_ka-adh-2,om_ka-adh-3</value> </property> <property> <name>ozone.om.service.ids</name> <value>adho</value> </property> <property> <name>ozone.security.enabled</name> <value>true</value> </property> </configuration>hive-site.xml<configuration> <property> <name>hive.metastore.kerberos.principal</name> <value>hive/_HOST@RU-CENTRAL1.INTERNAL</value> </property> <property> <name>hive.metastore.sasl.enabled</name> <value>true</value> </property> <property> <name>hive.metastore.uris</name> <value>thrift://ka-adh-2.ru-central1.internal:9083</value> </property> <property> <name>hive.metastore.warehouse.dir</name> <value>ofs://adho/apps/hive/warehouse</value> </property> <property> <name>metastore.truststore.password</name> <value>bigdata</value> </property> <property> <name>metastore.truststore.path</name> <value>/etc/ssl/truststore.jks</value> </property> <property> <name>metastore.use.SSL</name> <value>true</value> </property> </configuration>$ kubectl -n spark-helm create secret generic hadoop-conf --from-file=core-site.xml --from-file=hive-site.xml
Шаг 4. Запуск приложения Spark с помощью Helm
-
Создайте файл spark-app-values.yaml:
spark-app-values.yamlimage: registry: "<registry>" (1) repository: "<image>" (2) tag: "<tag>" (3) ## 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: name: "" credentials: {} # registry: private-docker-registry # username: user # password: pass #ServiceAccount name for Spark driver/executor pods serviceAccountName: "spark-helm" #Application #mainApplicationFile: path to the main app file (hdfs://, local://, etc.) mainApplicationFile: "ofs://demo/ozone/demo/word_count/demo-load.py" (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: #Seconds to keep a finished (Succeeded or Failed) SparkApplication CR around #before it is automatically deleted. A Failed application with restart #attempts remaining does not count as finished. Omit to keep finished CRs #until explicit deletion; 0 deletes as soon as the terminal status is recorded. #ttlSecondsAfterFinished: 3600 #mainClass: set only for JVM apps (e.g. SparkConnectServer) mainClass: "" #Hadoop configurations hadoopConfigsSecretName: "hadoop-conf" (5) #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: (6) principal: spark/spark-ozone.ru-central1.internal keytab: secretName: keytab-secret # Arguments passed to the main application class after the main file args: [] # - "1000" #Spark configuration #Key/value map rendered into spec.sparkConf sparkConf: (7) spark.artifactory.dir.path: /tmp/artifacts spark.jars.ivy: /tmp/ivy spark.local.dir: /tmp/data spark.sql.catalog.spark_catalog: org.apache.iceberg.spark.SparkSessionCatalog spark.sql.extensions: org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions spark.sql.security.confblacklist: spark.sql.extensions spark.driver.extraJavaOptions: -Djavax.net.ssl.trustStore=/etc/ssl/truststore.jks -Djavax.net.ssl.trustStorePassword=bigdata (8) #SSL / truststore settings. The referenced Secret must already exist; #the operator mounts the stores at /etc/ssl. #Enabled implicitly when secretName is set. ssl: (9) secretName: "custom-ssl-secret" 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: false 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: - networkpolicies1 Адрес OCI-реестра, из которого загружаются образы. 2 Имя репозитория в реестре. 3 Версия образа. 4 Путь к файлу Spark-приложения. 5 Секрет с конфигурациями HDFS, Ozone и Hive. 6 Параметры Kerberos (принципал и секрет keytab). 7 Параметры Spark. 8 Путь и пароль к truststore-хранилищу. 9 Секрет, содержащий truststore-файл. -
Установите приложение Spark с помощью Helm:
$ helm upgrade --install spark-application oci://<registry-address>/ng/charts/spark-apps:<version> -f spark-app-values.yaml --namespace spark-helm --create-namespaceгде
<registry-address>— адрес OCI-реестра, в который загружены Helm-чарты Spark.