diff --git a/docs/pages/kubernetes-access/helm/reference.mdx b/docs/pages/kubernetes-access/helm/reference.mdx
index d0a76b3b4ef14..ec5a03ea8c816 100644
--- a/docs/pages/kubernetes-access/helm/reference.mdx
+++ b/docs/pages/kubernetes-access/helm/reference.mdx
@@ -1326,6 +1326,79 @@ These labels can then be used with Teleport's RBAC policies to define access rul
+## `storage`
+
+### `storage.enabled`
+
+| Type | Default value |
+| - | - |
+| `bool` | `false` |
+
+Enables the creation of a Kubernetes persistent volume to hold Teleport agent state.
+
+[Kubernetes reference](https://kubernetes.io/docs/concepts/storage/persistent-volumes/)
+
+
+
+ ```yaml
+ storage:
+ enabled: true
+ ```
+
+
+ ```bash
+ --set storage.enabled=true
+ ```
+
+
+
+### `storage.storageClassName`
+
+| Type | Default value |
+| - | - |
+| `string` | `nil` |
+
+The storage class name the persistent volume should use when creating persistent volume claims. The provided storage class
+name needs to exist on the Kubernetes cluster for Teleport to use.
+
+[Kubernetes reference](https://kubernetes.io/docs/concepts/storage/storage-classes/)
+
+
+
+ ```yaml
+ storage:
+ storageClassName: teleport-storage-class
+ ```
+
+
+ ```bash
+ --set storage.storageClassName=teleport-storage-class
+ ```
+
+
+
+### `storage.requests`
+
+| Type | Default value |
+| - | - |
+| `string` | `128Mi` |
+
+The size of persistent volume to create.
+
+
+
+ ```yaml
+ storage:
+ requests: 128Mi
+ ```
+
+
+ ```bash
+ --set storage.requests=128Mi
+ ```
+
+
+
## `image`
| Type | Default value |
@@ -1554,6 +1627,32 @@ Kubernetes affinity to set for pod assignments.
+## `nodeSelector`
+
+| Type | Default value |
+| - | - |
+| `object` | `{}` |
+
+`nodeSelector` can be used to add a map of key-value pairs to constrain the nodes the agent pods will run on.
+
+[Kubernetes reference](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/)
+
+
+
+ ```yaml
+ nodeSelector:
+ role: node
+ region: us-east
+ ```
+
+
+ ```bash
+ --set nodeSelector.role=node \
+ --set nodeSelector.region=us-east
+ ```
+
+
+
## `annotations.config`
| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent |
diff --git a/examples/chart/teleport-kube-agent/.lint/node-selector.yaml b/examples/chart/teleport-kube-agent/.lint/node-selector.yaml
new file mode 100644
index 0000000000000..a9f3d5c4a9652
--- /dev/null
+++ b/examples/chart/teleport-kube-agent/.lint/node-selector.yaml
@@ -0,0 +1,5 @@
+authToken: auth-token
+proxyAddr: proxy.example.com:3080
+kubeClusterName: test-kube-cluster-name
+nodeSelector:
+ gravitational.io/k8s-role: node
diff --git a/examples/chart/teleport-kube-agent/.lint/stateful.yaml b/examples/chart/teleport-kube-agent/.lint/stateful.yaml
new file mode 100644
index 0000000000000..5424307ed38b8
--- /dev/null
+++ b/examples/chart/teleport-kube-agent/.lint/stateful.yaml
@@ -0,0 +1,6 @@
+authToken: auth-token
+proxyAddr: proxy.example.com:3080
+kubeClusterName: test-kube-cluster-name
+storage:
+ enabled: true
+ storageClassName: "aws-gp2"
diff --git a/examples/chart/teleport-kube-agent/README.md b/examples/chart/teleport-kube-agent/README.md
index 9f68582acf36a..d77dd51fc8846 100644
--- a/examples/chart/teleport-kube-agent/README.md
+++ b/examples/chart/teleport-kube-agent/README.md
@@ -9,13 +9,16 @@ with an existing Teleport cluster:
To use it, you will need:
- an existing Teleport cluster (at least proxy and auth services)
- a reachable proxy endpoint (`$PROXY_ENDPOINT` e.g. `teleport.example.com:3080` or `teleport.example.com:443`)
-- a reachable reverse tunnel port on the proxy (e.g. `teleport.example.com:3024`). The address is automatically retrieved from the Teleport proxy configuration.
-- a [static join
- token](https://goteleport.com/teleport/docs/admin-guide/#adding-nodes-to-the-cluster)
- for this Teleport cluster (`$JOIN_TOKEN`)
- - this chart does not currently support dynamic join tokens; please [file an
- issue](https://github.com/gravitational/teleport/issues/new?labels=type%3A+feature+request&template=feature_request.md)
- if you require support for dynamic tokens
+- a reachable reverse tunnel port on the proxy (e.g. `teleport.example.com:3024`). The address is automatically
+ retrieved from the Teleport proxy configuration.
+- either a static or dynamic join token for the Teleport Cluster
+ - a [static join token](https://goteleport.com/teleport/docs/admin-guide/#adding-nodes-to-the-cluster)
+ for this Teleport cluster (`$JOIN_TOKEN`) is used by default.
+ - optionally a [dynamic join token](https://goteleport.com/teleport/docs/admin-guide/#adding-nodes-to-the-cluster) can
+ be used on Kubernetes clusters that support persistent volumes. Set `storage.enabled=true` and
+ `storage.storageClassName=` in the helm configuration to use persistent
+ volumes.
+
## Combining roles
diff --git a/examples/chart/teleport-kube-agent/templates/deployment.yaml b/examples/chart/teleport-kube-agent/templates/deployment.yaml
index dc975359019b0..efc24ef8077ed 100644
--- a/examples/chart/teleport-kube-agent/templates/deployment.yaml
+++ b/examples/chart/teleport-kube-agent/templates/deployment.yaml
@@ -1,3 +1,8 @@
+#
+# Warning to maintainers, any changes to this file that are not specific to the Deployment need to also be duplicated
+# in the statefulset.yaml file.
+#
+{{- if not .Values.storage.enabled }}
{{- if .Values.teleportVersionOverride }}
{{- $_ := set . "teleportVersion" .Values.teleportVersionOverride }}
{{- else }}
@@ -65,6 +70,10 @@ spec:
{{- toYaml .Values.extraVolumeMounts | nindent 8 }}
{{- end }}
{{- end }}
+ {{- if .Values.nodeSelector }}
+ nodeSelector:
+ {{- toYaml .Values.nodeSelector | nindent 8 }}
+ {{- end }}
containers:
- name: "teleport"
image: "{{ if .Values.enterprise }}{{ .Values.enterpriseImage }}{{ else }}{{ .Values.image }}{{ end }}:{{ .teleportVersion }}"
@@ -128,3 +137,4 @@ spec:
{{- toYaml .Values.extraVolumes | nindent 6 }}
{{- end }}
serviceAccountName: {{ .Values.serviceAccountName | default .Release.Name }}
+{{- end }}
\ No newline at end of file
diff --git a/examples/chart/teleport-kube-agent/templates/statefulset.yaml b/examples/chart/teleport-kube-agent/templates/statefulset.yaml
new file mode 100644
index 0000000000000..1fcc4f008e599
--- /dev/null
+++ b/examples/chart/teleport-kube-agent/templates/statefulset.yaml
@@ -0,0 +1,144 @@
+#
+# Warning to maintainers, any changes to this file that are not specific to the StatefulSet need to also be duplicated
+# in the deployment.yaml file.
+#
+{{- if .Values.storage.enabled }}
+{{- if .Values.teleportVersionOverride }}
+ {{- $_ := set . "teleportVersion" .Values.teleportVersionOverride }}
+{{- else }}
+ {{- $_ := set . "teleportVersion" .Chart.Version }}
+{{- end }}
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+ name: {{ .Release.Name }}
+ namespace: {{ .Release.Namespace }}
+ labels:
+ app: {{ .Release.Name }}
+spec:
+ serviceName: {{ .Release.Name }}
+ replicas: {{ .Values.replicaCount }}
+ selector:
+ matchLabels:
+ app: {{ .Release.Name }}
+ template:
+ metadata:
+ annotations:
+ # ConfigMap checksum, to recreate the pod on config changes.
+ checksum/config: {{ include (print $.Template.BasePath "/config.yaml") . | sha256sum }}
+{{- if .Values.annotations.pod }}
+ {{- toYaml .Values.annotations.pod | nindent 8 }}
+{{- end }}
+ labels:
+ app: {{ .Release.Name }}
+ spec:
+ {{- if .Values.affinity }}
+ affinity:
+ {{- toYaml .Values.affinity | nindent 8 }}
+ {{- end }}
+ {{- if .Values.tolerations }}
+ tolerations:
+ {{- toYaml .Values.tolerations | nindent 6 }}
+ {{- end }}
+{{- if .Values.initContainers }}
+ initContainers: {{- toYaml .Values.initContainers | nindent 6 }}
+ {{- if .Values.resources }}
+ resources:
+ {{- toYaml .Values.resources | nindent 10 }}
+ {{- end }}
+ securityContext:
+ allowPrivilegeEscalation: false
+ capabilities:
+ drop:
+ - all
+ readOnlyRootFilesystem: true
+ runAsNonRoot: true
+ runAsUser: 9807
+ volumeMounts:
+ - mountPath: /etc/teleport
+ name: "config"
+ readOnly: true
+ - mountPath: /etc/teleport-secrets
+ name: "auth-token"
+ readOnly: true
+ - mountPath: /var/lib/teleport
+ name: "data"
+ {{- if .Values.extraVolumeMounts }}
+ {{- toYaml .Values.extraVolumeMounts | nindent 8 }}
+ {{- end }}
+{{- end }}
+ serviceAccountName: {{ .Values.serviceAccountName | default .Release.Name }}
+ {{- if .Values.nodeSelector }}
+ nodeSelector:
+ {{- toYaml .Values.nodeSelector | nindent 8 }}
+ {{- end }}
+ containers:
+ - name: "teleport"
+ image: "{{ if .Values.enterprise }}{{ .Values.enterpriseImage }}{{ else }}{{ .Values.image }}{{ end }}:{{ .teleportVersion }}"
+ args:
+ - "--diag-addr=0.0.0.0:3000"
+ {{- if .Values.insecureSkipProxyTLSVerify }}
+ - "--insecure"
+ {{- end }}
+ securityContext:
+ allowPrivilegeEscalation: false
+ capabilities:
+ drop:
+ - all
+ readOnlyRootFilesystem: true
+ runAsNonRoot: true
+ runAsUser: 9807
+ ports:
+ - name: diag
+ containerPort: 3000
+ protocol: TCP
+ livenessProbe:
+ httpGet:
+ path: /healthz
+ port: diag
+ initialDelaySeconds: 5 # wait 5s for agent to start
+ periodSeconds: 5 # poll health every 5s
+ failureThreshold: 6 # consider agent unhealthy after 30s (6 * 5s)
+ readinessProbe:
+ httpGet:
+ path: /readyz
+ port: diag
+ initialDelaySeconds: 5 # wait 5s for agent to register
+ periodSeconds: 5 # poll health every 5s
+ failureThreshold: 12 # consider agent unhealthy after 60s (12 * 5s)
+{{- if .Values.resources }}
+ resources:
+ {{- toYaml .Values.resources | nindent 10 }}
+{{- end }}
+ volumeMounts:
+ - mountPath: /etc/teleport
+ name: "config"
+ readOnly: true
+ - mountPath: /etc/teleport-secrets
+ name: "auth-token"
+ readOnly: true
+ - mountPath: /var/lib/teleport
+ name: "{{ .Release.Name }}-teleport-data"
+{{- if .Values.extraVolumeMounts }}
+ {{- toYaml .Values.extraVolumeMounts | nindent 8 }}
+{{- end }}
+ volumes:
+ - name: "config"
+ configMap:
+ name: {{ .Release.Name }}
+ - name: "auth-token"
+ secret:
+ secretName: {{ .Values.secretName }}
+{{- if .Values.extraVolumes }}
+ {{- toYaml .Values.extraVolumes | nindent 6 }}
+{{- end }}
+ volumeClaimTemplates:
+ - metadata:
+ name: "{{ .Release.Name }}-teleport-data"
+ spec:
+ accessModes: [ "ReadWriteOnce" ]
+ storageClassName: {{ .Values.storage.storageClassName }}
+ resources:
+ requests:
+ storage: {{ .Values.storage.requests }}
+{{- end }}
\ No newline at end of file
diff --git a/examples/chart/teleport-kube-agent/values.yaml b/examples/chart/teleport-kube-agent/values.yaml
index 8addda419c9df..0f2ce05aa1a34 100644
--- a/examples/chart/teleport-kube-agent/values.yaml
+++ b/examples/chart/teleport-kube-agent/values.yaml
@@ -57,6 +57,26 @@ podSecurityPolicy:
# Labels is a map of key values pairs about this cluster
labels: {}
+################################################################
+# Values that must be provided if using persistent storage for Teleport state.
+#
+# Assigning a persistent volume to Teleport agent allows the agent to store its security association with the Teleport
+# cluster for re-use when the pod is restarted. Without a persistent storage for this state, every time Teleport agent
+# starts it must use the authToken to create a new registration with the cluster. By using the persistent volume the
+# authToken can be routinely rotated without breaking agents' ability to restart, as the token is only used on the first
+# startup. When persistent volumes are enabled, the agent will be deployed as a StatefulSet instead of a Deployment to
+# Kubernetes.
+#
+# Fields:
+# enabled: Set to true to enable the use of StatefulSets and Persistent volumes.
+# storageClassName: The name of the kubernetes storage class to use when creating volumes. See https://kubernetes.io/docs/concepts/storage/storage-classes/
+# requests: The size of the volume to request from the persistent storage system
+################################################################
+storage:
+ enabled: false
+ storageClassName: ""
+ requests: 128Mi
+
################################################################
# Values that you shouldn't need to change.
################################################################
@@ -88,6 +108,10 @@ logLevel: INFO
# https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity
affinity: {}
+# nodeSelector to apply for pod assignment
+# https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/
+nodeSelector: {}
+
# Kubernetes annotations to apply
# https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
annotations: