Traefik como Ingress Controller en Kubernetes

Hace tiempo te estuve hablando del papel que jugaba el recurso Ingress Controller dentro del ecosistema de Kubernetes. En artículos anteriores te conté cómo hacer uso de Nginx para desarollar esta función e incluso cómo jugar con diferentes hostnames apoyándome en nip.io. Hoy quiero mostrarte cómo usar Traefik para este mismo objetivo.

Instalación de Traefik en el clúster de Kubernetes

Para mostrarte cómo funciona Traefik voy a usar un clúster de Kubernetes en Azure. Si todavía no tienes uno puedes crearlo con los siguiente comandos:

#Variables
RESOURCE_GROUP="traefik-aks"
LOCATION="northeurope"
AKS_NAME="traefik-demo"

#Create resource group
az group create -n $RESOURCE_GROUP -l $LOCATION

# Create AKS cluster
az aks create -n $AKS_NAME -g $RESOURCE_GROUP --node-count 1 --generate-ssh-keys

#Get the context for the new AKS
az aks get-credentials -n $AKS_NAME -g $RESOURCE_GROUP

Para poder utilizar Traefik necesitas crear una serie de recursos: el primero de ellos es una Service Account sobre la que aplicaremos después los permisos necesarios:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: traefik-ingress-controller
  namespace: kube-system

También hay que definir qué es lo que quiero que haga esta service account, en este ejemplo usando un ClusterRole, que básicamente es visualizar lo relativo a servicios, sus endpoints y secretos:

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: traefik-ingress-controller
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

Ahora, para asociar estos roles al service account anterior podemos usar un ClusterRoleBinding para que sea a nivel global del clúster y no asociado a un namespace en concreto (en el caso de que prefieras esto segundo deberías crear un RoleBinding en su lugar):

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
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: kube-system

Por último, falta desplegar el Ingress Controller de Traefik. Como se comenta en su documentación, puedes hacerlo de dos formas: usando Deployments o Daemonsets. En este ejemplo lo he desplegado con un Daemonset:

kind: DaemonSet
apiVersion: apps/v1
metadata:
  name: traefik-ingress-controller
  namespace: kube-system
  labels:
    k8s-app: traefik-ingress-lb
spec:
  selector:
    matchLabels:
      k8s-app: traefik-ingress-lb
      name: traefik-ingress-lb
  template:
    metadata:
      labels:
        k8s-app: traefik-ingress-lb
        name: traefik-ingress-lb
    spec:
      serviceAccountName: traefik-ingress-controller
      terminationGracePeriodSeconds: 60
      containers:
      - image: traefik:v1.7
        name: traefik-ingress-lb
        ports:
        - name: http
          containerPort: 80
          hostPort: 80
        - name: admin
          containerPort: 8080
          hostPort: 8080
        securityContext:
          capabilities:
            drop:
            - ALL
            add:
            - NET_BIND_SERVICE
        args:
        - --api
        - --kubernetes
        - --logLevel=INFO

Para finalizar con la configuración, necesitamos un recurso del tipo Service que será el punto de entrada a nuestro clúster utilizando Traefik:

kind: Service
apiVersion: v1
metadata:
  name: traefik-ingress-service
  namespace: kube-system
spec:
  type: LoadBalancer
  selector:
    k8s-app: traefik-ingress-lb
  ports:
    - protocol: TCP
      port: 80
      name: web

Mi ejemplo difiere de la configuración mostrada en la documentación en que el tipo de servicio es LoadBalancer, para que en este caso Azure me provea una IP pública (20.82.157.204) asociada al mismo.

Si has seguido todos estos pasos ya tienes Traefik configurado en tu clúster como Ingress Controller. Lo único que necesitas es crear Ingress Resources asociados a los Servicios que tiene que enrutar en base a las reglas que establezcas.

Ejemplo 1

Para poder probar Traefik puedes desplegar este ejemplo con la imagen de traefik/whoami.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: whoami
spec:
  selector:
    matchLabels:
      app: whoami
  replicas: 3
  template:
    metadata:
      labels:
        app: whoami
    spec:
      containers:
        - name: whoami
          image: traefik/whoami
          ports:
            - containerPort: 80

---
apiVersion: v1
kind: Service
metadata:
  name: whoami
spec:
  selector:
    app: whoami
  ports:
    - port: 7070
      targetPort: 80

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: whoami-ingress
  labels:
    name: whoami-ingress
spec:
  rules:
    - host: whoami.20.82.157.204.nip.io
      http:
        paths:
          - pathType: "Prefix"
            path: "/"
            backend:
              service:
                name: whoami
                port:
                  number: 7070

Utilizo un recurso de tipo Ingress para asociar un host a una ruta concreta y el servicio, haciendo uso de nip.io. En este caso si accedes a http://whoami.TU_IP.nip.io te llevará al servicio llamado whoami accediendo a través del puerto 7070, donde verás la siguiente información:

Ingress demo con la imagen whoami de Traefik

Ejemplo 2

Para finalizar, voy a configurar el acceso al UI de Traefik, que forma parte del DaemonSet desplegado, que nos da de forma visual información sobre todos los Ingress configurados en el clúster. Sin embargo, para este ejemplo voy a querer que, además de enrutarme correctamente al servicio y por lo tanto al despliegue de esta interfaz web, el usuario tenga que autenticarse. Para ello, voy a crear antes un secreto con las credenciales para este acceso. Como las contraseñas tienen que estar hasheadas en MD5, SHA1 o BCrypt puedes usar htpassword con el siguiente formato:

#Create a secret for the basic authentication
htpasswd -c auth gis

Esto me generará un archivo con las credenciales del usuario gis con el siguiente aspecto:

gis:$apr1$WsmLAtfK$ndZnFVqE1U26.ziSAIOur.

Por último, puedes crear el secreto utilizando kubectl y haciendo uso de este nuevo archivo:

kubectl create secret generic traefik-web-ui-secret --from-file=auth -n kube-system

Como el servicio está desplegado en kube-system, este secreto y el recurso Ingress también tienen que estar en el mismo namespace. Para configurar estas credenciales, debo añadir la configuración en forma de anotaciones.

apiVersion: v1
kind: Service
metadata:
  name: traefik-web-ui
  namespace: kube-system
spec:
  selector:
    k8s-app: traefik-ingress-lb
  ports:
    - name: web
      port: 80
      targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: traefik-web-ui
  namespace: kube-system
  annotations:
    traefik.ingress.kubernetes.io/auth-type: "basic"
    traefik.ingress.kubernetes.io/auth-secret: "traefik-web-ui-secret"
spec:
  rules:
    - host: traefik-ui.20.82.157.204.nip.io
      http:
        paths:
          - pathType: "Prefix"
            path: /
            backend:
              service:
                name: traefik-web-ui
                port:
                  name: web

Una vez que accedas a http://traefik-ui.TU_IP.nip.io e introduzcas las credenciales que has elegido podrás ver la interfaz que proporciona Traefik para ver las reglas configuradas:

Los frontends son las reglas que hemos definido y los backends son los recursos de tipo Service donde se muestran ademas los endpoints asociados a cada uno.

¡Saludos!