Le blog tech de Nicolas Steinmetz (Time Series, IoT, Web, Ops, Data)
Avec la sortie de Traefik 2, il était temps de mettre à jour le billet Kubernetes @ OVH - Traefik et Cert Manager pour le stockage des certificats en secrets pour tenir compte des modifications.
L’objectif est toujours de s’appuyer sur Cert-Manager pour la génération et le stockage des certificats Let’s Encrypt qui seront utilisés par Traefik. L’idée est de stocker ces certificats sous la forme d’un objet Certificate et de ne plus avoir à provisionner un volume pour les stocker. On peut dès lors avoir plusieurs instances de Traefik et non plus une seule à laquelle le volume serait attaché.
Installation de cert-manager :
# Install the CustomResourceDefinition resources separately
kubectl apply --validate=false -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.11/deploy/manifests/00-crds.yaml
# Create the namespace for cert-manager
kubectl create namespace cert-manager
# Add the Jetstack Helm repository
helm repo add jetstack https://charts.jetstack.io
# Update your local Helm chart repository cache
helm repo update
# Install the cert-manager Helm chart
helm install \
  --name cert-manager \
  --namespace cert-manager \
  --version v0.11.0 \
  jetstack/cert-manager
Nous allons ensuite devoir créer un Issuer dans chaque namespace pour avoir un générateur de certificats propre à chaque namespace. Cela est notamment du au fait que Traefik s’attend à ce que le secret et l’ingress utilisant ce secret soient dans le même namespace. Nous spécifions également que nous utiliserons traefik comme ingress pour la génération des certificats.
cert-manager/issuer.yml:
apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    # The ACME server URL
    server: https://acme-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: user@example.com
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-prod
    # Enable HTTP01 validations
    solvers:
    - selector: {}
      http01:
        ingress:
          class: traefik
Puis créons le “issuer” dans la/les namespace(s) voulu(s) :
# Create issuer in a given namespace
kubectl create -n <namespace> -f cert-manager/issuer.yml
Installons ensuite traefik V2
Créons le namespace traefik2 :
# Create namespace
kubectl create ns traefik2
# Change context to this namespace so that all commands are by default run for this namespace
# see https://github.com/ahmetb/kubectx
kubens traefik2
En premier lieu, Traefik V2 permet d’avoir un provider Kubernetes qui se base sur des Custom Ressources Definition (aka CRD).
Créeons le fichier traefik2/crd.yml :
---
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: ingressrouteudps.traefik.containo.us
spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: IngressRouteUDP
    plural: ingressrouteudps
    singular: ingressrouteudp
  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: tlsstores.traefik.containo.us
spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: TLSStore
    plural: tlsstores
    singular: tlsstore
  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
Vous pouvez retrouver les sources de ces CRD.
Continuons avec traefik2/rbac.yml - le fichier défini le compte de service (Service Account), le rôle au niveau du cluster (Cluster Role) et la liaison entre le rôle et le compte de service (Cluster Role Binding). Si vous venez d’une installation avec Traefik 1, ce n’est pas tout à fait la même définition des permissions.
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: traefik2-ingress-controller
  namespace: traefik2
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: traefik2-ingress-controller
  namespace: traefik2
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
      - ingressrouteudps
      - tlsoptions
      - tlsstores
    verbs:
      - get
      - list
      - watch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: traefik2-ingress-controller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: traefik2-ingress-controller
subjects:
- kind: ServiceAccount
  name: traefik2-ingress-controller
  namespace: traefik2
Nous pouvons alors songer à déployer Traefik V2 sous la forme d’un Deployment. Mais avant de produire le fichier, ce qu’il faut savoir ici :
Ingress. Dès lors, il faut activer les deux providers kubernetes disponibles avec Traefik V2 : KubernetesCRD et KubernetesIngress. Le premier provider permettra de profiter des nouveaux objets fournis par la CRD et le second permet que Traefik gère les Ingress traditionnelles de Kubernetes et notamment celles de cert-manager.KubernetesIngress ne supporte pas les annotationsKubernetesIngress, on se simplifie aussi la migration d’un socle Traefik V1 vers V2, au support des annotations près.traefik2/deployment.yml :
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: traefik2-ingress-controller
  labels:
    k8s-app: traefik2-ingress-lb
spec:
  replicas: 2
  selector:
    matchLabels:
      k8s-app: traefik2-ingress-lb
  template:
    metadata:
      labels:
        k8s-app: traefik2-ingress-lb
        name: traefik2-ingress-lb
    spec:
      serviceAccountName: traefik2-ingress-controller
      terminationGracePeriodSeconds: 60
      containers:
      - image: traefik:2.1.1
        name: traefik2-ingress-lb
        ports:
          - name: web
            containerPort: 80
          - name: admin
            containerPort: 8080
          - name: secure
            containerPort: 443
        readinessProbe:
          httpGet:
            path: /ping
            port: admin
          failureThreshold: 1
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 2
        livenessProbe:
          httpGet:
            path: /ping
            port: admin
          failureThreshold: 3
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 2
        args:
          - --entryPoints.web.address=:80
          - --entryPoints.secure.address=:443
          - --entryPoints.traefik.address=:8080
          - --api.dashboard=true
          - --api.insecure=true
          - --ping=true
          - --providers.kubernetescrd
          - --providers.kubernetesingress
          - --log.level=DEBUG
Pour permettre au cluster d’accéder aux différents ports, il faut définir un service via le fichier traefik2/service.yml :
---
kind: Service
apiVersion: v1
metadata:
  name: traefik2-ingress-service-clusterip
spec:
  selector:
    k8s-app: traefik2-ingress-lb
  ports:
    - protocol: TCP
      port: 80
      name: web
    - protocol: TCP
      port: 8080
      name: admin
    - protocol: TCP
      port: 443
      name: secure
  type: ClusterIP
Et pour avoir un accès de l’extérieur, il faut instancier un load-balancer via le fichier traefik/traefik-service-loadbalancer.yml
---
kind: Service
apiVersion: v1
metadata:
  name: traefik-ingress-service-lb
spec:
  selector:
    k8s-app: traefik2-ingress-lb
  ports:
    - protocol: TCP
      port: 80
      name: web
    - protocol: TCP
      port: 443
      name: secure
  type: LoadBalancer
Pour donner l’accès au dashboard via une url sécurisée par un certificat Let’s Encrypt, il faut déclarer un Ingress, dans le fichier traefik2/api-ingress.yml :
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: traefik2-web-ui
spec:
  entryPoints:
    - secure
  routes:
    - match: Host(`traefik2.k8s.cerenit.fr`)
      kind: Rule
      services:
        - name: traefik2-ingress-service-clusterip
          port: 8080
  tls:
    secretName: traefik2-cert
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: traefik2-web-ui-http
spec:
  entryPoints:
    - web
  routes:
    - match: Host(`traefik2.k8s.cerenit.fr`)
      kind: Rule
      services:
        - name: traefik2-ingress-service-clusterip
          port: 8080
L’idée est donc de rentre le dashboard accessible via l’url traefik2.k8s.cerenit.fr.
La section tls de l’ingress indique le nom d’hôte pour lequel le certificat va être disponible et le nom du secret contenant le certificat du site que nous n’avons pas encore créé.
Il nous faut donc créer ce certificat :
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: traefik2-cert
  namespace: traefik2
spec:
  secretName: traefik2-cert
  issuerRef:
    name: letsencrypt-prod
  commonName: traefik2.k8s.cerenit.fr
  dnsNames:
    - traefik2.k8s.cerenit.fr
Il ne reste plus qu’à faire pour instancier le tout :
kubectl apply -f traefik2/
Pour la génération du certificat, il conviendra de vérifier la sortie de
kubectl describe certificate traefik2-cert
A ce stade, il nous manque :
C’est là que les Middlewares rentrent en jeu.
Pour la redirection https: traefik2/middleware-redirect-https.yml
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: https-only
spec:
  redirectScheme:
    scheme: https
Pour l’authentification : traefik2/middleware-auth.yml
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: auth-traefik-webui
spec:
  basicAuth:
    secret: traefik-auth
Il faut alors créer un secret kubernetes qui contient une variable users contenant la/les ligne(s) d’authentification :
apiVersion: v1
kind: Secret
metadata:
  name: traefik-auth
  namespace: traefik2
data:
  users: |2
    dGVzdDokYXByMSRINnVza2trVyRJZ1hMUDZld1RyU3VCa1RycUU4d2ovCnRlc3QyOiRhcHIxJGQ5aHI5SEJCJDRIeHdnVWlyM0hQNEVzZ2dQL1FObzAK
Cela correspond à 2 comptes test/test et test2/test2, encodés en base64 et avec un mot de passe chiffré via htpasswd.
test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/
test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0
On peut alors mettre à jour notre fichier traefik2/api-ingress.yml et rajouter les deux middlewares que nous venons de définir :
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: traefik2-web-ui
spec:
  entryPoints:
    - secure
  routes:
    - match: Host(`traefik2.k8s.cerenit.fr`)
      middlewares:
        - name: auth-traefik-webui
      kind: Rule
      services:
        - name: traefik2-ingress-service-clusterip
          port: 8080
  tls:
    secretName: traefik2-cert
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: traefik2-web-ui-http
spec:
  entryPoints:
    - web
  routes:
    - match: Host(`traefik2.k8s.cerenit.fr`)
      middlewares:
        - name: https-only
      kind: Rule
      services:
        - name: traefik2-ingress-service-clusterip
          port: 8080
Et pour le prendre en compte:
kubectl apply -f traefik2/
Vous devez alors avoir une redirection automatique vers le endpoint en https et une mire d’authentification.
Pour ceux qui font une migration dans le même cluster de Traefik V1 vers Traefik V2 :
ingressClass de Traefik et créer un issuer cert-manager qui utilise cette même ingressClass.CertificateSources utiles: