Cómo desplegar Traefik v2.3.5 en Kubernetes con soporte SSL

En este artículo, te muestro, como de una manera bastante sencilla, podemos desplegar Traefik v2.3.5 en Kubernetes.

Desde hace unos días estoy armando un ambiente de Kubernetes, específicamente corriendo k3s y me dispuse a instalar el reverse proxy, por supuesto, todo este tipo de experiencias son material para este blog y porque creo, que alguien seguramente debe estar buscando como hacerlo.

K3s

Si estás montando tu cluster de Kubernetes en K3s, en el momento que lo vas a instalar, tenés que pedirle que se saltee la parte de Traefik, ya que lo vamos a hacer nosotros, de manera manual y con la última versión disponible.

Para Instalar K3s sin Traefik, debemos correr lo siguiente:

$ curl -sfL https://get.k3s.io | sh -s - --disable=traefik

Una vez que K3s está corriendo, que podemos verificarlo de la siguiente manera:

$ kubectl get nodes

El resultado se vería mas o menos así: En mi caso, tengo dos nodos corriendo, pero si solo usas uno, deberías ver el "master" solamente.

NAME               STATUS   ROLES    AGE   VERSION
kube-master-27aa   Ready    master   17h   v1.18.6+k3s1
kube-node-4f41     Ready    worker   17h   v1.18.6+k3s1

Declarando el recurso ingress y otras cosillas...

Vamos a empezar con la instalación, para eso, antes debemos declarar cual va a ser nuestro Ingress y algunas otras definiciones, usando CRD. Yo lo tengo especificado en un archivo llamado crd.yml y el mismo se ve de la siguiente manera:

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

---
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
      - networking.k8s.io
    resources:
      - ingresses
      - ingressclasses
    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/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

Para aplicar esto, tal cual esta, debemos correr:

$ kubectl apply -f crd.yml

El resultado, se verá mas o menos así:

customresourcedefinition.apiextensions.k8s.io/ingressroutes.traefik.containo.us created
customresourcedefinition.apiextensions.k8s.io/middlewares.traefik.containo.us created
customresourcedefinition.apiextensions.k8s.io/ingressroutetcps.traefik.containo.us created
customresourcedefinition.apiextensions.k8s.io/ingressrouteudps.traefik.containo.us created
customresourcedefinition.apiextensions.k8s.io/tlsoptions.traefik.containo.us created
customresourcedefinition.apiextensions.k8s.io/tlsstores.traefik.containo.us created
customresourcedefinition.apiextensions.k8s.io/traefikservices.traefik.containo.us created
clusterrole.rbac.authorization.k8s.io/traefik-ingress-controller created
clusterrolebinding.rbac.authorization.k8s.io/traefik-ingress-controller created

Desplegando Traefik

Ahora si se viene lo bueno, vamos a desplegar nuestro Traefik en un archivo llamado deployment.yml, el mismo tiene el siguiente contenido:

apiVersion: v1
kind: Service
metadata:
 name: traefik
spec:
 ports:
 - protocol: TCP
   name: web
   port: 80
 - protocol: TCP
   name: websecure
   port: 443
 type: LoadBalancer
 selector:
  app: traefik
---
apiVersion: v1
kind: ServiceAccount
metadata:
 namespace: default
 name: traefik-ingress-controller

---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: default
  name: traefik
  labels:
    app: traefik

spec:
  replicas: 1
  selector:
    matchLabels:
      app: traefik
  template:
    metadata:
      labels:
        app: traefik
    spec:
      serviceAccountName: traefik-ingress-controller
      containers:
       - name: traefik
         image: traefik:v2.3.5
         args:
            - --api
            - --log.level=ERROR
            - --accesslog
            - --entrypoints.web.Address=:80
            - --entrypoints.websecure.Address=:443
            - --entrypoints.web.http.redirections.entryPoint.to=:443
            - --entrypoints.web.http.redirections.entryPoint.scheme=https
            - --providers.kubernetescrd
            - --providers.kubernetesingress
            - --certificatesresolvers.myresolver.acme.tlschallenge
            - --certificatesresolvers.myresolver.acme.email=tuemail@tudominio.com
            - --certificatesresolvers.myresolver.acme.storage=acme.json
         ports:
            - name: web
              containerPort: 80
            - name: websecure
              containerPort: 443

Acá básicamente lo que hacemos es definir el servicio, que se va a llamar Traefik y el mismísimo deployment, publicamos puertos, definimos la configuración de Let's Encrypt, la redirección, para que todo lo que sirvamos sea a través de SSL seteamos el numero de replicas y por favor, no se olviden de poner su correo electrónico en esta sección:

certificatesresolvers.myresolver.acme.email=tuemail@tudominio.com

Una vez que estamos listos, vamos a aplicar este deployment:

$ kubectl apply -f deployment.yml

El resultado debería ser algo como esto:

service/traefik created
serviceaccount/traefik-ingress-controller created
deployment.apps/traefik created

Comprobando...

Buenos, vamos a ver que todo este andando como esperamos. Para esto, podemos ejecutar un comando que nos va a permitir ver nuestros pods, en este caso deberiamos ver el de Traefik y uno (o varios, dependiendo de los nodos que tengas en tu cluster).

$ kubectl get pods

El resultado debería ser algo como esto:

NAME                       READY   STATUS    RESTARTS   AGE
svclb-traefik-5f6qf        2/2     Running   0          2m59s
traefik-7b7f4d6f68-mrwb4   1/1     Running   0          2m57s

Todo parece estar bien, hagamos una comprobación más, corramos un pod de hello world para asegurarnos de que Traefik esta funcionando bien.

Este es el que vamos a correr y lo guardaremos con el nombre hello.yml

kind: Deployment
apiVersion: apps/v1
metadata:
   name: hello-world
spec:
   replicas: 1
   selector:
      matchLabels:
         app: hello-world
   template:
      metadata:
         labels:
            app: hello-world
      spec:
         containers:
            - name: hello-world
              image: containous/whoami
---
apiVersion: v1
kind: Service
metadata:
   name: hello-world
   labels:
      app: hello-world
spec:
   ports:
      - port: 80
        name: hello-world
   selector:
      app: hello-world
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: hello-ingress
spec:
  entryPoints:  
    - websecure
  routes:
  - match: Host(`midominio.com`)
    kind: Rule
    services:
    - name: hello-world
      port: 80
  tls:
      certResolver: myresolver

Ejecutamos con...

$ kubectl apply -f hello.yml

El resultado debería ser algo así:

deployment.apps/hello-world created
service/hello-world created
ingressroute.traefik.containo.us/hello-ingress created

Ahora, si apuntamos nuestro navegador a "midominio.com", veremos que nos redirige a https y nos mostrará algo parecido a esto:

Para ir cerrando

Como ves, no es muy complicado, es bastante rápido y sencillo. Da miedo lo largo y la cantidad de definiciones de los archivos yml, pero es un poco la gracia de Kubernetes, que sea algo completamente modular y que esos "módulos" puedan escalar, cambiar o ser diferentes dependiendo de nuestras necesidades.

Les dejo la receta lista para usar, solo tienen que cambiar su email en la configuración de Traefik y el nombre de dominio en el hello.yml.

xe-nvdk/easy-kubernetes-cookbook
This is my cookbook to run things on Kubernetes... Take a look, take wherever you need, and contribute it if you want it. - xe-nvdk/easy-kubernetes-cookbook

¿Qué te pareció este artículo? ¿Te sirvió? ¿Tienes alguna duda o comentario? Puedes dejarla aquí abajo.

Buena semana.