From efcb8cf8f3e77c48ceec8639edd77456fe08d077 Mon Sep 17 00:00:00 2001 From: Mike Yoder Date: Fri, 11 Feb 2022 16:21:10 -0800 Subject: [PATCH] A Helm chart for a cloudflare tunnel. This is in reference to github issue #569. I saw the example yaml in https://github.com/cloudflare/argo-tunnel-examples/tree/master/named-tunnel-k8s and thought it would be much more useful as a helm chart. And I had to make a helm chart anyway so I could deploy it with Terraform. Here's the result, and figured I'd contribute it back. --- helm/cloudflare-tunnel/.helmignore | 23 ++++ helm/cloudflare-tunnel/Chart.yaml | 22 ++++ helm/cloudflare-tunnel/templates/_helpers.tpl | 51 +++++++++ .../templates/configmap.yaml | 29 +++++ .../templates/deployment.yaml | 102 ++++++++++++++++++ helm/cloudflare-tunnel/templates/secret.yaml | 16 +++ .../templates/serviceaccount.yaml | 12 +++ helm/cloudflare-tunnel/values.yaml | 79 ++++++++++++++ 8 files changed, 334 insertions(+) create mode 100644 helm/cloudflare-tunnel/.helmignore create mode 100644 helm/cloudflare-tunnel/Chart.yaml create mode 100644 helm/cloudflare-tunnel/templates/_helpers.tpl create mode 100644 helm/cloudflare-tunnel/templates/configmap.yaml create mode 100644 helm/cloudflare-tunnel/templates/deployment.yaml create mode 100644 helm/cloudflare-tunnel/templates/secret.yaml create mode 100644 helm/cloudflare-tunnel/templates/serviceaccount.yaml create mode 100644 helm/cloudflare-tunnel/values.yaml diff --git a/helm/cloudflare-tunnel/.helmignore b/helm/cloudflare-tunnel/.helmignore new file mode 100644 index 00000000..0e8a0eb3 --- /dev/null +++ b/helm/cloudflare-tunnel/.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/helm/cloudflare-tunnel/Chart.yaml b/helm/cloudflare-tunnel/Chart.yaml new file mode 100644 index 00000000..c15ca5c6 --- /dev/null +++ b/helm/cloudflare-tunnel/Chart.yaml @@ -0,0 +1,22 @@ +apiVersion: v2 +name: cloudflare-tunnel +description: Creation of a cloudflared deployment - a reverse tunnel for an environment + +# A chart can be either an 'application' or a 'library' chart. +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "2022.1.3" + +# Let's look professional with a Cloudflare icon. +icon: https://developers.cloudflare.com/cloudflare-one/favicon-32x32.png diff --git a/helm/cloudflare-tunnel/templates/_helpers.tpl b/helm/cloudflare-tunnel/templates/_helpers.tpl new file mode 100644 index 00000000..9d856240 --- /dev/null +++ b/helm/cloudflare-tunnel/templates/_helpers.tpl @@ -0,0 +1,51 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "cloudflare-tunnel.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 "cloudflare-tunnel.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 "cloudflare-tunnel.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "cloudflare-tunnel.labels" -}} +helm.sh/chart: {{ include "cloudflare-tunnel.chart" . }} +{{ include "cloudflare-tunnel.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "cloudflare-tunnel.selectorLabels" -}} +app.kubernetes.io/name: {{ include "cloudflare-tunnel.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/helm/cloudflare-tunnel/templates/configmap.yaml b/helm/cloudflare-tunnel/templates/configmap.yaml new file mode 100644 index 00000000..07d52192 --- /dev/null +++ b/helm/cloudflare-tunnel/templates/configmap.yaml @@ -0,0 +1,29 @@ +# This configmap stores the configuration used by cloudflared. +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "cloudflare-tunnel.fullname" . }} +data: + config.yaml: | + # Name of the tunnel you want to run + tunnel: {{ .Values.cloudflare.tunnelName }} + # The location of the secret containing the tunnel credentials + credentials-file: /etc/cloudflared/creds/credentials.json + # General purpose TCP routing for the network + warp-routing: + enabled: {{ .Values.cloudflare.enableWarp }} + # Serves the metrics server under /metrics and the readiness server under /ready + metrics: 0.0.0.0:2000 + # Autoupdates applied in a k8s pod will be lost when the pod is removed or restarted, so + # autoupdate doesn't make sense in Kubernetes. However, outside of Kubernetes, we strongly + # recommend using autoupdate. + no-autoupdate: true + # The `ingress` block tells cloudflared which local service to route incoming + # requests to. For more about ingress rules, see + # https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/configuration/ingress + ingress: + {{- with .Values.cloudflare.ingress }} + {{- toYaml . | nindent 6 }} + {{- end }} + # This rule matches any traffic which didn't match a previous rule, and responds with HTTP 404. + - service: http_status:404 diff --git a/helm/cloudflare-tunnel/templates/deployment.yaml b/helm/cloudflare-tunnel/templates/deployment.yaml new file mode 100644 index 00000000..12f5bcfd --- /dev/null +++ b/helm/cloudflare-tunnel/templates/deployment.yaml @@ -0,0 +1,102 @@ +# Here we deploy cloudflared images. The tunnel credentials are stored in +# a k8s secret, and the configuration is stored in a k8s configmap. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "cloudflare-tunnel.fullname" . }} + labels: + {{- include "cloudflare-tunnel.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "cloudflare-tunnel.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + # These are here so the deployment rolls when the config or secret change. + checksum/configmap: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "cloudflare-tunnel.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "cloudflare-tunnel.fullname" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + args: + - tunnel + # Points cloudflared to the config file, which configures what + # cloudflared will actually do. This file is created by a ConfigMap. + - --config + - /etc/cloudflared/config/config.yaml + - run + livenessProbe: + httpGet: + # Cloudflared has a /ready endpoint which returns 200 if and only if + # it has an active connection to the edge. + path: /ready + port: 2000 + failureThreshold: 1 + initialDelaySeconds: 10 + periodSeconds: 10 + volumeMounts: + - name: config + mountPath: /etc/cloudflared/config + readOnly: true + # Each tunnel has an associated "credentials file" which authorizes machines + # to run the tunnel. cloudflared will read this file from its local filesystem, + # and it'll be stored in a k8s secret. + - name: creds + mountPath: /etc/cloudflared/creds + readOnly: true + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumes: + - name: creds + secret: + secretName: {{ include "cloudflare-tunnel.fullname" . }} + - name: config + configMap: + name: {{ include "cloudflare-tunnel.fullname" . }} + items: + - key: config.yaml + path: config.yaml + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + affinity: + {{- with .Values.affinity }} + {{- toYaml . | nindent 8 }} + {{- else }} + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 10 + podAffinityTerm: + topologyKey: kubernetes.io/hostname + labelSelector: + matchExpressions: + {{- range $k, $v := include "cloudflare-tunnel.selectorLabels" . | fromYaml }} + - key: {{ $k }} + operator: In + values: + - {{ $v }} + {{- end }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/cloudflare-tunnel/templates/secret.yaml b/helm/cloudflare-tunnel/templates/secret.yaml new file mode 100644 index 00000000..139b8a43 --- /dev/null +++ b/helm/cloudflare-tunnel/templates/secret.yaml @@ -0,0 +1,16 @@ +# This credentials secret allows cloudflared to authenticate itself +# to the Cloudflare infrastructure. +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "cloudflare-tunnel.fullname" . }} + labels: + {{- include "cloudflare-tunnel.labels" . | nindent 4 }} +stringData: + credentials.json: |- + { + "AccountTag": {{ .Values.cloudflare.account | quote }}, + "TunnelID": {{ .Values.cloudflare.tunnelId | quote }}, + "TunnelName": {{ .Values.cloudflare.tunnelName | quote }}, + "TunnelSecret": {{ .Values.cloudflare.secret | quote }} + } diff --git a/helm/cloudflare-tunnel/templates/serviceaccount.yaml b/helm/cloudflare-tunnel/templates/serviceaccount.yaml new file mode 100644 index 00000000..ecb321e7 --- /dev/null +++ b/helm/cloudflare-tunnel/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +# Here we create a service account with no privileges to run the +# deployment - just in case the default service account is different. +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "cloudflare-tunnel.fullname" . }} + labels: + {{- include "cloudflare-tunnel.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} diff --git a/helm/cloudflare-tunnel/values.yaml b/helm/cloudflare-tunnel/values.yaml new file mode 100644 index 00000000..0bb75695 --- /dev/null +++ b/helm/cloudflare-tunnel/values.yaml @@ -0,0 +1,79 @@ +# Default values for cloudflare-tunnel. + +# Cloudflare parameters. +cloudflare: + # Your Cloudflare account number. + account: "" + # The name of the tunnel this instance will serve + tunnelName: "" + # The ID of the above tunnel. + tunnelId: "" + # The secret for the tunnel. + secret: "" + # If true, turn on WARP routing for TCP + enableWarp: false + # Define ingress rules for the tunnel. See + # https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/configuration/configuration-file/ingress + ingress: [] + # The first rule proxies traffic to the httpbin sample service named web-server at port 80 + # - hostname: tunnel.example.com + # service: http://web-service:80 + # This rule sends traffic to the built-in hello-world HTTP server. This can help debug connectivity + # issues. If hello.example.com resolves and tunnel.example.com does not, then the problem is + # in the connection from cloudflared to your local service, not from the internet to cloudflared. + # - hostname: hello.example.com + # service: hello_world + +image: + repository: cloudflare/cloudflared + pullPolicy: IfNotPresent + # If supplied, this overrides "appVersion" + tag: "" + +replicaCount: 2 + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # 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: "" + +podAnnotations: {} + +# Security items common to everything in the pod. Here we require that it +# does not run as the user defined in the image, literally named "nonroot". +podSecurityContext: + runAsNonRoot: true + runAsUser: 65532 + +# Security items for one container. We lock it down. +securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +nodeSelector: {} + +tolerations: [] + +# Default affinity is to spread out over nodes; use this to override. +affinity: {}