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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
--- 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 --- 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: kube-system --- apiVersion: v1 kind: ServiceAccount metadata: name: traefik-ingress-controller namespace: kube-system |
1 |
kubectl 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
--- 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 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 --- kind: Service apiVersion: v1 metadata: name: traefik-ingress-service namespace: kube-system spec: selector: k8s-app: traefik-ingress-lb ports: - protocol: TCP port: 80 name: web - protocol: TCP port: 8080 name: admin |
1 |
kubectl 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
--- 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: extensions/v1beta1 kind: Ingress metadata: name: traefik-web-ui namespace: kube-system spec: rules: - host: traefik-ui.minikube http: paths: - path: / backend: serviceName: traefik-web-ui servicePort: web |
1 |
kubectl 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
--- apiVersion: v1 kind: Secret metadata: name: isotope-secrets type: Opaque data: encryptionPassword: U2VjcmV0SzhzUGFzd29yZA== --- kind: Deployment apiVersion: apps/v1 metadata: name: isotope-server labels: app: isotope component: server spec: replicas: 1 selector: matchLabels: app: isotope component: server version: latest template: metadata: labels: app: isotope component: server version: latest spec: containers: - name: isotope-server image: marcnuri/isotope:server-latest imagePullPolicy: Always ports: - containerPort: 9010 env: - name: ENCRYPTION_PASSWORD valueFrom: secretKeyRef: name: isotope-secrets key: encryptionPassword livenessProbe: httpGet: path: /actuator/health port: 9010 failureThreshold: 6 periodSeconds: 5 # Use startupProbe instead if your k8s version supports it initialDelaySeconds: 60 readinessProbe: httpGet: path: /actuator/health port: 9010 failureThreshold: 2 periodSeconds: 5 # startupProbe: # httpGet: # path: /actuator/health # port: 9010 # initialDelaySeconds: 20 # failureThreshold: 15 # periodSeconds: 10 --- kind: Deployment apiVersion: apps/v1 metadata: name: isotope-client labels: app: isotope component: client spec: replicas: 1 selector: matchLabels: app: isotope component: client version: latest template: metadata: labels: app: isotope component: client version: latest spec: containers: - name: isotope-client image: marcnuri/isotope:client-latest imagePullPolicy: Always ports: - containerPort: 80 livenessProbe: httpGet: path: /favicon.ico port: 80 failureThreshold: 6 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: isotope-server spec: ports: - name: http targetPort: 9010 port: 80 selector: app: isotope component: server --- apiVersion: v1 kind: Service metadata: name: isotope-client spec: ports: - name: http targetPort: 80 port: 80 selector: app: isotope component: client --- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: isotope annotations: kubernetes.io/ingress.class: traefik traefik.frontend.rule.type: PathPrefixStrip spec: rules: - host: isotope.minikube http: paths: - path: / backend: serviceName: isotope-client servicePort: http - path: /api backend: serviceName: isotope-server servicePort: http |
1 |
kubectl 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 conIsotope
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.