Erhalten Sie Zugang zu diesem und mehr als 300000 Büchern ab EUR 5,99 monatlich.
Si desea crear aplicaciones con un sistema de orquestación de contenedores de la mano de auténticos expertos, ha dado con el libro indicado. Esta guía recoge las explicaciones y los consejos de cuatro profesionales que trabajan en el ámbito de Kubernetes y poseen un amplio manejo en sistemas distribuidos, desarrollo de aplicaciones empresariales y código abierto. Asimismo, muchos de los métodos que se presentan en el libro se fundamentan en experiencias de empresas que utilizan Kubernetes con éxito en la fase de producción y están respaldados con ejemplos concretos de código. Gracias a esta guía, esté o no familiarizado con los conceptos básicos de Kubernetes, aprenderá todo lo que necesita para crear las mejores aplicaciones. o Configurar y desarrollar aplicaciones con Kubernetes. o Aprender patrones para monitorizar, asegurar los sistemas, y administrar actualizaciones, implementaciones y procesos de vuelta atrás. o nComprender las políticas de red de Kubernetes y dónde encaja la red de servicios. o Integrar servicios y aplicaciones heredadas, y desarrollar plataformas del más alto nivel con Kubernetes. o Ejecutar tareas de aprendizaje automático en Kubernetes. Este libro es ideal para aquellas personas que están familiarizadas con los conceptos básicos de Kubernetes y que quieren aprender las mejores prácticas que se emplean habitualmente. Brendan Burns es un destacado ingeniero en Microsoft Azure y cofundador del proyecto de código abierto Kubernetes. Eddie Villalba es ingeniero de software en la división de Ingeniería de Software Comercial de Microsoft, y es experto en la nube de código abierto y en Kubernetes. Dave Strebel es arquitecto de la nube nativa global en Microsoft Azure, y es experto en la nube de código abierto y en Kubernetes. Lachlan Evenson es gerente principal del programa en el equipo de cómputo de contenedores en Microsoft Azure
Sie lesen das E-Book in den Legimi-Apps auf:
Seitenzahl: 386
Das E-Book (TTS) können Sie hören im Abo „Legimi Premium” in Legimi-Apps auf:
Proyectos para crear aplicaciones de éxito con Kubernetes
Brendan Burns, Eddie Villalba,Dave Strebel y Lachlan Evenson
Proyectos para crear aplicacionesde éxito con Kubernetes
Brendan Burns, Eddie Villalba,Dave Strebel y Lachlan Evenson
Edición original publicada en inglés por O’Reilly con el título Kubernetes Best Practices, ISBN 978-1-492-05647-8 © Brendan Burns, Eddie Villalba, Dave Strebel y Lachlan Evenson, 2020. This translation is published and sold by permission of O’Reilly Media, Inc., which owns or controls all rights to publish and sell the same.
Título de la edición en español:
Guía práctica de Kubernetes
Primera edición en español, 2021© 2021 MARCOMBO, S.L. www.marcombo.com
Diseño de portada: Karen Montgomery
Ilustración: Rebecca Demarest
Traducción: Francisco Martínez Carreno
Corrección: Anna AlberolaProducció del ebook: booqlab
«Cualquier forma de reproducción, distribución, comunicación pública o transformación de esta obra solo puede ser realizada con la autorización de sus titulares, salvo excepción prevista por la ley. La presente publicación contiene la opinión del autor y tiene el objetivo de informar de forma precisa y concisa. La elaboración del contenido, aunque se ha trabajado de forma escrupulosa, no puede comportar una responsabilidad específica para el autor ni el editor de los posibles errores o imprecisiones que pudiera contener la presente obra.»
ISBN: 978-84-267-3244-6
Prefacio
Reconocimientos
1. Configuración de un servicio básico
Visión general de la aplicación
Gestión de archivos de configuración
Creación de un servicio replicado mediante Deployments
Mejores prácticas para la gestión de imágenes
Creación de una aplicación replicada
Configuración de Ingress externa para tráfico HTTP
Configuración de la aplicación con ConfigMaps
Gestión de autenticación con Secrets
Despliegue de una sencilla base de datos con estado
Creación de un equilibrador de carga TCP con Services
Uso de Ingress para enrutar el tráfico a un servidor de archivos estáticos
Parametrización de la aplicación utilizando Helm
Mejores prácticas en el despliegue de servicios
Resumen
2. Flujos de trabajo para desarrolladores
Objetivos
Creación de un clúster de desarrollo
Configuración de un clúster compartido por varios desarrolladores
Registro de usuarios
Creación y dotación de seguridad a un espacio de nombres
Administración de espacios de nombres
Servicios a nivel de clúster
Habilitación de flujos de trabajo para desarrolladores
Instalación inicial
Preparación de la fase de desarrollo activo
Preparación de pruebas y depuración
Mejores prácticas en el establecimiento de un entorno de desarrollo
Resumen
3. Monitorización y recopilación de registros en Kubernetes
Métricas versus registros
Técnicas de monitorización
Formas de monitorización
Visión general de las métricas en Kubernetes
cAdvisor
Servidor de métricas
kube-state-metrics
¿Qué métricas debemos monitorizar?
Herramientas de monitorización
Monitorización en Kubernetes con Prometheus
Descripción general de la recopilación de registros
Herramientas para la recopilación de registros
Recopilación de registros mediante la pila EFK
Alertas
Mejores prácticas para monitorización, recopilación de registros y alertas
Monitorización
Recopilación de registros
Alertas
Resumen
4. Configuración, Secrets y RBAC
Configuración mediante ConfigMaps y Secrets
ConfigMaps
Secrets
Mejores prácticas habituales para las API de ConfigMap y Secrets
Mejores prácticas específicas en Secrets
RBAC
Manual de RBAC
Sujetos
Reeglas
Roles
RoleBindings
Mejores prácticas de RBAC
Resumen
5. Integración continua, pruebas y despliegue
Control de versiones
Integración continua
Pruebas
Compilación de contenedores
Etiquetados de imágenes de contenedores
Despliegue continuo
Estrategias de despliegue
Pruebas en producción
Configuración de una pipeline y realización de un experimento de caos
Configuración de CI
Configuración de CD
Realización de la actualización de puesta en marcha
Un sencillo experimento de caos
Mejores prácticas para CI/CD
Resumen
6. Versionado, versiones de lanzamiento y puesta en marcha
Versionado
Versiones de lanzamiento
Puesta en marcha
Todo junto
Mejores prácticas para versionado, versiones de lanzamiento y puesta en marcha
Resumen
7. Distribución y preproducción de aplicaciones a nivel mundial
Distribución de la imagen
Parametrización del despliegue
Tráfico con equilibrio de carga a nivel mundial
Puesta en marcha confiable de software a nivel mundial
Validación previa al despliegue
Región de canario
Identificación de los tipos de región
Elaboración de la puesta en marcha a nivel global
Cuando algo sale mal
Mejores prácticas de puesta en marcha a nivel mundial
Resumen
8. Administración de recursos
Planificador de Kubernetes
Predicados
Prioridades
Técnicas avanzadas de planificación
Afinidad y antiafinidad de cápsulas
nodeSelector
Manchas y tolerancias
Administración de recursos de cápsulas
Solicitud de recursos
Límites a los recursos y calidad de servicio de cápsulas
PodDisruptionBudgets
Mínimo disponible
Máximo no disponible
Administración de recursos mediante espacios de nombres
ResourceQuota
LimitRange
Escalado de clúster
Escalado manual
Escalado automático de clúster
Escalado de aplicaciones
Escalado con HPA
HPA con métricas personalizadas
Vertical Pod Autoscaler
Mejores prácticas en la gestión de recursos
Resumen
9. Interconexión, seguridad en red y malla de servicios
Principios de red en Kubernetes
Complementos de red
Kubenet
Mejores prácticas en Kubenet
El complemento CNI
Mejores prácticas en CNI
Servicios en Kubernetes
Tipo de servicio ClusterIP
Tipo de servicio NodePort
Tipo de servicio ExternalName
Tipo de servicio LoadBalancer
Ingress y controladores Ingress
Administración del protocolo HTTP
Mejores prácticas en servicios y controladores Ingress
Políticas de seguridad de red
Mejores prácticas en política de red
Mallas de servicios
Mejores prácticas en malla de servicios
Resumen
10. Seguridad de cápsulas y contenedores
API de PodSecurityPolicy
Habilitación de PodSecurityPolicy
Anatomía de PodSecurityPolicy
Retos de PodSecurityPolicy
Políticas con incumplimientos razonables
Mucho esfuerzo
¿Están interesados nuestros desarrolladores en aprender PodSecurityPolicy?
La depuración es engorrosa
¿Confiamos en artefactos fuera de nuestro control?
Mejores prácticas en PodSecurityPolicy
Siguientes pasos en PodSecurityPolicy
Aislamiento de tareas y RuntimeClass
Utilización de RuntimeClass
Aplicaciones del tiempo de ejecución
Mejores prácticas en aislamiento de tareas y RuntimeClass
Otras consideraciones sobre la seguridad
Controladores de admisión
Herramientas de detección de intrusiones y anomalías
Resumen
11. Política y gobierno del clúster
Por qué la política y la gestión son importantes
¿En qué sentido esta política es diferente?
Motor de políticas nativas en la nube
Introducción a Gatekeeper
Ejemplos de políticas
Terminología Gatekeeper
Restricción
Rego
Plantilla de restricción
Definición de plantillas de restricción
Definición de restricciones
Replicación de datos
UX (Experiencias de usuario)
Auditoría
Familiarizándonos con Gatekeeper
Siguientes pasos en Gatekeeper
Mejores prácticas en política y gestión
Resumen
12. Administración de varios clústeres
¿Por qué varios clústeres?
Consideraciones sobre la utilización de varios clústeres en el diseño
Administración de despliegues de varios clústeres
Patrones de despliegue y administración
Enfoque de GitOps para la administración de clústeres
Herramientas de administración de varios clústeres
Federation de Kubernetes
Mejores prácticas en la gestión de un conjunto de clústeres
Resumen
13. Integración de servicios externos y Kubernetes
Importación de servicios a Kubernetes
Servicios sin selector para direcciones IP fijas
Servicios basados en CNAME para nombres DNS fijos
Enfoques basados en controlador activo
Exportación de servicios desde Kubernetes
Exportación de servicios mediante equilibradores de carga internos
Exportación de servicios en NodePorts
Integración entre máquinas externas y Kubernetes
Compartición de servicios entre Kubernetes
Herramientas de terceros
Mejores prácticas en conexión de clústeres y servicios externos
Resumen
14. Ejecución de aprendizaje automático en Kubernetes
¿Por qué Kubernetes es ideal para el aprendizaje automático?
Flujo de trabajo del aprendizaje automático
Aprendizaje automático para administradores de clúster de Kubernetes
Entrenamiento del modelo en Kubernetes
Entrenamiento del primer modelo en Kubernetes
Entrenamiento distribuido en Kubernetes
Restricciones de recursos
Planificación de particularidades
Hardware especializado
Bibliotecas, controladores y módulos de kernel
Almacenamiento
Almacenamiento y distribución del conjunto de datos entre nodos esclavos durante el entrenamiento
Puntos de control y modelos de grabación
Interconexión
Protocolos especializados
Preocupaciones del científico de datos
Mejores prácticas en aprendizaje automático en Kubernetes
Resumen
15. Creación de patrones de aplicaciones de alto nivel sobre Kubernetes
Enfoques para desarrollar abstracciones de alto nivel
Extensión de Kubernetes
Extensión de clústeres de Kubernetes
Ampliación de la experiencia de usuario de Kubernetes
Consideraciones de diseño en la creación de plataformas
Apoyo a la exportación de una imagen de contenedor
Soporte a los mecanismos existentes de servicios y descubrimiento de servicios
Mejores prácticas en la creación de plataformas de aplicaciones
Resumen
16. Gestión de aplicaciones con estado y apátridas
Volúmenes y montajes de volumen
Mejores prácticas en volúmenes
Almacenamiento en Kubernetes
PersistentVolume
PersistentVolumeClaims
Clases de almacenamiento
Interfaz de almacenamiento de contenedores y FlexVolume
Mejores prácticas en almacenamiento de Kubernetes
Aplicaciones con estado
StatefulSets
Operadores
Mejores prácticas en StatefulSet y Operators
Resumen
17. Control de admisión y autorización
Control de admisión
¿Qué son?
¿Por qué son importantes?
Tipos de controladores de admisión
Configuración de webhooks de admisión
Mejores prácticas en control de admisión
Autorización
Módulos de autorización
ABAC
RBAC
Webhook
Mejores prácticas de autorización
Resumen
18. Conclusión
Kubernetes es el estándar de facto para el desarrollo nativo en la nube. Es una potente herramienta que puede hacer que tu próxima aplicación sea más fácil de desarrollar, más rápida de desplegar y más fiable a la hora de operar. Sin embargo, para descubrir el poder de Kubernetes es necesario saber usarlo correctamente. Este libro está dirigido a cualquier persona que implemente aplicaciones del mundo real con Kubernetes y que esté interesada en aprender los diseños y las prácticas que se pueden emplear en las aplicaciones que se crean con esta herramienta.
Es importante destacar que este libro no es una introducción a Kubernetes. Suponemos que tú, lector, estás familiarizado a nivel básico con la API y con sus herramientas, y que sabes cómo crear e interactuar con el clúster de Kubernetes.
Este libro es un recurso para todos aquellos que quieran profundizar en cómo desplegar aplicaciones específicas y tareas en Kubernetes. Te será de utilidad tanto si estás a punto de desplegar tu primera aplicación con esta herramienta como si llevas años utilizando Kubernetes.
Entre los cuatro autores, acumulamos mucha experiencia ayudando a una gran cantidad de usuarios a desplegar sus aplicaciones en Kubernetes. A través de esta experiencia, hemos visto dónde encuentran más dificultades los usuarios, y los hemos ayudado a encontrar su camino hacia el éxito. Cuando nos sentamos a escribir este libro, intentamos plasmar estas experiencias para que muchas más personas pudieran aprender con las lecciones que asimilamos de la escucha de las necesidades de los usuarios. Esperamos que al ponerlo por escrito podamos difundir nuestro conocimiento y ayudarte a que puedas implementar y administrar con éxito tu aplicación en Kubernetes.
Aunque el libro se puede leer de principio a fin en una sola sesión, no es así como pretendemos que se haga. Lo diseñamos como una colección de capítulos independientes; cada capítulo ofrece una visión general completa de una tarea en particular de Kubernetes. Os animamos a que os sumerjáis en el contenido del libro para aprender sobre un tema o sobre algo en lo que tengáis un interés específico, y que luego lo dejéis para regresar a él solo cuando surja un nuevo tema.
A pesar de este enfoque independiente, hay algunos temas recurrentes a lo largo del libro. Por ejemplo, hay varios capítulos que tratan el desarrollo de aplicaciones en Kubernetes. El capítulo 2 se ocupa de los flujos de trabajo del desarrollador. El capítulo 5 trata sobre la integración continua y las pruebas. El capítulo 15 aborda la creación de plataformas de alto nivel sobre Kubernetes, y en el capítulo 16 se discute la gestión del estado de aplicaciones con estado. Además de tratar el desarrollo de aplicaciones, hay varios capítulos sobre servicios operativos en Kubernetes. El capítulo 1 se dedica a la configuración de un servicio básico, y el capítulo 3 trata la monitorización y las métricas. El capítulo 4 aborda la gestión de la configuración, mientras que el capítulo 6 se dedica a versiones y lanzamientos. El capítulo 7 se ocupa de la implantación de las aplicaciones a nivel mundial.
También hay varios capítulos sobre gestión de clústeres, incluido el capítulo 8 sobre gestión de recursos, el capítulo 9 sobre interconexión, el capítulo 10 sobre seguridad de cápsulas, el capítulo 11 sobre políticas y gobierno, el capítulo 12 sobre la administración de varios clústeres, y el capítulo 17 sobre control de admisión y autorización. Finalmente, hay varios capítulos que son totalmente independientes, que tratan sobre el aprendizaje automático (capítulo 14) y la integración con servicios externos (capítulo 13).
Aunque puede ser útil leer todos los capítulos antes de poner en práctica cualquier aspecto en el mundo real, tenemos la esperanza de que el libro se trate como un manual principal de referencia. Tiene la intención de ser una guía, ya que pone en práctica estos temas en el mundo real.
Este elemento indica un consejo o una sugerencia.
Este elemento indica una nota general.
Este elemento indica una advertencia o precaución.
Hay material complementario actualizado (ejemplos de código, ejercicios, etc.) disponible para su descarga en https://oreil.ly/KBPsample.
Si tienes alguna pregunta técnica o algún problema cuando utilices los ejemplos de código, por favor, envía un correo electrónico a [email protected].
Este libro está aquí para ayudarte a hacer tu trabajo. Los códigos de ejemplo que se facilitan en este libro puedes usarlos en tus programas y documentación. No necesitas ponerte en contacto con nosotros para pedir permiso, a menos que vayas a reproducir una parte importante del código. Por ejemplo, escribir un programa que use varios fragmentos de código de este libro no requiere permiso; vender o distribuir ejemplos de los libros de Marcombo requiere permiso; responder a una pregunta citando este libro y citar un código de ejemplo no requiere permiso; y la incorporación de una cantidad importante de código de los ejemplos de este libro en la documentación de tu producto requiere permiso.
Generalmente no pedimos que se incluya una atribución, pero apreciamos que se haga. Una atribución contiene título, autor, editor e ISBN. Por ejemplo: Guía práctica de Kubernetes de Brendan Burns, Eddie Villalba, Dave Strebel y Lachlan Evenson, Editorial Marcombo, ISBN: 978-84-267-2880-7.
Si crees que el uso por tu parte de los ejemplos de código no está justificado o no respeta los permisos otorgados más arriba, no dudes en ponerte en contacto con nosotros en [email protected].
A Brendan le gustaría dar las gracias a su maravillosa familia, Robin, Julia y Ethan, por el amor y el apoyo que le brindan en todo lo que hace; a la comunidad de Kubernetes, sin la cual nada de esto sería posible; y a sus fabulosos coautores, sin los cuales este libro no existiría.
A Dave le gustaría agradecer a su bella esposa, Jen, y a sus tres hijos, Max, Maddie y Mason, todo su apoyo. Agradece también a la comunidad de Kubernetes todos los consejos y la ayuda que ha brindado a lo largo de los años. Finalmente, le gustaría dar las gracias a sus coautores por hacer realidad esta aventura.
A Lachlan le gustaría agradecer a su esposa y a sus tres hijos su amor y su apoyo. También le gustaría dar las gracias a todos los componentes de la comunidad de Kubernetes, incluidas las maravillosas personas que han dedicado su tiempo a enseñarle a lo largo de los años. Quisiera enviar un agradecimiento especial a Joseph Sandoval por su tutoría. Y, finalmente, querría dar las gracias a sus fantásticos coautores por hacer posible este libro.
A Eddie le gustaría agradecer a su esposa, Sandra, su apoyo moral y que lo dejara desaparecer durante horas para escribir mientras ella estaba en el último trimestre de su primer embarazo. También le gustaría agradecer a su nueva hija, Giavanna, el impulso que le ha dado para seguir adelante. Finalmente, querría dar las gracias a la comunidad de Kubernetes y a sus coautores, que siempre han sido guías en su viaje para convertirse en un nativo de la nube.
A todos nos gustaría agradecer a Virginia Wilson su trabajo en el desarrollo del manuscrito y su ayuda a la hora de reunir todas nuestras ideas; y a Bridget Kromhout, Bilgin Ibryam, Roland Huß y Justin Domingus, por cuidar los detalles finales.
En este capítulo se describen las prácticas para configurar una sencilla aplicación multinivel en Kubernetes. La aplicación consta de una aplicación web básica y de una base de datos. Aunque seguramente no se trata de la aplicación más complicada, es un buen ejemplo para comenzar a orientarnos en la administración de una aplicación en Kubernetes.
La aplicación que usaremos para nuestro ejemplo no es particularmente compleja. Es un sencillo servicio de publicaciones que almacena sus datos en un backend de Redis. Tiene un servidor de archivos estáticos independiente que usa NGINX. Presenta dos rutas web en una sola URL. Una de ellas es para la interfaz de programación de aplicaciones (API) RESTful de la publicación, https://my-host.io/api, y la otra es un servidor de archivos en la URL principal, https://my-host.io. Utiliza el servicio Let’s Encrypt para administrar certificados de la capa de conexión segura (Secure Sockets Layer) (SSL). La Figura 1-1 presenta el diagrama de la aplicación. A lo largo de este capítulo vamos a ir creando esta aplicación, usando en primer lugar archivos de configuración YAML y después diagramas de Helm.
Figura 1-1Diagrama de la aplicación.
Antes de exponer en detalle cómo crear esta aplicación en Kubernetes, vale la pena discutir cómo vamos a gestionar las propias configuraciones. Con Kubernetes, todo se representa de forma declarativa. Esto significa que escribimos los estados deseados de la aplicación en el clúster (generalmente en archivos YAML o JSON) y estos estados deseados que se han declarado definen todas las partes de la aplicación. Este enfoque declarativo es mucho más conveniente que un enfoque imperativo, en el que el estado del clúster es la suma de una serie de cambios en el mismo. Si un clúster está configurado de forma imperativa, es muy complicado replicar y entender cómo el clúster ha llegado a estar en ese estado. Esto hace que sea muy difícil comprender la aplicación o recuperarse de los problemas que esta pueda tener.
Cuando se declara el estado de la aplicación, los programadores suelen preferir YAML a JSON, aunque Kubernetes soporta ambos tipos de archivo. Esto se debe a que YAML es algo menos prolijo y más editable que JSON. Sin embargo, vale la pena señalar que YAML es sensible al sangrado. A menudo, los errores en las configuraciones de Kubernetes se deben a un sangrado incorrecto en YAML. Si algo no se comporta como se espera, es aconsejable comprobar el sangrado.
Debido a que el estado declarativo contenido en estos archivos YAML sirve como fuente de verdad para la aplicación, la gestión correcta de este estado es fundamental para lograr nuestros objetivos. Cuando modifiquemos la aplicación para llevarla al estado deseado, nos interesará poder gestionar los cambios, validar que sean correctos, auditar quién realizó esos cambios y, posiblemente, si las cosas fallan poder volver al punto de partida. Afortunadamente, en el contexto de la ingeniería de software, ya hemos desarrollado las herramientas necesarias para gestionar tanto los cambios en el estado declarativo como la auditoría y el proceso de reversión. Es decir, las mejores prácticas se aplican directamente a la tarea de administrar el estado declarativo de la aplicación, en relación tanto con el control de versiones como con la revisión de código.
En la actualidad, la mayoría de los desarrolladores almacenan sus configuraciones de Kubernetes en Git. Aunque los detalles específicos del sistema de control de versiones no son importantes, en el ecosistema de Kubernetes muchas herramientas esperan archivos en un repositorio Git. Para la revisión de código hay mucha más heterogeneidad; aunque claramente GitHub es bastante popular, otros usan herramientas o servicios locales de revisión de código. Independientemente de cómo implementemos la revisión del código para la configuración de la aplicación, debemos tratarla con la misma diligencia y atención que aplicamos al control del código fuente.
Cuando se trata de diseñar el sistema de archivos de la aplicación para organizar los componentes, generalmente vale la pena usar la organización de carpetas que viene con el sistema de archivos. Por lo general, se utiliza un único directorio para incluir Application Service (servicio de la aplicación), cualquiera que sea la definición de Application Service que sea útil para el equipo de trabajo. Dentro de ese directorio, los subdirectorios se utilizan para los subcomponentes de la aplicación.
Para nuestra aplicación, presentamos los archivos de la siguiente manera:
journal/ frontend/ redis/ fileserver/
Dentro de cada directorio se encuentran los archivos YAML específicos que se necesitan para definir el servicio. Como veremos más adelante, a medida que vayamos desplegando nuestra aplicación en varias regiones o clústeres diferentes, la disposición de archivos se irá complicando.
Para describir nuestra aplicación, comenzaremos por el frontend y trabajaremos hacia abajo. La aplicación frontend para la publicación es una aplicación Node.js implementada en TypeScript. La aplicación completa ocupa demasiado espacio para incluirla en el libro. Presenta un servicio HTTP en el puerto 8080 que atiende peticiones de la ruta / api / * y usa el backend de Redis para añadir, eliminar o devolver las entradas habituales de la publicación. Esta aplicación se puede compilar en una imagen de contenedor utilizando el Dockerfile incluido y se puede enviar a nuestro repositorio de imágenes. Después, sustituimos el nombre de esta imagen en los ejemplos de YAML que vendrán a continuación.
Aunque, en general, la creación y el mantenimiento de imágenes de contenedores van más allá del alcance de este libro, vale la pena identificar algunas de las mejores prácticas para crear y dar nombre a las imágenes. El proceso de creación de imágenes puede ser vulnerable a «ataques en la cadena de suministro». En tales ataques, un usuario malintencionado inyecta código o dígitos binarios en alguna dependencia desde una fuente confiable que luego se incorpora a la aplicación. Debido al riesgo de tales ataques, es fundamental que cuando procedamos a crear las imágenes confiemos solo en proveedores de imágenes conocidos y confiables. Opcionalmente, podemos crear todas las imágenes desde cero. La creación a partir de cero es fácil para algunos lenguajes (por ejemplo, Go) que pueden crear binarios estáticos, pero es considerablemente más complicada para lenguajes interpretados como Python, JavaScript o Ruby.
Otras buenas prácticas para las imágenes se relacionan con los nombres. Aunque la versión de una imagen de contenedor en un archivo de imágenes es teóricamente mutable, debemos tratar la etiqueta de la versión como inmutable. En particular, alguna combinación de la versión semántica y el hash SHA del commit (confirmación) donde se ha creado la imagen es una buena práctica para nombrar imágenes (por ejemplo, v1.0.1-bfeda01f). Si no especificamos una versión de imagen, se usa la última por defecto. Aunque esto puede ser conveniente en la fase de desarrollo, no es una buena idea para su uso en la fase de producción porque la última cambia cada vez que se crea una nueva imagen.
Nuestra aplicación de frontend es apátrida. Para su estado depende totalmente del backend de Redis. Como consecuencia, podemos replicarla arbitrariamente sin afectar al tráfico. Aunque es poco probable que la aplicación soporte un uso a gran escala, es una buena idea que se ejecute en al menos dos réplicas para poder resolver una caída inesperada o poner en marcha una nueva versión de la aplicación sin paradas.
Aunque en Kubernetes ReplicaSet es el recurso que gestiona la replicación de una aplicación contenida en un contenedor, no es una buena práctica utilizarlo directamente. En su lugar, se utiliza el recurso Deployment (implementación). Deployment combina las capacidades de replicación de ReplicaSet con el versionado y la capacidad de realizar un despliegue por etapas. Mediante el uso de Deployment podemos utilizar las herramientas incorporadas en Kubernetes para pasar de una versión de la aplicación a la siguiente.
El recurso Deployment para nuestra aplicación tiene el siguiente aspecto:
apiVersion: extensions/v1beta1 kind: Deployment metadata: labels: app: frontend name: frontend namespace: default spec: replicas: 2 selector: matchLabels: app: frontend template: metadata: labels: app: frontend spec: containers: - image: my-repo/journal-server:v1-abcde imagePullPolicy: IfNotPresent name: frontend resources: request: cpu: "1.0" memory: "1G" limits: cpu: "1.0" memory: "1G"
Hay varias cosas a tener en cuenta en este Deployment. La primera es que utilizamos Labels (etiquetas) para identificar el Deployment, así como los ReplicaSets y las pods (cápsulas) que crea Deployment. Hemos añadido la etiqueta layer: frontend a todos estos recursos para que podamos examinar todos los recursos de una capa en particular en una sola petición. Esto lo veremos a medida que añadamos otros recursos, donde seguiremos el mismo procedimiento.
Además, hemos añadido comentarios en varias partes de YAML, aunque estos comentarios no se convierten en un recurso de Kubernetes almacenado en el servidor. Como ocurre con los comentarios en el código, sirven de ayuda para orientar a los desarrolladores que analizan la configuración por primera vez.
También debemos tener en cuenta que para los contenedores en Deployment hemos especificado tanto las peticiones de recursos de Request (solicitud) como las de Limit (límite), y hemos establecido que Request es igual a Limit. Cuando se ejecuta una aplicación, Request es la reserva de recursos que se garantiza en la máquina host (anfitriona) en la que se ejecuta. Limit indica la cantidad máxima de recursos que se le permitirá usar al contenedor. Cuando empezamos, al establecer Request igual a Limit (solicitud igual a límite) se consigue el comportamiento más previsible de la aplicación. Esta previsibilidad se produce a expensas de la utilización de recursos. Dado que la configuración en la que Request es igual a Limit evita que las aplicaciones se sobreprogramen o consuman recursos inútiles, no podremos impulsar la utilización óptima a menos que ajustemos Request y Limit muy cuidadosamente. A medida que avancemos en la comprensión del modelo de recursos de Kubernetes, podremos considerar modificar Request y Limit en la aplicación de forma independiente. Pero, en general, la mayor parte de los usuarios cree que merece la pena la estabilidad de la previsibilidad frente a lo que se reduce su utilización.
Ahora que tenemos definido el recurso Deployment, lo comprobaremos en el control de versiones y lo desplegaremos en Kubernetes:
git add frontend/deployment.yaml git commit -m "Added deployment" frontend/deployment.yaml kubectl apply -f frontend/deployment.yaml
También es una buena práctica comprobar que el contenido del clúster coincide exactamente con el contenido del control del código fuente. La mejor forma de comprobarlo es adoptar una aproximación GitOps y hacer el despliegue en producción solo desde una rama específica del control del código fuente, utilizando la automatización de Continuous Integration (integración continua) (CI)/Continuous Delivery (entrega continua) (CD). De esta manera, se garantiza que los contenidos del control del código fuente y producción coinciden. Aunque una pipeline (canalización) CI/CD completa puede parecer excesiva para una aplicación sencilla, la automatización en sí misma —independientemente de la fiabilidad que proporciona— normalmente merece la pena, a pesar del tiempo que conlleva montarla. Y CI/CD es extremadamente difícil de reequipar en una aplicación existente e implementada con un enfoque imperativo.
Hay algunas partes de esta descripción de la aplicación YAML (por ejemplo, ConfigMap y los volúmenes secret), así como la Quality of Service (calidad de servicio) de las cápsulas, que examinamos en secciones posteriores.
Los contenedores de nuestra aplicación ya están implementados, pero en este momento no es posible acceder a la aplicación. Por defecto, los recursos del clúster solo están disponibles dentro del mismo clúster. Para presentar nuestra aplicación al mundo, necesitamos crear un Service (servicio) y un balanceador de carga para proporcionar una dirección IP externa y traer tráfico a nuestros contenedores. Para la presentación externa vamos a usar dos recursos de Kubernetes. El primero es un Service que equilibra la carga de tráfico de Transmission Control Protocol (protocolo de control de transmisión) (TCP) o de User Datagram Protocol (protocolo de datagrama de usuario) (UDP). En nuestro caso, usamos el protocolo TCP. Y el segundo es un recurso Ingress (acceso), que proporciona balanceo de carga HTTP(S) con enrutamiento inteligente de peticiones basado en rutas y hosts HTTP. Con una aplicación tan sencilla como esta, te preguntarás por qué elegimos usar la Ingress más compleja. Pero, como verás en secciones posteriores, incluso esta sencilla aplicación servirá peticiones HTTP desde dos servicios diferentes. Además, al disponer de Ingress tenemos la ventaja de contar con cierta flexibilidad en caso de una futura expansión de nuestro servicio.
Antes de que se pueda definir el recurso Ingress, es necesario que exista un Service de Kubernetes al que Ingress apunte. Usaremos Labels (etiquetas) para dirigir el Service a las cápsulas que hemos creado en la sección anterior. Service es considerablemente más sencillo de definir que Deployment, y se ve de la siguiente manera:
apiVersion: v1 kind: Service metadata: labels: app: frontend name: frontend namespace: default spec: ports: - port: 8080 protocol: TCP targetPort: 8080 selector: app: frontend type: ClusterIP
Después de haber definido Service, podemos definir el recurso Ingress. A diferencia de los recursos de Service, Ingress necesita que un contenedor del controlador Ingress se ejecute en el clúster. Hay una serie de aplicaciones diferentes entre las que podemos elegir, ya sea las que proporciona el proveedor de la nube o las que se implementan utilizando servidores de código abierto. Si decidimos instalar un proveedor de Ingress de código abierto, es una buena idea utilizar el administrador de paquetes Helm (https://helm.sh) para su instalación y mantenimiento. Los proveedores de Ingress nginx o haproxy son las opciones más habituales:
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: frontend-ingress spec: rules: - http: paths: - path: /api backend: serviceName: frontend servicePort: 8080
Cada aplicación necesita un cierto grado de configuración. Esta configuración puede estar relacionada con el número de entradas de la publicación para mostrarlas por páginas, el color de un fondo en particular, una presentación especial para un día festivo o muchos otros tipos de configuraciones. Normalmente, una buena práctica a seguir es la de separar la información de la configuración de la propia aplicación.
Hay un par de razones para esta separación. La primera es que podríamos desear configurar el mismo código binario de la aplicación con diferentes configuraciones en función del escenario. Es posible, por ejemplo, que en Europa deseemos iluminar un especial de Pascua mientras que en China seguramente querremos mostrar un especial para el Año Nuevo Chino. Además de esta especialización en cuanto al entorno, hay razones de agilidad para realizar la separación. Por lo general, la versión en código binario contiene múltiples características nuevas y diferentes. Si activamos estas características mediante código, la única manera de modificar las características activas es crear y lanzar un nuevo binario, que puede ser un proceso costoso y lento.
El uso de la configuración para activar un conjunto de características significa que podemos, de forma inmediata (incluso de forma dinámica), activar y desactivar características en respuesta a las necesidades del usuario o a fallos en el código de la aplicación. Dependiendo de cada característica, se puede desplegar o replegar. Esta flexibilidad asegura que podamos progresar continuamente con la mayor parte de las características, incluso en el caso de que algunas necesiten replegarse para mejorar el rendimiento o corregir problemas.
En Kubernetes este tipo de configuración está representado por un recurso llamado ConfigMap. ConfigMap contiene pares clave/valor que representan la información de configuración o un archivo. Esta información de configuración se puede presentar a un contenedor en una cápsula, bien a través de archivos o bien mediante variables de entorno. Imaginemos que queremos configurar la aplicación de la publicación en línea para visualizar un número configurable de entradas por página. Para lograrlo, podemos definir ConfigMap como se indica a continuación:
kubectl create configmap frontend-config --from-literal=journalEntries=10
Para configurar la aplicación, presentamos la información de configuración como una variable de entorno en la propia aplicación. Para hacer esto, podemos añadir lo siguiente al recurso container en el Deployment que definimos antes:
... # The containers array in the PodTemplate inside the Deployment containers: - name: frontend ... env: - name: JOURNAL_ENTRIES valueFrom: configMapKeyRef: name: frontend-config key: journalEntries ...
Aunque esto muestra cómo podemos usar ConfigMap para configurar la aplicación, en el mundo real de Deployments puede que queramos poner en marcha cambios periódicos en la configuración con actualizaciones semanales o incluso con mayor frecuencia. Podría ser tentador hacer esto simplemente cambiando el propio ConfigMap, pero no sería una buena práctica. Hay varias razones para ello. La primera es que cambiar la configuración no desencadena una actualización de las cápsulas existentes; solo se aplica la configuración cuando la cápsula se vuelve a arrancar. Por este motivo, la actualización no se realiza correctamente y puede ser ad hoc o aleatoria.
Un enfoque más adecuado es poner un número de versión en el nombre del propio ConfigMap. En lugar de llamarlo frontend-config, lo llamamos frontendconfig-v1. Cuando queramos hacer un cambio, en lugar de actualizar el ConfigMap en uso, creamos una nueva v2 de ConfigMap y, a continuación, actualizamos el recurso Deployment de nuevo para utilizar esa configuración. Al hacerlo, se desencadena automáticamente la puesta en marcha del Deployment, utilizando las adecuadas health checking (pruebas de funcionamiento correcto) y las pausas entre cambios. Además, si alguna vez necesitamos hacer rollback (repliegue), la configuración v1 está disponible en el clúster y el rollback es tan simple como actualizar Deployment de nuevo.
Hasta ahora no hemos entrado en detalle en el servicio Redis al que se conecta nuestro frontend. Pero en cualquier aplicación real necesitamos que las conexiones entre nuestros servicios sean seguras. En parte, se trata de garantizar la seguridad de la comunicación de los usuarios y de sus datos. Además, es esencial evitar errores como, por ejemplo, conectar un frontend de desarrollo con una base de datos de producción.
La autenticación en la base de datos de Redis se realiza mediante una simple contraseña. Parecería conveniente pensar en almacenar esta contraseña en el código fuente de la aplicación, o en un archivo en la imagen, pero ninguna de estas opciones es buena idea por múltiples razones. La primera es que hemos filtrado nuestro secreto (la contraseña) en un entorno en el que no estamos necesariamente pensando en el control de acceso. Si ponemos una contraseña en el control del código fuente, estamos alineando el acceso al código fuente con el acceso a todos los datos secretos. Esto seguramente no sea correcto. Es probable que tengamos un conjunto más amplio de usuarios que puedan acceder a nuestro código fuente del que realmente debería tener acceso a nuestra instancia de Redis. De la misma manera, alguien que tiene acceso a la imagen de nuestro contenedor no necesariamente debe tener acceso a nuestra base de datos de producción.
Además de las preocupaciones sobre el control de acceso, otra razón para evitar la vinculación de los datos secretos al control del código fuente y/o a las imágenes es la parametrización. Deseamos poder utilizar el mismo código fuente e imágenes en una gran variedad de entornos (por ejemplo, desarrollo, canario y producción). Si los datos secretos están estrechamente ligados con el código fuente o con la imagen, se necesita un archivo imagen diferente (o un código diferente) para cada entorno.
Como acabamos de ver ConfigMaps en la sección anterior, el primer pensamiento podría ser el de almacenar la contraseña como si se tratara de una configuración y, a continuación, introducirla en la aplicación como una configuración específica de la misma. Estamos en lo cierto al creer que la separación de la configuración de la aplicación es la misma que la separación de los datos secretos de la aplicación. Pero la verdad es que los datos secretos son un concepto importante en sí mismo. Es probable que deseemos gestionar el control de acceso, la gestión y las actualizaciones de los datos secretos de una manera diferente a la de una configuración. Y lo que es más importante aún, queremos que nuestros desarrolladores piensen de forma diferente cuando accedan a los datos secretos y cuando accedan a la configuración. Por estas razones Kubernetes tiene incorporado el recurso Secret (secreto) para gestionar datos secretos.
Podemos crear una contraseña secreta para nuestra base de datos Redis de la siguiente manera:
kubectl create secret generic redis-passwd --from-literal=passwd=${RANDOM}
Obviamente, es posible que deseemos utilizar algo más que un número aleatorio para la contraseña. Además, es probable que tengamos interés en utilizar un servicio de gestión de secret/key (secreto/clave), ya sea a través del proveedor de cloud computing (computación en la nube) —como Microsoft Azure Key Vault— o mediante un proyecto de código abierto —como HashiCorp’s Vault—. Cuando se utilizan servicios de gestión de claves, estos generalmente tienen una integración más estrecha con los datos secretos de Kubernetes.
Por defecto, los datos secretos en Kubernetes se almacenan sin cifrar. Si deseamos almacenar datos secretos cifrados, podemos hacerlo a través de un proveedor de claves, que nos proporcione una clave que Kubernetes utilizará para cifrar todos los datos secretos en el clúster. Hay que tener en cuenta que, aunque esta acción protege las claves contra ataques directos a la base de datos etcd, necesitamos también tener la seguridad de que el acceso a través del servidor de la API de Kubernetes está debidamente protegido.
Después de haber almacenado la contraseña de Redis como un dato secreto en Kubernetes, necesitamos enlazar ese dato secreto a la aplicación en ejecución cuando se implemente en Kubernetes. Para hacer esto, podemos usar un Volume (volumen) de Kubernetes. Un Volume es un archivo o directorio que puede montarse en un contenedor en ejecución en un lugar especificado por el usuario. En el caso de datos secretos, Volume se crea como un sistema de archivos con respaldo de RAM tmpfs y, luego, se monta en el contenedor. Esto asegura que, incluso si la máquina está físicamente comprometida (bastante improbable en la nube, pero posible en el centro de datos), sea mucho más difícil que el atacante consiga los datos secretos.
Para añadir un volumen de datos secretos a Deployment, necesitamos especificar dos nuevas entradas en el YAML de Deployment. La primera es la entrada volume para la cápsula, que añade el volumen a la cápsula:
... volumes: - name: passwd-volume secret: secretName: redis-passwd
Con el volumen en la cápsula, es necesario montarlo en un contenedor específico. Lo hacemos mediante el campo volumeMounts en la descripción del contenedor:
... volumeMounts: - name: passwd-volume readOnly: true mountPath: "/etc/redis-passwd" ...
Esto incorporará el volumen de datos secretos al directorio redis-passwd para el acceso con el código de cliente. Poniendo todo esto junto, tenemos el Deployment completo de la siguiente manera:
apiVersion: extensions/v1beta1 kind: Deployment metadata: labels: app: frontend name: frontend namespace: default spec: replicas: 2 selector: matchLabels: app: frontend template: metadata: labels: app: frontend spec: containers: - image: my-repo/journal-server:v1-abcde imagePullPolicy: IfNotPresent name: frontend volumeMounts: - name: passwd-volume readOnly: true mountPath: "/etc/redis-passwd" resources: request: cpu: "1.0" memory: "1G" limits: cpu: "1.0" memory: "1G" volumes: - name: passwd-volume secret: secretName: redis-passwd
En este momento hemos configurado la aplicación de cliente, con lo que tenemos disponibles los datos secretos para la autenticación en el servicio de Redis. La configuración de Redis para usar esta contraseña es similar; la montamos en la cápsula de Redis y cargamos la contraseña desde el archivo.
Aunque conceptualmente el despliegue de una aplicación stateful (con estado) es similar al despliegue de un cliente como nuestro frontend, el estado trae consigo más complicaciones. La primera es que en Kubernetes podemos necesitar reprogramar una cápsula por una serie de razones, como pueden ser la comprobación de los nodos, una actualización o un rebalanceo. Cuando esto sucede, la cápsula podría trasladarse a una máquina diferente. Si los datos asociados con la instancia de Redis están localizados en una máquina en particular o dentro del propio contenedor, estos datos se pierden cuando el contenedor migra o se reinicia. Para evitar esto, al ejecutar tareas de estado en Kubernetes es importante usar PersistentVolumes remotos para administrar el estado asociado con la aplicación.
Hay una gran variedad de aplicaciones de PersistentVolumes en Kubernetes, y todas comparten características comunes. Como en los volúmenes de datos secretos descritos anteriormente, se asocian a una cápsula y se montan en un contenedor en un lugar determinado. A diferencia de los datos secretos, PersistentVolumes suelen estar montados en almacenamiento remoto a través de algún tipo de protocolo de red, ya sea basado en archivos —como Network File System (sistema de archivos de red) (NFS) o Server Message Block (bloque de mensajes del servidor) (SMB)— o basado en bloques —iSCSI, discos basados en la nube, etc.—.
Generalmente, para aplicaciones como bases de datos son preferibles los discos basados en bloques porque normalmente ofrecen un mejor rendimiento. Pero si el rendimiento no tiene mucha importancia, a veces los discos basados en archivos pueden ofrecer una mayor flexibilidad.
La gestión del estado, en general, es complicada, y Kubernetes no es una excepción. Si ejecutamos la aplicación en un entorno que soporta servicios con estado (por ejemplo, MySQL como servicio, Redis como servicio), generalmente es una buena idea usar esos servicios con estado. Inicialmente, el coste suplementario de un software como servicio (SaaS) con estado puede parecer caro. Pero cuando se tienen en cuenta todos los requisitos operativos de estado (copia de seguridad, localización de datos, redundancia, etc.) y el hecho de que la presencia de estado en un clúster de Kubernetes dificulta mover la aplicación entre clústeres, queda claro que en la mayoría de los casos vale la pena el precio adicional de las aplicaciones SaaS de almacenamiento. En entornos locales en los que no se dispone de SaaS de almacenamiento y que cuentan con un equipo de personas dedicado a proporcionar almacenamiento como servicio a toda la organización es, definitivamente, mejor práctica que permitir que cada equipo de trabajo haga lo suyo.
Para desplegar nuestro servicio Redis, utilizamos el recurso StatefulSet. Añadido después del lanzamiento inicial de Kubernetes como complemento a los recursos de ReplicaSet, StatefulSet ofrece unas garantías un poco más sólidas, como nombres consistentes (¡sin hashes aleatorios!) y un orden definido para la ampliación y la reducción de escala. Cuando implementamos una instancia única, esto es menos importante, pero cuando deseamos desplegar un estado replicado, estos atributos son muy convenientes.
Para obtener un PersistentVolume para nuestro Redis, utilizamos PersistentVolumeClaim. Podemos pensar que se trata de una demanda de «solicitud de recursos». Nuestro Redis declara en abstracto que quiere 50 GB de almacenamiento, y es el clúster de Kubernetes el que determina cómo aprovisionar el PersistentVolume apropiado. Hay dos razones para ello. La primera es que podemos escribir un StatefulSet que sea portátil entre diferentes nubes e instalaciones, donde los detalles de los discos pueden ser diferentes. La otra razón es que, aunque se pueden montar muchos tipos de PersistentVolume en una sola cápsula, podemos usar la demanda de volumen para escribir una plantilla que se pueda replicar y, sin embargo, tener cada cápsula con su propio PersistentVolume específico asignado.
El siguiente ejemplo muestra un Redis StatefulSet con PersistentVolumes:
apiVersion: apps/v1 kind: StatefulSet metadata: name: redis spec: serviceName: "redis" replicas: 1 selector: matchLabels: app: redis template: metadata: labels: app: redis spec: containers: - name: redis image: redis:5-alpine ports: - containerPort: 6379 name: redis volumeMounts: - name: data mountPath: /data volumeClaimTemplates: - metadata: name: data spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 10Gi
Esto implementa una única instancia de nuestro servicio Redis. Pero supongamos que queremos replicar el clúster de Redis para ampliar las lecturas y la resistencia a fallos. Para ello, es necesario aumentar el número de réplicas a tres, pero también necesitamos asegurar que las dos nuevas réplicas se conectan al master (maestro) de Redis para poder escribir en él.
Cuando creamos Service sin encabezamiento para StatefulSet de Redis, se crea una entrada DNS redis-0.redis; esta es la dirección IP de la primera réplica. Podemos utilizarla para crear un sencillo script que se puede lanzar en todos los contenedores:
Podemos crear este script como ConfigMap:
kubectl create configmap redis-config --from-file=launch.sh=launch.sh
A continuación, añadimos este ConfigMap a StatefulSet y lo utilizamos como comando para el contenedor. También agregamos la contraseña para la autenticación que hemos creado anteriormente en este capítulo.
El Redis completo de tres réplicas se ve de la siguiente manera:
apiVersion: apps/v1 kind: StatefulSet metadata: name: redis spec: serviceName: "redis" replicas: 3 selector: matchLabels: app: redis template: metadata: labels: app: redis spec: containers: - name: redis image: redis:5-alpine ports: - containerPort: 6379 name: redis volumeMounts: - name: data mountPath: /data - name: script mountPath: /script/launch.sh subPath: launch.sh - name: passwd-volume mountPath: /etc/redis-passwd command: - sh - -c - /script/launch.sh volumes: - name: script configMap: name: redis-config defaultMode: 0777 - name: passwd-volume secret: secretName: redis-passwd volumeClaimTemplates: - metadata: name: data spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 10Gi
Ahora que hemos implementado el servicio con estado de Redis, tenemos que ponerlo a disposición de nuestro frontend. Para ello, creamos dos Services de Kubernetes diferentes. El primero es el Service de lectura de datos de Redis. Debido a que Redis replica los datos a los tres miembros de StatefulSet, no nos importa a quién va dirigida nuestra solicitud. En consecuencia, utilizamos un Service básico para las lecturas:
apiVersion: v1 kind: Service metadata: labels: app: redis name: redis namespace: default spec: ports: - port: 6379 protocol: TCP targetPort: 6379 selector: app: redis sessionAffinity: None type: ClusterIP
Para habilitar la escritura, necesitamos apuntar al master de Redis (replica #0). Para ello, creamos un Service sin encabezamiento. Un Service sin encabezamiento no tiene una dirección IP del clúster, sino que programa una entrada DNS para cada cápsula en el StatefulSet.
Esto significa que podemos acceder al master a través del nombre DNS redis-0.redis:
apiVersion: v1 kind: Service metadata: labels: app: redis-write name: redis-write spec: clusterIP: None ports: - port: 6379 selector: app: redis
Por lo tanto, cuando queramos conectarnos a Redis por escrito o mediante pares de lectura/escritura transaccionales, podemos crear un cliente de escritura separado y conectado al servidor redis-0.redis.
El componente final de nuestra aplicación es un servidor de archivos estáticos. El servidor de archivos estáticos es responsable de servir archivos HTML, CSS, JavaScript y archivos de imágenes. Es muy eficaz y, a la vez, está enfocado a permitir que podamos separar el servicio de archivos estáticos de nuestro frontend, descrito anteriormente, que atiende las peticiones API. Podemos utilizar cómodamente un servidor de archivos estáticos de alto rendimiento como NGINX para servir archivos, lo cual permite al mismo tiempo que nuestros equipos de desarrollo se concentren en el código con el que implementar nuestra API.
Afortunadamente, el recurso Ingress hace que este principio de arquitectura de mini-microservicio sea muy fácil. Al igual que en el frontend, podemos usar el recurso Deployment para describir un servidor NGINX replicado. Vamos a crear las imágenes estáticas en el contenedor de NGINX y las desplegaremos en cada réplica. El recurso Desployment tiene el siguiente aspecto:
apiVersion: extensions/v1beta1 kind: Deployment metadata: labels: app: fileserver name: fileserver namespace: default spec: replicas: 2 selector: matchLabels: app: fileserver template: metadata: labels: app: fileserver spec: containers: - image: my-repo/static-files:v1-abcde imagePullPolicy: Always name: fileserver terminationMessagePath: /dev/termination-log terminationMessagePolicy: File resources: request: cpu: "1.0" memory: "1G" limits: cpu: "1.0" memory: "1G" dnsPolicy: ClusterFirst restartPolicy: Always
Ahora que hay un servidor web estático replicado funcionando, también crearemos un recurso Service para que actúe como equilibrador de carga:
apiVersion: v1 kind: Service metadata: labels: app: frontend name: frontend namespace: default spec: ports: - port: 80 protocol: TCP targetPort: 80 selector: app: frontend sessionAffinity: None type: ClusterIP
Ahora que tenemos un Service para el servidor de archivos estáticos, extendemos el recurso Ingress para que contenga la nueva ruta. Es importante tener en cuenta que debemos colocar la ruta / después de la ruta /api