I recently moved the front of this blog from Traefik 1 to Traefik 2, and to say the least, it's no picnic.
My Traefik use case
I use Traefik as a load balancer/reverse proxy front in a Kubernetes infrastructure. My use is very basic. Depending on certain path and/or domain, I redirect to separate pods. In the case below, I will consider that I have only one pod, this blog. I also manage my certificates with Traefik via Let's Encrypt.
Although this use case may seem basic, it took me several hours to upgrade from Traefik 1.7.20 to version 2.1.1.
Where the documentation promises a simple migration, it is unfortunately not the case...
Why not? Because the documentation takes a lot of shortcuts on some points and is very succinct on others. And that's where the problem is, if the implementation of Traefik 1 was simple, the multiplicity of possibilities with the new version made the configuration confusing.
So I propose you below to highlight the points that I changed between the two versions. I won't go back to the installation from scratch of Traefik, which you can find on their website.
Important information about the YAML code blocks below: I made the choice to install Traefik in a dedicated namespace. The codes below contain a reference to this namespace. Be careful in case you want to copy/paste to update this point.
Prerequisites for installation: CRDs (Custom Resources Definitions) and ClusterRole update
To be able to implement the new version of Traefik in a simple way, it is first necessary to create CRDs, which will, very roughly, define new types of resources that the Traefik controller will be able to control. I won't go further on CRDs here, a full article would be necessary.
So we will create a traefik2-prereq.yml file with the content below, you can find this file on the official documentation :
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: 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: 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: 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
Once this is done, we can load it into our Kubernetes cluster using the kubectl apply command:
kubectl apply -f ./traefik2-prereq.yml
Then, because of all these new resources, it is necessary to update our existing ClusterRole to allow Traefik to access this new information. So I modified my Clusterrole.yml file, which I replaced with the following content :
kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: traefik-ingress-controller namespace: traefik 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 verbs: - get - list - watch - apiGroups: - traefik.containo.us resources: - ingressroutes verbs: - get - list - watch - apiGroups: - traefik.containo.us resources: - ingressroutetcps verbs: - get - list - watch - apiGroups: - traefik.containo.us resources: - tlsoptions verbs: - get - list - watch - apiGroups: - traefik.containo.us resources: - traefikservices verbs: - get - list - watch
Once again, once completed, we apply this change:
kubectl apply -f ./clusterrole.yml
Deployment of the new version of Traefik
Now we will create the configuration file to deploy the pod containing Traefik 2. I have chosen to deploy Traefik as a DaemonSet, but the installation is similar if you want to deploy it in a pod. So I have the daemonset.yml file which has the following content :
apiVersion: extensions/v1beta1 kind: DaemonSet metadata: name: traefik-ingress-controller namespace: traefik labels: k8s-app: traefik-ingress-lb kubernetes.io/cluster-service: "true" spec: template: metadata: labels: k8s-app: traefik-ingress-lb name: traefik-ingress-lb spec: hostNetwork: true serviceAccountName: traefik-ingress-controller terminationGracePeriodSeconds: 60 tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule containers: - image: traefik:v2.1.1 name: traefik-ingress-lb imagePullPolicy: Always resources: requests: cpu: 100m memory: 20Mi volumeMounts: - mountPath: "/cert/" name: cert args: - --providers.kubernetescrd - --accesslog - --entrypoints.web.address=:80 - --entrypoints.websecure.address=:443 - --firstname.lastname@example.org - --certificatesresolvers.le.acme.storage=acme.json - --certificatesresolvers.le.acme.storage=/cert/acme.json - --certificatesResolvers.le.acme.httpChallenge.entryPoint=web ports: - name: web containerPort: 80 - name: websecure containerPort: 443 - name: admin containerPort: 8080 volumes: - name: cert hostPath: path: /home/kube/traefik/certs/ type: Directory
As you will have understood, some points are to be adapted according to your case of use. Once again, I am not making this post to copy the official documentation, to which I refer you if you have any doubt.
Note that it is possible to activate the traefik dashboard from the command line if necessary by adding the parameter "--api.insecure" in the arguments of the launch line.
You will note the presence of the necessary setting for Let's Encrypt. It is this setting that allows me to have a TLS certificate automatically generated by Traefik. Moreover, I created a volume in hostPath to store these certificates and avoid to request them each time I update my pod.
From there, I'm in V2... but I don't have a backend anymore...
That's why we don't apply the change right away, and we will first create the necessary to communicate with our pods.
Updating of services and creation of IngressRoutes
Traefik 2 changes the way it works compared to the previous version.
Indeed, in the first version, there was this configuration :
Frontend -> Backend
Pretty basic, but enough. The new version brings new functionality, especially middleware, which interfaces between the frontend and the backend. The latter allows for example to add basic auth on an application. As a result, we now have this configuration
Frontend -> Middleware -> Backend
Moreover, with the new features brought by Traefik, it is necessary to switch from Ingress with IngressRoutes, which we created via our CRDs earlier. It is this new Kubernetes object that carries all the necessary configuration to define the routing to our backend.
So I'm going to put below the two configuration files, which I had in version 1.7.20 and 2.1.1, so that you can see the differences directly.
Configuration file 1.7.20
apiVersion: v1 kind: Service metadata: name: traefik-web-ui namespace: traefik spec: selector: k8s-app: traefik-ingress-lb ports: - port: 8080 targetPort: 8080 name: api - port: 443 targetPort: 443 name: https - port: 80 targetPort: 80 name: http --- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: traefik-web-ui namespace: default annotations: kubernetes.io/ingress.class: traefik spec: rules: - http: paths: - path: / backend: serviceName: ghost-tfe-fr servicePort: 2368
Here is the equivalent on the new version:
apiVersion: v1 kind: Service metadata: name: traefik-web-ui namespace: traefik spec: selector: k8s-app: traefik-ingress-lb app: traefik-ingress-lb ports: - port: 8080 targetPort: 8080 name: api - port: 443 targetPort: 443 name: websecure - port: 80 targetPort: 80 name: web --- apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: traefik-web-ui namespace: default spec: entryPoints: - web routes: - kind: Rule match: Host(`tferdinand.net`) && PathPrefix(`/`) services: - name: ghost-tfe-fr port: 2368 helthcheck: path: / host: tferdinand.net intervalSeconds: 10 timeoutSeconds: 5 --- apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: traefik-web-ui-tls namespace: default spec: entryPoints: - websecure routes: - kind: Rule match: Host(`tferdinand.net`) && PathPrefix(`/`) services: - name: ghost-tfe-fr port: 2368 helthcheck: path: / host: tferdinand.net intervalSeconds: 10 timeoutSeconds: 5 tls: certResolver: le
As you can see, the new IngressRoute object carries much more information, including everything related to routing. Note that the hosts defined in the configuration files are the ones that will be used to generate the Let's Encrypt certificates at Traefik pod startup.
Now that everything is ready, we can apply our latest :
kubectl apply -f ./daemonset.yml kubectl apply -f ./service_ingress.yml
The new version of Traefik is now normally deployed. The Let's Encrypt certificate takes one to two minutes to be imported. Please note that if you are testing configurations, I advise you to disable the TLS, indeed, Let's Encrypt has a maximum query limit, limit that you may reach, preventing you from generating certificates.
And then... ?
Now that I've finished my migration to version 2, I plan to add a finer management of authorized TLS ciphers, in order to increase the security level of my applications. I'll probably write an article about it soon.
It's completely possible to set up a much finer configuration, I haven't done a complete overview of all the new features brought by version 2.
I made this article after the observation that I made by migrating from version, indeed, although it is announced breaking changes. I must admit that I did not expect such differences.
Note that there is a migration tool made available by Traefik on GitHub, which allows to convert Kubernetes configuration files from V1 to V2. However, when I tested it :
- Some configuration points do not work and refer to the documentation
- The generated configuration was not functional
I hope this article will be useful for your Traefik migration, don't hesitate to react in the comments!