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.
This commit is contained in:
Mike Yoder 2022-02-11 16:21:10 -08:00
parent ff4cfeda0c
commit efcb8cf8f3
8 changed files with 334 additions and 0 deletions

View File

@ -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/

View File

@ -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

View File

@ -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 }}

View File

@ -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

View File

@ -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 }}

View File

@ -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 }}
}

View File

@ -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 }}

View File

@ -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: {}