From 73998659b94e4d209b9b22d4a43593c692fb96c8 Mon Sep 17 00:00:00 2001 From: nileshrathi345 <113672051+nileshrathi345@users.noreply.github.com> Date: Mon, 10 Aug 2026 21:21:50 +0530 Subject: [PATCH] LIVY-979: Add Helm chart for local Livy-on-Kubernetes development Add dev/helmchart/ to deploy Livy, Spark History Server, RBAC, ingress, and optional cert-manager on a local Kubernetes cluster (e.g. Docker Desktop). Includes ASF license headers, validation script, README/Docker.md, and review fixes: no vendored chart tgz, consistent resource naming, unified RBAC/TLS config, and derived image tags from sparkVersion. Builds on the original contribution in https://github.com/apache/livy/pull/454. Co-authored-by: Asif Khatri <123077165+askhatri@users.noreply.github.com> --- .rat-excludes | 1 + dev/docker/README.md | 3 + dev/helmchart/.gitignore | 18 ++ dev/helmchart/.helmignore | 24 +++ dev/helmchart/Chart.lock | 9 + dev/helmchart/Chart.yaml | 46 +++++ dev/helmchart/Docker.md | 80 +++++++++ dev/helmchart/README.md | 161 +++++++++++++++++ dev/helmchart/templates/NOTES.txt | 40 +++++ dev/helmchart/templates/_helpers.tpl | 113 ++++++++++++ .../templates/certificate-default.yaml | 37 ++++ dev/helmchart/templates/clusterissuers.yaml | 37 ++++ dev/helmchart/templates/config.yaml | 84 +++++++++ dev/helmchart/templates/ingress.yaml | 86 +++++++++ dev/helmchart/templates/pvc.yaml | 42 +++++ dev/helmchart/templates/rbac-bindings.yaml | 43 +++++ dev/helmchart/templates/rbac-roles.yaml | 39 +++++ dev/helmchart/templates/rbac.yaml | 104 +++++++++++ dev/helmchart/templates/service-headless.yaml | 33 ++++ dev/helmchart/templates/service.yaml | 52 ++++++ dev/helmchart/templates/serviceaccount.yaml | 73 ++++++++ dev/helmchart/templates/statefulset.yaml | 164 ++++++++++++++++++ dev/helmchart/templates/storageclass.yaml | 48 +++++ dev/helmchart/test/validate.sh | 133 ++++++++++++++ dev/helmchart/values.yaml | 134 ++++++++++++++ 25 files changed, 1604 insertions(+) create mode 100644 dev/helmchart/.gitignore create mode 100644 dev/helmchart/.helmignore create mode 100644 dev/helmchart/Chart.lock create mode 100644 dev/helmchart/Chart.yaml create mode 100644 dev/helmchart/Docker.md create mode 100644 dev/helmchart/README.md create mode 100644 dev/helmchart/templates/NOTES.txt create mode 100644 dev/helmchart/templates/_helpers.tpl create mode 100644 dev/helmchart/templates/certificate-default.yaml create mode 100644 dev/helmchart/templates/clusterissuers.yaml create mode 100644 dev/helmchart/templates/config.yaml create mode 100644 dev/helmchart/templates/ingress.yaml create mode 100644 dev/helmchart/templates/pvc.yaml create mode 100644 dev/helmchart/templates/rbac-bindings.yaml create mode 100644 dev/helmchart/templates/rbac-roles.yaml create mode 100644 dev/helmchart/templates/rbac.yaml create mode 100644 dev/helmchart/templates/service-headless.yaml create mode 100644 dev/helmchart/templates/service.yaml create mode 100644 dev/helmchart/templates/serviceaccount.yaml create mode 100644 dev/helmchart/templates/statefulset.yaml create mode 100644 dev/helmchart/templates/storageclass.yaml create mode 100755 dev/helmchart/test/validate.sh create mode 100644 dev/helmchart/values.yaml diff --git a/.rat-excludes b/.rat-excludes index b22edb294..24a682ac7 100644 --- a/.rat-excludes +++ b/.rat-excludes @@ -30,3 +30,4 @@ logs/* docs/**/*.html docs/**/JB/** venv/* +dev/helmchart/Chart.lock diff --git a/dev/docker/README.md b/dev/docker/README.md index 9f0356161..1e2c39aa5 100644 --- a/dev/docker/README.md +++ b/dev/docker/README.md @@ -1,4 +1,7 @@ # Livy with standalone Spark Cluster + +For a local Kubernetes development environment, see [../helmchart/README.md](../helmchart/README.md). + ## Pre-requisite Following steps use Ubuntu as development environment but most of the instructions can be modified to fit another OS as well. * Install wsl if on windows, instructions available [here](https://ubuntu.com/tutorials/install-ubuntu-on-wsl2-on-windows-11-with-gui-support) diff --git a/dev/helmchart/.gitignore b/dev/helmchart/.gitignore new file mode 100644 index 000000000..7f1d315e4 --- /dev/null +++ b/dev/helmchart/.gitignore @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +charts/*.tgz diff --git a/dev/helmchart/.helmignore b/dev/helmchart/.helmignore new file mode 100644 index 000000000..ea9fa4c16 --- /dev/null +++ b/dev/helmchart/.helmignore @@ -0,0 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +.DS_Store +.git/ +.idea/ +*.swp +*.bak +*.tmp +test/ diff --git a/dev/helmchart/Chart.lock b/dev/helmchart/Chart.lock new file mode 100644 index 000000000..afe07ce88 --- /dev/null +++ b/dev/helmchart/Chart.lock @@ -0,0 +1,9 @@ +dependencies: +- name: ingress-nginx + repository: https://kubernetes.github.io/ingress-nginx + version: 4.12.1 +- name: cert-manager + repository: https://charts.jetstack.io + version: v1.16.2 +digest: sha256:787a1aa996a76ef0b6f327fdabc51f473ff0708890c3d7f57b95d165607568a5 +generated: "2026-08-06T11:09:10.754267+05:30" diff --git a/dev/helmchart/Chart.yaml b/dev/helmchart/Chart.yaml new file mode 100644 index 000000000..ca727bb29 --- /dev/null +++ b/dev/helmchart/Chart.yaml @@ -0,0 +1,46 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: v2 +name: livycluster +description: Local Apache Livy and Spark development environment on Kubernetes +type: application +version: 0.1.0 +appVersion: "1.0.0-SNAPSHOT" +home: https://livy.apache.org/ +sources: + - https://github.com/apache/livy +maintainers: + - name: Apache Livy + email: livy@apache.org +keywords: + - livy + - spark + - kubernetes +dependencies: + - name: ingress-nginx + version: 4.12.1 + repository: https://kubernetes.github.io/ingress-nginx + condition: ingress-nginx.enabled + tags: + - ingress + - name: cert-manager + version: v1.16.2 + repository: https://charts.jetstack.io + condition: cert-manager.enabled + tags: + - ingress diff --git a/dev/helmchart/Docker.md b/dev/helmchart/Docker.md new file mode 100644 index 000000000..dd82e0f07 --- /dev/null +++ b/dev/helmchart/Docker.md @@ -0,0 +1,80 @@ +# Building Container Images for the Kubernetes Helm Chart + +These steps build Spark and Livy images for the local Kubernetes development environment +described in [README.md](README.md). Version defaults match `values.yaml`. + +## Build Livy + +From the repository root: + +```shell +mvn -Pthriftserver -Pscala-2.12 -Pspark3 package +cp assembly/target/apache-livy-1.0.0-SNAPSHOT_2.12-bin.zip /tmp/ +``` + +## Spark image with Python bindings + +Download and extract Spark: + +```shell +wget https://archive.apache.org/dist/spark/spark-3.5.6/spark-3.5.6-bin-hadoop3.tgz +tar -xzf spark-3.5.6-bin-hadoop3.tgz +``` + +Build and push the Spark image: + +```shell +./spark-3.5.6-bin-hadoop3/bin/docker-image-tool.sh \ + -r \ + -t v3.5.6 \ + -p kubernetes/dockerfiles/spark/bindings/python/Dockerfile \ + build + +./spark-3.5.6-bin-hadoop3/bin/docker-image-tool.sh \ + -r \ + -t v3.5.6 \ + -p kubernetes/dockerfiles/spark/bindings/python/Dockerfile \ + push +``` + +## Livy image + +Create `/tmp/Dockerfile`: + +```dockerfile +FROM /spark-py:v3.5.6 + +ENV LIVY_VERSION=1.0.0-SNAPSHOT +ENV LIVY_PACKAGE=apache-livy-${LIVY_VERSION}_2.12-bin +ENV LIVY_HOME=/opt/livy +ENV LIVY_CONF_DIR=/conf +ENV PATH=$PATH:$LIVY_HOME/bin + +USER root + +COPY ${LIVY_PACKAGE}.zip / +RUN apt-get update && apt-get install -y unzip && \ + unzip /${LIVY_PACKAGE}.zip -d / && \ + mv /${LIVY_PACKAGE} /opt/ && \ + rm -rf ${LIVY_HOME} && \ + ln -s /opt/${LIVY_PACKAGE} ${LIVY_HOME} && \ + rm -f /${LIVY_PACKAGE}.zip + +RUN mkdir -p /var/log/livy && ln -s /var/log/livy ${LIVY_HOME}/logs + +WORKDIR ${LIVY_HOME} +ENTRYPOINT ["livy-server"] +``` + +Build and push: + +```shell +cd /tmp +docker build -t /livy:spark3.5.6 . +docker push /livy:spark3.5.6 +rm -f apache-livy-1.0.0-SNAPSHOT_2.12-bin.zip +``` + +Update `values.yaml` or pass `--set` flags so `image.livy.repository`, +`image.livy.tag`, `image.spark.repository`, and `image.spark.tag` match the images +you pushed. diff --git a/dev/helmchart/README.md b/dev/helmchart/README.md new file mode 100644 index 000000000..e7d2c1fd3 --- /dev/null +++ b/dev/helmchart/README.md @@ -0,0 +1,161 @@ +# Apache Livy on Kubernetes (Local Development) + +This Helm chart deploys Apache Livy and Apache Spark on a local Kubernetes cluster, +such as Docker Desktop with Kubernetes enabled. It is intended for development and +debugging without relying on cloud services. + +JIRA: [LIVY-979](https://issues.apache.org/jira/browse/LIVY-979) + +## Prerequisites + +1. Install [Docker Desktop](https://www.docker.com/products/docker-desktop/) and enable Kubernetes. +2. Install [Helm 3](https://helm.sh/docs/intro/install/). +3. Build and push the Spark and Livy container images described in [Docker.md](Docker.md). +4. Add a hosts entry that matches `clusterHost` in `values.yaml` (default `my-cluster.example.com`): + +```text +127.0.0.1 my-cluster.example.com +``` + +## Version alignment + +The chart defaults match a Livy build produced with: + +```shell +mvn -Pthriftserver -Pscala-2.12 -Pspark3 package +``` + +| Setting | Default | +|---------|---------| +| Livy | `1.0.0-SNAPSHOT` | +| Spark | `3.5.6` | +| Scala | `2.12` | + +Update `values.yaml` if you build with different profiles or versions. Image tags default +from `sparkVersion` when left empty (`v3.5.6` for Spark, `spark3.5.6` for Livy). + +## Build chart dependencies + +Chart dependencies are declared in `Chart.yaml` and resolved at deploy time. Do not +commit the downloaded archives under `charts/`: + +```shell +cd dev/helmchart +helm dependency build +``` + +When ingress is enabled, the chart installs `cert-manager` and `ingress-nginx` as +subcharts. On a fresh cluster, cert-manager CRDs are installed via +`cert-manager.installCRDs=true` (the chart default). + +References: +- [cert-manager nginx ingress tutorial](https://cert-manager.io/docs/tutorials/acme/nginx-ingress/) +- [cert-manager Helm chart](https://artifacthub.io/packages/helm/cert-manager/cert-manager) + +## Deploy the chart + +```shell +cd dev/helmchart +helm dependency build + +kubectl create namespace livy-dev + +helm -n livy-dev install livycluster . \ + --set image.livy.repository=/livy \ + --set image.spark.repository=/spark-py \ + --set ingress-nginx.controller.extraArgs.default-ssl-certificate=livy-dev/ingress-default-tls +``` + +Set `ingress-nginx.controller.extraArgs.default-ssl-certificate` to +`/` (defaults: `livy-dev/ingress-default-tls`). + +## Remote debugging + +Remote debugging is enabled by default (`debug.enabled: true`) on port `9010`, matching +the pattern used in `dev/docker/livy-dev-cluster/conf/livy/livy-env.sh`. Disable it for +non-debug deployments: + +```shell +helm upgrade livycluster . -n livy-dev --set debug.enabled=false +``` + +## Grafana and Loki (optional) + +Livy can link Spark driver and executor logs to Grafana when Loki is available. The chart +maps the following `values.yaml` keys to `livy.conf`: + +| values.yaml | livy.conf | +|-------------|-----------| +| `grafana.loki.enabled` | `livy.server.kubernetes.grafana.loki.enabled` | +| `grafana.url` | `livy.server.kubernetes.grafana.url` | +| `grafana.lokiDatasource` | `livy.server.kubernetes.grafana.loki.datasource` | +| `grafana.timeRange` | `livy.server.kubernetes.grafana.timeRange` | + +To enable Loki integration: + +1. Install Grafana and Loki in the cluster (for example with the [Loki stack Helm chart](https://grafana.com/docs/loki/latest/setup/install/helm/)). +2. Add a Loki datasource named `loki` in Grafana (or override `grafana.lokiDatasource`). +3. Deploy or upgrade with Loki integration enabled: + +```shell +helm upgrade livycluster . -n livy-dev \ + --set grafana.loki.enabled=true \ + --set grafana.url=http://grafana.livy-dev.svc.cluster.local:3000 +``` + +When enabled, Livy adds log links in the session UI that open Grafana Explore for the +matching Spark application labels. + +## Verify the deployment + +```shell +kubectl -n livy-dev get pods -w +``` + +### REST API smoke tests + +Create an interactive session: + +```shell +curl -k -X POST -H "Content-Type: application/json" \ + --data '{"kind": "spark"}' \ + https://my-cluster.example.com/livy/sessions | jq +``` + +Run a statement: + +```shell +curl -k -X POST \ + -H "Content-Type: application/json" \ + -d '{"kind": "spark", "code": "sc.parallelize(1 to 10).count()"}' \ + https://my-cluster.example.com/livy/sessions/0/statements | jq +``` + +Submit a batch job: + +```shell +curl -s -k -H "Content-Type: application/json" -X POST \ + -d '{ + "name": "testbatch1", + "className": "org.apache.spark.examples.SparkPi", + "numExecutors": 2, + "file": "local:///opt/spark/examples/jars/spark-examples_2.12-3.5.6.jar", + "args": ["10000"] + }' "https://my-cluster.example.com/livy/batches" | jq +``` + +Update the examples JAR path if you change `scalaBinaryVersion` or `sparkVersion` in +`values.yaml` (pattern: `spark-examples_-.jar`). + +## Chart validation + +Run the chart checks locally: + +```shell +dev/helmchart/test/validate.sh +``` + +## Related documentation + +- Docker image build steps: [Docker.md](Docker.md) +- Standalone Spark cluster (non-Kubernetes): [../docker/README.md](../docker/README.md) diff --git a/dev/helmchart/templates/NOTES.txt b/dev/helmchart/templates/NOTES.txt new file mode 100644 index 000000000..37fe0e91c --- /dev/null +++ b/dev/helmchart/templates/NOTES.txt @@ -0,0 +1,40 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +The Livy server has been installed. +Check its status by running: + kubectl -n {{ .Release.Namespace }} get pods -w +Connect to the Livy Web UI: +{{- if .Values.ingress.enabled }} + # Open in browser: + # https://{{ .Values.clusterHost }}/livy/ui + # + # If TLS fails on first install, set ingress-nginx default certificate to: + # {{ include "livycluster.defaultTlsCertificate" . }} +{{- else }} + kubectl -n {{ .Release.Namespace }} port-forward {{ include "livycluster.fullname" . }}-0 8998 + # Open in browser: http://localhost:8998 +{{- end }} +{{- if .Values.debug.enabled }} + +Remote debugging is enabled on port {{ .Values.debug.port }}. +{{- end }} +{{- if .Values.grafana.loki.enabled }} + +Grafana Loki integration is enabled. Open {{ .Values.grafana.url }} and use datasource +"{{ .Values.grafana.lokiDatasource }}" to inspect Spark driver and executor logs. +{{- end }} diff --git a/dev/helmchart/templates/_helpers.tpl b/dev/helmchart/templates/_helpers.tpl new file mode 100644 index 000000000..ff0683338 --- /dev/null +++ b/dev/helmchart/templates/_helpers.tpl @@ -0,0 +1,113 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "livycluster.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "livycluster.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "livycluster.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels for the Spark History Server component. +*/}} +{{- define "livycluster.historyLabels" -}} +app.kubernetes.io/name: {{ include "livycluster.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/component: history-server +helm.sh/chart: {{ include "livycluster.chart" . }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Selector labels for the Spark History Server component. +*/}} +{{- define "livycluster.historySelectorLabels" -}} +app.kubernetes.io/name: {{ include "livycluster.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/component: history-server +{{- end -}} + +{{/* +Spark image tag; defaults to v when not overridden. +*/}} +{{- define "livycluster.sparkImageTag" -}} +{{- .Values.image.spark.tag | default (printf "v%s" .Values.sparkVersion) -}} +{{- end -}} + +{{/* +Livy image tag; defaults to spark when not overridden. +*/}} +{{- define "livycluster.livyImageTag" -}} +{{- .Values.image.livy.tag | default (printf "spark%s" .Values.sparkVersion) -}} +{{- end -}} + +{{/* +Spark examples JAR path inside the container image. +*/}} +{{- define "livycluster.sparkExamplesJar" -}} +local:///opt/spark/examples/jars/spark-examples_{{ .Values.scalaBinaryVersion }}-{{ .Values.sparkVersion }}.jar +{{- end -}} + +{{/* +History server URL exposed through ingress. +*/}} +{{- define "livycluster.historyServerUrl" -}} +https://{{ .Values.clusterHost }}/historyserver +{{- end -}} + +{{/* +Default TLS certificate reference for ingress-nginx (namespace/secretName). +*/}} +{{- define "livycluster.defaultTlsCertificate" -}} +{{- printf "%s/%s" .Release.Namespace .Values.ingress.tls.secretName -}} +{{- end -}} + +{{/* +Optional JDWP agent for remote debugging. +*/}} +{{- define "livycluster.serverJavaOpts" -}} +{{- if .Values.debug.enabled -}} +-agentlib:jdwp=transport=dt_socket,server=y,address={{ .Values.debug.port }},suspend=n +{{- end -}} +{{- end -}} diff --git a/dev/helmchart/templates/certificate-default.yaml b/dev/helmchart/templates/certificate-default.yaml new file mode 100644 index 000000000..fe24907d2 --- /dev/null +++ b/dev/helmchart/templates/certificate-default.yaml @@ -0,0 +1,37 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- if and (index .Values "cert-manager").enabled .Values.defaultCertificate.enabled }} +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ include "livycluster.fullname" . }}-default-tls + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "livycluster.name" . }} + helm.sh/chart: {{ include "livycluster.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +spec: + secretName: {{ .Values.defaultCertificate.secretName }} + issuerRef: + name: {{ .Values.defaultCertificate.issuer }} + kind: {{ .Values.defaultCertificate.issuerKind }} + commonName: {{ printf "*.%s" .Values.clusterHost | quote }} + dnsNames: + - {{ .Values.clusterHost | quote }} +{{- end }} diff --git a/dev/helmchart/templates/clusterissuers.yaml b/dev/helmchart/templates/clusterissuers.yaml new file mode 100644 index 000000000..5cfad995b --- /dev/null +++ b/dev/helmchart/templates/clusterissuers.yaml @@ -0,0 +1,37 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- if and (index .Values "cert-manager").enabled .Values.clusterIssuers }} +{{- $name := include "livycluster.name" . -}} +{{- $chart := include "livycluster.chart" . -}} +{{- $releaseName := .Release.Name -}} +{{- $releaseService := .Release.Service -}} +{{- range .Values.clusterIssuers }} +--- +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: {{ .name }} + labels: + app.kubernetes.io/name: {{ $name }} + helm.sh/chart: {{ $chart }} + app.kubernetes.io/instance: {{ $releaseName }} + app.kubernetes.io/managed-by: {{ $releaseService }} +spec: +{{ toYaml .spec | indent 2 }} +{{- end }} +{{- end }} diff --git a/dev/helmchart/templates/config.yaml b/dev/helmchart/templates/config.yaml new file mode 100644 index 000000000..6a62b8b51 --- /dev/null +++ b/dev/helmchart/templates/config.yaml @@ -0,0 +1,84 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "livycluster.fullname" . }}-config + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "livycluster.name" . }} + helm.sh/chart: {{ include "livycluster.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +data: + livy.conf: |- + livy.file.local-dir-whitelist={{ .Values.livy.localDirWhitelist }} + livy.server.kubernetes.ingress.className=nginx + livy.server.kubernetes.ingress.create=true + livy.server.kubernetes.ingress.host={{ .Values.clusterHost }} + livy.server.kubernetes.ingress.protocol=https + livy.server.kubernetes.ingress.tls.secretName={{ .Values.ingress.tls.secretName }} + livy.server.kubernetes.grafana.loki.enabled={{ .Values.grafana.loki.enabled }} + livy.server.kubernetes.grafana.url={{ .Values.grafana.url }} + livy.server.kubernetes.grafana.loki.datasource={{ .Values.grafana.lokiDatasource }} + livy.server.kubernetes.grafana.timeRange={{ .Values.grafana.timeRange }} + livy.server.port=8998 + livy.server.recovery.mode=recovery + livy.server.recovery.state-store.url=/tmp/livy/store + livy.server.recovery.state-store=filesystem + livy.server.session.state-retain.sec=4h + livy.spark.deploy-mode=cluster + livy.spark.master=k8s://kubernetes.default.svc.cluster.local:443 + livy.ui.basePath=/livy + livy.ui.history-server-url={{ include "livycluster.historyServerUrl" . }} + livy-client.conf: |- + livy.rsc.client.connect.timeout=600s + livy.rsc.rpc.server.address={{ include "livycluster.fullname" . }}-0.{{ include "livycluster.fullname" . }}-headless.{{ .Release.Namespace }}.svc.cluster.local + livy.rsc.server.connect.timeout=600s + log4j.properties: |- + log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{1}: %m%n + log4j.appender.console.layout=org.apache.log4j.PatternLayout + log4j.appender.console.target=System.err + log4j.appender.console=org.apache.log4j.ConsoleAppender + log4j.logger.org.eclipse.jetty=WARN + log4j.rootCategory={{ .Values.livy.logLevel }}, console + spark-defaults.conf: |- + spark.driver.cores={{ .Values.spark.driverCores }} + spark.driver.extraJavaOptions=--add-opens java.base/jdk.internal.misc=ALL-UNNAMED -Dio.netty.tryReflectionSetAccessible=true + spark.driver.memory={{ .Values.spark.driverMemory }} + spark.eventLog.dir=file:///tmp/livy/store + spark.eventLog.enabled=true + spark.executor.extraJavaOptions=--add-opens java.base/jdk.internal.misc=ALL-UNNAMED -Dio.netty.tryReflectionSetAccessible=true + spark.kryo.registrationRequired=false + spark.kryo.unsafe=false + spark.kubernetes.authenticate.driver.serviceAccountName={{ include "livycluster.fullname" . }}-spark + spark.kubernetes.container.image.pullPolicy={{ .Values.image.spark.pullPolicy }} + spark.kubernetes.container.image={{ .Values.image.spark.repository }}:{{ include "livycluster.sparkImageTag" . }} + spark.kubernetes.driver.label.name=driver + spark.kubernetes.driver.volumes.persistentVolumeClaim.datafiles.mount.path=/tmp/livy/store + spark.kubernetes.driver.volumes.persistentVolumeClaim.datafiles.options.claimName={{ include "livycluster.fullname" . }} + spark.kubernetes.executor.label.name=executor + spark.kubernetes.executor.volumes.persistentVolumeClaim.datafiles.mount.path=/tmp/livy/store + spark.kubernetes.executor.volumes.persistentVolumeClaim.datafiles.options.claimName={{ include "livycluster.fullname" . }} + spark.kubernetes.file.upload.path=file:///tmp/livy/store + spark.kubernetes.namespace={{ .Release.Namespace }} + spark.network.timeout=800 + spark.serializer=org.apache.spark.serializer.KryoSerializer + spark.speculation=false + spark.sql.broadcastTimeout=1200 + spark.sql.warehouse.dir=/opt/spark/spark-warehouse diff --git a/dev/helmchart/templates/ingress.yaml b/dev/helmchart/templates/ingress.yaml new file mode 100644 index 000000000..261b05041 --- /dev/null +++ b/dev/helmchart/templates/ingress.yaml @@ -0,0 +1,86 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "livycluster.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "livycluster.name" . }} + helm.sh/chart: {{ include "livycluster.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} +spec: + ingressClassName: nginx + tls: + - hosts: + - {{ .Values.clusterHost | quote }} + secretName: {{ .Values.ingress.tls.secretName }} + rules: + - host: {{ .Values.clusterHost | quote }} + http: + paths: + - path: {{ .Values.ingress.path }} + pathType: Prefix + backend: + service: + name: {{ include "livycluster.fullname" . }} + port: + number: {{ .Values.service.port }} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "livycluster.fullname" . }}-history-ingress + namespace: {{ .Release.Namespace }} + labels: + {{- include "livycluster.historyLabels" . | nindent 4 }} + annotations: + nginx.ingress.kubernetes.io/configuration-snippet: | + proxy_set_header Accept-Encoding ""; + sub_filter_last_modified off; + sub_filter 'href="/' 'href="/historyserver/'; + sub_filter 'src="/' 'src="/historyserver/'; + sub_filter_once off; + sub_filter_types text/html text/css text/javascript application/javascript; + nginx.ingress.kubernetes.io/rewrite-target: /$1 + nginx.ingress.kubernetes.io/service-upstream: "true" + nginx.ingress.kubernetes.io/x-forwarded-prefix: /historyserver +spec: + ingressClassName: nginx + tls: + - hosts: + - {{ .Values.clusterHost | quote }} + secretName: {{ .Values.ingress.tls.secretName }} + rules: + - host: {{ .Values.clusterHost | quote }} + http: + paths: + - path: /historyserver/?(.*) + pathType: Prefix + backend: + service: + name: {{ include "livycluster.fullname" . }}-history-service + port: + number: 18080 +{{- end }} diff --git a/dev/helmchart/templates/pvc.yaml b/dev/helmchart/templates/pvc.yaml new file mode 100644 index 000000000..618aa9897 --- /dev/null +++ b/dev/helmchart/templates/pvc.yaml @@ -0,0 +1,42 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "livycluster.fullname" . }} + namespace: {{ .Release.Namespace }} +{{- with .Values.persistence.annotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} + labels: + app.kubernetes.io/name: {{ include "livycluster.name" . }} + helm.sh/chart: {{ include "livycluster.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +spec: + accessModes: + - {{ .Values.persistence.accessMode | quote }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} +{{- with .Values.persistence.storageClass }} + storageClassName: {{ if eq "-" . }}""{{ else }}{{ . | quote }}{{ end }} +{{- end }} +{{- end }} diff --git a/dev/helmchart/templates/rbac-bindings.yaml b/dev/helmchart/templates/rbac-bindings.yaml new file mode 100644 index 000000000..d44d21911 --- /dev/null +++ b/dev/helmchart/templates/rbac-bindings.yaml @@ -0,0 +1,43 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- if .Values.rbac.enabled }} +{{- $name := include "livycluster.name" . -}} +{{- $chart := include "livycluster.chart" . -}} +{{- $releaseName := .Release.Name -}} +{{- $releaseNamespace := .Release.Namespace -}} +{{- $releaseService := .Release.Service -}} +{{- range .Values.rbac.bindings }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: {{ if .namespace }}RoleBinding{{ else }}ClusterRoleBinding{{ end }} +metadata: + name: {{ $name }}-{{ .role }}{{ if .namespace }}-{{ .namespace }}{{ end }} + namespace: {{ $releaseNamespace }} + labels: + app.kubernetes.io/name: {{ $name }} + helm.sh/chart: {{ $chart }} + app.kubernetes.io/instance: {{ $releaseName }} + app.kubernetes.io/managed-by: {{ $releaseService }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: {{ if .cluster }}ClusterRole{{ else }}Role{{ end }} + name: {{ .role }} +subjects: +{{ toYaml .subjects | indent 2 }} +{{- end }} +{{- end }} diff --git a/dev/helmchart/templates/rbac-roles.yaml b/dev/helmchart/templates/rbac-roles.yaml new file mode 100644 index 000000000..2ddbaa682 --- /dev/null +++ b/dev/helmchart/templates/rbac-roles.yaml @@ -0,0 +1,39 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- if .Values.rbac.enabled }} +{{- $name := include "livycluster.name" . -}} +{{- $chart := include "livycluster.chart" . -}} +{{- $releaseName := .Release.Name -}} +{{- $releaseNamespace := .Release.Namespace -}} +{{- $releaseService := .Release.Service -}} +{{- range .Values.rbac.roles }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: {{ if .namespace }}Role{{ else }}ClusterRole{{ end }} +metadata: + name: {{ .name }} + namespace: {{ $releaseNamespace }} + labels: + app.kubernetes.io/name: {{ $name }} + helm.sh/chart: {{ $chart }} + app.kubernetes.io/instance: {{ $releaseName }} + app.kubernetes.io/managed-by: {{ $releaseService }} +rules: +{{ toYaml .rules | indent 2 }} +{{- end }} +{{- end }} diff --git a/dev/helmchart/templates/rbac.yaml b/dev/helmchart/templates/rbac.yaml new file mode 100644 index 000000000..25dd477dc --- /dev/null +++ b/dev/helmchart/templates/rbac.yaml @@ -0,0 +1,104 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- if .Values.rbac.enabled }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "livycluster.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "livycluster.name" . }} + helm.sh/chart: {{ include "livycluster.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +rules: + - apiGroups: [""] + resources: ["pods", "pods/log"] + verbs: ["*"] + - apiGroups: [""] + resources: ["configmaps", "services"] + verbs: ["create", "update", "get", "list", "patch"] + - apiGroups: [""] + resources: ["namespaces"] + verbs: ["get", "list"] + - apiGroups: ["", "rbac.authorization.k8s.io"] + resources: ["serviceaccounts", "rolebindings"] + verbs: ["create", "update"] + - apiGroups: ["networking.k8s.io"] + resources: ["ingresses"] + verbs: ["create", "update", "get", "list", "delete"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "livycluster.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "livycluster.name" . }} + helm.sh/chart: {{ include "livycluster.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "livycluster.fullname" . }} +subjects: + - name: {{ include "livycluster.fullname" . }} + namespace: {{ .Release.Namespace }} + kind: ServiceAccount +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "livycluster.fullname" . }}-spark + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "livycluster.name" . }} + helm.sh/chart: {{ include "livycluster.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +rules: + - apiGroups: [""] + resources: ["pods", "services"] + verbs: ["*"] + - apiGroups: [""] + resources: ["configmaps", "services"] + verbs: ["create", "update", "get", "list", "delete", "patch"] + - apiGroups: ["networking.k8s.io"] + resources: ["ingresses"] + verbs: ["create", "update", "get", "list", "delete"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "livycluster.fullname" . }}-spark + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "livycluster.name" . }} + helm.sh/chart: {{ include "livycluster.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "livycluster.fullname" . }}-spark +subjects: + - name: {{ include "livycluster.fullname" . }}-spark + namespace: {{ .Release.Namespace }} + kind: ServiceAccount +{{- end }} diff --git a/dev/helmchart/templates/service-headless.yaml b/dev/helmchart/templates/service-headless.yaml new file mode 100644 index 000000000..dd48e855a --- /dev/null +++ b/dev/helmchart/templates/service-headless.yaml @@ -0,0 +1,33 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: v1 +kind: Service +metadata: + name: {{ include "livycluster.fullname" . }}-headless + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "livycluster.name" . }} + helm.sh/chart: {{ include "livycluster.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +spec: + type: ClusterIP + clusterIP: None + selector: + app.kubernetes.io/name: {{ include "livycluster.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} diff --git a/dev/helmchart/templates/service.yaml b/dev/helmchart/templates/service.yaml new file mode 100644 index 000000000..9b7c71374 --- /dev/null +++ b/dev/helmchart/templates/service.yaml @@ -0,0 +1,52 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: v1 +kind: Service +metadata: + name: {{ default (include "livycluster.fullname" .) .Values.service.name }} + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "livycluster.name" . }} + helm.sh/chart: {{ include "livycluster.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + app.kubernetes.io/name: {{ include "livycluster.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "livycluster.fullname" . }}-history-service + namespace: {{ .Release.Namespace }} + labels: + {{- include "livycluster.historyLabels" . | nindent 4 }} +spec: + selector: + {{- include "livycluster.historySelectorLabels" . | nindent 4 }} + ports: + - protocol: TCP + port: 18080 + targetPort: 18080 diff --git a/dev/helmchart/templates/serviceaccount.yaml b/dev/helmchart/templates/serviceaccount.yaml new file mode 100644 index 000000000..95d9bac06 --- /dev/null +++ b/dev/helmchart/templates/serviceaccount.yaml @@ -0,0 +1,73 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- if .Values.rbac.enabled }} +{{- $name := include "livycluster.name" . -}} +{{- $chart := include "livycluster.chart" . -}} +{{- $releaseName := .Release.Name -}} +{{- $releaseNamespace := .Release.Namespace -}} +{{- $releaseService := .Release.Service -}} +{{- range .Values.rbac.serviceAccounts }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .name }} + namespace: {{ $releaseNamespace }} + labels: + app.kubernetes.io/name: {{ $name }} + helm.sh/chart: {{ $chart }} + app.kubernetes.io/instance: {{ $releaseName }} + app.kubernetes.io/managed-by: {{ $releaseService }} +{{- end }} +{{- end }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "livycluster.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "livycluster.name" . }} + helm.sh/chart: {{ include "livycluster.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- if .Values.global.imagePullSecrets }} +imagePullSecrets: + {{- toYaml .Values.global.imagePullSecrets | nindent 2 }} +{{- else if .Values.imagePullSecrets }} +imagePullSecrets: + {{- toYaml .Values.imagePullSecrets | nindent 2 }} +{{- end }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "livycluster.fullname" . }}-spark + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "livycluster.name" . }} + helm.sh/chart: {{ include "livycluster.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- if .Values.global.imagePullSecrets }} +imagePullSecrets: + {{- toYaml .Values.global.imagePullSecrets | nindent 2 }} +{{- else if .Values.imagePullSecrets }} +imagePullSecrets: + {{- toYaml .Values.imagePullSecrets | nindent 2 }} +{{- end }} diff --git a/dev/helmchart/templates/statefulset.yaml b/dev/helmchart/templates/statefulset.yaml new file mode 100644 index 000000000..462a194d8 --- /dev/null +++ b/dev/helmchart/templates/statefulset.yaml @@ -0,0 +1,164 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "livycluster.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "livycluster.name" . }} + helm.sh/chart: {{ include "livycluster.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +spec: + serviceName: {{ include "livycluster.fullname" . }}-headless + selector: + matchLabels: + app.kubernetes.io/name: {{ include "livycluster.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + template: + metadata: + labels: + app.kubernetes.io/name: {{ include "livycluster.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + spec: + serviceAccountName: {{ include "livycluster.fullname" . }} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.livy.repository }}:{{ include "livycluster.livyImageTag" . }}" + imagePullPolicy: {{ .Values.image.livy.pullPolicy }} + ports: + - name: http + containerPort: 8998 + protocol: TCP + livenessProbe: + httpGet: + path: /version + port: http + initialDelaySeconds: 60 + periodSeconds: 10 + failureThreshold: 6 + readinessProbe: + httpGet: + path: /version + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 6 + resources: + {{- toYaml .Values.resources | nindent 12 }} + env: + - name: HTTP2_DISABLE + value: "true" + - name: LIVY_CONF_DIR + value: "/conf" + - name: SPARK_CONF_DIR + value: "/conf" + - name: LIVY_LOG_DIR + value: "/opt/livy/logs" + {{- $javaOpts := include "livycluster.serverJavaOpts" . }} + {{- if $javaOpts }} + - name: LIVY_SERVER_JAVA_OPTS + value: {{ $javaOpts | quote }} + {{- end }} + volumeMounts: + {{- if .Values.persistence.enabled }} + - name: livy-store + mountPath: /tmp/livy/store + {{- if .Values.persistence.subPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + {{- end }} + - name: cluster-conf-cm + mountPath: /conf + volumes: + - name: cluster-conf-cm + configMap: + name: {{ include "livycluster.fullname" . }}-config + - name: livy-store + {{- if .Values.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.persistence.existingClaim | default (include "livycluster.fullname" .) }} + {{- else }} + emptyDir: {} + {{- end }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "livycluster.fullname" . }}-history + namespace: {{ .Release.Namespace }} + labels: + {{- include "livycluster.historyLabels" . | nindent 4 }} +spec: + serviceName: {{ include "livycluster.fullname" . }}-history + replicas: 1 + selector: + matchLabels: + {{- include "livycluster.historySelectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "livycluster.historySelectorLabels" . | nindent 8 }} + spec: + # Spark History Server requires a resolvable Hadoop user on local K8s clusters + # that do not provide proper Unix/Hadoop identity mapping. + securityContext: + runAsUser: 0 + containers: + - name: spark-history-server + image: "{{ .Values.image.spark.repository }}:{{ include "livycluster.sparkImageTag" . }}" + imagePullPolicy: {{ .Values.image.spark.pullPolicy }} + env: + - name: HADOOP_USER_NAME + value: root + - name: SPARK_HISTORY_OPTS + value: "-Dspark.history.fs.logDirectory=file:///tmp/livy/store" + - name: SPARK_NO_DAEMONIZE + value: "true" + command: + - /opt/spark/sbin/start-history-server.sh + ports: + - containerPort: 18080 + volumeMounts: + - name: spark-history-data + mountPath: /tmp/livy/store + volumes: + - name: spark-history-data + {{- if .Values.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.persistence.existingClaim | default (include "livycluster.fullname" .) }} + {{- else }} + emptyDir: {} + {{- end }} diff --git a/dev/helmchart/templates/storageclass.yaml b/dev/helmchart/templates/storageclass.yaml new file mode 100644 index 000000000..04d302ec3 --- /dev/null +++ b/dev/helmchart/templates/storageclass.yaml @@ -0,0 +1,48 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- $name := include "livycluster.name" . -}} +{{- $chart := include "livycluster.chart" . -}} +{{- $releaseName := .Release.Name -}} +{{- $releaseService := .Release.Service -}} +{{- range .Values.storageClasses }} +--- +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: {{ .name }} + labels: + app.kubernetes.io/name: {{ $name }} + helm.sh/chart: {{ $chart }} + app.kubernetes.io/instance: {{ $releaseName }} + app.kubernetes.io/managed-by: {{ $releaseService }} +{{- if .default }} + annotations: + storageclass.kubernetes.io/is-default-class: "true" +{{- end }} +provisioner: {{ .provisioner }} +{{- if .parameters }} +parameters: +{{ toYaml .parameters | indent 2 }} +{{- end }} +{{- if .reclaimPolicy }} +reclaimPolicy: {{ .reclaimPolicy }} +{{- end }} +{{- if .volumeBindingMode }} +volumeBindingMode: {{ .volumeBindingMode }} +{{- end }} +{{- end }} diff --git a/dev/helmchart/test/validate.sh b/dev/helmchart/test/validate.sh new file mode 100755 index 000000000..949e654da --- /dev/null +++ b/dev/helmchart/test/validate.sh @@ -0,0 +1,133 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +set -euo pipefail + +CHART_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +VALUES_FILE="${CHART_DIR}/values.yaml" +LICENSE_MARKER="Licensed to the Apache Software Foundation" +TEST_NAMESPACE="default" +TEST_RELEASE="livycluster" + +failures=0 + +echo "Checking ASF license headers in Helm source files..." +while IFS= read -r -d '' file; do + if ! grep -q "${LICENSE_MARKER}" "${file}"; then + echo "Missing license header: ${file}" + failures=$((failures + 1)) + fi +done < <(find "${CHART_DIR}" \ + \( -path "${CHART_DIR}/templates/*" -o -name "Chart.yaml" -o -name "values.yaml" \) \ + \( -name "*.yaml" -o -name "*.tpl" \) -print0) + +echo "Checking version consistency in values.yaml..." +for key in livyVersion scalaBinaryVersion sparkVersion clusterHost; do + if ! grep -q "^${key}:" "${VALUES_FILE}"; then + echo "Missing required values key: ${key}" + failures=$((failures + 1)) + fi +done + +spark_version="$(grep '^sparkVersion:' "${VALUES_FILE}" | awk '{print $2}' | tr -d '"')" + +if ! grep -Fq 'charts/*.tgz' "${CHART_DIR}/.gitignore"; then + echo "Expected dev/helmchart/.gitignore to exclude charts/*.tgz" + failures=$((failures + 1)) +fi + +repo_root="$(git -C "${CHART_DIR}" rev-parse --show-toplevel 2>/dev/null || true)" +if [[ -n "${repo_root}" ]] && git -C "${repo_root}" ls-files 'dev/helmchart/charts/*.tgz' | grep -q .; then + echo "Committed chart archives found under dev/helmchart/charts; run helm dependency build locally instead" + failures=$((failures + 1)) +fi + +echo "Checking rendered manifest contract..." +if command -v helm >/dev/null 2>&1; then + if [[ ! -f "${CHART_DIR}/charts/ingress-nginx-4.12.1.tgz" ]] || \ + [[ ! -f "${CHART_DIR}/charts/cert-manager-v1.16.2.tgz" ]]; then + echo "Fetching chart dependencies..." + helm dependency build "${CHART_DIR}" + fi + + rendered="$(mktemp)" + helm template "${TEST_RELEASE}" "${CHART_DIR}" \ + --namespace "${TEST_NAMESPACE}" \ + --set ingress-nginx.enabled=false \ + --set cert-manager.enabled=false \ + > "${rendered}" + + for expected in \ + "livy.server.kubernetes.ingress.host=my-cluster.example.com" \ + "livy.server.kubernetes.grafana.loki.enabled=false" \ + "livy.file.local-dir-whitelist=/opt/jars" \ + "log4j.rootCategory=INFO, console" \ + "spark.kubernetes.namespace=${TEST_NAMESPACE}" \ + "spark.driver.cores=1" \ + "spark.driver.memory=1g" \ + "spark.kubernetes.container.image=your_repository/spark-py:v${spark_version}"; do + if ! grep -q "${expected}" "${rendered}"; then + echo "Rendered manifest missing expected config: ${expected}" + failures=$((failures + 1)) + fi + done + + expected_sa="${TEST_RELEASE}" + if ! grep -q "serviceAccountName: ${expected_sa}" "${rendered}"; then + echo "Rendered Livy StatefulSet missing serviceAccountName: ${expected_sa}" + failures=$((failures + 1)) + fi + if ! grep -q "kind: ServiceAccount" "${rendered}" || ! grep -q "name: ${expected_sa}" "${rendered}"; then + echo "Rendered manifest missing ServiceAccount: ${expected_sa}" + failures=$((failures + 1)) + fi + + if ! grep -q 'spark-examples_{{ .Values.scalaBinaryVersion }}-{{ .Values.sparkVersion }}.jar' \ + "${CHART_DIR}/templates/_helpers.tpl"; then + echo "Spark examples JAR helper does not template scalaBinaryVersion and sparkVersion" + failures=$((failures + 1)) + fi + + expected_config_map="${TEST_RELEASE}-config" + if ! grep -q "name: ${expected_config_map}" "${rendered}"; then + echo "Rendered manifest missing ConfigMap: ${expected_config_map}" + failures=$((failures + 1)) + fi + + expected_history="${TEST_RELEASE}-history" + if ! grep -q "name: ${expected_history}" "${rendered}"; then + echo "Rendered manifest missing history StatefulSet: ${expected_history}" + failures=$((failures + 1)) + fi + + rm -f "${rendered}" + + echo "Running helm lint..." + helm lint "${CHART_DIR}" \ + --set ingress-nginx.enabled=false \ + --set cert-manager.enabled=false +else + echo "helm not installed; skipping helm template and lint checks" +fi + +if [[ "${failures}" -gt 0 ]]; then + echo "${failures} validation check(s) failed" + exit 1 +fi + +echo "Helm chart validation passed" diff --git a/dev/helmchart/values.yaml b/dev/helmchart/values.yaml new file mode 100644 index 000000000..d47232685 --- /dev/null +++ b/dev/helmchart/values.yaml @@ -0,0 +1,134 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Version alignment with pom.xml when built using: +# mvn -Pthriftserver -Pscala-2.12 -Pspark3 package +livyVersion: "1.0.0-SNAPSHOT" +scalaBinaryVersion: "2.12" +sparkVersion: "3.5.6" + +# Hostname used for ingress, TLS, livy.conf, and /etc/hosts. +clusterHost: my-cluster.example.com + +# Livy configuration surfaced through the chart ConfigMap. +livy: + localDirWhitelist: "/opt/jars" + logLevel: INFO + +image: + livy: + repository: your_repository/livy + # Leave empty to default to spark (e.g. spark3.5.6). + tag: "" + pullPolicy: IfNotPresent + spark: + repository: your_repository/spark-py + # Leave empty to default to v (e.g. v3.5.6). + tag: "" + pullPolicy: IfNotPresent + +imagePullSecrets: [] + +debug: + enabled: true + port: 9010 + +spark: + driverCores: "1" + driverMemory: "1g" + +tags: + ingress: true + +clusterIssuers: + - name: selfsigning-issuer + spec: + selfSigned: {} + +defaultCertificate: + enabled: true + issuer: selfsigning-issuer + issuerKind: ClusterIssuer + secretName: ingress-default-tls + +cert-manager: + enabled: true + installCRDs: true + ingressShim: + defaultIssuerName: selfsigning-issuer + defaultIssuerKind: ClusterIssuer + +ingress-nginx: + enabled: true + controller: + allowSnippetAnnotations: true + # Set default-ssl-certificate at install time, for example: + # --set ingress-nginx.controller.extraArgs.default-ssl-certificate=livy-dev/ingress-default-tls + extraArgs: {} + service: + loadBalancerIP: 127.0.0.1 + loadBalancerSourceRanges: [] + +grafana: + loki: + enabled: false + url: "http://localhost:3000" + lokiDatasource: loki + timeRange: 6h + +nameOverride: "" +fullnameOverride: "" + +rbac: + enabled: true + serviceAccounts: [] + roles: [] + bindings: [] + +service: + type: ClusterIP + port: 80 + name: "" + +ingress: + enabled: true + annotations: + kubernetes.io/tls-acme: "true" + nginx.ingress.kubernetes.io/rewrite-target: /$1 + path: /livy/?(.*) + tls: + secretName: ingress-default-tls + +persistence: + enabled: true + accessMode: ReadWriteOnce + size: 1Gi + annotations: {} + existingClaim: "" + subPath: "" + +storageClasses: [] + +resources: {} + +nodeSelector: {} + +affinity: {} + +tolerations: [] + +global: {}