Guía práctica de Kubernetes - Brendan Burns - E-Book

Guía práctica de Kubernetes E-Book

Brendan Burns

0,0

Beschreibung

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:

Android
iOS
von Legimi
zertifizierten E-Readern

Seitenzahl: 386

Das E-Book (TTS) können Sie hören im Abo „Legimi Premium” in Legimi-Apps auf:

Android
iOS
Bewertungen
0,0
0
0
0
0
0
Mehr Informationen
Mehr Informationen
Legimi prüft nicht, ob Rezensionen von Nutzern stammen, die den betreffenden Titel tatsächlich gekauft oder gelesen/gehört haben. Wir entfernen aber gefälschte Rezensionen.



Guía práctica de Kubernetes

Proyectos para crear aplicaciones de éxito con Kubernetes

Brendan Burns, Eddie Villalba,Dave Strebel y Lachlan Evenson

Guía práctica de Kubernetes

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

Contenidos

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

Prefacio

A quién va dirigido este libro

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.

Por qué hemos escrito este libro

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.

Exploración del libro

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.

Convenciones que se utilizan en el libro

Este elemento indica un consejo o una sugerencia.

Este elemento indica una nota general.

Este elemento indica una advertencia o precaución.

Uso de los ejemplos de código

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].

Reconocimientos

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.

CAPÍTULO 1Configuración de un servicio básico

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.

Visión general de la aplicación

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.

Gestión de archivos de configuració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.

Creación de un servicio replicado mediante Deployments

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.

Mejores prácticas para la gestión de imágenes

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.

Creación de una aplicación replicada

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.

Configuración de Ingress externa para tráfico HTTP

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

Configuración de la aplicación con ConfigMaps

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.

Gestión de autenticación con Secrets

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.

Despliegue de una sencilla base de datos con estado

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

Creación de un equilibrador de carga TCP con Services

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.

Uso de Ingress para enrutar el tráfico a un servidor de archivos estáticos

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