En este tutorial os voy a explicar como instalar Traefik v2 como ingress de vuestro cluster de Kubernetes.
Además de instalar Traefik v2, lo configuraremos para que nos redireccione todo el tráfico http a https y nos genere los certificados automáticamente con letsencrypt.
El tutorial estará dividido en 2 partes, una en la que explico como instalar y configurar Traefik y otra en la que explico como apuntar traefik a servicios que tenemos tanto en el cluster como fuera del cluster.
Antes de empezar Primero de todo, quiero aclarar que no soy experto y quizás haya algo que se pueda hacer de otra manera y mejor, pero yo me ciño a todo lo que he ido aprendiendo de la documentación oficial y otros tutoriales.
Kubernetes and Let’s Encrypt - Traefik
Traefik Documentation
Aseguraos de que no tenéis otro ingress instalado en vuestro cluster, si es así tenéis dos opciones: eliminarlo o usar un puerto distinto al 80 y 443.
Instalación de Traefik v2 Para el tutorial usaré el namespace default para instalarlo todo, vosotros podéis usar el que queráis.
Primero de todo vamos a crear una carpeta donde vamos a tener los yaml de todos los componentes para Traefik. Tenemos que crear y guardar los siguientes archivos:
RBAC.yaml
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
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/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
Esto es la asignación de permisos para traefik para que pueda administrar ciertas cosas de nuestro cluster. CRD.yaml
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
Esto creará los recursos que necesita Traefik para funcionar. Deployment.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-traefik
spec:
capacity:
storage: 2Gi
volumeMode: Filesystem
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: storage-nfs
mountOptions:
- nfsvers=4.1
nfs:
path: /mnt/SSD/kubernetes/traefik-prod
server: 10.0.4.20
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
finalizers:
- kubernetes.io/pvc-protection
labels:
app: traefik-ingress-lb
name: pvc-traefik
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 2Gi
storageClassName: storage-nfs
volumeName: pv-traefik
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: traefik-ingress-controller
labels:
k8s-app: traefik-ingress-lb
kubernetes.io/cluster-service: "true"
spec:
selector:
matchLabels:
k8s-app: traefik-ingress-lb
template:
metadata:
labels:
k8s-app: traefik-ingress-lb
name: traefik-ingress-lb
spec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
serviceAccountName: traefik-ingress-controller
terminationGracePeriodSeconds: 60
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- image: traefik:v2.2
name: traefik-ingress-lb
imagePullPolicy: Always
volumeMounts:
- mountPath: "/cert/"
name: cert
resources:
requests:
cpu: 120m
memory: 80Mi
args:
#- --log.level=DEBUG
- --api.insecure
- --serversTransport.insecureSkipVerify=true
- --accesslog
- --providers.kubernetescrd
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.udpep.address=:9000/udp
- --entrypoints.tcpep.address=:8000
- [email protected]
- --certificatesresolvers.default.acme.storage=/cert/acme.json
- --certificatesresolvers.default.acme.tlschallenge
# Please note that this is the staging Let's Encrypt server.
# Once you get things working, you should remove that whole line altogether.
# - --certificatesresolvers.default.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
# Production Let's Encrypt server.
- --certificatesresolvers.default.acme.caserver=https://acme-v02.api.letsencrypt.org/directory
volumes:
- name: cert
persistentVolumeClaim:
claimName: pvc-traefik
---
apiVersion: v1
kind: Service
metadata:
name: traefik
spec:
type: LoadBalancer
externalTrafficPolicy: Local
selector:
app: traefik
ports:
- protocol: TCP
port: 80
name: web
targetPort: 80
- protocol: TCP
port: 443
name: websecure
targetPort: 443
- protocol: TCP
port: 8080
name: admin
targetPort: 8080
- protocol: TCP
port: 8000
name: tcpep
targetPort: 8000
---
apiVersion: v1
kind: Service
metadata:
name: traefikudp
spec:
type: LoadBalancer
externalTrafficPolicy: Local
selector:
app: traefik
ports:
- protocol: UDP
port: 9000
name: udpep
targetPort: 9000
Esto es la instalación de Traefik en si con su dashboard Como podéis ver, en Deployment.yaml, al principio creo un PV y un PVC NFS para guardar todos los certificados. No sería posible usar Longhorn dado que habrá un ingress en cada nodo, y recordemos que Longhorn no permite RWM en sus volúmenes (Read Write Many).
A parte de eso solo tendréis que modificar vuestro email en la linea 81. Y los puertos 80 y 443 en caso de que tengáis otro ingress en el cluster y no lo hayáis eliminado.
Por último añadir estos 2 archivos más que son para la redirección HTTPS y parámetros de seguridad para el SSL:
HTTPSredirect.yaml
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
namespace: default
name: https-redirect
spec:
redirectScheme:
scheme: https
TLSOptions.yaml
apiVersion: traefik.containo.us/v1alpha1
kind: TLSOption
metadata:
name: secure-tls-option
namespace: default
spec:
minVersion: VersionTLS12
cipherSuites:
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 # TLS 1.2
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 # TLS 1.2
- TLS_AES_256_GCM_SHA384 # TLS 1.3
- TLS_CHACHA20_POLY1305_SHA256 # TLS 1.3
curvePreferences:
- CurveP521
- CurveP384
sniStrict: true
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: security
spec:
headers:
frameDeny: true
sslRedirect: true
browserXssFilter: true
contentTypeNosniff: true
#HSTS
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 31536000
useXForwardedFor: true
customFrameOptionsValue: "SAMEORIGIN"
referrerPolicy: "same-origin"
Una vez lo tengamos todo listo, entramos a la carpeta y ejecutamos:
kubectl apply -f .
Y con esto ya tendremos Traefik funcionando. Si vamos a http://IP_de_un_worker:8080/ veremos la interfaz de Traefik con los servicios que tengamos creados (por ahora ninguno).
Servicios de Kubernetes con Traefik como ingress Para esto os pondré una plantilla y ya vosotros la adaptáis como queráis a vuestro caso concreto, en mi caso os pondré de ejemplo el ingress y el deployment de Ghost.
Ghost.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: ghost
namespace: ghost
spec:
storageClassName: longhorn
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ghost
namespace: ghost
labels:
app: ghost
tier: frontend
host: byted
spec:
replicas: 1
selector:
matchLabels:
app: ghost
tier: frontend
host: byted
template:
metadata:
namespace: ghost
labels:
app: ghost
tier: frontend
host: byted
spec:
containers:
- name: ghost
image: ghost
imagePullPolicy: Always
ports:
- containerPort: 2368
volumeMounts:
- name: content
mountPath: /var/lib/ghost/content
env:
- name: url
value: "https://byted.xyz"
volumes:
- name: content
persistentVolumeClaim:
claimName: ghost
Ghost-Service-Proxy.yaml
apiVersion: v1
kind: Service
metadata:
name: ghost
namespace: ghost
spec:
ports:
- protocol: TCP
name: web
port: 2368
selector:
app: ghost
tier: frontend
host: byted
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
namespace: default
name: ghost-ingress
spec:
entryPoints:
- web
routes:
- kind: Rule
match: HostRegexp(`{host:.+}`)
services:
- name: ghost
namespace: ghost
port: 2368
middlewares:
- name: https-redirect
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
namespace: default
name: ghost-ingress-tls
spec:
entryPoints:
- websecure
routes:
- kind: Rule
match: Host(`byted.xyz`) || Host(`www.byted.xyz`)
services:
- name: ghost
namespace: ghost
port: 2368
tls:
certResolver: default
options:
name: secure-tls-option
namespace: default
El primer bloque corresponde al servicio de ghost en si, el segundo bloque corresponde a la redirección HTTP a HTTPS y el tercer bloque corresponde al ingress HTTPS. Y como podréis ver de la linea 51 a 53 añadimos las opciones de seguridad para el certificado.
Una vez tengamos los dos archivos guardados, entramos en la carpeta donde los hayamos guardado y hacemos:
kubectl apply -f Ghost.yaml
kubectl apply -f Ghost-Service-Proxy.yaml
Antes de aplicar las opciones de seguridad:
Después de aplicar las opciones de seguridad:
Con esto nos ya tendremos nuestra app lista y funcionando con un certificado SSL con puntuación A en el test de SSL Labs (en un futuro explicaré como reforzarlo todavía más para conseguir A+, pero eso ya depende del DNS de cada uno, no de Traefik).
SSL Server Test (Powered by Qualys SSL Labs)
A comprehensive free SSL test for your public web servers.
Servicios externos con Traefik como ingress En caso de tener un servicio ajeno a Kubernetes, también podemos usar Traefik para exponerlo y generar un certificado SSL.
Si el servicio que tenemos corre bajo HTTP se haría de la siguiente manera:
External-Service.yaml
apiVersion: v1
kind: Service
metadata:
name: external
namespace: default
spec:
ports:
- name: http
port: 80
type: ExternalName
externalName: 10.0.2.24
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
namespace: default
name: external-ingress
spec:
entryPoints:
- web
routes:
- kind: Rule
match: HostRegexp(`{host:.+}`)
services:
- name: external
port: 80
middlewares:
- name: https-redirect
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
namespace: default
name: external-ingress-tls
spec:
entryPoints:
- websecure
routes:
- kind: Rule
match: Host(`external.byted.xyz`)
services:
- name: external
port: 80
middlewares:
- name: security
tls:
certResolver: default
options:
name: secure-tls-option
namespace: default
El primer bloque corresponde al servicio de ghost en si, el segundo bloque corresponde a la redirección HTTP a HTTPS y el tercer bloque corresponde al ingress HTTPS. Donde 10.0.2.24 es la IP del host que tiene nuestro servicio y el puerto 80 en todo momento es el puerto donde está el servicio.
Por otro lado si tenemos un servicio que corre bajo HTTPS, se haría de una manera un poco distinta para indicarle a Traefik que es un servicio HTTPS y no HTTP.
External-Service.yaml
apiVersion: v1
kind: Service
metadata:
name: external
namespace: default
spec:
ports:
- name: https
port: 8443
type: ExternalName
externalName: 10.0.2.24
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
namespace: default
name: external-ingress
spec:
entryPoints:
- web
routes:
- kind: Rule
match: HostRegexp(`{host:.+}`)
services:
- name: external
port: 8443
scheme: https
middlewares:
- name: https-redirect
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
namespace: default
name: external-ingress-tls
spec:
entryPoints:
- websecure
routes:
- kind: Rule
match: Host(`external.byted.xyz`)
services:
- name: external
port: 8443
scheme: https
tls:
certResolver: default
options:
name: secure-tls-option
namespace: default
El primer bloque corresponde al servicio de ghost en si, el segundo bloque corresponde a la redirección HTTP a HTTPS y el tercer bloque corresponde al ingress HTTPS. Donde 10.0.2.24 es la IP del host que tiene nuestro servicio y el puerto 8443 en todo momento es el puerto donde está el servicio.
Una vez tengamos el archivo guardado, ya sea el HTTP o el HTTPS, entramos en la carpeta donde los hayamos guardado y hacemos:
kubectl apply -f External-Service.yaml
Y con esto doy concluido el tutorial. Cualquier pregunta o duda estoy a vuestra disposición.