Traefik is a reverse proxy which we can use as an ingress controller for the Kubernetes. Nowadays, security is one the most important thing. So, people prefer https
over http
. But the challenges with https are managing the TLS certificate, renewing them. Now cert-manager solves this challenge of managing certificates. Traefik also has the built in feature where it can issue certifcate from Let’s Encrypt. But what if our we are using EKS and we want to delegate the certificates management to ACM rather than deploying cert-manager or configuring traefik.
In this blog, I will cover how we can deploy traefik with Network Load Balancer as the Load Balancer type and delegate the cert management to ACM. The Kubernetes cluster has to be EKS. ACM doesnot provide us with the private key for the certificates. So, we can only attach directly to the services running on AWS and not on services running else where.
Prerequisites
- EKS Cluster
- Helm 3+
- kubectl client installed
- certificate on ACM
Deploying Traefik to use certificate from ACM
To issue public certificate, follow the blog: https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-public.html
Now let’s deploy the traefik:
helm repo add traefik https://helm.traefik.io/traefik
helm repo update
Next, we will need to make a slight change the traefik chart. Firstly, we will need to make these changes because we are delegating the certificate termination task to Load Balancer. So, the traffic between Load Balancer and pods are in plain text. So, we have to ensure that all traffic from load balancer are redirected to http port of traefik.
Execute the following command:
helm fetch traefik/traefik --untar
This will download the traefik Chart. Firstly, we will change the service.yaml
for the Helm Chart.
{{- if .Values.service.enabled -}}
{{ $tcpPorts := dict }}
{{ $udpPorts := dict }}
{{- range $name, $config := .Values.ports }}
{{- if or $config.http3 (eq (toString $config.protocol) "UDP") }}
{{ $_ := set $udpPorts $name $config }}
{{- end }}
{{- if eq (toString (default "TCP" $config.protocol)) "TCP" }}
{{ $_ := set $tcpPorts $name $config }}
{{- end }}
{{- end }}
apiVersion: v1
kind: List
metadata:
name: {{ template "traefik.fullname" . }}
items:
{{- if $tcpPorts }}
- apiVersion: v1
kind: Service
metadata:
name: {{ template "traefik.fullname" . }}
labels:
app.kubernetes.io/name: {{ template "traefik.name" . }}
helm.sh/chart: {{ template "traefik.chart" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- with .Values.service.labels }}
{{- toYaml . | nindent 8 }}
{{- end }}
annotations:
{{- with (merge .Values.service.annotationsTCP .Values.service.annotations) }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- $type := default "LoadBalancer" .Values.service.type }}
type: {{ $type }}
{{- with .Values.service.spec }}
{{- toYaml . | nindent 6 }}
{{- end }}
selector:
app.kubernetes.io/name: {{ template "traefik.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
ports:
{{- range $name, $config := $tcpPorts }}
{{- if $config.expose }}
- port: {{ default $config.port $config.exposedPort }}
name: {{ $name }}
# Changes are made here
{{ if eq $name "websecure" -}}
targetPort: "web"
{{ else -}}
targetPort: {{ $name | quote }}
{{ end -}}
# Changes
protocol: TCP
{{- if $config.nodePort }}
nodePort: {{ $config.nodePort }}
{{- end }}
{{- end }}
{{- end }}
{{- if eq $type "LoadBalancer" }}
{{- with .Values.service.loadBalancerSourceRanges }}
loadBalancerSourceRanges:
{{- toYaml . | nindent 6 }}
{{- end -}}
{{- end -}}
{{- with .Values.service.externalIPs }}
externalIPs:
{{- toYaml . | nindent 6 }}
{{- end -}}
{{- if .Values.service.ipFamilyPolicy }}
ipFamilyPolicy: {{ .Values.service.ipFamilyPolicy }}
{{- end }}
{{- with .Values.service.ipFamilies }}
ipFamilies:
{{- toYaml . | nindent 6 }}
{{- end -}}
{{- end }}
{{- if $udpPorts }}
- apiVersion: v1
kind: Service
metadata:
name: {{ template "traefik.fullname" . }}-udp
labels:
app.kubernetes.io/name: {{ template "traefik.name" . }}
helm.sh/chart: {{ template "traefik.chart" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- with .Values.service.labels }}
{{- toYaml . | nindent 8 }}
{{- end }}
annotations:
{{- with (merge .Values.service.annotationsUDP .Values.service.annotations) }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- $type := default "LoadBalancer" .Values.service.type }}
type: {{ $type }}
{{- with .Values.service.spec }}
{{- toYaml . | nindent 6 }}
{{- end }}
selector:
app.kubernetes.io/name: {{ template "traefik.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
ports:
{{- range $name, $config := $udpPorts }}
{{- if $config.expose }}
- port: {{ default $config.port $config.exposedPort }}
name: {{ $name }}
# Due to https://github.com/kubernetes/kubernetes/issues/58477 it is not possible
# to define the HTTP/3 UDP port as a containerPort. TCP and UDP containerPort will have
# the same value which will break the chart upgrade.
targetPort: {{ if $config.http3 }}{{ $config.port }}{{ else }}{{ $name | quote }}{{ end }}
protocol: UDP
{{- if $config.nodePort }}
nodePort: {{ $config.nodePort }}
{{- end }}
{{- end }}
{{- end }}
{{- if eq $type "LoadBalancer" }}
{{- with .Values.service.loadBalancerSourceRanges }}
loadBalancerSourceRanges:
{{- toYaml . | nindent 6 }}
{{- end -}}
{{- end -}}
{{- with .Values.service.externalIPs }}
externalIPs:
{{- toYaml . | nindent 6 }}
{{- end -}}
{{- if .Values.service.ipFamilyPolicy }}
ipFamilyPolicy: {{ .Values.service.ipFamilyPolicy }}
{{- end }}
{{- with .Values.service.ipFamilies }}
ipFamilies:
{{- toYaml . | nindent 6 }}
{{- end -}}
{{- end }}
{{- end -}}
Store the above content in traefik/templates/service.yaml
. The changes are enclosed in #
.Now we will create a values file named values-custom.yaml
:
ingressClass:
enabled: true
image:
name: traefik
tag: "2.6.1"
deployment:
enabled: true
kind: Deployment
replicas: 2
imagePullSecrets: []
ingressRoute:
dashboard:
enabled: false
service:
enabled: true
type: LoadBalancer
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: nlb
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "tcp"
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: <certificate-arn>
service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443"
Replace the placeholder <certificate-arn>
with the ARN for the certificate.
Now, deploy the traefik using the command:
helm upgrade -i traefik /path/to/traefik -f /path/to/traefik/values-custom.yaml --namespace traefik --create-namespace --atomic
This will deploy the traefik with NLB type Load Balancer. We can go to the listeners section in ELB and verify that the certificate is attached.
Conclusion
Summing up, since NLB terminates the TLS, we won’t be needing traefik to take care of the tls. So, every traffic coming from Load Balancer to traefik pod is plain text and should not be interpreted as https. So we are changing the service template to redirect all traffic to http endpoint of traefik. Following this blog, we can set up traefik ingress which uses ACm to manage certifcates.
Refernces:
- Enforce TLS using Traefik Middleware: https://blog.knoldus.com/how-to-enforce-tls-using-traefik-middleware/