Traefik 2.1 in Kubernetes

As I migrate from Docker Swarm to Kubernetes I decided to use Traefik. Here's how to replace NGINX with Traefik 2.1 for Kubernetes Ingress.

The documentation was sparse and sporadic. It was a gloomy weekend. I was grumpy and cranky. Seems to be my general mindset with Kubernetes. Anyways, after digging through tons of documentation, a couple of days of trial and error, I finally got the Traefik 2.1 container working in Kubernetes and dynamically updating it's configuration.

I'm first going to go over a little bit of what's needed.

Kind Name Purpose
ClusterRole - This is to set up the permissions so Traefik can talk to the Kubernetes API
ClusterRoleBinding - Binds the role to the Traefik containers
CustomResourceDefinition IngressRoute HTTP ingress type
CustomResourceDefinition Middleware Middleware types so you can use the Traefik middleware
CustomResourceDefinition IngressRouteTCP Raw TCP port ingress type
CustomResourceDefinition TLSOption TLS Options
CustomResourceDefinition TraefikService Traefik special services
DaemonSet - The actual running Traefik containers
Service - The Traefik service

Those types and objects are needed for Traefik to function. I split them into 3 distinct files, RBAC.yml, ResourceDefinitions.yml and Traefik.yml. It's pretty simple as to what is in each, so I will just give you the files.

RBAC.yml

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller

rules:
  - apiGroups:
      - ""
    resources:
      - services
      - endpoints
      - secrets
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses/status
    verbs:
      - update
  - apiGroups:
      - traefik.containo.us
    resources:
      - middlewares
      - ingressroutes
      - traefikservices
      - ingressroutetcps
      - tlsoptions
    verbs:
      - get
      - list
      - watch

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller

roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: traefik-ingress-controller
subjects:
  - kind: ServiceAccount
    name: traefik-ingress-controller
    namespace: default

ResourceDefinitions.yml

# All resources definition must be declared
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: ingressroutes.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: IngressRoute
    plural: ingressroutes
    singular: ingressroute
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: middlewares.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: Middleware
    plural: middlewares
    singular: middleware
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: ingressroutetcps.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: IngressRouteTCP
    plural: ingressroutetcps
    singular: ingressroutetcp
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: tlsoptions.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: TLSOption
    plural: tlsoptions
    singular: tlsoption
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: traefikservices.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: TraefikService
    plural: traefikservices
    singular: traefikservice
  scope: Namespaced

Traefik.yml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: traefik-ingress-controller

---
kind: DaemonSet
apiVersion: apps/v1
metadata:
  name: traefik
  labels:
    app: traefik

spec:
  selector:
    matchLabels:
      app: traefik
  template:
    metadata:
      labels:
        app: traefik
    spec:
      serviceAccountName: traefik-ingress-controller
      containers:
        - name: traefik
          image: traefik:v2.1
          args:
            - --log.level=DEBUG
            - --api
            - --api.insecure
            - --entrypoints.web.address=:80
            - --providers.kubernetescrd
          ports:
            - name: web
              containerPort: 80
              hostPort: 80
            - name: admin
              containerPort: 8080
              hostPort: 8080

---
apiVersion: v1
kind: Service
metadata:
  name: traefik
spec:
  selector:
    app: traefik
  ports:
    - protocol: TCP
      port: 80
      name: web
      targetPort: 80
    - protocol: TCP
      port: 8080
      name: admin
      targetPort: 8080

I chose to use DaemonSet instead of deployment because a DaemonSet will make sure that an instance of the service is running on every Kubernetes node. Thus it gives us instant high availability. And I like that.

I put each file into the same directory, they are the only files in that directory. Because I'm an original and creative person, I named the folder traefik. See, its original, I used a lowercase t.

I then ran the kubectl command to apply the configs. kubectl apply -f ./traefik. Now when I go to my Kubernetes cluster in a browser I don't get the ugly NGINX service unavailable page. Instead I get the non-customizable, not as ugly, 404 page from Traefik.

And we're done with getting Traefik 2.1 into Kubernetes. And now to figure out HTTPS with my own SSL cert and not use Lets Encrypt...because apparently I like doing things that don't have any documentation.