From cc593070ed84e22a96e59005b9480afbfae53083 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=B6ran=20P=C3=B6hner?=
 <10630407+groundhog2k@users.noreply.github.com>
Date: Tue, 3 Jan 2023 15:11:00 +0100
Subject: [PATCH] Consider supporting etcd #1187 (#1192)

* First etcd draft

* Added initialization scripts

* Cleaned script

* Added monitoring and finalized chart description

* Consider supporting etcd
Fixes #1187

* Fixed missing maintainers
---
 charts/etcd/.helmignore                     |  23 +++
 charts/etcd/Chart.yaml                      |  12 ++
 charts/etcd/README.md                       | 143 ++++++++++++++
 charts/etcd/RELEASENOTES.md                 |   6 +
 charts/etcd/templates/_helpers.tpl          |  62 ++++++
 charts/etcd/templates/scripts.yaml          |  67 +++++++
 charts/etcd/templates/service-internal.yaml |  18 ++
 charts/etcd/templates/service.yaml          |  41 ++++
 charts/etcd/templates/serviceaccount.yaml   |  12 ++
 charts/etcd/templates/servicemonitor.yaml   |  34 ++++
 charts/etcd/templates/statefulset.yaml      | 193 ++++++++++++++++++
 charts/etcd/values.yaml                     | 206 ++++++++++++++++++++
 12 files changed, 817 insertions(+)
 create mode 100644 charts/etcd/.helmignore
 create mode 100644 charts/etcd/Chart.yaml
 create mode 100644 charts/etcd/README.md
 create mode 100644 charts/etcd/RELEASENOTES.md
 create mode 100644 charts/etcd/templates/_helpers.tpl
 create mode 100644 charts/etcd/templates/scripts.yaml
 create mode 100644 charts/etcd/templates/service-internal.yaml
 create mode 100644 charts/etcd/templates/service.yaml
 create mode 100644 charts/etcd/templates/serviceaccount.yaml
 create mode 100644 charts/etcd/templates/servicemonitor.yaml
 create mode 100644 charts/etcd/templates/statefulset.yaml
 create mode 100644 charts/etcd/values.yaml

diff --git a/charts/etcd/.helmignore b/charts/etcd/.helmignore
new file mode 100644
index 00000000..0e8a0eb3
--- /dev/null
+++ b/charts/etcd/.helmignore
@@ -0,0 +1,23 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*.orig
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
+.vscode/
diff --git a/charts/etcd/Chart.yaml b/charts/etcd/Chart.yaml
new file mode 100644
index 00000000..148981a7
--- /dev/null
+++ b/charts/etcd/Chart.yaml
@@ -0,0 +1,12 @@
+apiVersion: v2
+name: etcd
+description: A Helm chart for etcd on Kubernetes
+
+type: application
+
+maintainers:
+  - name: groundhog2k
+
+version: 0.1.0
+
+appVersion: "v3.5.6"
diff --git a/charts/etcd/README.md b/charts/etcd/README.md
new file mode 100644
index 00000000..ce36a1ee
--- /dev/null
+++ b/charts/etcd/README.md
@@ -0,0 +1,143 @@
+# Etcd
+
+![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v3.5.6](https://img.shields.io/badge/AppVersion-v3.5.6-informational?style=flat-square)
+
+## Changelog
+
+see [RELEASENOTES.md](RELEASENOTES.md)
+
+A Helm chart for a Etcd HA-cluster on Kubernetes
+
+## TL;DR
+
+```bash
+helm repo add groundhog2k https://groundhog2k.github.io/helm-charts/
+helm install my-release groundhog2k/etcd
+```
+
+## Introduction
+
+This chart uses the original [Etcd image from Quay.io](https://quay.io/repository/coreos/etcd) to deploy a stateful Etcd cluster in Kubernetes.
+
+It fully supports deployment of the multi-architecture docker image.
+
+## Prerequisites
+
+- Kubernetes 1.12+
+- Helm 3.x
+- PV provisioner support in the underlying infrastructure
+
+## Installing the Chart
+
+To install the chart with the release name `my-release`:
+
+```bash
+helm install my-release groundhog2k/etcd
+```
+
+## Uninstalling the Chart
+
+To uninstall/delete the `my-release` deployment:
+
+```bash
+helm uninstall my-release
+```
+
+## Common parameters
+
+| Key | Type | Default | Description |
+|-----|------|---------|-------------|
+| fullnameOverride | string | `""` | Fully override the deployment name |
+| nameOverride | string | `""` | Partially override the deployment name |
+
+## Deployment parameters
+
+| Key | Type | Default | Description |
+|-----|------|---------|-------------|
+| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy |
+| image.registry | string | `"quay.io/coreos"` | Image registry |
+| image.repository | string | `"etcd"` | Image name |
+| image.tag | string | `""` | Image tag |
+| imagePullSecrets | list | `[]` | Image pull secrets |
+| extraInitContainers | list | `[]` | Extra init containers |
+| extaContainers | list | `[]` | Extra containers for usage as sidecars |
+| startupProbe | object | `see values.yaml` | Startup probe configuration |
+| livenessProbe | object | `see values.yaml` | Liveness probe configuration |
+| readinessProbe | object | `see values.yaml` | Readiness probe configuration |
+| customStartupProbe | object | `{}` | Custom startup probe (overwrites default startup probe configuration) |
+| customLivenessProbe | object | `{}` | Custom liveness probe (overwrites default liveness probe configuration) |
+| customReadinessProbe | object | `{}` | Custom readiness probe (overwrites default readiness probe configuration) |
+| resources | object | `{}` | Resource limits and requests |
+| nodeSelector | object | `{}` | Deployment node selector |
+| podAnnotations | object | `{}` | Additional pod annotations |
+| podSecurityContext | object | `see values.yaml` | Pod security context |
+| securityContext | object | `see values.yaml` | Container security context |
+| env | list | `[]` | Additional container environmment variables |
+| args | list | `[]` | Additional container command arguments |
+| rbac.create | bool | `true` | Enable creation of RBAC |
+| serviceAccount.annotations | object | `{}` | Additional service account annotations |
+| serviceAccount.create | bool | `true` | Enable service account creation |
+| serviceAccount.name | string | `""` | Optional name of the service account |
+| affinity | object | `{}` | Affinity for pod assignment |
+| tolerations | list | `[]` | Tolerations for pod assignment |
+| podManagementPolicy | string | `"Parallel"` | Pod management policy |
+| updateStrategyType | string | `"RollingUpdate"` | Pod update strategy |
+| replicas | int | `1` | Number of replicas (Due to the nature of etcd cluster initialization this value must be set before deploying the cluster) |
+| revisionHistoryLimit | int | `nil` | Maximum number of revisions maintained in revision history
+| podDisruptionBudget | object | `{}` | Pod disruption budget |
+| podDisruptionBudget.minAvailable | int | `nil` | Minimum number of pods that must be available after eviction |
+| podDisruptionBudget.maxUnavailable | int | `nil` | Maximum number of pods that can be unavailable after eviction |
+| clusterDomain | string | `"cluster.local"` | Kubernetes cluster domain (DNS) suffix |
+
+## Service parameters
+
+| Key | Type | Default | Description |
+|-----|------|---------|-------------|
+| service.type | string | `"ClusterIP"` | Service type |
+| service.clusterIP | string | `nil` | The cluster ip address (only relevant for type LoadBalancer or NodePort) |
+| service.loadBalancerIP | string | `nil` | The load balancer ip address (only relevant for type LoadBalancer) |
+| service.client.port | int | `2379` | Client service port |
+| service.client.nodePort | int | `nil` | Service node port (only relevant for type LoadBalancer or NodePort)|
+| service.peer.port | int | `2380` | Peer service port |
+| service.peer.nodePort | int | `nil` | Service node port (only relevant for type LoadBalancer or NodePort)|
+| service.annotations | object | `{}` | Additional service annotations |
+
+## Service monitor parameters
+
+| Key | Type | Default | Description |
+|-----|------|---------|-------------|
+| serviceMonitor.enabled | bool | `false` | Enable service monitor |
+| serviceMonitor.additionalLabels | object | `{}` | Additional labels for the service monitor object |
+| serviceMonitor.annotations | object | `{}` | Annotations for the service monitor object |
+| serviceMonitor.interval | Duration | `nil` | Scrape interval for prometheus |
+| serviceMonitor.scrapeTimeout | Duration | `nil` | Scrape timeout value |
+| serviceMonitor.extraEndpointParameters | object | `nil` | Extra parameters rendered to the [service monitor endpoint](https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api.md#endpoint) |
+| serviceMonitor.extraParameters | object | `nil` | Extra parameters rendered to the [service monitor object](https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api.md#servicemonitorspec) |
+
+## Storage parameters
+
+| Key | Type | Default | Description |
+|-----|------|---------|-------------|
+| storage.accessModes[0] | string | `"ReadWriteOnce"` | Storage access mode |
+| storage.volumeName | string | `"etcd-data"` | Internal volume name and prefix of a created PVC |
+| storage.persistentVolumeClaimName | string | `nil` | PVC name when existing storage volume should be used |
+| storage.requestedSize | string | `nil` | Size for new PVC, when no existing PVC is used |
+| storage.className | string | `nil` | Storage class name |
+
+## Etcd settings
+
+| Key | Type | Default | Description |
+|-----|------|---------|-------------|
+| settings.clusterToken | bool | `"etcd-cluster-0"` | Unique cluser token |
+| settings.https.enabled | bool | `false` | Enable HTTPS |
+| settings.https.autoTls | bool | `false` | Automatic TLS mode of etcd (TLS certs. created automaically) |
+| settings.shutdownDelay | int | `3` | Delay after termination request to give etcd process time for graceful shutdown |
+  
+## Etcd secrets configuration
+
+| Key | Type | Default | Description |
+|-----|------|---------|-------------|
+| extraSecrets | list | `[]` | A list of additional existing secrets that will be mounted into the container |
+| extraSecrets[].name | string | `nil` | Name of the existing K8s secret |
+| extraSecrets[].mountPath | string | `nil` | Mount path where the secret should be mounted into the container (f.e. /mysecretfolder) |
+| extraEnvSecrets | list | `[]` | A list of existing secrets that will be mounted into the container as environment variables |
diff --git a/charts/etcd/RELEASENOTES.md b/charts/etcd/RELEASENOTES.md
new file mode 100644
index 00000000..a4be679f
--- /dev/null
+++ b/charts/etcd/RELEASENOTES.md
@@ -0,0 +1,6 @@
+# Changelog
+
+| Chart version | App version | Change description |
+| :------------ | :---------- | :----------------- |
+| 0.1.0 | v3.5.6 | Initial version |
+| | | |
diff --git a/charts/etcd/templates/_helpers.tpl b/charts/etcd/templates/_helpers.tpl
new file mode 100644
index 00000000..e36ad5c1
--- /dev/null
+++ b/charts/etcd/templates/_helpers.tpl
@@ -0,0 +1,62 @@
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "etcd.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 "etcd.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 "etcd.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "etcd.labels" -}}
+helm.sh/chart: {{ include "etcd.chart" . }}
+{{ include "etcd.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "etcd.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "etcd.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "etcd.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create }}
+{{- default (include "etcd.fullname" .) .Values.serviceAccount.name }}
+{{- else }}
+{{- default "default" .Values.serviceAccount.name }}
+{{- end }}
+{{- end }}
diff --git a/charts/etcd/templates/scripts.yaml b/charts/etcd/templates/scripts.yaml
new file mode 100644
index 00000000..b1d6133c
--- /dev/null
+++ b/charts/etcd/templates/scripts.yaml
@@ -0,0 +1,67 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: {{ include "etcd.fullname" . }}-scripts
+  labels:
+    {{- include "etcd.labels" . | nindent 4 }}
+data:
+  startup.sh: |
+    #!/bin/sh
+    {{- $replicaCount := int .Values.replicas }}
+    {{- $etcdFullname := include "etcd.fullname" . }}
+    {{- $etcdInternalServiceName := printf "%s-internal" $etcdFullname }}
+    {{- $initialCluster := list }}
+    {{- $protocol := (or .Values.settings.https.enabled .Values.settings.https.autoTls) | ternary "https" "http" }}
+    {{- $servicefqdn := printf "%s.%s.svc.%s" $etcdInternalServiceName .Release.Namespace .Values.clusterDomain }}
+    echo "Initializing Etcd instance..."
+    export ETCD_DATA_DIR="/data/etcd"
+    export ETCD_NAME="${HOSTNAME}"
+    export ETCD_INITIAL_CLUSTER_TOKEN="{{ .Values.settings.clusterToken }}"
+    export ETCD_INITIAL_CLUSTER_STATE="new"
+    export ETCD_LISTEN_CLIENT_URLS="{{ $protocol }}://0.0.0.0:2379"
+    export ETCD_LISTEN_PEER_URLS="{{ $protocol }}://0.0.0.0:2380"
+    {{- if .Values.serviceMonitor.enabled }}
+    export ETCD_LISTEN_METRICS_URLS="http://0.0.0.0:12379"
+    {{- end }}
+    export ETCD_ADVERTISE_CLIENT_URLS="{{ $protocol }}://${HOSTNAME}.{{ $servicefqdn }}:2379"
+    export ETCD_INITIAL_ADVERTISE_PEER_URLS="{{ $protocol }}://${HOSTNAME}.{{ $servicefqdn }}:2380"
+    {{- range $e, $i := until $replicaCount }}
+    {{- $initialCluster = append $initialCluster (printf "%s-%d=%s://%s-%d.%s:%d" $etcdFullname $i $protocol $etcdFullname $i $servicefqdn 2380) }}
+    {{- end }}
+    export ETCD_INITIAL_CLUSTER="{{ join "," $initialCluster | quote }}"
+    {{- if .Values.settings.https.autoTls }}
+    export ETCD_AUTO_TLS="true"
+    export ETCD_PEER_AUTO_TLS="true"
+    {{- end }}
+    mkdir -p ${ETCD_DATA_DIR}
+    chmod 700 ${ETCD_DATA_DIR}
+    echo "Finished."
+    echo "Starting etcd..."
+    etcd $@ &
+    etcdproc=$!
+    trap "_terminate $etcdproc 15 {{ .Values.settings.shutdownDelay }}" 15
+    trap "_terminate $etcdproc 9 {{ .Values.settings.shutdownDelay }}" 9
+    wait $etcdproc
+
+    # Terminates a child process
+    # $1 - PID of child process
+    # $2 - Kill signal number
+    # $3 - Delay before terminate (leave empty if no delay desired)
+    _terminate() {
+      local childproc=$1
+      local signal=$2
+      local delay=$3
+      log "Terminating entrypoint"
+      etcd
+      kill -s $signal $childproc
+      if [ ! -z "$delay" ]; then
+        log "Waiting $delay seconds before termination..."
+        sleep $delay
+      fi
+
+      log "Bye bye"
+    }
+  
+  healthcheck.sh: |
+    #!/bin/sh
+    etcdctl endpoint health {{ (or .Values.settings.https.enabled .Values.settings.https.autoTls) | ternary "--insecure-skip-tls-verify=true --insecure-transport=false" "" }}
diff --git a/charts/etcd/templates/service-internal.yaml b/charts/etcd/templates/service-internal.yaml
new file mode 100644
index 00000000..679653d1
--- /dev/null
+++ b/charts/etcd/templates/service-internal.yaml
@@ -0,0 +1,18 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ include "etcd.fullname" . }}-internal
+  labels:
+    {{- include "etcd.labels" . | nindent 4 }}
+spec:
+  clusterIP: None
+  publishNotReadyAddresses: true
+  ports:
+    - port: {{ .Values.service.client.port }}
+      targetPort: client
+      name: client
+    - port: {{ .Values.service.peer.port }}
+      targetPort: peer
+      name: peer
+  selector:
+    {{- include "etcd.selectorLabels" . | nindent 4 }}
diff --git a/charts/etcd/templates/service.yaml b/charts/etcd/templates/service.yaml
new file mode 100644
index 00000000..94ebe973
--- /dev/null
+++ b/charts/etcd/templates/service.yaml
@@ -0,0 +1,41 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ include "etcd.fullname" . }}
+  labels:
+    {{- include "etcd.labels" . | nindent 4 }}
+  {{- with .Values.service.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+spec:
+  type: {{ .Values.service.type }}
+  ports:
+    - port: {{ .Values.service.client.port }}
+      targetPort: client
+      name: client
+      {{- if and ( or (eq .Values.service.type "LoadBalancer") (eq .Values.service.type "NodePort") ) (.Values.service.client.nodePort) }}
+      nodePort: {{ .Values.service.client.nodePort }}
+      {{- end }}
+    - port: {{ .Values.service.peer.port }}
+      targetPort: peer
+      name: peer
+      {{- if and ( or (eq .Values.service.type "LoadBalancer") (eq .Values.service.type "NodePort") ) (.Values.service.peer.nodePort) }}
+      nodePort: {{ .Values.service.peer.nodePort }}
+      {{- end }}
+    {{- if .Values.serviceMonitor.enabled }}
+    - port: {{ .Values.service.prometheus.port }}
+      targetPort: prometheus
+      name: prometheus
+      {{- if and ( or (eq .Values.service.type "LoadBalancer") (eq .Values.service.type "NodePort") ) (.Values.service.prometheus.nodePort) }}
+      nodePort: {{ .Values.service.prometheus.nodePort }}
+      {{- end }}
+    {{- end }}
+  {{- if and (eq .Values.service.type "LoadBalancer") (.Values.service.loadBalancerIP) }}
+  loadBalancerIP: {{ .Values.service.loadBalancerIP }}
+  {{- end }}
+  {{- if .Values.service.clusterIP }}
+  clusterIP: {{ .Values.service.clusterIP }}
+  {{- end }}
+  selector:
+    {{- include "etcd.selectorLabels" . | nindent 4 }}
diff --git a/charts/etcd/templates/serviceaccount.yaml b/charts/etcd/templates/serviceaccount.yaml
new file mode 100644
index 00000000..15ce3c8c
--- /dev/null
+++ b/charts/etcd/templates/serviceaccount.yaml
@@ -0,0 +1,12 @@
+{{- if .Values.serviceAccount.create -}}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: {{ include "etcd.serviceAccountName" . }}
+  labels:
+    {{- include "etcd.labels" . | nindent 4 }}
+  {{- with .Values.serviceAccount.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+{{- end }}
diff --git a/charts/etcd/templates/servicemonitor.yaml b/charts/etcd/templates/servicemonitor.yaml
new file mode 100644
index 00000000..4dfd6339
--- /dev/null
+++ b/charts/etcd/templates/servicemonitor.yaml
@@ -0,0 +1,34 @@
+{{- if .Values.serviceMonitor.enabled }}
+apiVersion: monitoring.coreos.com/v1
+kind: ServiceMonitor
+metadata:
+  name: {{ include "etcd.fullname" . }}
+  labels:
+    {{- include "etcd.labels" . | nindent 4 }}
+    {{- with .Values.serviceMonitor.additionalLabels }}
+    {{- toYaml . | nindent 4 }}
+    {{- end }}
+  {{- with .Values.serviceMonitor.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+spec:
+  endpoints:
+    - port: "prometheus"
+      path: "/metrics"
+      {{- if .Values.serviceMonitor.interval }}
+      interval: {{ .Values.serviceMonitor.interval }}
+      {{- end }}
+      {{- if .Values.serviceMonitor.scrapeTimeout }}
+      scrapeTimeout: {{ .Values.serviceMonitor.scrapeTimeout }}
+      {{- end }}
+      {{- with .Values.serviceMonitor.extraEndpointParameters }}
+      {{- toYaml . | nindent 6 }}
+      {{- end }}
+  {{- with .Values.serviceMonitor.extraParameters }}
+  {{- toYaml . | nindent 2 }}
+  {{- end }}
+  selector:
+    matchLabels:
+      {{- include "etcd.selectorLabels" . | nindent 6 }}
+{{- end }}
diff --git a/charts/etcd/templates/statefulset.yaml b/charts/etcd/templates/statefulset.yaml
new file mode 100644
index 00000000..c75a0f03
--- /dev/null
+++ b/charts/etcd/templates/statefulset.yaml
@@ -0,0 +1,193 @@
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+  name: {{ include "etcd.fullname" . }}
+  labels:
+    {{- include "etcd.labels" . | nindent 4 }}
+spec:
+  replicas: {{ .Values.replicas }}
+  {{- if .Values.revisionHistoryLimit }}
+  revisionHistoryLimit: {{ .Values.revisionHistoryLimit }}
+  {{- end }}
+  serviceName: {{ include "etcd.fullname" . }}-internal
+  podManagementPolicy: {{ .Values.podManagementPolicy }}
+  updateStrategy: 
+    type: {{ .Values.updateStrategyType }}
+  selector:
+    matchLabels:
+      {{- include "etcd.selectorLabels" . | nindent 6 }}
+  template:
+    metadata:
+      annotations:
+        checksum/scripts: {{ include (print $.Template.BasePath "/scripts.yaml") . | sha256sum }}
+      {{- with .Values.podAnnotations }}
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      labels:
+        {{- include "etcd.selectorLabels" . | nindent 8 }}
+    spec:
+      {{- with .Values.imagePullSecrets }}
+      imagePullSecrets:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      serviceAccountName: {{ include "etcd.serviceAccountName" . }}
+      securityContext:
+        {{- toYaml .Values.podSecurityContext | nindent 8 }}
+      initContainers: 
+      {{- with .Values.extraInitContainers }}
+      {{- toYaml . | nindent 8 }}
+      {{- end }}
+      containers:
+        - name: {{ .Chart.Name }}
+          securityContext:
+            {{- toYaml .Values.securityContext | nindent 12 }}
+          image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+          imagePullPolicy: {{ .Values.image.pullPolicy }}
+          ports:
+            - name: client
+              containerPort: 2379
+            - name: peer
+              containerPort: 2380
+            {{- if .Values.serviceMonitor.enabled }}
+            - name: prometheus
+              containerPort: 12379
+            {{- end }}
+          {{- if .Values.customStartupProbe }}
+          startupProbe:
+            {{- toYaml .Values.customStartupProbe | nindent 12 }}
+          {{- else }}
+          {{- if .Values.startupProbe.enabled }}
+          startupProbe:
+            exec:
+              command:
+                - /scripts/healthcheck.sh
+          {{- with .Values.startupProbe }}
+            initialDelaySeconds: {{ .initialDelaySeconds }}
+            timeoutSeconds: {{ .timeoutSeconds }}
+            failureThreshold: {{ .failureThreshold }}
+            successThreshold: {{ .successThreshold }}
+            periodSeconds: {{ .periodSeconds }}
+          {{- end }}
+          {{- end }}
+          {{- end }}
+          {{- if .Values.customLivenessProbe }}
+          livenessProbe:
+            {{- toYaml .Values.customLivenessProbe | nindent 12 }}
+          {{- else }}
+          {{- if .Values.livenessProbe.enabled }}              
+          livenessProbe:
+            exec:
+              command:
+                - /scripts/healthcheck.sh
+          {{- with .Values.livenessProbe }}
+            initialDelaySeconds: {{ .initialDelaySeconds }}
+            timeoutSeconds: {{ .timeoutSeconds }}
+            failureThreshold: {{ .failureThreshold }}
+            successThreshold: {{ .successThreshold }}
+            periodSeconds: {{ .periodSeconds }}                
+          {{- end }}
+          {{- end }}
+          {{- end }}
+          {{- if .Values.customReadinessProbe }}
+          readinessProbe:
+            {{- toYaml .Values.customReadinessProbe | nindent 12 }}
+          {{- else }}
+          {{- if .Values.readinessProbe.enabled }}
+          readinessProbe:
+            exec:
+              command:
+                - /scripts/healthcheck.sh
+          {{- with .Values.readinessProbe }}
+            initialDelaySeconds: {{ .initialDelaySeconds }}
+            timeoutSeconds: {{ .timeoutSeconds }}
+            failureThreshold: {{ .failureThreshold }}
+            successThreshold: {{ .successThreshold }}
+            periodSeconds: {{ .periodSeconds }}                
+          {{- end }}
+          {{- end }}
+          {{- end }}
+          {{- with .Values.resources }}
+          resources:
+            {{- toYaml . | nindent 12 }}
+          {{- end }}
+          command:
+            - /scripts/startup.sh
+          {{- if .Values.args }}
+          args:
+            {{- range .Values.args }}
+            - {{ . }}
+            {{- end }}
+          {{- end }}
+          {{- with .Values.env }}
+          env:
+            {{- toYaml . | nindent 12 }}
+          {{- end }}
+          envFrom:
+          {{- range .Values.extraEnvSecrets }}
+            - secretRef:
+                name: {{ . }}
+          {{- end }}
+          volumeMounts:
+            - name: {{ .Values.storage.volumeName }}
+              mountPath: /data
+            - name: tmp
+              mountPath: /tmp
+            - name: scripts
+              mountPath: /scripts
+            {{- range $secret := .Values.extraSecrets }}
+            - name: {{ $secret.name }}
+              mountPath: {{ $secret.mountPath }}
+            {{- end }}
+      {{- with .Values.extraContainers }}
+      {{- 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 }}
+      volumes:
+        - name: tmp
+          emptyDir: {}
+        - name: scripts
+          configMap:
+            name: {{ include "etcd.fullname" . }}-scripts
+            defaultMode: 0555
+        {{- range $secret := .Values.extraSecrets }}
+        - name: {{ $secret.name }}
+          secret:
+            secretName: {{ $secret.name }}
+            defaultMode: 0440
+        {{- end }}
+  {{- if .Values.storage.persistentVolumeClaimName }}
+        - name: {{ .Values.storage.volumeName }}
+          persistentVolumeClaim:
+            claimName: {{ .Values.storage.persistentVolumeClaimName }}
+  {{- else }}
+  {{- if not .Values.storage.requestedSize }}
+        - name: {{ .Values.storage.volumeName }}
+          emptyDir: {}
+  {{- else }}
+  volumeClaimTemplates:
+    - metadata:
+        name: {{ .Values.storage.volumeName }}
+      spec:
+        {{- with .Values.storage.accessModes }}
+        accessModes:
+          {{- toYaml . | nindent 10 }}
+        {{- end }}
+        {{- if .Values.storage.className }}
+        storageClassName: {{ .Values.storage.className }}
+        {{- end }}
+        resources:
+          requests:
+            storage: {{ .Values.storage.requestedSize }}
+  {{- end }}
+  {{- end }}
\ No newline at end of file
diff --git a/charts/etcd/values.yaml b/charts/etcd/values.yaml
new file mode 100644
index 00000000..e6837636
--- /dev/null
+++ b/charts/etcd/values.yaml
@@ -0,0 +1,206 @@
+# Default values for Etcd deployment
+
+## Etcd container image
+image:
+  registry: "quay.io/coreos"
+  repository: "etcd"
+  pullPolicy: IfNotPresent
+  tag: ""
+
+## Pull secrets and name override options
+imagePullSecrets: []
+nameOverride: ""
+fullnameOverride: ""
+
+## Number of etcd replicas in the cluster
+## Due to the nature of etcd cluster initialization this value must be set before deploying the cluster
+## Automatic scaling or manually scaling the etcd cluster after first deployment is not supported
+replicas: 1
+
+## Optional service account
+serviceAccount:
+  # Specifies whether a service account should be created
+  create: false
+  # 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: ""
+
+## Additional pod annotations
+podAnnotations: {}
+
+## Pod management policy
+podManagementPolicy: Parallel
+
+## Pod update strategy
+updateStrategyType: RollingUpdate
+
+## Pod security context uses file system group 999 (postgres)
+podSecurityContext:
+  fsGroup: 999
+
+## Default security options to run PostgreSQL as non-root (postgres user), read only container without privilege escalation
+securityContext:
+  allowPrivilegeEscalation: false
+  privileged: false
+  readOnlyRootFilesystem: true
+  runAsNonRoot: true
+  runAsGroup: 999
+  runAsUser: 999
+
+## Etcd service ports (default: Client port 2379, Peer port 2380)
+service:
+  type: ClusterIP
+  ## Client service port
+  client:
+    port: 2379
+    ## The node port (only relevant for type LoadBalancer or NodePort)
+    nodePort:
+  ## Peer service port
+  peer:
+    port: 2380
+    ## The node port (only relevant for type LoadBalancer or NodePort)
+    nodePort:
+  ## Prometheus service port
+  prometheus:
+    port: 12379
+    ## The node port (only relevant for type LoadBalancer or NodePort)
+    nodePort:
+  ## The cluster ip address (only relevant for type LoadBalancer or NodePort)
+  clusterIP:
+  ## The loadbalancer ip address (only relevant for type LoadBalancer)
+  loadBalancerIP:
+  # Annotations to add to the service
+  annotations: {}
+
+## Service monitor configuration for Prometheus metrics
+serviceMonitor:
+  ## Enable service monitor
+  enabled: false
+  ## Additional labels for the service monitor object
+  additionalLabels: {}
+  ## Annotations for the service monitor object
+  annotations: {}
+  ## The scrape interval for prometheus
+  # interval:
+  ## The scrape timeout value
+  # scrapeTimeout:
+  ## Extra parameters rendered to the service monitor endpoint
+  extraEndpointParameters: {}
+  ## Extra parameters rendered to the service monitor
+  extraParameters: {}
+
+resources: {}
+  # limits:
+  #   cpu: 100m
+  #   memory: 128Mi
+  # requests:
+  #   cpu: 100m
+  #   memory: 128Mi
+
+## Additional node selector
+nodeSelector: {}
+
+tolerations: []
+
+affinity: {}
+
+## Maximum number of revisions maintained in revision history
+revisionHistoryLimit:
+
+## Custom startup probe (overwrites default startup probe)
+customStartupProbe: {}
+
+## Default startup check
+startupProbe:
+  enabled: true
+  initialDelaySeconds: 10
+  timeoutSeconds: 5
+  failureThreshold: 30
+  successThreshold: 1
+  periodSeconds: 10
+
+## Custom liveness probe (overwrites default liveness probe)
+customLivenessProbe: {}
+
+## Default health check
+livenessProbe:
+  enabled: true
+  initialDelaySeconds: 10
+  timeoutSeconds: 5
+  failureThreshold: 3
+  successThreshold: 1
+  periodSeconds: 10
+
+## Custom readiness probe (overwrites default readiness probe)
+customReadinessProbe: {}
+
+## Default readiness probe
+readinessProbe:
+  enabled: true
+  initialDelaySeconds: 10
+  timeoutSeconds: 5
+  failureThreshold: 3
+  successThreshold: 1
+  periodSeconds: 10
+
+## Extra init containers
+extraInitContainers: []
+
+## Extra containers for usage as sidecars
+extraContainers: []
+
+## Additional environment variables
+env: []
+
+## Arguments for the container entrypoint process
+args: []
+
+## A list of existing secrets that will be mounted into the container as environment variables
+extraEnvSecrets: []
+
+## A list of additional existing secrets that will be mounted into the etcd container
+## The mounted files of the secrets can be used for custom configuration options (see https.enabled)
+extraSecrets: []
+    ## Name of the existing K8s secret
+#  - name:
+    ## Mount path where the secret should be mounted into the container (f.e. /mysecretfolder)
+#    mountPath:
+
+## Default Kubernetes cluster domain
+clusterDomain: cluster.local
+
+## Etcd specific settings
+settings:
+  ## Unique cluser token
+  clusterToken: "etcd-cluster-0"
+  ## Configure secure transport
+  ## Certificates must be mounted into the container using `extraSecrets:` or generated automatically using autoTls: true
+  ## Other tls options have to be added manually using environment variables or args: (see https://etcd.io/docs/v3.5/op-guide/clustering/#tls and https://etcd.io/docs/v3.5/op-guide/configuration/)
+  https:
+    ## Enable HTTPS
+    enabled: false
+    ## Automatic TLS mode of etcd (TLS certs. created automaically)
+    autoTls: false
+
+  ## Delay after termination request to give etcd process time for graceful shutdown
+  shutdownDelay: 3
+
+## Storage parameters
+storage:
+  ##  Set persistentVolumenClaimName to reference an existing PVC
+  persistentVolumeClaimName:
+
+  ## Internal volume name and prefix of a created PVC
+  volumeName: "etcd-data"
+
+  ## Alternative set requestedSize to define a size for a dynmaically created PVC
+  requestedSize:
+
+  ## the storage class name
+  className:
+
+  ## Default access mode (ReadWriteOnce)
+  accessModes:
+    - ReadWriteOnce
-- 
GitLab