
Al construir el proceso de CI/CD utilizando Kubernetes, a veces surge el problema de la incompatibilidad entre los requisitos de la nueva infraestructura y la aplicación que se está trasladando a ella. En particular, en la etapa de construcción de la aplicación, es importante obtener uno una imagen que se utilizará en todas los entornos y clústeres del proyecto. Este principio es la base de una correcta gestión de contenedores (esto ya lo ha mencionado nuestro director técnico).
Sin embargo, no es raro ver situaciones en las que el código del sitio utiliza un framework listo para usar, cuyo uso impone restricciones sobre su futura explotación. Y si en un 'entorno normal' esto se puede manejar fácilmente, en Kubernetes un comportamiento así puede convertirse en un problema, especialmente cuando te enfrentas a ello por primera vez. Aunque una mente ingeniosa puede ofrecer soluciones de infraestructura que parecen obvias e incluso buenas a primera vista... es importante recordar que la mayoría de las situaciones pueden y deben ser tratadas arquitectónicamente..
Analizaremos soluciones de workaround populares para el almacenamiento de archivos, que pueden llevar a consecuencias desagradables durante la explotación del clúster, y también señalaremos un camino más adecuado.
Almacenamiento de estáticos
Para ilustrar, consideremos una aplicación web que utiliza un generador de estáticos para obtener un conjunto de imágenes, estilos y otros. Por ejemplo, en el framework PHP Yii hay un gestor de activos integrado que genera nombres de directorios únicos. En consecuencia, al final obtenemos un conjunto de rutas que no se cruzan entre sí para los estáticos del sitio (esto se hace por varias razones, como para evitar duplicados al usar el mismo recurso en múltiples componentes). Así, de forma predeterminada, al primer acceso al módulo del recurso web, se genera y distribuye la estática (en realidad, a menudo son symlinks, pero hablaremos de esto más adelante) con un catálogo raíz común único para este despliegue:
-
webroot/assets/2072c2df/css/… -
webroot/assets/2072c2df/images/… -
webroot/assets/2072c2df/js/…
¿Cuáles son las implicaciones de esto en el contexto del clúster?
Un ejemplo simple
Tomemos un caso bastante común, donde nginx se encuentra delante de PHP para servir estáticos y procesar solicitudes simples. La forma más sencilla es Deployment con dos contenedores:
apiVersion: apps/v1
kind: Deployment
metadata:
name: site
spec:
selector:
matchLabels:
component: backend
template:
metadata:
labels:
component: backend
spec:
volumes:
- name: nginx-config
configMap:
name: nginx-configmap
containers:
- name: php
image: own-image-with-php-backend:v1.0
command: ["/usr/local/sbin/php-fpm","-F"]
workingDir: /var/www
- name: nginx
image: nginx:1.16.0
command: ["/usr/sbin/nginx", "-g", "daemon off;"]
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/conf.d/default.conf
subPath: nginx.confDe manera simplificada, la configuración de nginx se reduce a lo siguiente:
apiVersion: v1
kind: ConfigMap
metadata:
name: "nginx-configmap"
data:
nginx.conf: |
server {
listen 80;
server_name _;
charset utf-8;
root /var/www;
access_log /dev/stdout;
error_log /dev/stderr;
location / {
index index.php;
try_files $uri $uri/ /index.php?$args;
}
location ~ .php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
}
} La primera vez que se accede al sitio, los activos aparecen en el contenedor con PHP. Sin embargo, en el caso de dos contenedores dentro de un solo pod, nginx no conoce estos archivos estáticos que (según la configuración) deben ser entregados precisamente por él. Como resultado, para todas las solicitudes a archivos CSS y JS, el cliente verá un error 404. La solución más simple aquí sería organizar un directorio común para los contenedores. Una opción primitiva es un directorio compartido. emptyDir:
apiVersion: apps/v1
kind: Deployment
metadata:
name: site
spec:
selector:
matchLabels:
component: backend
template:
metadata:
labels:
component: backend
spec:
volumes:
- name: assets
emptyDir: {}
- name: nginx-config
configMap:
name: nginx-configmap
containers:
- name: php
image: own-image-with-php-backend:v1.0
command: ["/usr/local/sbin/php-fpm","-F"]
workingDir: /var/www
volumeMounts:
- name: assets
mountPath: /var/www/assets
- name: nginx
image: nginx:1.16.0
command: ["/usr/sbin/nginx", "-g", "daemon off;"]
volumeMounts:
- name: assets
mountPath: /var/www/assets
- name: nginx-config
mountPath: /etc/nginx/conf.d/default.conf
subPath: nginx.confAhora los archivos estáticos generados en el contenedor se entregan correctamente por nginx. Pero recuerdo que esta es una solución primitiva, así que está lejos de ser ideal y tiene sus matices y fallos, que se detallan a continuación.
Almacenamiento más avanzado
Ahora imaginemos una situación en la que un usuario accede al sitio, carga una página con los estilos existentes en el contenedor, y mientras lee esa página, hemos desplegado nuevamente el contenedor. En el directorio de activos se ha quedado vacío y se requiere una solicitud a PHP para iniciar la generación de nuevos. Sin embargo, incluso después de esto, los enlaces a la antigua estática no serán válidos, lo que llevará a errores en la visualización de la estática.
Además, probablemente tengamos un proyecto más o menos cargado, lo que significa que una sola copia de la aplicación no será suficiente:
- Escalaremos Deployment a dos réplicas.
- En la primera solicitud al sitio, se crearon activos en una réplica.
- En algún momento, el ingress decidió (con el fin de balancear la carga) enviar la solicitud a la segunda réplica, y allí esos activos aún no están. O puede que ya no estén, porque estamos usando
RollingUpdatey actualmente estamos haciendo el despliegue.
En resumen, el resultado son nuevamente errores.
Para no perder los antiguos activos, se puede cambiar emptyDir en hostPath, almacenando la estática físicamente en un nodo del clúster. Este enfoque es malo porque en realidad tenemos que vincular nuestra aplicación a un nodo específico del clúster ya que, en caso de movernos a otros nodos, el directorio no contendrá los archivos necesarios. O se requeriría una sincronización de fondo del directorio entre nodos.
¿Cuáles son las posibles soluciones?
- Si el hardware y los recursos lo permiten, se puede usar para organizar un directorio accesible de manera uniforme para las necesidades de la estática. se recomienda discos SSD, al menos una replicación triple y una conexión 'gorda' estable entre los nodos del clúster.
- Una opción menos exigente sería organizar un servidor NFS. Sin embargo, entonces hay que tener en cuenta el posible aumento del tiempo de respuesta en el procesamiento de solicitudes por parte del servidor web, y la resistencia a fallos dejará mucho que desear. Las consecuencias de una falla son catastróficas: la pérdida de un mount condena al clúster a la muerte bajo la presión de la carga LA, que se dispara al cielo.
Además de todo lo anterior, para todas las opciones de creación de almacenamiento permanente se requerirá una limpieza de fondo de conjuntos de archivos obsoletos acumulados durante un cierto período de tiempo. Delante de los contenedores con PHP se puede colocar un DaemonSet de nginx en caché, que almacenará copias de los activos durante un tiempo limitado. Este comportamiento se puede configurar fácilmente mediante proxy_cache con la profundidad de almacenamiento en días o gigabytes de espacio en disco.
Combinar este método con los sistemas de archivo distribuidos mencionados anteriormente abre un enorme campo de posibilidades, con limitaciones solo en el presupuesto y en el potencial técnico de quienes lo implementen y mantengan. Por experiencia, diremos que cuanto más simple sea el sistema, más estable funcionará. Al añadir capas como estas, mantener la infraestructura se vuelve mucho más complicado, y con ello aumenta el tiempo dedicado al diagnóstico y recuperación en caso de fallos.
Recomendación
Si la implementación de las opciones de almacenamiento propuestas también le parece injustificada (compleja, costosa...), vale la pena mirar la situación desde otro ángulo. Es decir, profundizar en la arquitectura del proyecto y erradicar el problema en el código, vinculándose a alguna estructura de datos estática en la imagen, con una definición clara del contenido o del procedimiento de "calentamiento" y/o precompilación de los activos en la etapa de construcción de la imagen. Así, obtenemos un comportamiento absolutamente predecible y un conjunto idéntico de archivos para todos los entornos y réplicas de la aplicación en ejecución.
Si volvemos al ejemplo concreto con el framework Yii y no profundizamos en su estructura (lo cual no es el objetivo del artículo), basta con señalar dos enfoques populares:
- Cambiar el proceso de construcción de la imagen para colocar los activos en un lugar predecible. Así lo sugieren/implementan en extensiones como .
- Definir hashes específicos para los directorios de activos, tal como se explica, por ejemplo, en (empezando desde la diapositiva №35). Por cierto, el autor de la presentación finalmente (¡y no sin razones!) aconseja después de construir los activos en el servidor de construcción, cargarlos en un almacenamiento central (como S3), delante del cual colocar un CDN.
Archivos subidos
Otro caso que seguramente será útil al trasladar la aplicación a un clúster de Kubernetes es el almacenamiento de archivos de usuario en el sistema de archivos. Por ejemplo, tenemos nuevamente una aplicación en PHP que recibe archivos a través de un formulario de carga, realiza ciertas acciones con ellos durante su ejecución y los devuelve.
El lugar donde estos archivos deben colocarse, en el contexto de Kubernetes, debe ser compartido para todas las réplicas de la aplicación. Dependiendo de la complejidad de la aplicación y de la necesidad de organizar la persistencia de estos archivos, ese lugar puede ser las opciones mencionadas anteriormente de dispositivos compartidos, pero, como vemos, tienen sus desventajas.
Recomendación
Una de las soluciones es utilizar un almacenamiento compatible con S3 (aunque sea algún tipo de categoría auto-alojada como minio). Cambiar a trabajar con S3 requerirá ajustes a nivel de código, y cómo se entregará el contenido en el front-end, ya hemos .
Sesiones de usuario
Es importante señalar por separado la organización del almacenamiento de sesiones de usuario. A menudo, esto también implica archivos en disco, lo que en el ámbito de Kubernetes puede resultar en constantes solicitudes de autorización al usuario si su solicitud se dirige a otro contenedor.
En parte, el problema se resuelve activando stickySessions en el ingress (esta característica es compatible con todos los controladores de ingress populares; más detalles en ), para vincular al usuario a un pod específico de la aplicación:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: nginx-test
annotations:
nginx.ingress.kubernetes.io/affinity: "cookie"
nginx.ingress.kubernetes.io/session-cookie-name: "route"
nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"
spec:
rules:
- host: stickyingress.example.com
http:
paths:
- backend:
serviceName: http-svc
servicePort: 80
path: /Pero esto no eliminará los problemas en despliegues posteriores.
Recomendación
Un enfoque más adecuado sería trasladar la aplicación a almacenamiento de sesiones en memcached, Redis y soluciones similares — en general, deshacerse por completo de las opciones basadas en archivos.
Conclusión
Las soluciones de infraestructura consideradas en el texto solo merecen ser aplicadas como «muletas» temporales (lo cual suena más elegantemente en inglés como workaround). Son relevantes en las primeras etapas de la migración de la aplicación a Kubernetes, pero no deben «echar raíces».
El camino recomendado es deshacerse de ellas a favor de una modificación arquitectónica de la aplicación de acuerdo con el ya bien conocido . Sin embargo, llevar la aplicación a un estado sin estado inevitablemente requerirá cambios en el código, y aquí es importante encontrar un equilibrio entre las capacidades/requerimientos del negocio y las perspectivas de implementación y mantenimiento del camino elegido.
P.D.
También puedes leer en nuestro blog:
- «»;
- «»;
- «» (de Red Hat);
- «».
Fuente: habr.com
