Traefik & CRD & Let’s Encrypt

    This document is intended to be a fully working example demonstrating how to set up Traefik in Kubernetes, with the dynamic configuration coming from the , and TLS setup with Let’s Encrypt. However, for the sake of simplicity, we’re using docker image for the Kubernetes cluster setup.

    Please note that for this setup, given that we’re going to use ACME’s TLS-ALPN-01 challenge, the host you’ll be running it on must be able to receive connections from the outside on port 443. And of course its internet facing IP address must match the domain name you intend to use.

    In the following, the Kubernetes resources defined in YAML configuration files can be applied to the setup in two different ways:

    • the first, and usual way, is simply with the command.
    • the second, which can be used for this tutorial, is to directly place the files in the directory used by the k3s docker image for such inputs (/var/lib/rancher/k3s/server/manifests).

    Kubectl Version

    Our starting point is the docker-compose configuration file, to start the k3s cluster. You can start it with:

    1. server:
    2. image: rancher/k3s:v1.17.2-k3s1
    3. command: server --disable-agent --no-deploy traefik
    4. environment:
    5. - K3S_CLUSTER_SECRET=somethingtotallyrandom
    6. - K3S_KUBECONFIG_OUTPUT=/output/kubeconfig.yaml
    7. - K3S_KUBECONFIG_MODE=666
    8. volumes:
    9. # k3s will generate a kubeconfig.yaml in this directory. This volume is mounted
    10. # on your host, so you can then 'export KUBECONFIG=/somewhere/on/your/host/out/kubeconfig.yaml',
    11. # in order for your kubectl commands to work.
    12. - /somewhere/on/your/host/out:/output
    13. # This directory is where you put all the (yaml) configuration files of
    14. # the Kubernetes resources.
    15. - /somewhere/on/your/host/in:/var/lib/rancher/k3s/server/manifests
    16. ports:
    17. - 6443:6443
    18. node:
    19. image: rancher/k3s:v1.17.2-k3s1
    20. privileged: true
    21. links:
    22. - server
    23. environment:
    24. - K3S_URL=https://server:6443
    25. - K3S_CLUSTER_SECRET=somethingtotallyrandom
    26. volumes:
    27. # this is where you would place a alternative traefik image (saved as a .tar file with
    28. # 'docker save'), if you want to use it, instead of the traefik:v2.9 image.
    29. - /somewhere/on/your/host/custom-image:/var/lib/rancher/k3s/agent/images

    Cluster Resources

    Let’s now have a look (in the order they should be applied, if using kubectl apply) at all the required resources for the full setup.

    First, you will need to install Traefik CRDs containing the definition of the IngressRoute and the Middleware kinds, and the RBAC authorization resources which will be referenced through the serviceAccountName of the deployment.

    1. # Install Traefik Resource Definitions:
    2. kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v2.9/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
    3. # Install RBAC for Traefik:
    4. kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v2.9/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml

    Services

    Then, the services. One for Traefik itself, and one for the app it routes for, i.e. in this case our demo HTTP server: whoami.

    1. kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v2.9/docs/content/user-guides/crd-acme/02-services.yml
    1. kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v2.9/docs/content/user-guides/crd-acme/03-deployments.yml
    1. apiVersion: v1
    2. kind: ServiceAccount
    3. metadata:
    4. namespace: default
    5. name: traefik-ingress-controller
    6. ---
    7. kind: Deployment
    8. apiVersion: apps/v1
    9. metadata:
    10. name: traefik
    11. labels:
    12. spec:
    13. replicas: 1
    14. selector:
    15. matchLabels:
    16. app: traefik
    17. template:
    18. metadata:
    19. labels:
    20. app: traefik
    21. spec:
    22. serviceAccountName: traefik-ingress-controller
    23. containers:
    24. - name: traefik
    25. image: traefik:v2.9
    26. args:
    27. - --api.insecure
    28. - --accesslog
    29. - --entrypoints.web.Address=:8000
    30. - --entrypoints.websecure.Address=:4443
    31. - --providers.kubernetescrd
    32. - --certificatesresolvers.myresolver.acme.tlschallenge
    33. - [email protected]
    34. - --certificatesresolvers.myresolver.acme.storage=acme.json
    35. # Please note that this is the staging Let's Encrypt server.
    36. # Once you get things working, you should remove that whole line altogether.
    37. - --certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
    38. ports:
    39. - name: web
    40. containerPort: 8000
    41. - name: websecure
    42. containerPort: 4443
    43. - name: admin
    44. containerPort: 8080
    45. ---
    46. kind: Deployment
    47. apiVersion: apps/v1
    48. metadata:
    49. namespace: default
    50. name: whoami
    51. labels:
    52. app: whoami
    53. spec:
    54. replicas: 2
    55. selector:
    56. matchLabels:
    57. app: whoami
    58. template:
    59. metadata:
    60. labels:
    61. app: whoami
    62. spec:
    63. - name: whoami
    64. image: traefik/whoami
    65. ports:
    66. containerPort: 80

    Port Forwarding

    Now, as an exception to what we said above, please note that you should not let the ingressRoute resources below be applied automatically to your cluster. The reason is, as soon as the ACME provider of Traefik detects we have TLS routers, it will try to generate the certificates for the corresponding domains. And this will not work, because as it is, our Traefik pod is not reachable from the outside, which will make the ACME TLS challenge fail. Therefore, for the whole thing to work, we must delay applying the ingressRoute resources until we have port-forwarding set up properly, which is the next step.

    1. kubectl port-forward --address 0.0.0.0 service/traefik 8000:8000 8080:8080 443:4443 -n default

    Also, and this is out of the scope if this guide, please note that because of the privileged ports limitation on Linux, the above command might fail to listen on port 443. In which case you can use tricks such as elevating caps of kubectl with setcaps, or using authbind, or setting up a NAT between your host and the WAN. Look it up.

    We can now finally apply the actual ingressRoutes, with:

    1. apiVersion: traefik.containo.us/v1alpha1
    2. kind: IngressRoute
    3. metadata:
    4. name: simpleingressroute
    5. namespace: default
    6. spec:
    7. entryPoints:
    8. - web
    9. routes:
    10. - match: Host(`your.example.com`) && PathPrefix(`/notls`)
    11. kind: Rule
    12. services:
    13. - name: whoami
    14. port: 80
    15. ---
    16. apiVersion: traefik.containo.us/v1alpha1
    17. kind: IngressRoute
    18. metadata:
    19. name: ingressroutetls
    20. namespace: default
    21. spec:
    22. entryPoints:
    23. - websecure
    24. routes:
    25. - match: Host(`your.example.com`) && PathPrefix(`/tls`)
    26. kind: Rule
    27. services:
    28. - name: whoami
    29. port: 80
    30. tls:
    31. certResolver: myresolver

    Give it a few seconds for the ACME TLS challenge to complete, and you should then be able to access your whoami pod (routed through Traefik), from the outside. Both with or (just for fun, do not do that in production) without TLS:

    1. curl [-k] https://your.example.com/tls
    1. curl http://your.example.com:8000/notls

    Force TLS v1.2+

    Nowadays, TLS v1.0 and v1.1 are deprecated. In order to force TLS v1.2 or later on all your IngressRoute, you can define the default TLSOption:

    1. ---
    2. apiVersion: traefik.containo.us/v1alpha1
    3. kind: TLSOption
    4. metadata:
    5. name: default
    6. namespace: default
    7. spec:
    8. minVersion: VersionTLS12
    9. cipherSuites:
    10. - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 # TLS 1.2
    11. - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 # TLS 1.2
    12. - TLS_AES_256_GCM_SHA384 # TLS 1.3
    13. - TLS_CHACHA20_POLY1305_SHA256 # TLS 1.3
    14. curvePreferences:
    15. - CurveP521
    16. - CurveP384
    17. sniStrict: true