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.3 image.
    29. - /sowewhere/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, the definition of the IngressRoute and the Middleware kinds. Also note the RBAC authorization resources; they’ll be referenced through the serviceAccountName of the deployment, later on.

    1. apiVersion: apiextensions.k8s.io/v1beta1
    2. kind: CustomResourceDefinition
    3. metadata:
    4. name: ingressroutes.traefik.containo.us
    5. spec:
    6. group: traefik.containo.us
    7. version: v1alpha1
    8. names:
    9. kind: IngressRoute
    10. plural: ingressroutes
    11. singular: ingressroute
    12. scope: Namespaced
    13. ---
    14. apiVersion: apiextensions.k8s.io/v1beta1
    15. kind: CustomResourceDefinition
    16. metadata:
    17. name: middlewares.traefik.containo.us
    18. spec:
    19. group: traefik.containo.us
    20. version: v1alpha1
    21. names:
    22. kind: Middleware
    23. plural: middlewares
    24. singular: middleware
    25. scope: Namespaced
    26. ---
    27. apiVersion: apiextensions.k8s.io/v1beta1
    28. kind: CustomResourceDefinition
    29. metadata:
    30. name: ingressroutetcps.traefik.containo.us
    31. spec:
    32. group: traefik.containo.us
    33. version: v1alpha1
    34. names:
    35. kind: IngressRouteTCP
    36. plural: ingressroutetcps
    37. singular: ingressroutetcp
    38. scope: Namespaced
    39. ---
    40. apiVersion: apiextensions.k8s.io/v1beta1
    41. kind: CustomResourceDefinition
    42. metadata:
    43. name: ingressrouteudps.traefik.containo.us
    44. spec:
    45. group: traefik.containo.us
    46. version: v1alpha1
    47. names:
    48. kind: IngressRouteUDP
    49. plural: ingressrouteudps
    50. singular: ingressrouteudp
    51. scope: Namespaced
    52. ---
    53. apiVersion: apiextensions.k8s.io/v1beta1
    54. kind: CustomResourceDefinition
    55. metadata:
    56. name: tlsoptions.traefik.containo.us
    57. spec:
    58. version: v1alpha1
    59. names:
    60. kind: TLSOption
    61. plural: tlsoptions
    62. singular: tlsoption
    63. scope: Namespaced
    64. ---
    65. apiVersion: apiextensions.k8s.io/v1beta1
    66. kind: CustomResourceDefinition
    67. metadata:
    68. name: tlsstores.traefik.containo.us
    69. spec:
    70. group: traefik.containo.us
    71. version: v1alpha1
    72. names:
    73. kind: TLSStore
    74. plural: tlsstores
    75. singular: tlsstore
    76. scope: Namespaced
    77. ---
    78. apiVersion: apiextensions.k8s.io/v1beta1
    79. kind: CustomResourceDefinition
    80. metadata:
    81. name: traefikservices.traefik.containo.us
    82. spec:
    83. group: traefik.containo.us
    84. version: v1alpha1
    85. names:
    86. kind: TraefikService
    87. plural: traefikservices
    88. singular: traefikservice
    89. scope: Namespaced
    90. ---
    91. kind: ClusterRole
    92. apiVersion: rbac.authorization.k8s.io/v1beta1
    93. metadata:
    94. name: traefik-ingress-controller
    95. rules:
    96. - apiGroups:
    97. - ""
    98. resources:
    99. - services
    100. - endpoints
    101. - secrets
    102. verbs:
    103. - get
    104. - list
    105. - watch
    106. - apiGroups:
    107. - extensions
    108. - networking.k8s.io
    109. resources:
    110. - ingresses
    111. - ingressclasses
    112. verbs:
    113. - get
    114. - list
    115. - watch
    116. - apiGroups:
    117. - extensions
    118. resources:
    119. - ingresses/status
    120. verbs:
    121. - update
    122. - apiGroups:
    123. - traefik.containo.us
    124. resources:
    125. - middlewares
    126. - ingressroutes
    127. - traefikservices
    128. - ingressroutetcps
    129. - ingressrouteudps
    130. - tlsoptions
    131. - tlsstores
    132. verbs:
    133. - get
    134. - list
    135. - watch
    136. ---
    137. kind: ClusterRoleBinding
    138. apiVersion: rbac.authorization.k8s.io/v1beta1
    139. metadata:
    140. name: traefik-ingress-controller
    141. roleRef:
    142. apiGroup: rbac.authorization.k8s.io
    143. kind: ClusterRole
    144. name: traefik-ingress-controller
    145. subjects:
    146. - kind: ServiceAccount
    147. name: traefik-ingress-controller
    148. namespace: default

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

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

    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:

    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: