Brendan Burns

Guía práctica de Kubernetes


Скачать книгу

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