Isotope Mail: Cómo desplegar Isotope+Traefik en Kubernetes
Introducción
Isotope mail client es una aplicación webmail de código libre y uno de los proyectos personales en los que he invertido más tiempo durante el pasado año. Puedes leer más información acerca de las funcionalidades de Isotope en una publicación anterior.
A pesar de que todavía no hay una versión oficial, la aplicación está bastante estable. En este post describiré la forma en la que se puede desplegar Isotope un un clúster de Kubernetes. En el tutorial se ha empleado minikube + kubectl, pero los mismos pasos podrían reproducirse en un clúster de K8s en producción.
Traefik v1
Pese a que no forma parte de la implementación, Traefik (o cualquiera de sus alternativas) es una pieza fundamental del despliegue ya que actuará como API gateway (reverse-proxy) enrutando cada una delas peticiones al componente/servicio pertinente de Isotope.
El primer paso es crear un Ingress controller para Traefik (en el caso en el que todavía no haya ninguno en el clúster). Seguiremos la documentación oficial de Traefik para crear el nuevo Ingress controller.
Role Based Access Control configuration (Sólo Kubernetes 1.6+)
A partir de la versión 1.6 de Kubernetes, se ha introducido Role Based Access Control (RBAC) para permitir un control de acceso más granular a recursos basándose en los roles de usuarios individuales.
Para permitir que Traefik pueda acceder a la API global de Kubernetes es necesario crear un ClusterRole y un ClusterRoleBinding.
1---
2kind: ClusterRole
3apiVersion: rbac.authorization.k8s.io/v1beta1
4metadata:
5 name: traefik-ingress-controller
6rules:
7 - apiGroups:
8 - ""
9 resources:
10 - services
11 - endpoints
12 - secrets
13 verbs:
14 - get
15 - list
16 - watch
17 - apiGroups:
18 - extensions
19 resources:
20 - ingresses
21 verbs:
22 - get
23 - list
24 - watch
25 - apiGroups:
26 - extensions
27 resources:
28 - ingresses/status
29 verbs:
30 - update
31---
32kind: ClusterRoleBinding
33apiVersion: rbac.authorization.k8s.io/v1beta1
34metadata:
35 name: traefik-ingress-controller
36roleRef:
37 apiGroup: rbac.authorization.k8s.io
38 kind: ClusterRole
39 name: traefik-ingress-controller
40subjects:
41 - kind: ServiceAccount
42 name: traefik-ingress-controller
43 namespace: kube-system
44---
45apiVersion: v1
46kind: ServiceAccount
47metadata:
48 name: traefik-ingress-controller
49 namespace: kube-system
1kubectl apply -f https://raw.githubusercontent.com/manusa/isotope-mail/master/deployment-examples/k8s/traefik.rbac.yaml
Desplegar Traefik empleando un DaemonSet
El siguiente paso es desplegar el ingress controller de Traefik utilizando un DaemonSet.
Si seguimos la documentación oficial de Traefik’, veremos que este paso también se puede conseguir empleando un Deployment. Para este tutorial y debido a que estamos usando Minikube, utilizaremos un DaemonSet ya que es más fácil de configurar y exponer con Minikube (Minikube tiene problemas a la hora de asignar IPs externas a un Ingress que utilice un Ingress controller de Traefik desplegado con un Deployment).
1---
2kind: DaemonSet
3apiVersion: apps/v1
4metadata:
5 name: traefik-ingress-controller
6 namespace: kube-system
7 labels:
8 k8s-app: traefik-ingress-lb
9spec:
10 selector:
11 matchLabels:
12 k8s-app: traefik-ingress-lb
13 template:
14 metadata:
15 labels:
16 k8s-app: traefik-ingress-lb
17 name: traefik-ingress-lb
18 spec:
19 serviceAccountName: traefik-ingress-controller
20 terminationGracePeriodSeconds: 60
21 containers:
22 - image: traefik:v1.7
23 name: traefik-ingress-lb
24 ports:
25 - name: http
26 containerPort: 80
27 hostPort: 80
28 - name: admin
29 containerPort: 8080
30 hostPort: 8080
31 securityContext:
32 capabilities:
33 drop:
34 - ALL
35 add:
36 - NET_BIND_SERVICE
37 args:
38 - --api
39 - --kubernetes
40 - --logLevel=INFO
41---
42kind: Service
43apiVersion: v1
44metadata:
45 name: traefik-ingress-service
46 namespace: kube-system
47spec:
48 selector:
49 k8s-app: traefik-ingress-lb
50 ports:
51 - protocol: TCP
52 port: 80
53 name: web
54 - protocol: TCP
55 port: 8080
56 name: admin
1kubectl apply -f https://raw.githubusercontent.com/manusa/isotope-mail/master/deployment-examples/k8s/traefik.ds.yaml
Traefik UI
De forma opcional podemos crear un Servicio y un Ingress para el dashboard de Traefik para poder monitorizar el despliegue del nuevo DaemonSet.
1---
2apiVersion: v1
3kind: Service
4metadata:
5 name: traefik-web-ui
6 namespace: kube-system
7spec:
8 selector:
9 k8s-app: traefik-ingress-lb
10 ports:
11 - name: web
12 port: 80
13 targetPort: 8080
14---
15apiVersion: extensions/v1beta1
16kind: Ingress
17metadata:
18 name: traefik-web-ui
19 namespace: kube-system
20spec:
21 rules:
22 - host: traefik-ui.minikube
23 http:
24 paths:
25 - path: /
26 backend:
27 serviceName: traefik-web-ui
28 servicePort: web
1kubectl apply -f https://raw.githubusercontent.com/manusa/isotope-mail/master/deployment-examples/k8s/traefik.ui.yaml
Es importante resaltar que para la configuración del Ingress estamos empleando traefik-ui.minikube
como el host público. En un entorno de producción deberíamos de añadir el hostname real. En este sentido, en nuestro entorno de desarrollo, es necesario añadir la IP de nuestro clúster de Kubernetes local (minikube ip
/ kubectl get ingress
) en nuestro fichero de hosts del sistema (/etc/hosts
).
Por último, podemos apuntar nuestro navegador hacia traefik-ui.minikube y cargar el dashboard de Traefik.
Isotope
El último paso del tutorial es desplegar Isotope.
1---
2apiVersion: v1
3kind: Secret
4metadata:
5 name: isotope-secrets
6type: Opaque
7data:
8 encryptionPassword: U2VjcmV0SzhzUGFzd29yZA==
9---
10kind: Deployment
11apiVersion: apps/v1
12metadata:
13 name: isotope-server
14 labels:
15 app: isotope
16 component: server
17spec:
18 replicas: 1
19 selector:
20 matchLabels:
21 app: isotope
22 component: server
23 version: latest
24 template:
25 metadata:
26 labels:
27 app: isotope
28 component: server
29 version: latest
30 spec:
31 containers:
32 - name: isotope-server
33 image: marcnuri/isotope:server-latest
34 imagePullPolicy: Always
35 ports:
36 - containerPort: 9010
37 env:
38 - name: ENCRYPTION_PASSWORD
39 valueFrom:
40 secretKeyRef:
41 name: isotope-secrets
42 key: encryptionPassword
43 livenessProbe:
44 httpGet:
45 path: /actuator/health
46 port: 9010
47 failureThreshold: 6
48 periodSeconds: 5
49 # Use startupProbe instead if your k8s version supports it
50 initialDelaySeconds: 60
51 readinessProbe:
52 httpGet:
53 path: /actuator/health
54 port: 9010
55 failureThreshold: 2
56 periodSeconds: 5
57# startupProbe:
58# httpGet:
59# path: /actuator/health
60# port: 9010
61# initialDelaySeconds: 20
62# failureThreshold: 15
63# periodSeconds: 10
64---
65kind: Deployment
66apiVersion: apps/v1
67metadata:
68 name: isotope-client
69 labels:
70 app: isotope
71 component: client
72spec:
73 replicas: 1
74 selector:
75 matchLabels:
76 app: isotope
77 component: client
78 version: latest
79 template:
80 metadata:
81 labels:
82 app: isotope
83 component: client
84 version: latest
85 spec:
86 containers:
87 - name: isotope-client
88 image: marcnuri/isotope:client-latest
89 imagePullPolicy: Always
90 ports:
91 - containerPort: 80
92 livenessProbe:
93 httpGet:
94 path: /favicon.ico
95 port: 80
96 failureThreshold: 6
97 periodSeconds: 5
98---
99apiVersion: v1
100kind: Service
101metadata:
102 name: isotope-server
103spec:
104 ports:
105 - name: http
106 targetPort: 9010
107 port: 80
108 selector:
109 app: isotope
110 component: server
111---
112apiVersion: v1
113kind: Service
114metadata:
115 name: isotope-client
116spec:
117 ports:
118 - name: http
119 targetPort: 80
120 port: 80
121 selector:
122 app: isotope
123 component: client
124---
125apiVersion: extensions/v1beta1
126kind: Ingress
127metadata:
128 name: isotope
129 annotations:
130 kubernetes.io/ingress.class: traefik
131 traefik.frontend.rule.type: PathPrefixStrip
132spec:
133 rules:
134 - host: isotope.minikube
135 http:
136 paths:
137 - path: /
138 backend:
139 serviceName: isotope-client
140 servicePort: http
141 - path: /api
142 backend:
143 serviceName: isotope-server
144 servicePort: http
1kubectl apply -f https://raw.githubusercontent.com/manusa/isotope-mail/master/deployment-examples/k8s/isotope.yaml
Secret
La primera entrada en la configuración Yaml es un Kubernetes secret codificado en Base64 que se utilizará para configurar la clave simétrica de encriptado del componente Isotope Server.
Isotope Server Deployment
La siguiente entrada en el Yaml es la configuración del Deployment para el componente Isotope Server. Debido a que este deployment es más complejo que el del componente Client, la configuración común de ambos componentes se describirá en la siguiente sección (Isotope Client Deployment).
En la sección env, declaramos la variable de entrono ENCRYPTION_PASSWORD
y le asignamos el valor del Secret que hemos declarado en el paso anterior. Esta variable estará disponible para todos los Pods creados por Kubernetes a partir de la configuración de este Deployment, por tanto, todos los Pods compartirán la misma clave de encriptado y serán compatibles entre sí.
También definiremos dos probes de manera que Traefik y Kubernetes sepan cuándo los Pods del componente Isotope Server están listos y ya se puede enrutar tráfico hacia ellos. Liveness probe se empleará para determinar si el contenedor sigue “vivo”. En caso contrario, Kubernetes reiniciará el Pod ya que el estado de la aplicación se considerará “roto”. También vamos a utilizar la propiedad initialDelaySeconds
ya que Isotope Server tarda varios segundos en arrancar, de este modo podremos evitar falsos positivos. En este sentido, si la versión de Kubernetes del clúster lo soporta, es preferible definir un Startup probe (adicional) y evitar configurar la propiedad initialDelaySeconds
ya que su resultado no es determinista (la aplicación podría tardar algo más en arrancar de lo especificado).
También hemos definido un Readiness probe en esta sección. Esta sonda es similar al Liveness probe y se empleará para indicar si la aplicación esta disponible para recibir tráfico. Si por cualquier razón la aplicación no admite tráfico (número máximo de conexiones, etc.) la sonda marcará el Pod como “down” pero, a diferencia de la anterior, Kubernetes no lo reiniciará.
Para ambas sondas estamos utilizando una petición HTTP apuntando al endpoint de “health check” de Spring Actuator configurado en el componente Isotope Server.
Isotope Client Deployment
Del mismo modo que hemos hecho para el componente Servidor, ahora definiremos un Deployment para el componente Client de Isotope.
En la sección spec
definimos el número de réplicas que queremos de nuestros Pods, en este caso, una. La propiedad selector
, aunque opcional en versiones anteriores del API, ahora es obligatoria y se empleará por Kubernetes para determinar el número de Pods que hay actualmente en ejecución y arrancar más réplicas si es necesario (deberán coincidir con las “labels” de la sección template
).
La propiedad template
dentro de la sección spec
se utiliza para definir las especificaciones de los Pod. Para Isotope Cliente estamos deifiniendo un Pod de un único contenedor basado en la imagen Docker marcnuri/isotope:client-latest
y exponiendo el puerto Http.
De igual forma que para del Deployment de Isotope Server, definimos un Liveness probe para identificar si el Pod está inestable y estado “roto” para que Kubernetes pueda reiniciarlo de forma automática.
Services
La siguiente sección de la configuración Yaml define un Service para cada uno de los Deployments (server/client) de forma que estos queden expuestos al clúster.
Para facilitar la definición del Ingress en el siguiente paso, ambos servicios expondrán el puerto Http (80).
Ingress
La última sección de la configuración define un Ingress empleando el Ingress Controller que hemos desplegado en los primeros pasos del tutorial.
Vamos a emplear isotope.minikube
como public host, aunque cómo ya se ha mencionado antes, en un entorno de producción deberíamos de utilizar un hostname real (al igual que antes, tendremos que añadir una nueva entrada en el fichero de hosts del sistema /etc/hosts
).
La configuración especifica que todo el tráfico que llegue hasta http://isotope.minikube/api
debe ser enrutado por Traefik al servicio isotope-server, y todo el que llegue a http://isotope.minikube/
al servicio isotope-client.
La entradatraefik.frontend.rule.type: PathPrefixStrip
en la configuración quita /api
del path en las peticiones al servicio isotope-server, de este mode, no hay que modificar ni añadir configuraciones extra al componente Isotope Server para que sea compatible con nuestro despliegue.
Traefik dashboard con Isotope
Una vez la configuración de Isotope se haya desplegado, el dashboard de Traefik se actualizará automáticamente y mostrará las nuevas rutas para Isotope.
Isotope deployment
Si todo ha ido correctamente el dashboard de Traefik mostrará los componentes de Isotope en estado healthy y podremos apuntar nuestro navegador a http://isotope.minikube dónde Isotope estará listo y disponible.