Configure Kerberos and SSL for Spark on Kubernetes using Helm
This article shows how to enable SSL and Kerberos for Spark operator in a Kubernetes cluster using Helm and kubectl.
|
NOTE
Technically, Kerberos and SSL can be configured independently for Spark in Kubernetes.
However, it is recommended to enable SSL together with Kerberos so that Spark can communicate with an ADH cluster over an encrypted channel.
|
Prerequisites
-
A Kubernetes cluster (1.32 or later) with access configured through
kubectl. -
Helm (3.8.0 or higher) — a package manager that allows quick deployment of Docker images in Kubernetes.
-
Spark artifacts (Docker images and Helm charts) loaded into your private OCI registry. These artifacts can be found in offline packages, which can be requested from the Arenadata support team. To run Spark on Kubernetes, you need to unpack the following images:
-
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>
-
-
If Spark in Kubernetes interacts with ADH services (for example, HDFS/Ozone, Hive Metastore, and so on), an installed ADH cluster is required. The ADH cluster should be kerberized and SSL should be enabled. The ADH cluster version should be compatible with ADH Cloud.
Deployment steps
The steps below describe how to install and configure Spark components on Kubernetes. Configure external access, Ingress controllers, load balancers, DNS, and cloud annotations according to your Kubernetes infrastructure.
Step 1. Install Spark operator
-
Create a Helm values file 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 A list of namespaces for the operator to manage resources. 2 The address of your OCI registry to pull images from. 3 The repository name within your registry. 4 The image version. 5 Credentials to access your private Docker registry. -
Install Spark operator:
$ helm upgrade --install spark-operator oci://<registry-address>/ng/charts/spark-operator:<version> -f spark-operator-values.yaml --namespace spark-operator-min --create-namespacewhere
<registry-address>is the address of your OCI registry with loaded Spark Helm charts.Example output:
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:
-
Verify Spark operator installation using the command below:
$ kubectl get pods -n spark-operator-minThe output:
NAME READY STATUS RESTARTS AGE spark-operator-7f8bcf45cf-kbv9m 1/1 Running 0 5m27s
Step 2. Define permissions for the ServiceAccount
-
Create 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 -
Apply the ServiceAccount configuration:
$ kubectl apply -f sa.yamlExample output:
serviceaccount/spark-helm created role.rbac.authorization.k8s.io/spark-helm created rolebinding.rbac.authorization.k8s.io/spark-helm created
Step 3. Create secrets
-
Create a Kubernetes secret for a truststore. The truststore secret is required to allow Spark in Kubernetes to connect to your SSL-protected ADH cluster and its services. The secret must be created from the truststore file which was used to enable SSL in your ADH cluster. Your truststore file should include certificates required to access ADH services (Ozone/HDFS, Hive Metastore).
$ kubectl create secret generic custom-ssl-secret --namespace spark-helm --from-file=truststore.jks=truststore.jks -
Using Kerberos operator (install it, if not yet installed), create a keytab secret. The keytab secret is required to allow Spark in Kubernetes to authenticate when connecting to your kerberized ADH cluster. For this, create 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: 1hApply the configuration:
$ kubectl apply -f keytab.yamlThe result:
keytab.krb5.arenadata.io/keytab-secret created
-
Create secrets based on core-site.xml and hive-site.xml to allow Spark to connect to HDFS/Ozone and Hive Metastore in your ADH cluster:
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
Step 4. Submit a Spark application using Helm
-
Create 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 The address of your OCI registry to pull images from. 2 The repository name within your registry. 3 The image version. 4 Path to the Spark application file. 5 Secret with HDFS, Ozone, Hive configurations. 6 Kerberos configurations (principal and keytab secret). 7 Spark configurations. 8 Path and password to access the truststore. 9 Secret with truststore. -
Install the Spark application using Helm:
$ helm upgrade --install spark-application oci://<registry-address>/ng/charts/spark-apps:<version> -f spark-app-values.yaml --namespace spark-helm --create-namespacewhere
<registry-address>is the address of your OCI registry with loaded Spark Helm charts.