How to, in a Kubernetes cluster running Traefik as its Ingress controller:
Cloudflare is a CDN and DNS provider that has a free tier which includes HTTPS between the browser and their servers. This allows you to provide your services over HTTPS without having to deal with certificates yourself. You might need or want to handle certificates yourself and ensure that communication between your cluster and your cdn is encrypted - this blog post is not for you! If you want fast and simple SSL encryption for your site then:
Disappointed that this isn't being handled by Kubernetes? Me too. I spent a good few hours trying to get Traefik to enforce both https and www without Cloudflare being involved and what I mostly got for my effort was infinite redirects or server errors. So I turned Cloudflare back on and left it at that. I'd love it if my cluster handled this all itself but honestly using Cloudflare or similar is faster and easier. I did look at using cert-manager and Let's Encrypt but I wasn't able to get it working easily so I decided to use my time elsewhere and let Cloudflare handle it. One less thing for me to worry about.
I'm certain that it's possible and that I'm just missing some crucial knowledge, but I've spent enough time trying to fix something that isn't broken.
This is actually handled by Kubernetes.
I use Traefik as my ingress controller, primarily because it does not require an external loadbalancer to function. In an ideal world I'd be using NGINX like the cool kids but keeping things cheap appeals to me more. It was also very easy to get running.
Something I found I was unable to do with Traefik was easily configure it to enforce www for my domains. After asking for help on /r/kubernetes here I was advised to run a service in the cluster that would become a last-ditch attempt to handle a URL before returning a 404.
The catchall service is handled by a very small Docker image running a very simple NGINX configuration - that's the Dockerfile
and redirects.conf
blocks below. The rest of the code samples are just the Kubernetes config required to get the service up and running.
FROM nginx:alpine
COPY ./redirects.conf /etc/nginx/conf.d/default.conf
CMD nginx -g "daemon off;"
daemon off
command to run NGINX in the foreground (details below)server {
listen 80;
return 301 https://www.$host$request_uri;
}
https://www.
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: catchall
annotations:
kubernetes.io/ingress.class: traefik
spec:
rules:
- http:
paths:
- path: /
backend:
serviceName: catchall
servicePort: 80
apiVersion: apps/v1
kind: Deployment
metadata:
name: catchall
labels:
app: catchall
spec:
replicas: 1
selector:
matchLabels:
app: catchall
template:
metadata:
labels:
app: catchall
spec:
containers:
- name: catchall
image: terrarum/nginx-catchall:latest
ports:
- containerPort: 80
apiVersion: v1
kind: Service
metadata:
name: catchall
labels:
app: catchall
spec:
type: NodePort
ports:
- name: http
port: 80
targetPort: 80
selector:
app: catchall
The Deployment and Service are pretty standard so I won't explain what's going on in those.
The Ingress is where the magic happens - because it does not define a host it receives any request that does not match any existing Ingress that does define a host.
If you kind of know what you're doing in Kubernetes the above should have pointed you in the right direction, but I'll expand on some things here.
This is the official build of NGINX using Alpine Linux. This gives us a powerful url rewriting tool inside a very small Docker image - exactly what we want for a passive catchall service that ideally never gets hit.
COPY ./redirects.conf /etc/nginx/conf.d/default.conf
This should be pretty self-explanatory but for the sake of completion: this copies the redirects.conf
NGINX configuration into the Docker image. This allows us to download a stock official Docker image and customise it.
CMD nginx -g "daemon off;"
This runs nginx with the deamon off
argument set globally (-g
). If you just run nginx
, the process that this spawns creates a daemon that runs NGINX in a new process and then quits your original nginx
process. When the command quits Docker goes sees this as the process exiting and then kills the container. In order for the Docker image to run forever we need the nginx
command to run in a foreground process which the above command accomplishes.
return 301 https://www.$host$request_uri;
NGINX gives you the $host
and $request_uri
variables to use in your config. Let's say I was hosting https://www.example.com
and someone visits http://example.com/about?utm_source=abc123
:
$host
would equal example.com
$request_uri
would equal /about?utm_source=abc123
So the final result would be https://www.example.com/about?utm_source=abc123
.
This also work for https://example.com
, but does not work for http://www.example.com
. At the moment I am using Cloudflare as my CDN/DNS provider and have enabled their 'Always Use HTTPS' option as I was unable to find a clean way to enforce https purely inside Traefik. Right now if I enable
As stated above the Deployment and Service files are not doing anything special so explaining them is outside the scope of this post.
Kubernetes Ingresses describe the hosts which they apply to like so:
spec:
rules:
- host: www.helm108.com
http:
paths:
- path: /
backend:
serviceName: helm108
servicePort: http
This instructs Kubernetes to forward any requests for www.helm108.com
to the helm108
service.
Our catchall Ingress looks like this:
spec:
rules:
- http:
paths:
- path: /
backend:
serviceName: catchall
servicePort: 80
Almost identical, just missing a host. This makes it match any request that is not caught by a specific ingress such as the helm108.com ingress.