From 7fb1d0ce732a02db187a2041e53d76092140591e Mon Sep 17 00:00:00 2001 From: Lukasz Zajaczkowski Date: Thu, 24 Sep 2026 14:03:39 +0200 Subject: [PATCH 1/7] ferro tunnel --- .../templates/tunnel-controller.yaml | 84 +++++++++++++++++++ .../templates/tunnel-secret.yaml | 15 ++++ charts/deployment-operator/values.yaml | 27 ++++++ charts/deployment-operator/values.yaml.liquid | 18 ++++ 4 files changed, 144 insertions(+) create mode 100644 charts/deployment-operator/templates/tunnel-controller.yaml create mode 100644 charts/deployment-operator/templates/tunnel-secret.yaml diff --git a/charts/deployment-operator/templates/tunnel-controller.yaml b/charts/deployment-operator/templates/tunnel-controller.yaml new file mode 100644 index 000000000..555a5ceb5 --- /dev/null +++ b/charts/deployment-operator/templates/tunnel-controller.yaml @@ -0,0 +1,84 @@ +{{- if and .Values.tunnelController.enabled .Values.tunnelController.server }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "deployment-operator.fullname" . }}-tunnel-controller + labels: + app.kubernetes.io/component: tunnel-controller + {{- include "deployment-operator.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/component: tunnel-controller + {{- include "deployment-operator.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + app.kubernetes.io/component: tunnel-controller + {{- include "deployment-operator.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "deployment-operator.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: tunnel-controller + image: {{ .Values.tunnelController.image.repository }}:{{ .Values.tunnelController.image.tag | default .Chart.AppVersion }} + imagePullPolicy: {{ .Values.tunnelController.image.pullPolicy }} + args: + - --server={{ .Values.tunnelController.server }} + - --token-file=/var/run/ferrotunnel/token + {{- if .Values.tunnelController.tlsSecret.name }} + - --tls-ca=/var/run/ferrotunnel/tls/ca.crt + - --tls-cert=/var/run/ferrotunnel/tls/tls.crt + - --tls-key=/var/run/ferrotunnel/tls/tls.key + {{- end }} + volumeMounts: + - name: token + mountPath: /var/run/ferrotunnel + readOnly: true + {{- if .Values.tunnelController.tlsSecret.name }} + - name: tls + mountPath: /var/run/ferrotunnel/tls + readOnly: true + {{- end }} + resources: + {{- toYaml .Values.tunnelController.resources | nindent 12 }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + volumes: + - name: token + secret: + secretName: {{ required "tunnelController.tokenSecret.name is required when tunnelController.enabled is true" .Values.tunnelController.tokenSecret.name }} + items: + - key: {{ .Values.tunnelController.tokenSecret.key }} + path: token + {{- if .Values.tunnelController.tlsSecret.name }} + - name: tls + secret: + secretName: {{ .Values.tunnelController.tlsSecret.name }} + items: + - key: {{ .Values.tunnelController.tlsSecret.caKey }} + path: ca.crt + - key: {{ .Values.tunnelController.tlsSecret.certKey }} + path: tls.crt + - key: {{ .Values.tunnelController.tlsSecret.keyKey }} + path: tls.key + {{- 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 }} +{{- end }} diff --git a/charts/deployment-operator/templates/tunnel-secret.yaml b/charts/deployment-operator/templates/tunnel-secret.yaml new file mode 100644 index 000000000..82a50d70a --- /dev/null +++ b/charts/deployment-operator/templates/tunnel-secret.yaml @@ -0,0 +1,15 @@ +{{- if and .Values.tunnelController.enabled .Values.tunnelController.server .Values.tunnelController.token }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.tunnelController.tokenSecret.name }} + labels: + app.kubernetes.io/component: tunnel-controller + {{- include "deployment-operator.labels" . | nindent 4 }} +type: Opaque +data: + token: {{ .Values.tunnelController.token | b64enc }} + ca.crt: {{ .Values.tunnelController.ca }} + tls.crt: {{ .Values.tunnelController.cert }} + tls.key: {{ .Values.tunnelController.key }} +{{- end }} diff --git a/charts/deployment-operator/values.yaml b/charts/deployment-operator/values.yaml index 21a1f1a60..e9ed2436f 100644 --- a/charts/deployment-operator/values.yaml +++ b/charts/deployment-operator/values.yaml @@ -213,3 +213,30 @@ agentk: serviceMonitor: # Specifies whether to create a ServiceMonitor resource for collecting Prometheus metrics enabled: false + +# Separate Deployment from the operator pod. One replica, because a second +# replica would register the same tunnel ids twice. The Deployment is rendered +# only once server is set; Console supplies that address with the token and +# client certificate. +tunnelController: + enabled: true + image: + repository: ghcr.io/pluralsh/ferrotunnel-client + pullPolicy: IfNotPresent + tag: "" + server: "" + # Populated by values.yaml.liquid from deploy-operator service configuration. + # ca, cert, and key are already base64 so the liquid file stays one line. + token: "" + ca: "" + cert: "" + key: "" + tokenSecret: + name: ferrotunnel-token + key: token + tlsSecret: + name: "" + caKey: ca.crt + certKey: tls.crt + keyKey: tls.key + resources: {} diff --git a/charts/deployment-operator/values.yaml.liquid b/charts/deployment-operator/values.yaml.liquid index 0a78c47a4..2e793dedd 100644 --- a/charts/deployment-operator/values.yaml.liquid +++ b/charts/deployment-operator/values.yaml.liquid @@ -25,6 +25,24 @@ agentk: tag: {{ configuration.agentkTag }} {% endif %} +{% if configuration.tunnelToken %} +tunnelController: + enabled: true + server: "{{ configuration.tunnelServer }}" + token: "{{ configuration.tunnelToken }}" + ca: "{{ configuration.tunnelCa }}" + cert: "{{ configuration.tunnelCert }}" + key: "{{ configuration.tunnelKey }}" + tokenSecret: + name: ferrotunnel-client + key: token + tlsSecret: + name: ferrotunnel-client + caKey: ca.crt + certKey: tls.crt + keyKey: tls.key +{% endif %} + {% if configuration.replicas %} replicaCount: {{ configuration.replicas }} {% endif %} From 4c1dc664ae0a7c47168f0d145af05771b40c2e9b Mon Sep 17 00:00:00 2001 From: Lukasz Zajaczkowski Date: Thu, 24 Sep 2026 15:13:11 +0200 Subject: [PATCH 2/7] review --- .../templates/tunnel-controller.yaml | 25 +++++++++++++------ .../templates/tunnel-secret.yaml | 2 +- charts/deployment-operator/values.yaml | 6 ++--- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/charts/deployment-operator/templates/tunnel-controller.yaml b/charts/deployment-operator/templates/tunnel-controller.yaml index 555a5ceb5..5dc56690f 100644 --- a/charts/deployment-operator/templates/tunnel-controller.yaml +++ b/charts/deployment-operator/templates/tunnel-controller.yaml @@ -1,4 +1,9 @@ -{{- if and .Values.tunnelController.enabled .Values.tunnelController.server }} +{{- if and .Values.tunnelController.enabled .Values.tunnelController.server .Values.tunnelController.token }} +{{- $tag := .Values.tunnelController.image.tag | default .Chart.AppVersion }} +{{- $tlsName := .Values.tunnelController.tlsSecret.name }} +{{- if and (not $tlsName) .Values.tunnelController.ca .Values.tunnelController.cert .Values.tunnelController.key }} +{{- $tlsName = .Values.tunnelController.tokenSecret.name }} +{{- end }} apiVersion: apps/v1 kind: Deployment metadata: @@ -11,12 +16,12 @@ spec: selector: matchLabels: app.kubernetes.io/component: tunnel-controller - {{- include "deployment-operator.selectorLabels" . | nindent 6 }} + app.kubernetes.io/instance: {{ .Release.Name }} template: metadata: labels: app.kubernetes.io/component: tunnel-controller - {{- include "deployment-operator.selectorLabels" . | nindent 8 }} + app.kubernetes.io/instance: {{ .Release.Name }} spec: {{- with .Values.imagePullSecrets }} imagePullSecrets: @@ -27,12 +32,16 @@ spec: {{- toYaml .Values.podSecurityContext | nindent 8 }} containers: - name: tunnel-controller - image: {{ .Values.tunnelController.image.repository }}:{{ .Values.tunnelController.image.tag | default .Chart.AppVersion }} + {{- if .Values.global.registry }} + image: "{{ .Values.global.registry }}/ferrotunnel-client:{{ $tag }}" + {{- else }} + image: "{{ .Values.tunnelController.image.repository }}:{{ $tag }}" + {{- end }} imagePullPolicy: {{ .Values.tunnelController.image.pullPolicy }} args: - --server={{ .Values.tunnelController.server }} - --token-file=/var/run/ferrotunnel/token - {{- if .Values.tunnelController.tlsSecret.name }} + {{- if $tlsName }} - --tls-ca=/var/run/ferrotunnel/tls/ca.crt - --tls-cert=/var/run/ferrotunnel/tls/tls.crt - --tls-key=/var/run/ferrotunnel/tls/tls.key @@ -41,7 +50,7 @@ spec: - name: token mountPath: /var/run/ferrotunnel readOnly: true - {{- if .Values.tunnelController.tlsSecret.name }} + {{- if $tlsName }} - name: tls mountPath: /var/run/ferrotunnel/tls readOnly: true @@ -57,10 +66,10 @@ spec: items: - key: {{ .Values.tunnelController.tokenSecret.key }} path: token - {{- if .Values.tunnelController.tlsSecret.name }} + {{- if $tlsName }} - name: tls secret: - secretName: {{ .Values.tunnelController.tlsSecret.name }} + secretName: {{ $tlsName }} items: - key: {{ .Values.tunnelController.tlsSecret.caKey }} path: ca.crt diff --git a/charts/deployment-operator/templates/tunnel-secret.yaml b/charts/deployment-operator/templates/tunnel-secret.yaml index 82a50d70a..04a8780b7 100644 --- a/charts/deployment-operator/templates/tunnel-secret.yaml +++ b/charts/deployment-operator/templates/tunnel-secret.yaml @@ -8,7 +8,7 @@ metadata: {{- include "deployment-operator.labels" . | nindent 4 }} type: Opaque data: - token: {{ .Values.tunnelController.token | b64enc }} + {{ .Values.tunnelController.tokenSecret.key }}: {{ .Values.tunnelController.token | b64enc }} ca.crt: {{ .Values.tunnelController.ca }} tls.crt: {{ .Values.tunnelController.cert }} tls.key: {{ .Values.tunnelController.key }} diff --git a/charts/deployment-operator/values.yaml b/charts/deployment-operator/values.yaml index e9ed2436f..2f499f0d7 100644 --- a/charts/deployment-operator/values.yaml +++ b/charts/deployment-operator/values.yaml @@ -215,9 +215,9 @@ agentk: enabled: false # Separate Deployment from the operator pod. One replica, because a second -# replica would register the same tunnel ids twice. The Deployment is rendered -# only once server is set; Console supplies that address with the token and -# client certificate. +# replica would register the same tunnel ids twice. The Deployment and Secret +# are rendered together once server and token are set. Client certificate +# values turn on the TLS mounts even when tlsSecret.name is empty. tunnelController: enabled: true image: From e1ad4356f7e70d54f376ac778a5782a340a64fb4 Mon Sep 17 00:00:00 2001 From: Lukasz Zajaczkowski Date: Mon, 28 Sep 2026 11:38:48 +0200 Subject: [PATCH 3/7] install tunnel controller from CRD --- .../deployments.plural.sh_tunnelclients.yaml | 101 ++++++++++++++++++ ...ployments.plural.sh_tunnelcontrollers.yaml | 68 ++++++++++++ .../templates/deployment.yaml | 4 + .../deployment-operator/templates/secret.yaml | 8 +- .../templates/tunnel-controller.yaml | 93 ---------------- .../templates/tunnel-secret.yaml | 15 --- .../templates/tunnelcontroller.yaml | 13 +++ charts/deployment-operator/values.yaml | 26 ++--- charts/deployment-operator/values.yaml.liquid | 20 ++-- 9 files changed, 205 insertions(+), 143 deletions(-) create mode 100644 charts/deployment-operator/crds/deployments.plural.sh_tunnelclients.yaml create mode 100644 charts/deployment-operator/crds/deployments.plural.sh_tunnelcontrollers.yaml delete mode 100644 charts/deployment-operator/templates/tunnel-controller.yaml delete mode 100644 charts/deployment-operator/templates/tunnel-secret.yaml create mode 100644 charts/deployment-operator/templates/tunnelcontroller.yaml diff --git a/charts/deployment-operator/crds/deployments.plural.sh_tunnelclients.yaml b/charts/deployment-operator/crds/deployments.plural.sh_tunnelclients.yaml new file mode 100644 index 000000000..a20fd1fc4 --- /dev/null +++ b/charts/deployment-operator/crds/deployments.plural.sh_tunnelclients.yaml @@ -0,0 +1,101 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: tunnelclients.deployments.plural.sh +spec: + group: deployments.plural.sh + names: + kind: TunnelClient + plural: tunnelclients + singular: tunnelclient + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Id registered with the tunnel server + jsonPath: '.spec.tunnelId' + name: Tunnel ID + type: string + - jsonPath: '.status.conditions[?(@.type=="Ready")].status' + name: Ready + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: One local endpoint registered on the FerroTunnel server. + properties: + spec: + description: Desired endpoint for this tunnel. + properties: + tunnelId: + description: DNS label sent to the tunnel server and later used as the HTTP Host. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + upstream: + description: Address the controller can reach from this cluster. + properties: + host: + description: Host or IP of the local service. + minLength: 1 + type: string + port: + description: TCP port of the local service. + format: uint16 + maximum: 65535.0 + minimum: 1.0 + type: integer + required: + - host + - port + type: object + required: + - tunnelId + - upstream + type: object + status: + description: Status of a TunnelClient. `Ready` is True once the tunnel id is registered. + nullable: true + properties: + conditions: + items: + description: Condition contains details for one aspect of the current state of this API Resource. + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + type: string + status: + description: status of the condition, one of True, False, Unknown. + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + required: + - spec + title: TunnelClient + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/deployment-operator/crds/deployments.plural.sh_tunnelcontrollers.yaml b/charts/deployment-operator/crds/deployments.plural.sh_tunnelcontrollers.yaml new file mode 100644 index 000000000..e7e8e6268 --- /dev/null +++ b/charts/deployment-operator/crds/deployments.plural.sh_tunnelcontrollers.yaml @@ -0,0 +1,68 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: tunnelcontrollers.deployments.plural.sh +spec: + group: deployments.plural.sh + names: + kind: TunnelController + listKind: TunnelControllerList + plural: tunnelcontrollers + singular: tunnelcontroller + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.image + name: Image + type: string + - jsonPath: '.status.conditions[?(@.type=="Ready")].status' + name: Ready + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: TunnelController runs one FerroTunnel client Deployment for this cluster. + properties: + spec: + description: Client image. The controller creates the Secret and Deployment. + properties: + image: + description: ferrotunnel-client image, including the tag. Empty defaults to ghcr.io/pluralsh/ferrotunnel-client:master. + type: string + type: object + status: + description: Observed state of TunnelController. + properties: + conditions: + items: + description: Condition contains details for one aspect of the current state of this API Resource. + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + observedGeneration: + format: int64 + type: integer + reason: + type: string + status: + type: string + type: + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/deployment-operator/templates/deployment.yaml b/charts/deployment-operator/templates/deployment.yaml index 574a16bf4..d34b99f9b 100644 --- a/charts/deployment-operator/templates/deployment.yaml +++ b/charts/deployment-operator/templates/deployment.yaml @@ -80,6 +80,10 @@ spec: - -cache-dir={{ .Values.cache.dir }} - -cache-persist-interval={{ .Values.cache.persistInterval }} {{- end }} + {{- if .Values.ferrotunnel.enabled }} + - -ferrotunnel-server={{ .Values.ferrotunnel.server }} + - -ferrotunnel-service-account={{ include "deployment-operator.serviceAccountName" . }} + {{- end }} {{ if .Values.extraArgs }} {{ toYaml .Values.extraArgs | nindent 10 }} {{ end }} diff --git a/charts/deployment-operator/templates/secret.yaml b/charts/deployment-operator/templates/secret.yaml index 51902d09f..b04125a4d 100644 --- a/charts/deployment-operator/templates/secret.yaml +++ b/charts/deployment-operator/templates/secret.yaml @@ -6,4 +6,10 @@ metadata: {{ include "deployment-operator.labels" . | indent 4 }} type: Opaque stringData: - DEPLOY_TOKEN: {{ .Values.secrets.deployToken }} \ No newline at end of file + DEPLOY_TOKEN: {{ .Values.secrets.deployToken }} + {{- if .Values.ferrotunnel.enabled }} + FERROTUNNEL_TOKEN: {{ .Values.ferrotunnel.token | quote }} + FERROTUNNEL_CA: {{ .Values.ferrotunnel.ca | b64dec | quote }} + FERROTUNNEL_CERT: {{ .Values.ferrotunnel.cert | b64dec | quote }} + FERROTUNNEL_KEY: {{ .Values.ferrotunnel.key | b64dec | quote }} + {{- end }} \ No newline at end of file diff --git a/charts/deployment-operator/templates/tunnel-controller.yaml b/charts/deployment-operator/templates/tunnel-controller.yaml deleted file mode 100644 index 5dc56690f..000000000 --- a/charts/deployment-operator/templates/tunnel-controller.yaml +++ /dev/null @@ -1,93 +0,0 @@ -{{- if and .Values.tunnelController.enabled .Values.tunnelController.server .Values.tunnelController.token }} -{{- $tag := .Values.tunnelController.image.tag | default .Chart.AppVersion }} -{{- $tlsName := .Values.tunnelController.tlsSecret.name }} -{{- if and (not $tlsName) .Values.tunnelController.ca .Values.tunnelController.cert .Values.tunnelController.key }} -{{- $tlsName = .Values.tunnelController.tokenSecret.name }} -{{- end }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "deployment-operator.fullname" . }}-tunnel-controller - labels: - app.kubernetes.io/component: tunnel-controller - {{- include "deployment-operator.labels" . | nindent 4 }} -spec: - replicas: 1 - selector: - matchLabels: - app.kubernetes.io/component: tunnel-controller - app.kubernetes.io/instance: {{ .Release.Name }} - template: - metadata: - labels: - app.kubernetes.io/component: tunnel-controller - app.kubernetes.io/instance: {{ .Release.Name }} - spec: - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - serviceAccountName: {{ include "deployment-operator.serviceAccountName" . }} - securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} - containers: - - name: tunnel-controller - {{- if .Values.global.registry }} - image: "{{ .Values.global.registry }}/ferrotunnel-client:{{ $tag }}" - {{- else }} - image: "{{ .Values.tunnelController.image.repository }}:{{ $tag }}" - {{- end }} - imagePullPolicy: {{ .Values.tunnelController.image.pullPolicy }} - args: - - --server={{ .Values.tunnelController.server }} - - --token-file=/var/run/ferrotunnel/token - {{- if $tlsName }} - - --tls-ca=/var/run/ferrotunnel/tls/ca.crt - - --tls-cert=/var/run/ferrotunnel/tls/tls.crt - - --tls-key=/var/run/ferrotunnel/tls/tls.key - {{- end }} - volumeMounts: - - name: token - mountPath: /var/run/ferrotunnel - readOnly: true - {{- if $tlsName }} - - name: tls - mountPath: /var/run/ferrotunnel/tls - readOnly: true - {{- end }} - resources: - {{- toYaml .Values.tunnelController.resources | nindent 12 }} - securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - volumes: - - name: token - secret: - secretName: {{ required "tunnelController.tokenSecret.name is required when tunnelController.enabled is true" .Values.tunnelController.tokenSecret.name }} - items: - - key: {{ .Values.tunnelController.tokenSecret.key }} - path: token - {{- if $tlsName }} - - name: tls - secret: - secretName: {{ $tlsName }} - items: - - key: {{ .Values.tunnelController.tlsSecret.caKey }} - path: ca.crt - - key: {{ .Values.tunnelController.tlsSecret.certKey }} - path: tls.crt - - key: {{ .Values.tunnelController.tlsSecret.keyKey }} - path: tls.key - {{- 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 }} -{{- end }} diff --git a/charts/deployment-operator/templates/tunnel-secret.yaml b/charts/deployment-operator/templates/tunnel-secret.yaml deleted file mode 100644 index 04a8780b7..000000000 --- a/charts/deployment-operator/templates/tunnel-secret.yaml +++ /dev/null @@ -1,15 +0,0 @@ -{{- if and .Values.tunnelController.enabled .Values.tunnelController.server .Values.tunnelController.token }} -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Values.tunnelController.tokenSecret.name }} - labels: - app.kubernetes.io/component: tunnel-controller - {{- include "deployment-operator.labels" . | nindent 4 }} -type: Opaque -data: - {{ .Values.tunnelController.tokenSecret.key }}: {{ .Values.tunnelController.token | b64enc }} - ca.crt: {{ .Values.tunnelController.ca }} - tls.crt: {{ .Values.tunnelController.cert }} - tls.key: {{ .Values.tunnelController.key }} -{{- end }} diff --git a/charts/deployment-operator/templates/tunnelcontroller.yaml b/charts/deployment-operator/templates/tunnelcontroller.yaml new file mode 100644 index 000000000..b4e5a5468 --- /dev/null +++ b/charts/deployment-operator/templates/tunnelcontroller.yaml @@ -0,0 +1,13 @@ +{{- $ferrotunnelTag := .Values.ferrotunnel.image.tag | default "master" }} +apiVersion: deployments.plural.sh/v1alpha1 +kind: TunnelController +metadata: + name: {{ include "deployment-operator.fullname" . }}-ferrotunnel + labels: + {{- include "deployment-operator.labels" . | nindent 4 }} +spec: + {{- if .Values.global.registry }} + image: {{ .Values.global.registry }}/ferrotunnel-client:{{ $ferrotunnelTag }} + {{- else }} + image: {{ .Values.ferrotunnel.image.repository }}:{{ $ferrotunnelTag }} + {{- end }} diff --git a/charts/deployment-operator/values.yaml b/charts/deployment-operator/values.yaml index 2f499f0d7..26baf37ee 100644 --- a/charts/deployment-operator/values.yaml +++ b/charts/deployment-operator/values.yaml @@ -214,29 +214,15 @@ agentk: # Specifies whether to create a ServiceMonitor resource for collecting Prometheus metrics enabled: false -# Separate Deployment from the operator pod. One replica, because a second -# replica would register the same tunnel ids twice. The Deployment and Secret -# are rendered together once server and token are set. Client certificate -# values turn on the TLS mounts even when tlsSecret.name is empty. -tunnelController: - enabled: true +# The chart always creates a TunnelController. values.yaml.liquid fills the server, token, +# and client certificate once Console supplies them, and the controller writes the Secret and Deployment. +ferrotunnel: + enabled: false + server: "" image: repository: ghcr.io/pluralsh/ferrotunnel-client - pullPolicy: IfNotPresent - tag: "" - server: "" - # Populated by values.yaml.liquid from deploy-operator service configuration. - # ca, cert, and key are already base64 so the liquid file stays one line. + tag: master token: "" ca: "" cert: "" key: "" - tokenSecret: - name: ferrotunnel-token - key: token - tlsSecret: - name: "" - caKey: ca.crt - certKey: tls.crt - keyKey: tls.key - resources: {} diff --git a/charts/deployment-operator/values.yaml.liquid b/charts/deployment-operator/values.yaml.liquid index 2e793dedd..cfd39d4ed 100644 --- a/charts/deployment-operator/values.yaml.liquid +++ b/charts/deployment-operator/values.yaml.liquid @@ -20,27 +20,19 @@ global: agentk: config: kasAddress: {{ configuration.kasAddress }} -{% if configuration.agentkTag %} - image: - tag: {{ configuration.agentkTag }} -{% endif %} -{% if configuration.tunnelToken %} -tunnelController: +{% if configuration.tunnelServer and configuration.tunnelToken and configuration.tunnelCa and configuration.tunnelCert and configuration.tunnelKey %} +ferrotunnel: enabled: true server: "{{ configuration.tunnelServer }}" token: "{{ configuration.tunnelToken }}" ca: "{{ configuration.tunnelCa }}" cert: "{{ configuration.tunnelCert }}" key: "{{ configuration.tunnelKey }}" - tokenSecret: - name: ferrotunnel-client - key: token - tlsSecret: - name: ferrotunnel-client - caKey: ca.crt - certKey: tls.crt - keyKey: tls.key +{% endif %} +{% if configuration.agentkTag %} + image: + tag: {{ configuration.agentkTag }} {% endif %} {% if configuration.replicas %} From 0682bbc805a3b4d4db08e834baa5368a955467d7 Mon Sep 17 00:00:00 2001 From: Lukasz Zajaczkowski Date: Mon, 28 Sep 2026 11:44:57 +0200 Subject: [PATCH 4/7] install tunnel controller from CRD --- .../crds/deployments.plural.sh_tunnelcontrollers.yaml | 2 +- charts/deployment-operator/templates/tunnelcontroller.yaml | 2 +- charts/deployment-operator/values.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/charts/deployment-operator/crds/deployments.plural.sh_tunnelcontrollers.yaml b/charts/deployment-operator/crds/deployments.plural.sh_tunnelcontrollers.yaml index e7e8e6268..5472b3ab4 100644 --- a/charts/deployment-operator/crds/deployments.plural.sh_tunnelcontrollers.yaml +++ b/charts/deployment-operator/crds/deployments.plural.sh_tunnelcontrollers.yaml @@ -28,7 +28,7 @@ spec: description: Client image. The controller creates the Secret and Deployment. properties: image: - description: ferrotunnel-client image, including the tag. Empty defaults to ghcr.io/pluralsh/ferrotunnel-client:master. + description: ferrotunnel-client image, including the tag. Empty defaults to ghcr.io/pluralsh/ferrotunnel-client:latest. type: string type: object status: diff --git a/charts/deployment-operator/templates/tunnelcontroller.yaml b/charts/deployment-operator/templates/tunnelcontroller.yaml index b4e5a5468..59fe975c9 100644 --- a/charts/deployment-operator/templates/tunnelcontroller.yaml +++ b/charts/deployment-operator/templates/tunnelcontroller.yaml @@ -1,4 +1,4 @@ -{{- $ferrotunnelTag := .Values.ferrotunnel.image.tag | default "master" }} +{{- $ferrotunnelTag := .Values.ferrotunnel.image.tag | default "latest" }} apiVersion: deployments.plural.sh/v1alpha1 kind: TunnelController metadata: diff --git a/charts/deployment-operator/values.yaml b/charts/deployment-operator/values.yaml index 26baf37ee..77a9a93fa 100644 --- a/charts/deployment-operator/values.yaml +++ b/charts/deployment-operator/values.yaml @@ -221,7 +221,7 @@ ferrotunnel: server: "" image: repository: ghcr.io/pluralsh/ferrotunnel-client - tag: master + tag: latest token: "" ca: "" cert: "" From 215f729d5d47332d2de1ef90521cd03e79a5c2e2 Mon Sep 17 00:00:00 2001 From: Lukasz Zajaczkowski Date: Mon, 28 Sep 2026 12:36:57 +0200 Subject: [PATCH 5/7] install tunnel controller from CRD --- .../crds/deployments.plural.sh_tunnelcontrollers.yaml | 2 +- charts/deployment-operator/templates/tunnelcontroller.yaml | 2 +- charts/deployment-operator/values.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/charts/deployment-operator/crds/deployments.plural.sh_tunnelcontrollers.yaml b/charts/deployment-operator/crds/deployments.plural.sh_tunnelcontrollers.yaml index 5472b3ab4..e7e8e6268 100644 --- a/charts/deployment-operator/crds/deployments.plural.sh_tunnelcontrollers.yaml +++ b/charts/deployment-operator/crds/deployments.plural.sh_tunnelcontrollers.yaml @@ -28,7 +28,7 @@ spec: description: Client image. The controller creates the Secret and Deployment. properties: image: - description: ferrotunnel-client image, including the tag. Empty defaults to ghcr.io/pluralsh/ferrotunnel-client:latest. + description: ferrotunnel-client image, including the tag. Empty defaults to ghcr.io/pluralsh/ferrotunnel-client:master. type: string type: object status: diff --git a/charts/deployment-operator/templates/tunnelcontroller.yaml b/charts/deployment-operator/templates/tunnelcontroller.yaml index 59fe975c9..b4e5a5468 100644 --- a/charts/deployment-operator/templates/tunnelcontroller.yaml +++ b/charts/deployment-operator/templates/tunnelcontroller.yaml @@ -1,4 +1,4 @@ -{{- $ferrotunnelTag := .Values.ferrotunnel.image.tag | default "latest" }} +{{- $ferrotunnelTag := .Values.ferrotunnel.image.tag | default "master" }} apiVersion: deployments.plural.sh/v1alpha1 kind: TunnelController metadata: diff --git a/charts/deployment-operator/values.yaml b/charts/deployment-operator/values.yaml index 77a9a93fa..26baf37ee 100644 --- a/charts/deployment-operator/values.yaml +++ b/charts/deployment-operator/values.yaml @@ -221,7 +221,7 @@ ferrotunnel: server: "" image: repository: ghcr.io/pluralsh/ferrotunnel-client - tag: latest + tag: master token: "" ca: "" cert: "" From 167193fa60044ea4b0f34f3272ee1a840f30aebf Mon Sep 17 00:00:00 2001 From: Lukasz Zajaczkowski Date: Mon, 28 Sep 2026 13:22:18 +0200 Subject: [PATCH 6/7] install tunnel controller from CRD --- charts/deployment-operator/values.yaml.liquid | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/charts/deployment-operator/values.yaml.liquid b/charts/deployment-operator/values.yaml.liquid index cfd39d4ed..e0a4de492 100644 --- a/charts/deployment-operator/values.yaml.liquid +++ b/charts/deployment-operator/values.yaml.liquid @@ -20,6 +20,10 @@ global: agentk: config: kasAddress: {{ configuration.kasAddress }} +{% if configuration.agentkTag %} + image: + tag: {{ configuration.agentkTag }} +{% endif %} {% if configuration.tunnelServer and configuration.tunnelToken and configuration.tunnelCa and configuration.tunnelCert and configuration.tunnelKey %} ferrotunnel: @@ -29,10 +33,14 @@ ferrotunnel: ca: "{{ configuration.tunnelCa }}" cert: "{{ configuration.tunnelCert }}" key: "{{ configuration.tunnelKey }}" -{% endif %} -{% if configuration.agentkTag %} + {% if configuration.ferrotunnelTag %} image: - tag: {{ configuration.agentkTag }} + tag: {{ configuration.ferrotunnelTag }} + {% endif %} +{% elsif configuration.ferrotunnelTag %} +ferrotunnel: + image: + tag: {{ configuration.ferrotunnelTag }} {% endif %} {% if configuration.replicas %} From 1bb4112199e6924168b1feaba1a10f18d9872aae Mon Sep 17 00:00:00 2001 From: Lukasz Zajaczkowski Date: Tue, 29 Sep 2026 10:19:04 +0200 Subject: [PATCH 7/7] update CRD --- .../deployments.plural.sh_tunnelcontrollers.yaml | 11 ++++++----- .../templates/tunnelcontroller.yaml | 14 +++++++++----- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/charts/deployment-operator/crds/deployments.plural.sh_tunnelcontrollers.yaml b/charts/deployment-operator/crds/deployments.plural.sh_tunnelcontrollers.yaml index e7e8e6268..af96b99a7 100644 --- a/charts/deployment-operator/crds/deployments.plural.sh_tunnelcontrollers.yaml +++ b/charts/deployment-operator/crds/deployments.plural.sh_tunnelcontrollers.yaml @@ -13,7 +13,7 @@ spec: scope: Namespaced versions: - additionalPrinterColumns: - - jsonPath: .spec.image + - jsonPath: .spec.template.spec.containers[0].image name: Image type: string - jsonPath: '.status.conditions[?(@.type=="Ready")].status' @@ -25,11 +25,12 @@ spec: description: TunnelController runs one FerroTunnel client Deployment for this cluster. properties: spec: - description: Client image. The controller creates the Secret and Deployment. + description: Optional pod template. The controller creates the Secret and Deployment. properties: - image: - description: ferrotunnel-client image, including the tag. Empty defaults to ghcr.io/pluralsh/ferrotunnel-client:master. - type: string + template: + description: Optional override for the secure default client pod template. Set the ferrotunnel-client image on the tunnel-controller container. + type: object + x-kubernetes-preserve-unknown-fields: true type: object status: description: Observed state of TunnelController. diff --git a/charts/deployment-operator/templates/tunnelcontroller.yaml b/charts/deployment-operator/templates/tunnelcontroller.yaml index b4e5a5468..ce9b0891d 100644 --- a/charts/deployment-operator/templates/tunnelcontroller.yaml +++ b/charts/deployment-operator/templates/tunnelcontroller.yaml @@ -6,8 +6,12 @@ metadata: labels: {{- include "deployment-operator.labels" . | nindent 4 }} spec: - {{- if .Values.global.registry }} - image: {{ .Values.global.registry }}/ferrotunnel-client:{{ $ferrotunnelTag }} - {{- else }} - image: {{ .Values.ferrotunnel.image.repository }}:{{ $ferrotunnelTag }} - {{- end }} + template: + spec: + containers: + - name: tunnel-controller + {{- if .Values.global.registry }} + image: {{ .Values.global.registry }}/ferrotunnel-client:{{ $ferrotunnelTag }} + {{- else }} + image: {{ .Values.ferrotunnel.image.repository }}:{{ $ferrotunnelTag }} + {{- end }}