Conceptos

La sección de conceptos te ayudará a conocer los componentes de Kubernetes así como las abstracciones que utiliza para representar tu cluster. Además, te ayudará a obtener un conocimiento más profundo sobre cómo funciona Kubernetes.

Introducción

En Kubernetes se utilizan los objetos de la API de Kubernetes para describir el estado deseado del clúster: qué aplicaciones u otras cargas de trabajo se quieren ejecutar, qué imagenes de contenedores usan, el número de replicas, qué red y qué recursos de almacenamiento quieres que tengan disponibles, etc. Se especifica el estado deseado del clúster mediante la creación de objetos usando la API de Kubernetes, típicamente mediante la interfaz de línea de comandos, kubectl. También se puede usar la API de Kubernetes directamente para interactuar con el clúster y especificar o modificar tu estado deseado.

Una vez que se especifica el estado deseado, el Plano de Control de Kubernetes realizará las acciones necesarias para que el estado actual del clúster coincida con el estado deseado. Para ello, Kubernetes realiza diferentes tareas de forma automática, como pueden ser: parar o arrancar contenedores, escalar el número de réplicas de una aplicación dada, etc. El Plano de Control de Kubernetes consiste en un grupo de daemons que corren en tu clúster:

  • El Master de Kubernetes es un conjunto de tres daemons que se ejecutan en un único nodo del clúster, que se denomina nodo master. Estos daemons son: kube-apiserver, kube-controller-manager y kube-scheduler.

  • Los restantes nodos no master contenidos en tu clúster, ejecutan los siguientes dos daemons:

    • kubelet, el cual se comunica con el Master de Kubernetes.
    • kube-proxy, un proxy de red que implementa los servicios de red de Kubernetes en cada nodo.

Objetos de Kubernetes

Kubernetes tiene diferentes abstracciones que representan el estado de tu sistema: aplicaciones contenerizadas desplegadas y cargas de trabajo, sus recursos de red y almacenamiento asociados e información adicional acerca de lo que el clúster está haciendo en un momento dado. Estas abstracciones están representadas por objetos de la API de Kubernetes. Puedes revisar [Entendiendo los Objetos de Kubernetes] (/docs/concepts/overview/working-with-objects/kubernetes-objects/) para obtener más detalles.

Los objetos básicos de Kubernetes incluyen:

Además, Kubernetes contiene abstracciónes de nivel superior llamadas Controladores. Los Controladores se basan en los objetos básicos y proporcionan funcionalidades adicionales sobre ellos. Incluyen:

Plano de Control de Kubernetes

Los distintos componentes del Plano de Control de Kubernetes, tales como el Master de Kubernetes y el proceso kubelet, gobiernan cómo Kubernetes se comunica con el clúster. El Plano de Control mantiene un registro de todos los Objetos de Kubernetes presentes en el sistema y ejecuta continuos bucles de control para gestionar el estado de los mismos. En un momento dado, los bucles del Plano de Control responderán a los cambios que se realicen en el clúster y ejecutarán las acciones necesarias para hacer que el estado actual de todos los objetos del sistema converjan hacia el estado deseado que has proporcionado.

Por ejemplo, cuando usas la API de Kubernetes para crear un Deployment, estás proporcionando un nuevo estado deseado para el sistema. El Plano de Control de Kubernetes registra la creación del objeto y lleva a cabo tus instrucciones ejecutando las aplicaciones requeridas en los nodos del clúster, haciendo de esta manera que el estado actual coincida con el estado deseado.

El Master de Kubernetes

El Master de Kubernetes es el responsable de mantener el estado deseado de tu clúster. Cuando interactuas con Kubernetes, como por ejemplo cuando utilizas la interfaz de línea de comandos kubectl, te estás comunicando con el master de tu clúster de Kubernetes.

Por "master" entendemos la colección de daemons que gestionan el estado del clúster. Típicamente, estos daemons se ejecutan todos en un único nodo del clúster, y este nodo recibe por tanto la denominación de master. El master puede estar replicado por motivos de disponibilidad y redundancia.

Kubernetes Nodes

En un clúster de Kubernetes, los nodos son las máquinas (máquinas virtuales, servidores físicos, etc) que ejecutan tus aplicaciones y flujos de trabajo en la nube. El master de Kubernetes controla cada nodo, por lo que en raras ocasiones interactuarás con los nodos directamente.

Metadatos de los Objectos

Siguientes pasos

Si quieres empezar a contribuir a la documentación de Kubernetes accede a la página Empieza a contribuir.

1 - Introducción

1.1 - ¿Qué es Kubernetes?

Esta página ofrece una visión general sobre Kubernetes.

Kubernetes es una plataforma portable y extensible de código abierto para administrar cargas de trabajo y servicios. Kubernetes facilita la automatización y la configuración declarativa. Tiene un ecosistema grande y en rápido crecimiento. El soporte, las herramientas y los servicios para Kubernetes están ampliamente disponibles.

Google liberó el proyecto Kubernetes en el año 2014. Kubernetes se basa en la experiencia de Google corriendo aplicaciones en producción a gran escala por década y media, junto a las mejores ideas y prácticas de la comunidad.

¿Por qué necesito Kubernetes y qué puede hacer por mi?

Kubernetes tiene varias características. Puedes pensar en Kubernetes como:

  • una plataforma de contenedores
  • una plataforma de microservicios
  • una plataforma portable de nube

y mucho más.

Kubernetes ofrece un entorno de administración centrado en contenedores. Kubernetes orquesta la infraestructura de cómputo, redes y almacenamiento para que las cargas de trabajo de los usuarios no tengan que hacerlo. Esto ofrece la simplicidad de las Plataformas como Servicio (PaaS) con la flexibilidad de la Infraestructura como Servicio (IaaS) y permite la portabilidad entre proveedores de infraestructura.

¿Qué hace de Kubernetes una plataforma?

A pesar de que Kubernetes ya ofrece muchas funcionalidades, siempre hay nuevos escenarios que se benefician de nuevas características. Los flujos de trabajo de las aplicaciones pueden optimizarse para acelerar el tiempo de desarrollo. Una solución de orquestación propia puede ser suficiente al principio, pero suele requerir una automatización robusta cuando necesita escalar. Es por ello que Kubernetes fue diseñada como una plataforma: para poder construir un ecosistema de componentes y herramientas que hacen más fácil el desplegar, escalar y administrar aplicaciones.

Las etiquetas, o Labels, le permiten a los usuarios organizar sus recursos como deseen. Las anotaciones, o Annotations, les permiten asignar información arbitraria a un recurso para facilitar sus flujos de trabajo y hacer más fácil a las herramientas administrativas inspeccionar el estado.

Además, el Plano de Control de Kubernetes usa las mismas APIs que usan los desarrolladores y usuarios finales. Los usuarios pueden escribir sus propios controladores, como por ejemplo un planificador o scheduler, usando sus propias APIs desde una herramienta de línea de comandos.

Este diseño ha permitido que otros sistemas sean construidos sobre Kubernetes.

Lo que Kubernetes no es

Kubernetes no es una Plataforma como Servicio (PaaS) convencional. Ya que Kubernetes opera a nivel del contenedor y no a nivel del hardware, ofrece algunas características que las PaaS también ofrecen, como deployments, escalado, balanceo de carga, registros y monitoreo. Dicho esto, Kubernetes no es monolítico y las soluciones que se ofrecen de forma predeterminada son opcionales e intercambiables.

Kubernetes ofrece los elementos esenciales para construir una plataforma para desarrolladores, preservando la elección del usuario y la flexibilidad en las partes más importantes.

Entonces, podemos decir que Kubernetes:

  • No limita el tipo de aplicaciones que soporta. Kubernetes busca dar soporte a un número diverso de cargas de trabajo, que incluyen aplicaciones con y sin estado así como aplicaciones que procesan datos. Si la aplicación puede correr en un contenedor, debería correr bien en Kubernetes.
  • No hace deployment de código fuente ni compila tu aplicación. Los flujos de integración, entrega y deployment continuo (CI/CD) vienen determinados por la cultura y preferencia organizacional y sus requerimientos técnicos.
  • No provee servicios en capa de aplicación como middleware (por ejemplo, buses de mensaje), frameworks de procesamiento de datos (como Spark), bases de datos (como MySQL), caches o sistemas de almacenamiento (como Ceph). Es posible correr estas aplicaciones en Kubernetes, o acceder a ellos desde una aplicación usando un mecanismo portable como el Open Service Broker.
  • No dictamina las soluciones de registros, monitoreo o alerta que se deben usar. Hay algunas integraciones que se ofrecen como prueba de concepto, y existen mecanismos para recolectar y exportar métricas.
  • No provee ni obliga a usar un sistema o lenguaje de configuración (como jsonnet) sino que ofrece una API declarativa que puede ser usada con cualquier forma de especificación declarativa
  • No provee ni adopta un sistema exhaustivo de mantenimiento, administración o corrección automática de errores

Además, Kubernetes no es un mero sistema de orquestación. De hecho, Kubernetes elimina la necesidad de orquestar. Orquestación se define como la ejecución de un flujo de trabajo definido: haz A, luego B y entonces C. Kubernetes está compuesto de un conjunto de procesos de control independientes y combinables entre si que llevan el estado actual hacia el estado deseado. No debería importar demasiado como llegar de A a C. No se requiere control centralizado y, como resultado, el sistema es más fácil de usar, más poderoso, robusto, resiliente y extensible.

¿Por qué usar contenedores?

¿Te preguntas las razones para usar contenedores?

Why Containers?

La Manera Antigua de desplegar aplicaciones era instalarlas en un servidor usando el administrador de paquetes del sistema operativo. La desventaja era que los ejecutables, la configuración, las librerías y el ciclo de vida de todos estos componentes se entretejían unos a otros. Podíamos construir imágenes de máquina virtual inmutables para tener rollouts y rollbacks predecibles, pero las máquinas virtuales son pesadas y poco portables.

La Manera Nueva es desplegar contenedores basados en virtualización a nivel del sistema operativo, en vez del hardware. Estos contenedores están aislados entre ellos y con el servidor anfitrión: tienen sus propios sistemas de archivos, no ven los procesos de los demás y el uso de recursos puede ser limitado. Son más fáciles de construir que una máquina virtual, y porque no están acoplados a la infraestructura y sistema de archivos del anfitrión, pueden llevarse entre nubes y distribuciones de sistema operativo.

Ya que los contenedores son pequeños y rápidos, una aplicación puede ser empaquetada en una imagen de contenedor. Esta relación uno a uno entre aplicación e imagen nos abre un abanico de beneficios para usar contenedores. Con contenedores, podemos crear imágenes inmutables al momento de la compilación en vez del despliegue ya que las aplicaciones no necesitan componerse junto al resto del stack ni atarse al entorno de infraestructura de producción. Generar una imagen de contenedor al momento de la compilación permite tener un entorno consistente que va desde desarrollo hasta producción. De igual forma, los contenedores son más transparentes que las máquinas virtuales y eso hace que el monitoreo y la administración sean más fáciles. Esto se aprecia más cuando los ciclos de vida de los contenedores son administrados por la infraestructura en vez de un proceso supervisor escondido en el contenedor. Por último, ya que solo hay una aplicación por contenedor, administrar el despliegue de la aplicación se reduce a administrar el contenedor.

En resumen, los beneficios de usar contenedores incluyen:

  • Ágil creación y despliegue de aplicaciones: Mayor facilidad y eficiencia al crear imágenes de contenedor en vez de máquinas virtuales
  • Desarrollo, integración y despliegue continuo: Permite que la imagen de contenedor se construya y despliegue de forma frecuente y confiable, facilitando los rollbacks pues la imagen es inmutable
  • Separación de tareas entre Dev y Ops: Puedes crear imágenes de contenedor al momento de compilar y no al desplegar, desacoplando la aplicación de la infraestructura
  • Observabilidad No solamente se presenta la información y métricas del sistema operativo, sino la salud de la aplicación y otras señales
  • Consistencia entre los entornos de desarrollo, pruebas y producción: La aplicación funciona igual en un laptop y en la nube
  • Portabilidad entre nubes y distribuciones: Funciona en Ubuntu, RHEL, CoreOS, tu datacenter físico, Google Kubernetes Engine y todo lo demás
  • Administración centrada en la aplicación: Eleva el nivel de abstracción del sistema operativo y el hardware virtualizado a la aplicación que funciona en un sistema con recursos lógicos
  • Microservicios distribuidos, elásticos, liberados y débilmente acoplados: Las aplicaciones se separan en piezas pequeñas e independientes que pueden ser desplegadas y administradas de forma dinámica, y no como una aplicación monolítica que opera en una sola máquina de gran capacidad
  • Aislamiento de recursos: Hace el rendimiento de la aplicación más predecible
  • Utilización de recursos: Permite mayor eficiencia y densidad

¿Qué significa Kubernetes? ¿Qué significa K8S?

El nombre Kubernetes proviene del griego y significa timonel o piloto. Es la raíz de gobernador y de cibernética. K8s es una abrevación que se obtiene al reemplazar las ocho letras "ubernete" con el número 8.

Siguientes pasos

1.2 - Componentes de Kubernetes

Este documento describe los distintos componentes que son necesarios para operar un clúster de Kubernetes.

Componentes del plano de control

Los componentes que forman el plano de control toman decisiones globales sobre el clúster (por ejemplo, la planificación) y detectan y responden a eventos del clúster, como la creación de un nuevo pod cuando la propiedad replicas de un controlador de replicación no se cumple.

Estos componentes pueden ejecutarse en cualquier nodo del clúster. Sin embargo para simplificar, los scripts de instalación típicamente se inician en el mismo nodo de forma exclusiva, sin que se ejecuten contenedores de los usuarios en esos nodos. El plano de control se ejecuta en varios nodos para garantizar la alta disponibilidad.

kube-apiserver

El servidor de la API es el componente del plano de control de Kubernetes que expone la API de Kubernetes. Se trata del frontend de Kubernetes, recibe las peticiones y actualiza acordemente el estado en etcd.

La principal implementación de un servidor de la API de Kubernetes es kube-apiserver. Es una implementación preparada para ejecutarse en alta disponiblidad y que puede escalar horizontalmente para balancear la carga entre varias instancias.

etcd

Almacén de datos persistente, consistente y distribuido de clave-valor utilizado para almacenar toda a la información del clúster de Kubernetes.

Si tu clúster utiliza etcd como sistema de almacenamiento, échale un vistazo a la documentación sobre estrategias de backup.

Puedes encontrar información detallada sobre etcd en su documentación oficial.

kube-scheduler

Componente del plano de control que está pendiente de los Pods que no tienen ningún nodo asignado y seleciona uno donde ejecutarlo.

Para decidir en qué nodo se ejecutará el pod, se tienen en cuenta diversos factores: requisitos de recursos, restricciones de hardware/software/políticas, afinidad y anti-afinidad, localización de datos dependientes, entre otros.

kube-controller-manager

Componente del plano de control que ejecuta los controladores de Kubernetes.

Lógicamente cada controlador es un proceso independiente, pero para reducir la complejidad, todos se compilan en un único binario y se ejecuta en un mismo proceso.

Estos controladores incluyen:

  • Controlador de nodos: es el responsable de detectar y responder cuándo un nodo deja de funcionar
  • Controlador de replicación: es el responsable de mantener el número correcto de pods para cada controlador de replicación del sistema
  • Controlador de endpoints: construye el objeto Endpoints, es decir, hace una unión entre los Services y los Pods
  • Controladores de tokens y cuentas de servicio: crean cuentas y tokens de acceso a la API por defecto para los nuevos Namespaces.

cloud-controller-manager

cloud-controller-manager ejecuta controladores que interactúan con proveedores de la nube. El binario cloud-controller-manager es una característica alpha que se introdujo en la versión 1.6 de Kubernetes.

cloud-controller-manager sólo ejecuta ciclos de control específicos para cada proveedor de la nube. Es posible desactivar estos ciclos en kube-controller-manager pasando la opción --cloud-provider= external cuando se arranque el kube-controller-manager.

cloud-controller-manager permite que el código de Kubernetes y el del proveedor de la nube evolucionen de manera independiente. Anteriormente, el código de Kubernetes dependía de la funcionalidad específica de cada proveedor de la nube. En el futuro, el código que sea específico a una plataforma debería ser mantenido por el proveedor de la nube y enlazado a cloud-controller-manager al correr Kubernetes.

Los siguientes controladores dependen de alguna forma de un proveedor de la nube:

  • Controlador de nodos: es el responsable de detectar y actuar cuándo un nodo deja de responder
  • Controlador de rutas: para configurar rutas en la infraestructura de nube subyacente
  • Controlador de servicios: para crear, actualizar y eliminar balanceadores de carga en la nube
  • Controlador de volúmenes: para crear, conectar y montar volúmenes e interactuar con el proveedor de la nube para orquestarlos

Componentes de nodo

Los componentes de nodo corren en cada nodo, manteniendo a los pods en funcionamiento y proporcionando el entorno de ejecución de Kubernetes.

kubelet

Agente que se ejecuta en cada nodo de un clúster. Se asegura de que los contenedores estén corriendo en un pod.

El agente kubelet toma un conjunto de especificaciones de Pod, llamados PodSpecs, que han sido creados por Kubernetes y garantiza que los contenedores descritos en ellos estén funcionando y en buen estado.

kube-proxy

kube-proxy permite abstraer un servicio en Kubernetes manteniendo las reglas de red en el anfitrión y haciendo reenvío de conexiones.

Runtime de contenedores

El runtime de los contenedores es el software responsable de ejecutar los contenedores. Kubernetes soporta varios de ellos: Docker, containerd, cri-o, rktlet y cualquier implementación de la interfaz de runtime de contenedores de Kubernetes, o Kubernetes CRI.

Addons

Los addons son pods y servicios que implementan funcionalidades del clúster. Estos pueden ser administrados por Deployments, ReplicationControllers y otros. Los addons asignados a un espacio de nombres se crean en el espacio kube-system.

Más abajo se describen algunos addons. Para una lista más completa de los addons disponibles, por favor visite Addons.

DNS

Si bien los otros addons no son estrictamente necesarios, todos los clústers de Kubernetes deberían tener un DNS interno del clúster ya que la mayoría de los ejemplos lo requieren.

El DNS interno del clúster es un servidor DNS, adicional a los que ya podrías tener en tu red, que sirve registros DNS a los servicios de Kubernetes.

Los contenedores que son iniciados por Kubernetes incluyen automáticamente este servidor en sus búsquedas DNS.

Interfaz Web (Dashboard)

El Dashboard es una interfaz Web de propósito general para clústeres de Kubernetes. Le permite a los usuarios administrar y resolver problemas que puedan presentar tanto las aplicaciones como el clúster.

Monitor de recursos de contenedores

El Monitor de recursos de contenedores almacena de forma centralizada series de tiempo con métricas sobre los contenedores, y provee una interfaz para navegar estos datos.

Registros del clúster

El mecanismo de registros del clúster está a cargo de almacenar los registros de los contenedores de forma centralizada, proporcionando una interfaz de búsqueda y navegación.

1.3 - API de Kubernetes

Las convenciones globales de la API se describen en el documento de convenciones de la API.

Los endpoints, tipos de recursos y ejemplos se describen en la Referencia de la API.

El acceso remoto a la API se discute en el documento Controlando el acceso a la API.

La API de Kubernetes sirve como base para el esquema de configuración declarativa del sistema. La herramienta de línea de comandos kubectl puede ser usada para crear, actualizar, eliminar y consultar objetos a través de la API.

Kubernetes también almacena el estado de los recursos de la API en forma serializada. Actualmente esto se hace en etcd.

Kubernetes está compuesto, en si mismo, por varios componentes que interactúan a través de su API.

Cambios a la API

En nuestra experiencia, cualquier sistema exitoso necesita crecer y evolucionar al cambiar o emerger nuevos casos de uso. Por lo tanto, esperamos que la API de Kubernetes cambie y crezca continuamente. Dicho esto, nuestro objetivo es no romper la compatibilidad con los clientes ya existentes, por un período de tiempo razonable. En general, podemos esperar que se agreguen nuevos recursos y propiedades con cierta frecuencia. Para eliminar un recurso o propiedad, se requiere seguir la política de obsolescencia de la API.

En el documento de cambios a la API describimos como cambiar la API y definimos lo que es considerado como un cambio compatible.

Definiciones OpenAPI y Swagger

Los detalles completos de la API se documentan usando OpenAPI.

A partir de Kubernetes 1.10, el servidor de API de Kubernetes provee una especificación OpenAPI en el endpoint /openapi/v2.

Se puede solicitar un formato en particular utilizando las siguientes cabeceras HTTP:

Cabecera Valores admitidos
Accept application/json, application/com.github.proto-openapi.spec.v2@v1.0+protobuf (el content-type predeterminado es application/json si esta cabecera contiene */* o se omite)
Accept-Encoding gzip (esta cabecera es opcional)

Antes de 1.14, los endpoints separados por formato (/swagger.json, /swagger-2.0.0.json, /swagger-2.0.0.pb-v1, /swagger-2.0.0.pb-v1.gz) servían la especificación OpenAPI en distintos formatos. Estos endpoints se consideran obsoletos y serán removidos en Kubernetes 1.14.

Ejemplos:

Antes de 1.10 A partir de 1.10
GET /swagger.json GET /openapi/v2 Accept: application/json
GET /swagger-2.0.0.pb-v1 GET /openapi/v2 Accept: application/com.github.proto-openapi.spec.v2@v1.0+protobuf
GET /swagger-2.0.0.pb-v1.gz GET /openapi/v2 Accept: application/com.github.proto-openapi.spec.v2@v1.0+protobuf Accept-Encoding: gzip

Kubernetes implementa un formato alternativo de serialización basado en Protocol Buffer (Protobuf) diseñado principalmente para las comunicaciones dentro del clúster. Este formato está documentado en su propuesta de diseño y los archivos IDL de cada esquema se encuentran en los paquetes de Go que definen los objetos de la API.

Antes de 1.14, el apiserver de Kubernetes ofrecía una API para obtener la especificación Swagger v1.2 de la API de Kubernetes en /swaggerapi. Este endpoint se considera obsoleto y será removido en Kubernetes 1.14.

Versionado de la API

Para facilitar la eliminación de propiedades o reestructurar la representación de un recurso, Kubernetes soporta múltiples versiones de la API en distintas rutas como /api/v1 or /apis/extensions/v1beta1.

Se versiona a nivel de la API en vez de a nivel de los recursos o propiedades para asegurarnos de que la API presenta una visión clara y consistente de los recursos y el comportamiento del sistema, y para controlar el acceso a las APIs experimentales o que estén terminando su ciclo de vida. Los esquemas de serialización JSON y Protobuf siguen los mismos lineamientos para los cambios, es decir, estas descripciones cubren ambos formatos.

Se ha de tener en cuenta que hay una relación indirecta entre el versionado de la API y el versionado del resto del software. La propuesta de versionado de la API y releases describe esta relación.

Las distintas versiones de la API implican distintos niveles de estabilidad y soporte. El criterio para cada nivel se describe en detalle en la documentación de Cambios a la API. A continuación se ofrece un resumen:

  • Nivel "alpha":
    • El nombre de la versión contiene alpha (p. ej., v1alpha1).
    • Puede tener muchos errores. Activar esta característica podría exponer errores. Desactivada por defecto.
    • El soporte para esta característica podría ser eliminado sin previo aviso.
    • La API podría volverse incompatible en el futuro y sin previo aviso.
    • Se recomienda su uso solo en clústers efímeros y de prueba ya que hay mayor riesgo de errores y carece de soporte a largo plazo
  • Nivel "beta":
    • El nombre de la versión contiene beta (p. ej., v2beta3).
    • El código ha sido probado. Activar esta característica es seguro. Activada por defecto.
    • Los detalles de esta característica podrían cambiar, pero se mantendrá el soporte.
    • El esquema y/o la semántica de un objeto podría volverse incompatible en el futuro. Si esto pasa, se ofrecerán instrucciones para migrar a una nueva versión. Esto podría requerir eliminar, editar o volver a crear objetos. El proceso de edición podría requerir planificación, incluyendo tiempo de inactividad para aplicaciones que usaban esta característica.
    • No se recomienda para aplicaciones críticas de negocio ya que podría volverse incompatible en futuras versiones. Si tiene múltiples clústeres que pueden actualizarse de forma independiente se podría decidir correr este riesgo.
    • Por favor, ¡pruebe las características en fase beta y comparta sus comentarios! Una vez que salgan de la fase beta, sería más difícil hacer cambios.
  • Nivel estable:
    • El nombre de la versión es vX donde X es un entero.
    • Las versiones estables de las características aparecerán en los siguientes releases.

Grupos de API

Para que sea más fácil extender la API de Kubernetes, se han creado los grupos de API. Estos grupos se especifican en una ruta REST y en la propiedad apiVersion de un objeto serializado.

Actualmente hay varios grupos de API en uso:

  1. El grupo core (o group) en la ruta REST /api/v1 y usa apiVersion: v1.

  2. Los grupos con entidad propia están en la ruta REST /apis/$NOMBRE_GRUPO/$VERSION y usan apiVersion: $NOMBRE_GRUPO/$VERSION. (p. ej., apiVersion: batch/v1). La lista completa de los grupos soportados está disponible en la Referencia de la API.

Hay dos rutas soportadas para extender la API con recursos personalizados:

  1. CustomResourceDefinition es para los usuarios que tengan necesidades CRUD muy básicas.
  2. Los usuarios que necesiten la semántica completa de la API pueden implementar su propio apiserver usando el agregador para hacerlo transparente para los clientes.

Activar los grupos de API

Ciertos recursos y grupos de API están activados por defecto. Pueden activarse o desactivarse con la opción --runtime-config en apiserver. --runtime-config acepta valores separados por coma. Por ejemplo, para desactivar batch/v1, use la opción --runtime-config=batch/v1=false. Para activar batch/v2alpha1, pase la opción --runtime-config=batch/v2alpha1. Esta opción acepta pares de clave=valor separados por coma que describen la configuración operativa del apiserver.

IMPORTANTE: Activar o desactivar grupos o recursos requiere reiniciar el apiserver y el controller-manager para que estos reconozcan los cambios a --runtime-config.

Activar recursos en los grupos

Los DaemonSets, Deployments, HorizontalPodAutoscalers, Ingresses, Jobs y ReplicaSets están activados por defecto.

Se pueden activar otros recursos con la opción --runtime-config del apiserver. Por ejemplo, como --runtime-config acepta valores separados por coma, puede desactivar los Deployments y los Ingress con la opción --runtime-config=extensions/v1beta1/deployments=false,extensions/v1beta1/ingresses=false

1.4 - Objetos de Kubernetes

1.4.1 - Entender los Objetos de Kubernetes

Esta página explica cómo se representan los objetos de Kubernetes en la API de Kubernetes, y cómo puedes definirlos en formato .yaml.

Entender los Objetos de Kubernetes

Los Objetos de Kubernetes son entidades persistentes dentro del sistema de Kubernetes. Kubernetes utiliza estas entidades para representar el estado de tu clúster. Específicamente, pueden describir:

  • Qué aplicaciones corren en contenedores (y en qué nodos)
  • Los recursos disponibles para dichas aplicaciones
  • Las políticas acerca de cómo dichas aplicaciones se comportan, como las políticas de reinicio, actualización, y tolerancia a fallos

Un objeto de Kubernetes es un "registro de intención" -- una vez que has creado el objeto, el sistema de Kubernetes se pondrá en marcha para asegurar que el objeto existe. Al crear un objeto, en realidad le estás diciendo al sistema de Kubernetes cómo quieres que sea la carga de trabajo de tu clúster; esto es, el estado deseado de tu clúster.

Para trabajar con los objetos de Kubernetes -- sea para crearlos, modificarlos, o borrarlos -- necesitarás usar la API de Kubernetes. Cuando utilizas el interfaz de línea de comandos kubectl, por ejemplo, este realiza las llamadas necesarias a la API de Kubernetes en tu lugar. También puedes usar la API de Kubernetes directamente desde tus programas utilizando alguna de las Librerías de Cliente.

Alcance y Estado de un Objeto

Cada objeto de Kubernetes incluye dos campos como objetos anidados que determinan la configuración del objeto: el campo de objeto spec y el campo de objeto status. El campo spec, que es obligatorio, describe el estado deseado del objeto -- las características que quieres que tenga el objeto. El campo status describe el estado actual del objeto, y se suministra y actualiza directamente por el sistema de Kubernetes. En cualquier momento, el Plano de Control de Kubernetes gestiona de forma activa el estado actual del objeto para que coincida con el estado deseado requerido.

Por ejemplo, un Deployment de Kubernetes es un objeto que puede representar una aplicación de tu clúster. Cuando creas el Deployment, puedes especificar en el spec del Deployment que quieres correr tres réplicas de la aplicación. El sistema de Kubernetes lee el spec del Deployment y comienza a instanciar réplicas de tu aplicación -- actualizando el estado para conciliarlo con tu spec. Si cualquiera de las instancias falla (un cambio de estado), el sistema de Kubernetes soluciona la diferencia entre la spec y el estado llevando a cabo una correción -- en este caso, iniciando otra instancia de reemplazo.

Para obtener más información acerca de la spec, el status, y los metadatos de los objetos, echa un vistazo a las Normas de la API de Kubernetes.

Describir un Objeto de Kubernetes

Cuando creas un objeto en Kubernetes, debes especificar la spec del objeto que describe su estado deseado, así como información básica del mismo (como el nombre). Cuando usas la API de Kubernetes para crear el objeto (bien de forma directa o usando kubectl), dicha petición a la API debe incluir toda la información en formato JSON en el cuerpo de la petición. A menudo, le proporcionas la información a kubectl como un archivo .yaml. kubectl convierte esa información a JSON cuando realiza la llamada a la API.

Aquí hay un ejemplo de un archivo .yaml que muestra los campos requeridos y la spec del objeto Deployment de Kubernetes:

apiVersion: apps/v1 # Usa apps/v1beta2 para versiones anteriores a 1.9.0
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2 # indica al controlador que ejecute 2 pods
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

Una forma de crear un Deployment utilizando un archivo .yaml como el indicado arriba sería ejecutar el comando kubectl apply en el interfaz de línea de comandos, pasándole el archivo .yaml como argumento. Aquí tienes un ejemplo de cómo hacerlo:

kubectl apply -f https://k8s.io/examples/application/deployment.yaml --record

La salida del comando sería algo parecido a esto:

deployment.apps/nginx-deployment created

Campos requeridos

En el archivo .yaml del objeto de Kubernetes que quieras crear, obligatoriamente tendrás que indicar los valores de los siguientes campos (como mínimo):

  • apiVersion - Qué versión de la API de Kubernetes estás usando para crear este objeto
  • kind - Qué clase de objeto quieres crear
  • metadata - Datos que permiten identificar unívocamente al objeto, incluyendo una cadena de texto para el name, UID, y opcionalmente el namespace

También deberás indicar el campo spec del objeto. El formato del campo spec es diferente según el tipo de objeto de Kubernetes, y contiene campos anidados específicos de cada objeto. La Referencia de la API de Kubernetes puede servirte de ayuda para encontrar el formato de la spec para cada uno de los objetos que puedes crear usando Kubernetes. Por ejemplo, el formato de la spec para un objeto de tipo Pod lo puedes encontrar aquí, y el formato de la spec para un objeto de tipo Deployment lo puedes encontrar aquí.

Siguientes pasos

  • Aprender más acerca de los objetos básicos más importantes de Kubernetes, como el Pod.

1.4.2 - Nombres

Todos los objetos de la API REST de Kubernetes se identifica de forma inequívoca mediante un Nombre y un UID.

Para aquellos atributos provistos por el usuario que no son únicos, Kubernetes provee de etiquetas y anotaciones.

Echa un vistazo al documento de diseño de identificadores para información precisa acerca de las reglas sintácticas de los Nombres y UIDs.

Nombres

Una cadena de caracteres proporcionada por el cliente que identifica un objeto en la URL de un recurso, como por ejemplo, /api/v1/pods/nombre-del-objeto.

Los nombres de los objetos son únicos para cada tipo de objeto. Sin embargo, si se elimina el objeto, se puede crear un nuevo objeto con el mismo nombre.

Por regla general, los nombres de los recursos de Kubernetes no deben exceder la longitud máxima de 253 caracteres y deben incluir caracteres alfanuméricos en minúscula, -, y .; aunque algunos recursos tienen restricciones más específicas.

UIDs

Una cadena de caracteres generada por Kubernetes para identificar objetos de forma única.

Cada objeto creado a lo largo de toda la vida de un clúster Kubernetes tiene un UID distinto. Está pensado para distinguir entre ocurrencias históricas de entidades similares.

1.4.3 - Espacios de nombres

Kubernetes soporta múltiples clústeres virtuales respaldados por el mismo clúster físico. Estos clústeres virtuales se denominan espacios de nombres (namespaces).

Cuándo Usar Múltiple Espacios de Nombre

Los espacios de nombres están pensados para utilizarse en entornos con muchos usuarios distribuidos entre múltiples equipos, o proyectos. Para aquellos clústeres con unas pocas decenas de usuarios, no deberías necesitar crear o pensar en espacios de nombres en absoluto. Empieza a usarlos solamente si necesitas las características que proporcionan.

Los espacios de nombres proporcionan un campo de acción para los nombres. Los nombres de los recursos tienen que ser únicos dentro de cada espacio de nombres, pero no entre dichos espacios de nombres.

Los espacios de nombres son una forma de dividir los recursos del clúster entre múltiples usuarios (via cuotas de recursos).

En futuras versiones de Kubernetes, los objetos de un mismo espacio de nombres tendrán las mismas políticas de control de acceso por defecto.

No es necesario usar múltiples espacios de nombres sólo para separar recursos ligeramente diferentes, como versiones diferentes de la misma aplicación: para ello utiliza etiquetas para distinguir tus recursos dentro del mismo espacio de nombres.

Trabajar con Espacios de Nombres

La creación y borrado de espacios de nombres se describe en la documentación de la Guía de Administración para espacios de nombres.

Ver espacios de nombre

Puedes listar los espacios de nombres actuales dentro de un clúster mediante:

kubectl get namespaces
NAME          STATUS    AGE
default       Active    1d
kube-system   Active    1d
kube-public   Active    1d

Kubernetes arranca con tres espacios de nombres inicialmente:

  • default El espacio de nombres por defecto para aquellos objetos que no especifican ningún espacio de nombres
  • kube-system El espacio de nombres para aquellos objetos creados por el sistema de Kubernetes
  • kube-public Este espacio de nombres se crea de forma automática y es legible por todos los usuarios (incluyendo aquellos no autenticados). Este espacio de nombres se reserva principalmente para uso interno del clúster, en caso de que algunos recursos necesiten ser visibles y legibles de forma pública para todo el clúster. La naturaleza pública de este espacio de nombres es simplemente por convención, no es un requisito.

Establecer el espacio de nombres para una petición

Para indicar de forma temporal el espacio de nombres para una petición, usa la opción --namespace.

Por ejemplo:

kubectl --namespace=<insert-namespace-name-here> run nginx --image=nginx
kubectl --namespace=<insert-namespace-name-here> get pods

Establecer la preferencia de espacio de nombres

Puedes indicar de forma permanente el espacio de nombres para todas las llamadas futuras a comandos kubectl en dicho contexto.

kubectl config set-context --current --namespace=<insert-namespace-name-here>
# Validate it
kubectl config view | grep namespace:

Espacios de nombres y DNS

Cuando creas un Servicio, se crea una entrada DNS correspondiente. Esta entrada tiene la forma <service-name>.<namespace-name>.svc.cluster.local, que significa que si un contenedor simplemente usa <service-name>, se resolverá al servicio que sea local al espacio de nombres. Esto es de utilidad para poder emplear la misma configuración entre múltiples espacios de nombres como Development, Staging y Production. Si quieres referenciar recursos entre distintos espacios de nombres, entonces debes utilizar el nombre cualificado completo de dominio (FQDN).

No Todos los Objetos están en un Espacio de nombres

La mayoría de los recursos de Kubernetes (ej. pods, services, replication controllers, y otros) están en algunos espacios de nombres. Sin embargo, los recursos que representan a los propios espacios de nombres no están a su vez en espacios de nombres. De forma similar, los recursos de bajo nivel, como los nodos y los volúmenes persistentes, no están en ningún espacio de nombres.

Para comprobar qué recursos de Kubernetes están y no están en un espacio de nombres:

# In a namespace
kubectl api-resources --namespaced=true

# Not in a namespace
kubectl api-resources --namespaced=false

1.4.4 - Etiquetas y Selectores

Las etiquetas son pares de clave/valor que se asocian a los objetos, como los pods. El propósito de las etiquetas es permitir identificar atributos de los objetos que son relevantes y significativos para los usuarios, pero que no tienen significado para el sistema principal. Se puede usar las etiquetas para organizar y seleccionar subconjuntos de objetos. Las etiquetas se pueden asociar a los objetos a la hora de crearlos y posteriormente modificarlas o añadir nuevas. Cada objeto puede tener un conjunto de etiquetas clave/valor definidas, donde cada clave debe ser única para un mismo objeto.

"metadata": {
  "labels": {
    "key1" : "value1",
    "key2" : "value2"
  }
}

Las etiquetas permiten consultar y monitorizar los objetos de forma más eficiente y son ideales para su uso en UIs y CLIs. El resto de información no identificada debe ser registrada usando anotaciones.

Motivación

Las etiquetas permiten que los usuarios mapeen sus estructuras organizacionales en los propios objetos sin acoplamiento, sin forzar a los clientes a almacenar estos mapeos.

Los despliegues de servicios y los procesos en lotes suelen requerir a menudo la gestión de entidades multi-dimensionales (ej., múltiples particiones o despliegues, múltiples entregas, múltiples capas, múltiples microservicios por capa). Tal gestión a menudo requiere de operaciones horizontales que rompen la encapsulación de representaciones estrictamente jerárquicas, especialmente jerarquías rígidas determinadas por la infraestructura en vez de por los usuarios.

Ejemplos de etiquetas:

  • "release" : "stable", "release" : "canary"
  • "environment" : "dev", "environment" : "qa", "environment" : "production"
  • "tier" : "frontend", "tier" : "backend", "tier" : "cache"
  • "partition" : "customerA", "partition" : "customerB"
  • "track" : "daily", "track" : "weekly"

Estos son sólo algunos ejemplos de etiquetas de uso común; eres libre de establecer tus propias normas. Ten en cuenta que la clave de cada etiqueta debe ser única dentro de cada objeto.

Sintaxis y conjunto de caracteres

Las etiquetas son pares de clave/valor. Las claves válidas de etiqueta tienen dos partes: un prefijo opcional y un nombre, separados por una barra (/). La parte del nombre es obligatoria y debe ser menor o igual a 63 caracteres, empezando y terminando con un carácter alfanumérico ([a-z0-9A-Z]), con guiones (-), guiones bajos (_), puntos (.), y cualquier carácter alfanumérico en medio. El prefijo es opcional. Si se indica, este debe ser un subdominio DNS: una serie de etiquetas DNS separadas por puntos (.), no mayores de 253 caracteres en total, seguidas de una barra (/).

Si se omite el prefijo, la clave de la etiqueta se entiende que es privada para el usuario. Los componentes automatizados del sistema (ej. kube-scheduler, kube-controller-manager, kube-apiserver, kubectl, u otros de terceras partes) que añaden etiquetas a los objetos de usuario deben especificar obligatoriamente un prefijo.

Los prefijos kubernetes.io/ y k8s.io/ están reservados para el sistema de Kubernetes.

Los valores de etiqueta válidos deben tener como máximo 63 caracteres y empezar y terminar con un carácter alfanumérico ([a-z0-9A-Z]), con guiones (-), guiones bajos (_), puntos (.), y cualquier carácter alfanumérico en medio.

Selectores de etiquetas

Al contrario que los nombres y UIDs, las etiquetas no garantizan la unicidad. En general, se espera que muchos objetos compartan la(s) misma(s) etiqueta(s).

A través del selector de etiqueta, el cliente/usuario puede identificar un conjunto de objetos. El selector de etiqueta es la primitiva principal de agrupación en Kubernetes.

La API actualmente soporta dos tipos de selectores: basados en igualdad y basados en conjunto. Un selector de etiqueta puede componerse de múltiples requisitos separados por coma. En el caso de múltiples requisitos, todos ellos deben ser satisfechos de forma que las comas actúan como operadores AND (&&) lógicos.

La semántica de selectores vacíos o no espefificados es dependiente del contexto, y los tipos de la API que utilizan los selectores deberían documentar su propia validación y significado.

Requisito basado en Igualdad

Los requisitos basados en Igualdad o Desigualdad permiten filtrar por claves y valores de etiqueta. Los objetos coincidentes deben satisfacer todas y cada una de las etiquetas indicadas, aunque puedan tener otras etiquetas adicionalmente. Se permiten tres clases de operadores =,==,!=. Los dos primeros representan la igualdad (y son simplemente sinónimos), mientras que el último representa la desigualdad. Por ejemplo:

environment = production
tier != frontend

El primero selecciona todos los recursos cuya clave es igual a environment y su valor es igual a production. El último selecciona todos los recursos cuya clave es igual a tier y su valor distinto de frontend, y todos los recursos que no tengan etiquetas con la clave tier. Se podría filtrar los recursos de production que excluyan frontend usando comas: environment=production,tier!=frontend

Un escenario de uso de requisitos basados en igualdad es aquel donde los Pods pueden especificar los criterios de selección de nodo. Por ejemplo, el Pod de abajo selecciona aquellos nodos con la etiqueta "accelerator=nvidia-tesla-p100".

apiVersion: v1
kind: Pod
metadata:
  name: cuda-test
spec:
  containers:
    - name: cuda-test
      image: "registry.k8s.io/cuda-vector-add:v0.1"
      resources:
        limits:
          nvidia.com/gpu: 1
  nodeSelector:
    accelerator: nvidia-tesla-p100

Requisito basado en Conjunto

Los requisitos de etiqueta basados en Conjuntos permiten el filtro de claves en base a un conjunto de valores. Se puede utilizar tres tipos de operadores: in,notin y exists (sólo el identificador clave). Por ejemplo:

environment in (production, qa)
tier notin (frontend, backend)
partition
!partition

El primer ejemplo selecciona todos los recursos cuya clave es igual a environment y su valor es igual a production o qa. El segundo ejemplo selecciona todos los recursos cuya clave es igual a tier y sus valores son distintos de frontend y backend, y todos los recursos que no tengan etiquetas con la clavetier. El tercer ejemplo selecciona todos los recursos que incluyan una etiqueta con la clave partition; sin comprobar los valores. El cuarto ejemplo selecciona todos los recursos que no incluyan una etiqueta con la clave partition; sin comprobar los valores. De forma similar, el separador de coma actúa como un operador AND . Así, el filtro de recursos con una clave igual a partition (sin importar el valor) y con un environment distinto de qa puede expresarse como partition,environment notin (qa). El selector basado en conjunto es una forma genérica de igualdad puesto que environment=production es equivalente a environment in (production); y lo mismo aplica para != y notin.

Los requisitos basados en conjunto pueden alternarse con aquellos basados en igualdad. Por ejemplo: partition in (customerA, customerB),environment!=qa.

API

Filtro con LIST y WATCH

Las operaciones LIST y WATCH pueden especificar selectores de etiqueta para filtrar el conjunto de objetos devueltos usando un parámetro de consulta. Ambos requisitos están permitidos (se presentan aquí como aparecerían en la cadena URL de consulta):

  • requisitios basados en igualdad: ?labelSelector=environment%3Dproduction,tier%3Dfrontend
  • requisitios basados en conjunto: ?labelSelector=environment+in+%28production%2Cqa%29%2Ctier+in+%28frontend%29

Es posible utilizar ambos estilos de selección de etiquetas para mostrar u observar recursos con un cliente REST. Por ejemplo, para enfocarse en apiserver con kubectl usando el estilo basado en igualdad se puede ejecutar:

kubectl get pods -l environment=production,tier=frontend

o usando requisitos basados en conjunto:

kubectl get pods -l 'environment in (production),tier in (frontend)'

Como ya se ha comentado, los requisitos basados en conjunto son más expresivos.  Por ejemplo, se puede implementar el operador OR sobre valores:

kubectl get pods -l 'environment in (production, qa)'

o restringir la coincidencia negativa mediante el operador notin:

kubectl get pods -l 'environment,environment notin (frontend)'

Establecer referencias en los objetos de la API

Algunos objetos de Kubernetes, como los services y los replicationcontrollers, también hacen uso de los selectores de etiqueta para referirse a otros conjuntos de objetos, como los pods.

Service y ReplicationController

El conjunto de pods que un service expone se define con un selector de etiqueta. De forma similar, el conjunto de pods que un replicationcontroller debería gestionar se define con un selector de etiqueta.

Los selectores de etiqueta para ambos objetos se definen en los archivos json o yaml usando mapas, y únicamente se permite los selectores basados en igualad:

"selector": {
    "component" : "redis",
}

ó

selector:
    component: redis

este selector (respectivamente en formato json o yaml) es equivalente a component=redis o component in (redis).

Recursos que soportan requisitos basados en conjunto

Algunos recursos más recientes, como el Job, el Deployment, el Replica Set, y el Daemon Set, sí permiten requisitos basados en conjunto.

selector:
  matchLabels:
    component: redis
  matchExpressions:
    - {key: tier, operator: In, values: [cache]}
    - {key: environment, operator: NotIn, values: [dev]}

matchLabels es un mapa de pares {key,value}. Una única combinación de {key,value} en el mapa matchLabels es equivalente a un elemento en matchExpressions donde el campo key es "key", el operator es "In", y la matriz values contiene únicamente "value". matchExpressions es una lista de requisitos de selección de pod. Los operadores permitidos son In, NotIn, Exists, y DoesNotExist. El conjunto de valores no puede ser vacío en el caso particular de In y NotIn. Todos los requisitos, tanto de matchLabels como de matchExpressions se combinan entre sí con el operador AND -- todos ellos deben ser satisfechos.

Seleccionar conjuntos de objetos

Un caso de uso de selección basada en etiquetas es la posibilidad de limitar los nodos en los que un pod puede desplegarse. Ver la documentación sobre selección de nodo para más información.

1.4.5 - Anotaciones

Puedes usar las anotaciones de Kubernetes para adjuntar metadatos arbitrarios a los objetos, de tal forma que clientes como herramientas y librerías puedan obtener fácilmente dichos metadatos.

Adjuntar metadatos a los objetos

Puedes usar las etiquetas o anotaciones para adjuntar metadatos a los objetos de Kubernetes. Las etiquetas pueden utilizarse para seleccionar objetos y para encontrar colecciones de objetos que satisfacen ciertas condiciones. Por el contrario, las anotaciones no se utilizan para identificar y seleccionar objetos. Los metadatos de una anotación pueden ser pequeños o grandes, estructurados o no estructurados, y pueden incluir caracteres no permitidos en las etiquetas.

Las anotaciones, al igual que las etiquetas, son mapas de clave/valor:

"metadata": {
  "annotations": {
    "key1" : "value1",
    "key2" : "value2"
  }
}

Aquí se presentan algunos ejemplos de información que podría ser indicada como anotaciones:

  • Campos gestionados por una capa de configuración declarativa. Adjuntando dichos campos como anotaciones permitiría diferenciarlos de los valores por defecto establecidos por clientes o servidores, además de los campos auto-generados y los campos modificados por sistemas de auto-escalado.

  • Información acerca de la construcción, entrega, o imagen como marcas de fecha, IDs de entrega, rama de Git, número de PR, funciones hash de imágenes, y direcciones de registro.

  • Referencias a los repositorios de trazas, monitorización, analíticas, o auditoría.

  • Información de librería de cliente o herramienta que puede usarse con fines de depuración de código: por ejemplo, nombre, versión, e información de construcción.

  • Información de usuario o procedencia de herramienta/sistema, como las URLs de los objetos provenientes de otros componentes del ecosistema.

  • Metadatos para una herramienta ligera de lanzamiento de aplicaciones: por ejemplo, configuración o puntos de control.

  • Número de teléfono o contacto de las personas a cargo, o entradas de directorio que especifican dónde puede encontrarse dicha información, como la página web de un equipo de trabajo.

  • Directivas del usuario final a las implementaciones para modificar el comportamiento o solicitar funcionalidades no estándar.

En vez de usar anotaciones, podrías almacenar este tipo de información en una base de datos externa o un directorio, pero eso complicaría enormemente la posibilidad de crear librerías compartidas de cliente, así como herramientas para el despliegue, gestión, introspección, y similares.

Sintaxis y conjunto de caracteres

Las Anotaciones son entradas clave/valor. Una clave válida para una anotación tiene dos partes: un prefijo opcional y un nombre, separados por una barra (/). La parte del nombre es obligatoria y debe tener 63 caracteres o menos, empezando y terminando con un carácter alfanumérico ([a-z0-9A-Z]) con guiones (-), guiones bajos (_), puntos (.) en medio. El prefijo es opcional. Si se indica, el prefijo debe ser un subdominio DNS: una serie de etiquetas DNS separadas por puntos (.), no superior a 253 caracteres en total, seguida de una barra (/).

Si se omite el prefijo, la clave de la anotación se entiende que es privada para el usuario. Los componentes automatizados del sistema (e.g. kube-scheduler, kube-controller-manager, kube-apiserver, kubectl, u otros de terceros) que añaden anotaciones a los objetos de usuario deben, pues, especificar un prefijo.

Los prefijos kubernetes.io/ y k8s.io/ se reservan para el uso exclusivo de los componentes principales de Kubernetes.

Siguientes pasos

Aprende más acerca de las Etiquetas y Selectores.

1.4.6 - Selectores de Campo

Los selectores de campo te permiten seleccionar recursos de Kubernetes basados en el valor de uno o más campos del recurso. Aquí se presentan varios ejemplos de consultas de selectores de campo:

  • metadata.name=my-service
  • metadata.namespace!=default
  • status.phase=Pending

Este comando kubectl selecciona todos los Pods para los cuales el valor del campo status.phase es igual a Running:

kubectl get pods --field-selector status.phase=Running

Campos soportados

Los selectores de campos soportados varían según el tipo de recursos de Kubernetes. Todos los tipos de recursos permiten los campos metadata.name y metadata.namespace. El uso de un selector de campo no soportado provoca un error. Por ejemplo:

kubectl get ingress --field-selector foo.bar=baz
Error from server (BadRequest): Unable to find "ingresses" that match label selector "", field selector "foo.bar=baz": "foo.bar" is not a known field selector: only "metadata.name", "metadata.namespace"

Operadores soportados

Puedes usar los operadores =, ==, y != en los selectores de campo (= y == significan lo mismo). Este comando de kubectl, por ejemplo, selecciona todos los servicios de Kubernetes que no están en el espacio de nombres default:

kubectl get services --field-selector metadata.namespace!=default

Selectores anidados

De la misma manera que con una etiqueta y otros selectores, los selectores de campo pueden anidarse como una lista de elementos separados por coma. Este comando de kubectl selecciona todos los Pods para los que el campo status.phase no es igual a Running y el campo spec.restartPolicy es igual a Always:

kubectl get pods --field-selector=status.phase!=Running,spec.restartPolicy=Always

Múltiples tipos de recursos

Puedes usar los selectores de campo entre múltiples tipos de recursos. Este comando de kubectl selecciona todos los Statefulsets y Services que no están en el espacio de nombres default:

kubectl get statefulsets,services --all-namespaces --field-selector metadata.namespace!=default

1.4.7 - Finalizadores

Los finalizadores son atributos de un namespace que instruyen a Kubernetes a esperar a que ciertas condiciones sean satisfechas antes que pueda borrar definitivamente un objeto que ha sido marcado para eliminarse. Los finalizadores alertan a los controladores para borrar recursos que poseian esos objetos eliminados.

Cuando instruyes a Kubernetes a borrar un objeto que tiene finalizadores especificados, la API de Kubernetes marca ese objeto para eliminacion configurando el campo metadata.deletionTimestamp, y retorna un codigo de estado 202 (HTTP "Aceptado"). El objeto a borrar permanece en un estado de terminacion mientras el plano de contol, u otros componentes, ejecutan las acciones definidas en los finalizadores. Luego de que esas acciones son completadas, el controlador borra los finalizadores relevantes del objeto. Cuando el campo metadata.finalizers esta vacio, Kubernetes considera el proceso de eliminacion completo y borra el objeto.

Puedes utilizar finalizadores para controlar garbage collection de recursos. Por ejemplo, puedes definir un finalizador para borrar recursos relacionados o infraestructura antes que el controlador elimine el objeto.

Puedes usar finalizadores para controlar garbage collection de los recursos alertando a los controladores para que ejecuten tareas de limpieza especificas antes de eliminar el recurso.

Los finalizadores usualmente no especifican codigo a ejecutar, sino que son generalmente listas de parametros referidos a un recurso especifico, similares a las anotaciones. Kubernetes especifica algunos finalizadores automaticamente, pero podrías especificar tus propios.

Cómo funcionan los finalizadores

Cuando creas un recurso utilizando un archivo de manifiesto, puedes especificar finalizadores mediante el campo metadata.finalizers. Cuando intentas eliminar el recurso, el servidor API que maneja el pedido de eliminación ve los valores en el campo finalizadores y hace lo siguiente:

  • Modifica el objecto para agregar un campo metadata.deletionTimestamp con el momento en que comenzaste la eliminación.
  • Previene que el objeto sea eliminado hasta que su campo metadata.finalizers este vacío.
  • Retorna un codigo de estado 202 (HTTP "Aceptado")

El controlador que meneja ese finalizador recibe la actualización del objecto configurando el campo metadata.deletionTimestamp, indicando que la eliminación del objeto ha sido solicitada. El controlador luego intenta satisfacer los requerimientos de los finalizadores especificados para ese recurso. Cada vez que una condición del finalizador es satisfecha, el controlador remueve ese parametro del campo finalizadores. Cuando el campo finalizadores esta vacío, un objeto con un campo deletionTimestamp configurado es automaticamente borrado. Puedes tambien utilizar finalizadores para prevenir el borrado de recursos no manejados.

Un ejemplo usual de un finalizador es kubernetes.io/pv-protection, el cual previene el borrado accidental de objetos PersistentVolume. Cuando un objeto PersistentVolume está en uso por un Pod, Kubernetes agrega el finalizador pv-protection. Si intentas elimiar el PersistentVolume, este pasa a un estado Terminating, pero el controlador no puede eliminarlo ya que existe el finalizador. Cuando el Pod deja de utilizar el PersistentVolume, Kubernetes borra el finalizador pv-protection y el controlador borra el volumen.

Referencias de dueño, etiquetas y finalizadores (#owners-labels-finalizers)

Al igual que las etiquetas, las referencias de dueño describen las relaciones entre objetos en Kubernetes, pero son utilizadas para un propósito diferente. Cuando un controlador maneja objetos como Pods, utiliza etiquetas para identificar cambios a grupos de objetos relacionados. Por ejemplo, cuando un Job crea uno o más Pods, el controlador del Job agrega etiquetas a esos pods para identificar cambios a cualquier Pod en el cluster con la misma etiqueta.

El controlador del Job tambien agrega referencias de dueño a esos Pods, referidas al Job que creo a los Pods. Si borras el Job mientras estos Pods estan corriendo, Kubernetes utiliza las referencias de dueño (no las etiquetas) para determinar cuáles Pods en el cluster deberían ser borrados.

Kubernetes también procesa finalizadores cuando identifica referencias de dueño en un recurso que ha sido marcado para eliminación.

En algunas situaciones, los finalizadores pueden bloquear el borrado de objetos dependientes, causando que el objeto inicial a borrar permanezca más de lo esperado sin ser completamente eliminado. En esas situaciones, deberías chequear finalizadores y referencias de dueños en los objetos y sus dependencias para intentar solucionarlo.

Siguientes pasos

1.4.8 - Etiquetas recomendadas

Puedes visualizar y gestionar los objetos de Kubernetes con herramientas adicionales a kubectl y el propio tablero de control. Un conjunto común de etiquetas permite a dichas herramientas trabajar de forma interoperable, describiendo los objetos de una forma común que todas las herramientas puedan entender.

Además del soporte a herramientas, las etiquetas recomendadas describen las aplicaciones de forma que puedan ser consultadas.

Los metadatos se organizan en torno al concepto de una aplicación. Kubernetes no es una plataforma como servicio (PaaS) y ni tiene o restringe la definición formal de una aplicación. Al contrario, las aplicaciones son informales y se describen mediante el uso de los metadatos. La definición de lo que contiene una aplicación es imprecisa.

Las etiquetas compartidas y las anotaciones comparten un prefijo común: app.kubernetes.io. Las etiquetas sin un prefijo son privadas para los usuarios. El prefijo compartido garantiza que las etiquetas compartidas no entran en conflicto con las etiquetas personalizadas de usuario.

Etiquetas

Para beneficiarse al máximo del uso de estas etiquetas, estas deberían aplicarse a cada objeto de recurso.

Clave Descripción Ejemplo Tipo
app.kubernetes.io/name El nombre de la aplicación mysql string
app.kubernetes.io/instance Un nombre único que identifique la instancia de la aplicación wordpress-abcxzy string
app.kubernetes.io/version La versión actual de la aplicación (ej., la versión semántica, cadena hash de revisión, etc.) 5.7.21 string
app.kubernetes.io/component El componente dentro de la arquitectura database string
app.kubernetes.io/part-of El nombre de una aplicación de nivel superior de la cual es parte esta aplicación wordpress string
app.kubernetes.io/managed-by La herramienta usada para gestionar la operativa de una aplicación helm string

Para ilustrar estas etiquetas en acción, consideremos el siguiente objeto StatefulSet:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  labels:
    app.kubernetes.io/name: mysql
    app.kubernetes.io/instance: wordpress-abcxzy
    app.kubernetes.io/version: "5.7.21"
    app.kubernetes.io/component: database
    app.kubernetes.io/part-of: wordpress
    app.kubernetes.io/managed-by: helm

Aplicaciones e Instancias de Aplicaciones

Una misma aplicación puede desplegarse una o más veces en un clúster de Kubernetes e, incluso, el mismo espacio de nombres. Por ejemplo, wordpress puede instalarse más de una vez de forma que sitios web diferentes sean instalaciones diferentes de wordpress.

El nombre de una aplicación y el nombre de la instancia se almacenan de forma separada. Por ejemplo, WordPress tiene un app.kubernetes.io/name igual a wordpress mientras que tiene un nombre de instancia, representado como app.kubernetes.io/instance con un valor de wordpress-abcxzy. Esto permite identificar tanto a la aplicación como a sus instancias. Cada instancia de una aplicación tiene su propio nombre único.

Ejemplos

Para ilustrar las diferentes formas en que se puede utilizar las etiquetas, los siguientes ejemplos presentan distintas complejidades.

Un Servicio Simple sin Estado

Considera el caso de un servicio simple sin estado desplegado mediante el uso de un objeto Deployment y Service. Los dos siguientes extractos de código representan cómo usar las etiquetas de la forma más sencilla.

El objeto Deployment se utiliza para supervisar los pods que ejecutan la propia aplicación.

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: myservice
    app.kubernetes.io/instance: myservice-abcxzy
...

El objeto Service se utiliza para exponer la aplicación.

apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: myservice
    app.kubernetes.io/instance: myservice-abcxzy
...

Aplicación Web con una Base de Datos

Considera una aplicación un poco más complicada: una aplicación web (WordPress) que utiliza una base de datos (MySQL), instalada utilizando Helm. Los siguientes extractos de código ilustran la parte inicial de los objetos utilizados para desplegar esta aplicación.

El comienzo del objeto Deployment siguiente se utiliza para WordPress:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: wordpress
    app.kubernetes.io/instance: wordpress-abcxzy
    app.kubernetes.io/version: "4.9.4"
    app.kubernetes.io/managed-by: helm
    app.kubernetes.io/component: server
    app.kubernetes.io/part-of: wordpress
...

El objeto Service se emplea para exponer WordPress:

apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: wordpress
    app.kubernetes.io/instance: wordpress-abcxzy
    app.kubernetes.io/version: "4.9.4"
    app.kubernetes.io/managed-by: helm
    app.kubernetes.io/component: server
    app.kubernetes.io/part-of: wordpress
...

MySQL se expone como un objeto StatefulSet con metadatos tanto para sí mismo como para la aplicación global que lo contiene:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  labels:
    app.kubernetes.io/name: mysql
    app.kubernetes.io/instance: wordpress-abcxzy
    app.kubernetes.io/managed-by: helm
    app.kubernetes.io/component: database
    app.kubernetes.io/part-of: wordpress
    app.kubernetes.io/version: "5.7.21"
...

El objeto Service se usa para exponer MySQL como parte de WordPress:

apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: mysql
    app.kubernetes.io/instance: wordpress-abcxzy
    app.kubernetes.io/managed-by: helm
    app.kubernetes.io/component: database
    app.kubernetes.io/part-of: wordpress
    app.kubernetes.io/version: "5.7.21"
...

Con los objetos StatefulSet y Service de MySQL te darás cuenta que se incluye la información acerca de MySQL y Wordpress, la aplicación global.

1.5 - Gestión de objetos usando kubectl

2 - Arquitectura de Kubernetes

2.1 - Nodos

Un nodo es una máquina de trabajo en Kubernetes, previamente conocida como minion. Un nodo puede ser una máquina virtual o física, dependiendo del tipo de clúster. Cada nodo está gestionado por el componente máster y contiene los servicios necesarios para ejecutar pods. Los servicios en un nodo incluyen el container runtime, kubelet y el kube-proxy. Accede a la sección The Kubernetes Node en el documento de diseño de arquitectura para más detalle.

Estado del Nodo

El estado de un nodo comprende la siguiente información:

Direcciones

El uso de estos campos varía dependiendo del proveedor de servicios en la nube y/o de la configuración en máquinas locales.

  • HostName: El nombre de la máquina huésped como aparece en el kernel del nodo. Puede ser reconfigurado a través del kubelet usando el parámetro --hostname-override.
  • ExternalIP: La dirección IP del nodo que es accesible externamente (que está disponible desde fuera del clúster).
  • InternalIP: La dirección IP del nodo que es accesible únicamente desde dentro del clúster.

Estados

El campo conditions describe el estado de todos los nodos en modo Running.

Estado Descripción
OutOfDisk True si no hay espacio suficiente en el nodo para añadir nuevos pods; sino False
Ready True si el nodo está en buen estado y preparado para aceptar nuevos pods, Falso si no puede aceptar nuevos pods, y Unknown si el controlador aún no tiene constancia del nodo después del último node-monitor-grace-period (por defecto cada 40 segundos)
MemoryPressure True si hay presión en la memoria del nodo -- es decir, si el consumo de memoria en el nodo es elevado; sino False
PIDPressure True si el número de PIDs consumidos en el nodo es alto -- es decir, si hay demasiados procesos en el nodo; sino False
DiskPressure True si hay presión en el tamaño del disco -- esto es, si la capacidad del disco es baja; sino False
NetworkUnavailable True si la configuración de red del nodo no es correcta; sino False

El estado del nodo se representa como un objeto JSON. Por ejemplo, la siguiente respuesta describe un nodo en buen estado:

"conditions": [
  {
    "type": "Ready",
    "status": "True"
  }
]

Si el status de la condición Ready se mantiene como Unknown o False por más tiempo de lo que dura un pod-eviction-timeout, se pasa un argumento al kube-controller-manager y todos los pods en el nodo se marcan para borrado por el controlador de nodos. El tiempo de desalojo por defecto es de cinco minutos. En algunos casos, cuando el nodo se encuentra inaccesible, el API Server no puede comunicar con el kubelet del nodo. La decisión de borrar pods no se le puede hacer llegar al kubelet hasta que la comunicación con el API Server se ha restablecido. Mientras tanto, los pods marcados para borrado pueden continuar ejecutándose en el nodo aislado.

En versiones de Kubernetes anteriores a 1.5, el controlador de nodos forzaba el borrado de dichos pods inaccesibles desde el API Server. Sin embargo, desde la versión 1.5, el nodo controlador no fuerza el borrado de pods hasta que se confirma que dichos pods han dejado de ejecutarse en el clúster. Pods que podrían estar ejecutándose en un nodo inalcanzable se muestran como Terminating o Unknown. En aquellos casos en los que Kubernetes no puede deducir si un nodo ha abandonado el clúster de forma permanente, puede que sea el administrador el que tenga que borrar el nodo de forma manual. Borrar un objeto Node en un clúster de Kubernetes provoca que los objetos Pod que se ejecutaban en el nodo sean eliminados en el API Server y libera sus nombres.

En la versión 1.12, la funcionalidad TaintNodesByCondition se eleva a beta, de forma que el controlador del ciclo de vida de nodos crea taints de forma automática, que representan estados de nodos. De forma similar, el planificador de tareas ignora estados cuando evalúa un nodo; en su lugar mira los taints del nodo y las tolerancias de los pods.

En la actualidad, los usuarios pueden elegir entre la versión de planificación antigua y el nuevo, más flexible, modelo de planificación. Un pod que no tiene definida ninguna tolerancia es planificado utilizando el modelo antiguo, pero si un nodo tiene definidas ciertas tolerancias, sólo puede ser asignado a un nodo que lo permita.

Capacidad

Describe los recursos disponibles en el nodo: CPU, memoria, y el número máximo de pods que pueden ser planificados dentro del nodo.

Información

Información general sobre el nodo: versión del kernel, versión de Kubernetes (versiones del kubelet y del kube-proxy), versión de Docker (si se utiliza), nombre del sistema operativo. Toda esta información es recogida por el kubelet en el nodo.

Gestión

A diferencia de pods y services, los nodos no son creados por Kubernetes de forma inherente; o son creados de manera externa por los proveedores de servicios en la nube como Google Compute Engine, o existen en la colección de máquinas virtuales o físicas. De manera que cuando Kubernetes crea un nodo, crea un objeto que representa el nodo. Después de ser creado, Kubernetes comprueba si el nodo es válido o no. Por ejemplo, si intentas crear un nodo con el siguiente detalle:

{
  "kind": "Node",
  "apiVersion": "v1",
  "metadata": {
    "name": "10.240.79.157",
    "labels": {
      "name": "my-first-k8s-node"
    }
  }
}

Kubernetes crea un objeto Node internamente (la representación), y valida el nodo comprobando su salud en el campo metadata.name. Si el nodo es válido -- es decir, si todos los servicios necesarios están ejecutándose -- el nodo es elegible para correr un pod. Sino, es ignorado para cualquier actividad del clúster hasta que se convierte en un nodo válido.

Actualmente, hay tres componentes que interactúan con la interfaz de nodos de Kubernetes: controlador de nodos, kubelet y kubectl.

Controlador de Nodos

El controlador de nodos es un componente maestro en Kubernetes que gestiona diferentes aspectos de los nodos.

El controlador juega múltiples papeles en la vida de un nodo. El primero es asignar un bloque CIDR (Class Inter-Domain Routing) al nodo cuando este se registra (si la asignación CIDR está activada) que contendrá las IPs disponibles para asignar a los objetos que se ejecutarán en ese nodo.

El segundo es mantener actualizada la lista interna del controlador con la lista de máquinas disponibles a través del proveedor de servicios en la nube. Cuando Kubernetes se ejecuta en la nube, si un nodo deja de responder, el controlador del nodo preguntará al proveedor si la máquina virtual de dicho nodo continúa estando disponible. Si no lo está, el controlador borrará dicho nodo de su lista interna.

El tercero es el de monitorizar la salud de los nodos. El controlador de nodos es el responsable de actualizar la condición NodeReady del campo NodeStatus a ConditionUnknown cuando un nodo deja de estar accesible (por ejemplo, si deja de recibir señales de vida del nodo indicando que está disponible, conocidas como latidos o hearbeats en inglés) y, también es responsable de posteriormente desalojar todos los pods del nodo si este continúa estando inalcanzable. Por defecto, cuando un nodo deja de responder, el controlador sigue reintentando contactar con el nodo durante 40 segundos antes de marcar el nodo con ConditionUnknown y, si el nodo no se recupera de ese estado pasados 5 minutos, empezará a drenar los pods del nodo para desplegarlos en otro nodo que esté disponible. El controlador comprueba el estado de cada nodo cada --node-monitor-period segundos.

En versiones de Kubernetes previas a 1.13, NodeStatus es el heartbeat del nodo. Empezando con 1.13 la funcionalidad de node lease se introduce como alfa (NodeLease, KEP-0009). Cuando la funcionalidad está habilitada, cada nodo tiene un objeto Lease asociado en el namespace kube-node-lease que se renueva periódicamente y ambos, el NodeStatus y el Lease son considerados como hearbeats del nodo. Node leases se renuevan con frecuencia, mientras que NodeStatus se transmite desde el nodo al máster únicamente si hay cambios o si ha pasado cierto tiempo (por defecto, 1 minuto, que es más que la cuenta atrás por defecto de 40 segundos que marca un nodo como inalcanzable). Al ser los node lease más ligeros que NodeStatus, los hearbeats resultan más económicos desde las perspectivas de escalabilidad y de rendimiento.

En Kubernetes 1.4, se actualizó la lógica del controlador de nodos para gestionar mejor los casos en los que un gran número de nodos tiene problemas alcanzando el nodo máster (Por ejemplo, cuando el nodo máster es el que tiene un problema de red). Desde 1.4, el controlador de nodos observa el estado de todos los nodos en el clúster cuando toma decisiones sobre desalojo de pods.

En la mayoría de los casos, el controlador de nodos limita el ritmo de desalojo --node-eviction-rate (0.1 por defecto) por segundo, lo que significa que no desalojará pods de más de un nodo cada diez segundos.

El comportamiento de desalojo de nodos cambia cuando un nodo en una zona de disponibilidad tiene problemas. El controlador de nodos comprobará qué porcentaje de nodos en la zona no se encuentran en buen estado (es decir, que su condición NodeReady tiene un valor ConditionUnknown o ConditionFalse) al mismo tiempo. Si la fracción de nodos con problemas es de al menos --unhealthy-zone-threshold (0.55 por defecto) entonces se reduce el ratio de desalojos: si el clúster es pequeño (por ejemplo, tiene menos o los mismos nodos que --large-cluster-size-threshold - 50 por defecto) entonces los desalojos se paran. Sino, el ratio se reduce a --secondary-node-eviction-rate (0.01 por defecto) por segundo. La razón por la que estas políticas se implementan por zonas de disponibilidad es debido a que una zona puede quedarse aislada del nodo máster mientras que las demás continúan conectadas. Si un clúster no comprende más de una zona, todo el clúster se considera una única zona.

La razón principal por la que se distribuyen nodos entre varias zonas de disponibilidad es para que el volumen de trabajo se transfiera a aquellas zonas que se encuentren en buen estado cuando una de las zonas se caiga. Por consiguiente, si todos los nodos de una zona se encuentran en mal estado, el nodo controlador desaloja al ritmo normal --node-eviction-rate. En el caso extremo de que todas las zonas se encuentran en mal estado (es decir, no responda ningún nodo del clúster), el controlador de nodos asume que hay algún tipo de problema con la conectividad del nodo máster y paraliza todos los desalojos hasta que se restablezca la conectividad.

Desde la versión 1.6 de Kubernetes el controlador de nodos también es el responsable de desalojar pods que están ejecutándose en nodos con NoExecute taints, cuando los pods no permiten dichos taints. De forma adicional, como una funcionalidad alfa que permanece deshabilitada por defecto, el NodeController es responsable de añadir taints que se corresponden con problemas en los nodos del tipo nodo inalcanzable o nodo no preparado. En esta sección de la documentación hay más detalles acerca de los taints NoExecute y de la funcionalidad alfa.

Desde la versión 1.8, el controlador de nodos puede ser responsable de la creación de taints que representan condiciones de nodos. Esta es una funcionalidad alfa en 1.8.

Auto-Registro de Nodos

Cuando el atributo del kubelet --register-node está habilitado (el valor por defecto), el kubelet intentará auto-registrarse con el API Server. Este es el patrón de diseño preferido, y utilizado por la mayoría de distribuciones.

Para auto-registro, el kubelet se inicia con las siguientes opciones:

  • --kubeconfig - La ruta a las credenciales para autentificarse con el API Server.
  • --cloud-provider - Cómo comunicarse con un proveedor de servicios para leer meta-datos sobre si mismo.
  • --register-node - Registro automático con el API Server.
  • --register-with-taints - Registro del nodo con la lista de taints proporcionada (separada por comas <key>=<value>:<effect>). Esta opción se ignora si el atributo--register-node no está habilitado.
  • --node-ip - La dirección IP del nodo.
  • --node-labels - Etiquetas para añadir al nodo durante el registro en el clúster (ver las restricciones que impone el NodeRestriction admission plugin en 1.13+).
  • --node-status-update-frequency - Especifica la frecuencia con la que el nodo envía información de estado al máster.

Cuando el Node authorization mode y el NodeRestriction admission plugin están habilitados, los kubelets sólo tienen permisos para crear/modificar su propio objeto Node.

Administración Manual de Nodos

Los administradores del clúster pueden crear y modificar objetos Node.

Si un administrador desea crear objetos Node de forma manual, debe levantar kubelet con el atributo --register-node=false.

Los administradores del clúster pueden modificar recursos Node (independientemente del valor de --register-node). Dichas modificaciones incluyen crear etiquetas en el nodo y/o marcarlo como no-planificable (de forma que pods no pueden ser planificados para instalación en el nodo).

Etiquetas y selectores de nodos pueden utilizarse de forma conjunta para controlar las tareas de planificación, por ejemplo, para determinar un subconjunto de nodos elegibles para ejecutar un pod.

Marcar un nodo como no-planificable impide que nuevos pods sean planificados en dicho nodo, pero no afecta a ninguno de los pods que existían previamente en el nodo. Esto resulta de utilidad como paso preparatorio antes de reiniciar un nodo, etc. Por ejemplo, para marcar un nodo como no-planificable, se ejecuta el siguiente comando:

kubectl cordon $NODENAME

Capacidad del Nodo

La capacidad del nodo (número de CPUs y cantidad de memoria) es parte del objeto Node. Normalmente, nodos se registran a sí mismos y declaran sus capacidades cuando el objeto Node es creado. Si se está haciendo administración manual, las capacidades deben configurarse en el momento de añadir el nodo.

El planificador de Kubernetes asegura la existencia de recursos suficientes para todos los pods que se ejecutan en un nodo. Comprueba que la suma recursos solicitados por los pods no exceda la capacidad del nodo. Incluye todos los pods iniciados por el kubelet, pero no tiene control sobre contenedores iniciados directamente por el runtime de contenedores ni sobre otros procesos que corren fuera de contenedores.

Para reservar explícitamente recursos en la máquina huésped para procesos no relacionados con pods, sigue este tutorial reserva de recursos para daemons de sistema.

Objeto API

Un nodo es un recurso principal dentro de la REST API de Kubernetes. Más detalles sobre el objeto en la API se puede encontrar en: Object Node API.

2.2 - Comunicación entre Nodos y el Plano de Control

Este documento cataloga las diferentes vías de comunicación entre el kube-apiserver y el clúster de Kubernetes. La intención es permitir a los usuarios personalizar sus instalaciones para proteger sus configuraciones de red de forma que el clúster pueda ejecutarse en una red insegura (o en un proveedor de servicios en la nube con direcciones IP públicas)

Nodo al Plano de Control

La API de Kubernetes usa el patrón de "hub-and-spoke". Todo uso de la API desde los nodos (o los pods que ejecutan) termina en el servidor API. Ninguno de los otros componentes del plano de control está diseñado para exponer servicios remotos. El servidor API está configurado para escuchar conexiones remotas en un puerto seguro HTTPS (normalmente 443) con una o más formas de autenticación de cliente habilitada. Una o más formas de autorización deben ser habilitadas, especialmente si las peticiones anónimas o los tokens de cuenta de servicio están permitidos.

Los nodos deben ser aprovisionados con el certificado raíz público del clúster, de modo que puedan conectarse de forma segura al servidor API en conjunto con credenciales de cliente válidas. Un buen enfoque es que las credenciales de cliente proporcionadas al kubelet estén en forma de certificado de cliente. Véase el TLS bootstrapping de kubelet para ver cómo aprovisionar certificados de cliente kubelet de forma automática.

Los Pods que deseen conectar con el apiserver pueden hacerlo de forma segura a través de una cuenta de servicio, de esta forma Kubernetes inserta de forma automática el certificado raíz público y un bearer token válido en el pod cuando es instanciado. El servicio kubernetes (en todos los namespaces) se configura con una dirección IP virtual que es redireccionada (via kube-proxy) al punto de acceso HTTPS en el apiserver.

Los componentes del plano de control también se comunican con el apiserver del clúster a través de un puerto seguro.

Como resultado, el modo de operación para las conexiones desde los nodos y pods que se ejecutan en los nodos al plano de control es seguro por defecto y puede ejecutarse en redes públicas y/o inseguras.

Plano de control al nodo

Hay dos vías de comunicación primaria desde el plano de control (apiserver) y los nodos. La primera es desde el apiserver al proceso kubelet que se ejecuta en cada nodo del clúster. La segunda es desde el apiserver a cualquier nodo, pod o servicio a través de la funcionalidad proxy del apiserver.

Apiserver al kubelet

Las conexiones del apiserver al kubelet se utilizan para:

  • Recoger entradas de registro de pods.
  • Conectar (a través de kubectl) con pods en ejecución.
  • Facilitar la funcionalidad port-forwarding del kubelet.

Estas conexiones terminan en el endpoint HTTPS del kubelet. Por defecto, el apiserver no verifica el certificado del kubelet, por lo que la conexión es vulnerable a ataques del tipo "ataque de intermediario" ("man-in-the-middle"), e insegura para conectar a través de redes públicas y/o no fiables.

Para verificar esta conexión, se utiliza el atributo --kubelet-certificate-authority que provee el apiserver con un certificado raíz con el que verificar el certificado del kubelet.

Si esto no es posible, se utiliza un túnel SSH entre el apiserver y el kubelet para conectar a través de redes públicas o de no confianza.

Finalmente, la autenticación y/o autorización al kubelet debe ser habilitada para proteger la API de kubelet.

Apiserver para nodos, pods y servicios

Las conexiones desde el apiserver a un nodo, pod o servicio se realizan por defecto con conexiones HTTP simples y, por consiguiente, no son autenticadas o encriptadas. Pueden ser ejecutadas en una conexión segura HTTPS con el prefijo https: al nodo, pod o nombre de servicio en la URL de la API, pero no validarán el certificado proporcionado por el punto final HTTPS ni proporcionarán las credenciales del cliente. Por tanto, aunque la conexión estará cifrada, no proporcionará ninguna garantía de integridad. Estas conexiones no son actualmente seguras para ejecutarse en redes públicas o no fiables.

Túneles SSH

Kubernetes ofrece soporte para túneles SSH que protegen la comunicación entre el plano de control y los nodos. En este modo de configuración, el apiserver inicia un túnel SSH a cada nodo en el clúster (conectando al servidor SSH en el puerto 22) y transfiere todo el tráfico destinado a un kubelet, nodo, pod o servicio a través del túnel. El túnel garantiza que dicho tráfico no es expuesto fuera de la red en la que se ejecutan los nodos.

Servicio Konnectivity

FEATURE STATE: Kubernetes v1.18 [beta]

En sustitución de los túneles SSH, el servicio Konnectivity proporciona un proxy de nivel TCP para la comunicación entre el plano de control y el clúster. El servicio Konnectivity consta de dos partes: el servidor Konnectivity en la red del plano de control y los agentes Konnectivity en la red de nodos.

Los agentes Konnectivity inician conexiones con el servidor Konnectivity y mantienen las conexiones de red. Tras habilitar el servicio Konnectivity, todo el tráfico del plano de control a los nodos pasa por estas conexiones.

Sigue la Tarea del servicio Konnectivity para configurar el servicio Konnectivity en tu clúster.

Siguientes pasos

2.3 - Conceptos subyacentes del Cloud Controller Manager

El concepto del Cloud Controller Manager (CCM) (no confundir con el ejecutable) fue creado originalmente para permitir que Kubernetes y el código específico de proveedores de servicios en la nube evolucionen de forma independiente. El Cloud Controller Manager se ejecuta a la par con otros componentes maestros como el Kubernetes Controller Manager, el API Server y el planificador. También puede ejecutarse como un extra, en cuyo caso se ejecuta por encima de Kubernetes.

El diseño del Cloud Controller Manager está basado en un sistema de plugins, lo que permite a nuevos proveedores de servicios integrarse de forma fácil con Kubernetes. Se está trabajando en implementar nuevos proveedores de servicios y para migrar los existentes del antiguo modelo al nuevo CCM.

Este documento describe los conceptos tras el Cloud Controller Manager y detalla sus funciones asociadas.

En la siguiente imagen, se puede visualizar la arquitectura de un cluster de Kubernetes que no utiliza el Cloud Controller Manager:

Arquitectura previa a CCM

Diseño

En el diagrama anterior, Kubernetes y el proveedor de servicios en la nube están integrados a través de diferentes componentes:

  • Kubelet
  • Kubernetes controller manager
  • Kubernetes API server

El CCM consolida toda la lógica dependiente de la nube de estos tres componentes para crear un punto de integración único. La nueva arquitectura con CCM se muestra a continuación:

Arquitectura CCM

Componentes del CCM

El CCM secciona parte de la funcionalidad del Kubernetes Controller Manager (KCM) y la ejecuta como procesos independientes. Específicamente, aquellos controladores en el KCM que son dependientes de la nube:

  • Controlador de Nodos
  • Controlador de Volúmenes
  • Controlador de Rutas
  • Controlador de Servicios

En la versión 1.9, el CCM se encarga de la ejecución de los siguientes controladores:

  • Controlador de Nodos
  • Controlador de Rutas
  • Controlador de Servicios

El plan original para habilitar volúmenes en CCM era utilizar volúmenes Flex con soporte para volúmenes intercambiables. Sin embargo, otro programa conocido como CSI (Container Storage Interface) se está planeando para reemplazar Flex.

Considerando todo lo anterior, se ha decidido esperar hasta que CSI esté listo.

Funciones del CCM

El CCM hereda sus funciones de componentes que son dependientes de un proveedor de servicios en la nube. Esta sección se ha estructurado basado en dichos componentes:

1. Kubernetes Controller Manager

La mayoría de las funciones del CCM derivan del KCM. Como se ha mencionado en la sección anterior, el CCM es responsable de los siguientes circuitos de control:

  • Controlador de Nodos
  • Controlador de Rutas
  • Controlador de Servicios

Controlador de Nodos

El controlador de nodos es responsable de inicializar un nodo obteniendo información del proveedor de servicios sobre los nodos ejecutándose en el clúster. El controlador de nodos lleva a cabo las siguientes funciones:

  1. Inicializa un nodo con etiquetas de región y zona específicas del proveedor.
  2. Inicializa un nodo con detalles de la instancia específicos del proveedor, como por ejemplo, el tipo o el tamaño.
  3. Obtiene las direcciones de red del nodo y su hostname.
  4. En caso de que el nodo deje de responder, comprueba la nube para ver si el nodo ha sido borrado. Si lo ha sido, borra el objeto nodo en Kubernetes.

Controlador de Rutas

El controlador de Rutas es responsable de configurar rutas en la nube para que contenedores en diferentes nodos dentro de un clúster kubernetes se puedan comunicar entre sí.

Controlador de Servicios

El controlador de servicios es responsable de monitorizar eventos de creación, actualización y borrado de servicios. Basándose en el estado actual de los servicios en el clúster Kubernetes, configura balanceadores de carga del proveedor (como Amazon ELB, Google LB, or Oracle Cloud Infrastructure Lb) de forma que estos reflejen los servicios definidos en Kubernetes. Adicionalmente, se asegura de que los sistemas de apoyo de servicios para balanceadores de carga en la nube se encuentren actualizados.

2. Kubelet

El controlador de nodos incluye la funcionalidad del kubelet que es dependiente de la nube. Previa a la introducción de CCM, el kubelet era responsable de inicializar un nodo con detalles específicos al proveedor como direcciones IP, etiquetas de región/zona y tipo de instancia. La introduccion de CCM transfiere esta inicialización del kubelet al CCM.

En este nuevo modelo, el kubelet inicializa un nodo sin información especifica del proveedor de servicios. Sin embargo, añade un taint al nodo recién creado de forma que este no esté disponible para el planificador hasta que el CCM completa el nodo con la información específica del proveedor. Sólo entonces elimina el taint y el nodo se vuelve accesible.

Mecanismo de Plugins (extensiones)

El Cloud Controller Manager utiliza interfaces Go(lang), lo que permite que implementaciones de cualquier proveedor de servicios sean conectadas. Específicamente, utiliza el CloudProvider Interface definido aquí.

La implementación de los cuatro controladores referenciados en este documento, algunas estructuras de inicialización junto con el interface CloudProvider, permanecerán como parte del núcleo de Kubernetes.

Para más información sobre el desarrollo de extensiones/plugins, consultar Desarrollo del CCM.

Autorización

Esta sección divide el nivel de acceso requerido por varios objetos API para que el CCM pueda llevar acabo sus operaciones.

Controlador de Nodos

El controlador de nodos sólo opera con objetos Nodo. Necesita de acceso total para obtener, listar, crear, actualizar, arreglar, monitorizar y borrar objetos Nodo.

v1/Node:

  • Get
  • List
  • Create
  • Update
  • Patch
  • Watch
  • Delete

Controlador de Rutas

El controlador de rutas permanece a la escucha de eventos de creación de nodos y configura sus rutas. Necesita acceso a los objetos Nodo.

v1/Node:

  • Get

Controlador de Servicios

El controlador de servicios permanece a la escucha de eventos de creación, actualización y borrado de objetos Servicio, y se encarga de configurar los endpoints para dichos servicios.

Para acceder a los objetos Servicio, necesita permisos para listar y monitorizar. Para el mantenimiento de servicios necesita permisos para parchear y actualizar.

Para configurar endpoints para los servicios necesita permisos para crear, listar, obtener, monitorizar y actualizar.

v1/Service:

  • List
  • Get
  • Watch
  • Patch
  • Update

Otros

La implementación del núcleo de CCM requiere acceso para crear eventos, y para asegurar la seguridad de operaciones; necesita acceso para crear ServiceAccounts.

v1/Event:

  • Create
  • Patch
  • Update

v1/ServiceAccount:

  • Create

El RBAC ClusterRole para CCM se muestra a continuación:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cloud-controller-manager
rules:
- apiGroups:
  - ""
  resources:
  - events
  verbs:
  - create
  - patch
  - update
- apiGroups:
  - ""
  resources:
  - nodes
  verbs:
  - '*'
- apiGroups:
  - ""
  resources:
  - nodes/status
  verbs:
  - patch
- apiGroups:
  - ""
  resources:
  - services
  verbs:
  - list
  - patch
  - update
  - watch
- apiGroups:
  - ""
  resources:
  - serviceaccounts
  verbs:
  - create
- apiGroups:
  - ""
  resources:
  - persistentvolumes
  verbs:
  - get
  - list
  - update
  - watch
- apiGroups:
  - ""
  resources:
  - endpoints
  verbs:
  - create
  - get
  - list
  - watch
  - update

Implementaciones de Proveedores

Los siguientes proveedores de servicios en la nube han implementado CCMs:

Administración del Clúster

Instrucciones para configurar y ejecutar el CCM pueden encontrarse aquí.

2.4 - Leases

Los sistemas distribuidos suelen necesitar leases, que proporcionan un mecanismo para bloquear recursos compartidos y coordinar la actividad entre los miembros de un conjunto. En Kubernetes, el concepto de lease (arrendamiento) está representado por objetos Lease en el grupo API de coordination.k8s.io, que se utilizan para capacidades críticas del sistema, como los heartbeats del nodo y la elección del líder a nivel de componente.

Heartbeats del nodo

Kubernetes utiliza la API Lease para comunicar los heartbeats de los nodos kubelet al servidor API de Kubernetes. Para cada Nodo , existe un objeto Lease con un nombre que coincide en el espacio de nombres kube-node-lease. Analizando a detalle, cada hearbeat es una solicitud update a este objeto Lease, actualizando el campo spec.renewTime del objeto Lease. El plano de control de Kubernetes utiliza la marca de tiempo de este campo para determinar la disponibilidad de este «Nodo».

Ve Objetos Lease de nodos para más detalles.

Elección del líder

Kubernetes también utiliza Leases para asegurar que sólo una instancia de un componente se está ejecutando en un momento dado. Esto lo utilizan componentes del plano de control como kube-controller-manager y kube-scheduler en configuraciones de HA, donde sólo una instancia del componente debe estar ejecutándose activamente mientras las otras instancias están en espera.

Identidad del servidor API

FEATURE STATE: Kubernetes v1.26 [beta] (enabled by default: true)

A partir de Kubernetes v1.26, cada kube-apiserver utiliza la API Lease para publicar su identidad al resto del sistema. Aunque no es particularmente útil por sí mismo, esto proporciona un mecanismo para que los clientes puedan descubrir cuántas instancias de kube-apiserver están operando el plano de control de Kubernetes. La existencia de los objetos leases de kube-apiserver permite futuras capacidades que pueden requerir la coordinación entre cada kube-apiserver.

Puedes inspeccionar los leases de cada kube-apiserver buscando objetos leases en el namespace kube-system con el nombre kube-apiserver-<sha256-hash>. También puedes utilizar el selector de etiquetas apiserver.kubernetes.io/identity=kube-apiserver:

kubectl -n kube-system get lease -l apiserver.kubernetes.io/identity=kube-apiserver
NAME                                        HOLDER                                                                           AGE
apiserver-07a5ea9b9b072c4a5f3d1c3702        apiserver-07a5ea9b9b072c4a5f3d1c3702_0c8914f7-0f35-440e-8676-7844977d3a05        5m33s
apiserver-7be9e061c59d368b3ddaf1376e        apiserver-7be9e061c59d368b3ddaf1376e_84f2a85d-37c1-4b14-b6b9-603e62e4896f        4m23s
apiserver-1dfef752bcb36637d2763d1868        apiserver-1dfef752bcb36637d2763d1868_c5ffa286-8a9a-45d4-91e7-61118ed58d2e        4m43s

El hash SHA256 utilizado en el nombre del lease se basa en el nombre de host del sistema operativo visto por ese servidor API. Cada kube-apiserver debe ser configurado para utilizar un nombre de host que es único dentro del clúster. Las nuevas instancias de kube-apiserver que utilizan el mismo nombre de host asumirán los leases existentes utilizando una nueva identidad de titular, en lugar de instanciar nuevos objetos leases. Puedes comprobar el nombre de host utilizado por kube-apiserver comprobando el valor de la etiqueta kubernetes.io/hostname:

kubectl -n kube-system get lease apiserver-07a5ea9b9b072c4a5f3d1c3702 -o yaml
apiVersion: coordination.k8s.io/v1
kind: Lease
metadata:
  creationTimestamp: "2023-07-02T13:16:48Z"
  labels:
    apiserver.kubernetes.io/identity: kube-apiserver
    kubernetes.io/hostname: master-1
  name: apiserver-07a5ea9b9b072c4a5f3d1c3702
  namespace: kube-system
  resourceVersion: "334899"
  uid: 90870ab5-1ba9-4523-b215-e4d4e662acb1
spec:
  holderIdentity: apiserver-07a5ea9b9b072c4a5f3d1c3702_0c8914f7-0f35-440e-8676-7844977d3a05
  leaseDurationSeconds: 3600
  renewTime: "2023-07-04T21:58:48.065888Z"

Los leases caducados de los kube-apiservers que ya no existen son recogidos por los nuevos kube-apiservers después de 1 hora.

Puedes desactivar el lease de identidades del servidor API desactivando la opción APIServerIdentity de los interruptores de funcionalidades.

Cargas de trabajo

Tu propia carga de trabajo puede definir su propio uso de los leases. Por ejemplo, puede ejecutar un controlador en la que un miembro principal o líder realiza operaciones que sus compañeros no realizan. Tú defines un Lease para que las réplicas del controlador puedan seleccionar o elegir un líder, utilizando la API de Kubernetes para la coordinación. Si utilizas un lease, es una buena práctica definir un nombre para el lease que esté obviamente vinculado a el producto o componente. Por ejemplo, si tienes un componente denominado Ejemplo Foo, utilice un lease denominado ejemplo-foo.

Si un operador de clúster u otro usuario final puede desplegar varias instancias de un componente, selecciona un nombre prefijo y elije un mecanismo (como el hash del nombre del despliegue) para evitar colisiones de nombres para los leases.

Puedes utilizar otro enfoque siempre que consigas el mismo resultado: los distintos productos de software no entren en conflicto entre sí.

2.5 - Container Runtime Interface (CRI)

CRI es una interfaz de plugin que permite que kubelet use una amplia variedad de container runtimes, sin necesidad de volver a compilar los componentes del clúster.

Necesitas un container runtime ejecutándose en cada Nodo en tu clúster, de manera que kubelet pueda iniciar los Pods y sus contenedores.

Container Runtime Interface (CRI) es el protocolo principal para la comunicación entre el kubelet y el Container Runtime.

La Kubernetes Container Runtime Interface (CRI) define el principal protocolo de gRPC para la comunicación entre los componentes de clúster kubelet y container runtime.

API

FEATURE STATE: Kubernetes v1.23 [stable]

Kubelet actúa como un cliente cuando se conecta al runtime del contenedor a través de gRPC. El runtime y los endpoints del servicio de imágenes deben estar disponibles en el runtime del contenedor, que se puede configurar por separado dentro de kubelet usando --image-service-endpoint banderas de línea de comando.

Para Kubernetes v1.32, kubelet prefiere usar CRI v1. Si el runtime del contenedor no es compatible con v1 del CRI, kubelet intenta negociar cualquier versión compatible anterior. Kubelet v1.32 también puede negociar CRI v1alpha2, pero esta versión se considera obsoleta. Si kubelet no puede negociar una versión CRI soportada, kubelet se da por vencido y no se registra como nodo.

Actualizando

Al actualizar Kubernetes, kubelet intenta seleccionar automáticamente la última versión de CRI al reiniciar el componente. Si eso falla, entonces la alternativa se llevará a cabo como se mencionó anteriormente. Si se requirió una rellamada de gRPC porque el runtime del contenedor se ha actualizado, entonces el runtime del contenedor también debe soportar la versión seleccionada inicialmente o se espera que la rellamada falle. Esto requiere un reinicio de kubelet.

Siguientes pasos

3 - Contenedores

3.1 - RuntimeClass

FEATURE STATE: Kubernetes v1.20 [stable]

Esta página describe el recurso RuntimeClass y el mecanismo de selección del motor de ejecución.

RuntimeClass es una característica que permite seleccionar la configuración del motor de ejecución para los contenedores. La configuración del motor de ejecución para los contenedores se utiliza para ejecutar los contenedores de un Pod.

Motivación

Se puede seleccionar un RuntimeClass diferente entre diferentes Pods para proporcionar equilibrio entre rendimiento y seguridad. Por ejemplo, si parte de la carga de trabajo requiere un alto nivel de garantía de seguridad, se podrían planificar esos Pods para ejecutarse en un motor de ejecución que use virtualización de hardware. Así se beneficiaría con un mayor aislamiento del motor de ejecución alternativo, con el coste de alguna sobrecarga adicional.

También se puede utilizar el RuntimeClass para ejecutar distintos Pods con el mismo motor de ejecución pero con distintos parámetros.

Configuración

  1. Configurar la implementación del CRI en los nodos (depende del motor de ejecución)
  2. Crear los recursos RuntimeClass correspondientes.

1. Configurar la implementación del CRI en los nodos

La configuración disponible utilizando RuntimeClass dependen de la implementación de la Interfaz del Motor de ejecución de Containers (CRI). Véase la sección Configuración del CRI para más información sobre cómo configurar la implementación del CRI.

Las configuraciones tienen un nombre de handler (manipulador) correspondiente, referenciado por la RuntimeClass. El handler debe ser una etiqueta DNS 1123 válida (alfanumérico + caracter -).

2. Crear los recursos RuntimeClass correspondientes.

Cada configuración establecida en el paso 1 tiene un nombre de handler, que identifica a dicha configuración. Para cada handler, hay que crear un objeto RuntimeClass correspondiente.

Actualmente el recurso RuntimeClass sólo tiene dos campos significativos: el nombre del RuntimeClass (metadata.name) y el handler. La definición del objeto se parece a ésta:

apiVersion: node.k8s.io/v1  # La RuntimeClass se define en el grupo node.k8s.io
kind: RuntimeClass
metadata:
  name: myclass  # Nombre por el que se referenciará la RuntimeClass
  # no contiene espacio de nombres
handler: myconfiguration  # El nombre de la configuración CRI correspondiente

El nombre de un objeto RuntimeClass debe ser un nombre de subdominio DNS válido.

Uso

Una vez se han configurado las RuntimeClasses para el clúster, el utilizarlas es muy sencillo. Solo se especifica un runtimeClassName en la especificación del Pod. Por ejemplo:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  runtimeClassName: myclass
  # ...

Así se informa a Kubelet del nombre de la RuntimeClass a utilizar para este pod. Si dicha RuntimeClass no existe, o el CRI no puede ejecutar el handler correspondiente, el pod entrará en la fase final Failed. Se puede buscar por el correspondiente evento con el mensaje de error.

Si no se especifica ninguna runtimeClassName, se usará el RuntimeHandler por defecto, lo que equivale al comportamiento cuando la opción RuntimeClass está deshabilitada.

Configuración del CRI

Para más detalles sobre cómo configurar los motores de ejecución del CRI, véase instalación del CRI.

dockershim

El CRI dockershim incorporado por Kubernetes no soporta manejadores del motor de ejecución.

containerd

Los handlers del motor de ejecución se configuran mediante la configuración de containerd en /etc/containerd/config.toml. Los handlers válidos se configuran en la sección de motores de ejecución:

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.${HANDLER_NAME}]

Véase la configuración de containerd para más detalles: https://github.com/containerd/containerd/blob/main/docs/cri/config.md

CRI-O

Los handlers del motor de ejecución se configuran a través de la configuración del CRI-O en /etc/crio/crio.conf. Los manejadores válidos se configuran en la tabla crio.runtime

[crio.runtime.runtimes.${HANDLER_NAME}]
  runtime_path = "${PATH_TO_BINARY}"

Véase la documentación de la configuración de CRI-O para más detalles.

Planificación

FEATURE STATE: Kubernetes v1.16 [beta]

Especificando el campo scheduling en una RuntimeClass se pueden establecer restricciones para asegurar que los Pods ejecutándose con dicha RuntimeClass se planifican en los nodos que la soportan.

Para asegurar que los pods sean asignados en nodos que soportan una RuntimeClass determinada, ese conjunto de nodos debe tener una etiqueta común que se selecciona en el campo runtimeclass.scheduling.nodeSelector. El nodeSelector de la RuntimeClass se combina con el nodeSelector del pod durante la admisión, haciéndose efectiva la intersección del conjunto de nodos seleccionados por ambos. Si hay conflicto, el pod se rechazará.

Si los nodos soportados se marcan para evitar que los pods con otra RuntimeClass se ejecuten en el nodo, se pueden añadir tolerations al RuntimeClass. Igual que con el nodeSelector, las tolerancias se mezclan con las tolerancias del pod durante la admisión, haciéndose efectiva la unión del conjunto de nodos tolerados por ambos.

Para saber más sobre configurar el selector de nodos y las tolerancias, véase Asignando Pods a Nodos.

Sobrecarga del Pod

FEATURE STATE: Kubernetes v1.18 [beta]

Se pueden especificar recursos de sobrecarga adicional que se asocian a los Pods que estén ejecutándose. Declarar la sobrecarga permite al clúster (incluido el planificador) contabilizarlo al tomar decisiones sobre los Pods y los recursos. Para utilizar la sobrecarga de pods, se debe haber habilitado la feature gate PodOverhead (lo está por defecto).

La sobrecarga de pods se define en la RuntimeClass a través del los campos de overhead. Con estos campos se puede especificar la sobrecarga de los pods en ejecución que utilizan esta RuntimeClass para asegurar que estas sobrecargas se cuentan en Kubernetes.

Siguientes pasos

3.2 - Variables de entorno de un Container

Esta página explica los recursos disponibles para Containers dentro del entorno de un Container.

Entorno del Container

El entorno de los Containers de Kubernetes, añade múltiples recursos importantes a los Containers:

  • Un sistema de ficheros que es la combinación de una imagen y uno o más volúmenes.
  • Información sobre el propio Container.
  • Información sobre otros objetos en el clúster.

Información del Container

El hostname de un Container es el nombre del Pod donde el Container está funcionando. Está disponible a través del comando hostname o con la función gethostname de la libc.

El nombre del Pod y el namespace están disponibles como variables de entorno a través de la downward API.

Las variables de entorno definidas por el usuario en la definición del Pod están también disponibles en el Container, así como cualquier variable de entorno definida de forma estática en la imagen de Docker.

Información del Cluster

Una lista de todos los servicios que se ejecutaban cuando se creó el Container está disponible a través de variables de entorno. La sintaxis de estas variables de entorno coincide con la de los links de Docker.

Para un servicio llamado foo que mapea un Container llamado bar, las siguientes variables de entorno estan definidas:

FOO_SERVICE_HOST=<El host donde está funcionando el servicio>
FOO_SERVICE_PORT=<El puerto dónde está funcionando el servicio>

Los servicios tienen direcciones IP dedicadas y están disponibles para el Container a través de DNS, si el complemento para DNS está habilitado.

Siguientes pasos

3.3 - Container Lifecycle Hooks

Esta página describe como los contenedores gestionados por kubelet pueden utilizar el framework Container lifecycle hook (hook del ciclo de vida del contenedor) para ejecutar código disparado por eventos durante la gestión de su ciclo de vida (lifecycle).

Introducción

De manera análoga a muchos frameworks de lenguajes de programación que tienen componentes hooks de lifecycle, como Angular, Kubernetes también proporciona esta funcionalidad para los contenedores. Los hooks permiten a los contenedores conocer los eventos en su gestión de ciclo de vida y ejecutar el código implementado en un controlador cuando el hook de ciclo de vida correspondiente es ejecutado.

Hooks de contenedores

Hay dos hooks expuestos en los contenedores:

PostStart

Este hook se ejecuta inmediatamente después de crear un contenedor. Sin embargo, no es posible garantizar que el hook se ejecute antes del ENTRYPOINT del contenedor. No se le pasa ningún parámetro.

PreStop

Este hook se llama inmediatamente antes de que se finalice un contenedor debido a una solicitud de API o evento de gestión como un fallo liveness, o contención de recursos entre otros. Una llamada al hook de Prestop falla si el contenedor ya está en estado terminated (finalizado) o completed (completado). Es bloqueante, lo que significa que es sincrónico, por lo que debe completarse antes de que la llamada para eliminar el contenedor pueda ser enviada. No se le pasa ningún parámetro.

Puedes encontrar información más detallada sobre el comportamiento de finalización de un contenedor Finalización de Pods.

Implementación de controladores de hooks

Los contenedores pueden acceder a un hook implementando y registrado en un controlador de este hook. Hay dos tipos de controladores de hooks que se pueden implementar para los contenedores:

  • Exec: ejecuta un comando específico, como pre-stop.sh, dentro de cgroups y namespaces del contenedor. Los recursos consumidos por el comando serán tomados en cuenta para el contenedor.
  • HTTP: ejecuta una petición HTTP contra un endpoint específico dentro del contenedor.

Ejecución de controladores de hooks

Cuando se llama un hook de gestión de ciclo de vida de un contenedor, el sistema de gestión de Kubernetes ejecuta el controlador en el contenedor registrado para este hook.

Las llamadas al controlador de hooks son síncronas dentro del contexto del Pod que contiene el contenedor. Esto significa que para un hook PostStart, el ENTRYPOINT del contenedor y el hook se disparan de forma asíncrona. Sin embargo, si el hook tarda demasiado en ejecutarse o se cuelga, el contenedor no puede alcanzar el estado de running (en ejecución).

El comportamiento es similar para un hook PreStop. Si el hook se cuelga durante la ejecución, la fase del Pod permanece en un estado de terminating (finalizando) y se cancela después del terminationGracePeriodSeconds (finalización después del periodo de gracia) del pod en cuestión. Si un hook PostStart o PreStop falla, se mata el contenedor.

Los usuarios deben hacer que sus controladores de hooks sean lo más livianos posible. Hay casos, sin embargo, que los comandos de larga ejecución tienen sentido, como cuando se guarda el estado antes de detener un contenedor.

Garantías de entrega de hooks

La entrega de un hook está destinada a ser enviada al menos una vez, lo que significa que un hook puede ser llamado varias veces para cualquier evento dado, tanto para PostStart como para PreStop. Depende de la implementación del hook manejar esto correctamente.

En general, solo se realizan entregas individuales. Si, por ejemplo, un receptor hook HTTP está inactivo y no puede recibir tráfico, no hay ningún reintento. Sin embargo, en algunos casos puede ocurrir una doble entrega. Por ejemplo, si un Kubelet se reinicia durante la ejecución de envio de un hook, el hook puede volver a enviarse después de que el kubelet se levante.

Depurando controladores de hooks

Los logs de un controlador de hooks no son expuestos en los eventos del Pod. Si un controlador falla por alguna razón, emite un evento. Para PostStart, es el evento FailedPostStartHook, y para PreStop, el evento FailedPreStopHook. Puedes ver que eventos están en ejecución con el comando kubectl describe pod <pod_name>. El siguiente ejemplo muestra los eventos en ejecución a través del comando anterior:

Events:
  FirstSeen  LastSeen  Count  From                                                   SubobjectPath          Type      Reason               Message
  ---------  --------  -----  ----                                                   -------------          --------  ------               -------
  1m         1m        1      {default-scheduler }                                                          Normal    Scheduled            Successfully assigned test-1730497541-cq1d2 to gke-test-cluster-default-pool-a07e5d30-siqd
  1m         1m        1      {kubelet gke-test-cluster-default-pool-a07e5d30-siqd}  spec.containers{main}  Normal    Pulling              pulling image "test:1.0"
  1m         1m        1      {kubelet gke-test-cluster-default-pool-a07e5d30-siqd}  spec.containers{main}  Normal    Created              Created container with docker id 5c6a256a2567; Security:[seccomp=unconfined]
  1m         1m        1      {kubelet gke-test-cluster-default-pool-a07e5d30-siqd}  spec.containers{main}  Normal    Pulled               Successfully pulled image "test:1.0"
  1m         1m        1      {kubelet gke-test-cluster-default-pool-a07e5d30-siqd}  spec.containers{main}  Normal    Started              Started container with docker id 5c6a256a2567
  38s        38s       1      {kubelet gke-test-cluster-default-pool-a07e5d30-siqd}  spec.containers{main}  Normal    Killing              Killing container with docker id 5c6a256a2567: PostStart handler: Error executing in Docker Container: 1
  37s        37s       1      {kubelet gke-test-cluster-default-pool-a07e5d30-siqd}  spec.containers{main}  Normal    Killing              Killing container with docker id 8df9fdfd7054: PostStart handler: Error executing in Docker Container: 1
  38s        37s       2      {kubelet gke-test-cluster-default-pool-a07e5d30-siqd}                         Warning   FailedSync           Error syncing pod, skipping: failed to "StartContainer" for "main" with RunContainerError: "PostStart handler: Error executing in Docker Container: 1"
  1m         22s       2      {kubelet gke-test-cluster-default-pool-a07e5d30-siqd}  spec.containers{main}  Warning   FailedPostStartHook

Siguientes pasos

4 - Cargas de trabajo

4.1 - Pods

4.1.1 - Pods

Los Pods son las unidades de computación desplegables más pequeñas que se pueden crear y gestionar en Kubernetes.

¿Qué és un Pod?

Un Pod (como en una vaina de ballenas o vaina de guisantes) es un grupo de uno o más contenedores (como contenedores Docker), con almacenamiento/red compartidos, y unas especificaciones de cómo ejecutar los contenedores. Los contenidos de un Pod son siempre coubicados, coprogramados y ejecutados en un contexto compartido. Un Pod modela un "host lógico" específico de la aplicación: contiene uno o más contenedores de aplicaciones relativamente entrelazados. Antes de la llegada de los contenedores, ejecutarse en la misma máquina física o virtual significaba ser ejecutado en el mismo host lógico.

Mientras que Kubernetes soporta más runtimes de contenedores a parte de Docker, este último es el más conocido y ayuda a describir Pods en términos de Docker.

El contexto compartido de un Pod es un conjunto de namespaces de Linux, cgroups y, potencialmente, otras facetas de aislamiento, las mismas cosas que aíslan un contenedor Docker. Dentro del contexto de un Pod, las aplicaciones individuales pueden tener más subaislamientos aplicados.

Los contenedores dentro de un Pod comparten dirección IP y puerto, y pueden encontrarse a través de localhost. También pueden comunicarse entre sí mediante comunicaciones estándar entre procesos, como semáforos de SystemV o la memoria compartida POSIX. Los contenedores en diferentes Pods tienen direcciones IP distintas y no pueden comunicarse por IPC sin configuración especial. Estos contenedores normalmente se comunican entre sí a través de las direcciones IP del Pod.

Las aplicaciones dentro de un Pod también tienen acceso a volúmenes compartidos, que se definen como parte de un Pod y están disponibles para ser montados en el sistema de archivos de cada aplicación.

En términos de Docker, un Pod se modela como un grupo de contenedores de Docker con namespaces y volúmenes de sistemas de archivos compartidos.

Al igual que los contenedores de aplicaciones individuales, los Pods se consideran entidades relativamente efímeras (en lugar de duraderas). Como se explica en ciclo de vida del pod, los Pods se crean, se les asigna un identificador único (UID) y se planifican en nodos donde permanecen hasta su finalización (según la política de reinicio) o supresión. Si un nodo muere, los Pods programados para ese nodo se programan para su eliminación después de un período de tiempo de espera. Un Pod dado (definido por su UID) no se "replanifica" a un nuevo nodo; en su lugar, puede reemplazarse por un Pod idéntico, con incluso el mismo nombre si lo desea, pero con un nuevo UID (consulte controlador de replicación para obtener más detalles).

Cuando se dice que algo tiene la misma vida útil que un Pod, como un volumen, significa que existe mientras exista ese Pod (con ese UID). Si ese Pod se elimina por cualquier motivo, incluso si se crea un reemplazo idéntico, el recurso relacionado (por ejemplo, el volumen) también se destruye y se crea de nuevo.

diagrama de Pod

Un Pod de múltiples contenedores que contiene un extractor de archivos y un servidor web que utiliza un volumen persistente para el almacenamiento compartido entre los contenedores.

Motivación para los Pods

Gestión

Los Pods son un modelo del patrón de múltiples procesos de cooperación que forman una unidad de servicio cohesiva. Simplifican la implementación y la administración de las aplicaciones proporcionando una abstracción de mayor nivel que el conjunto de las aplicaciones que lo constituyen. Los Pods sirven como unidad de despliegue, escalado horizontal y replicación. La colocación (coprogramación), el destino compartido (por ejemplo, la finalización), la replicación coordinada, el uso compartido de recursos y la gestión de dependencias se controlan automáticamente para los contenedores en un Pod.

Recursos compartidos y comunicación

Los Pods permiten el intercambio de datos y la comunicación entre los contenedores que lo constituyen.

Todas las aplicaciones en un Pod utilizan el mismo namespace de red (la misma IP y puerto) y, por lo tanto, pueden "encontrarse" entre sí y comunicarse utilizando localhost. Debido a esto, las aplicaciones en un Pod deben coordinar su uso de puertos. Cada Pod tiene una dirección IP en un espacio de red compartido que tiene comunicación completa con otros servidores físicos y Pods a través de la red.

Los contenedores dentro del Pod ven que el hostname del sistema es el mismo que el nombre configurado para el Pod. Hay más información sobre esto en la sección networking.

Además de definir los contenedores de aplicaciones que se ejecutan en el Pod, el Pod especifica un conjunto de volúmenes de almacenamiento compartido. Los volúmenes permiten que los datos sobrevivan a reinicios de contenedores y se compartan entre las aplicaciones dentro del Pod.

Usos de Pods

Los Pods pueden ser usados para alojar pilas de aplicaciones integradas (por ejemplo, LAMP), pero su objetivo principal es apoyar los programas de ayuda coubicados y coadministrados, como:

  • sistemas de gestión de contenido, loaders de datos y archivos, gestores de caché locales, etc.
  • copia de seguridad de registro y punto de control, compresión, rotación, captura de imágenes, etc.
  • observadores de cambio de datos, adaptadores de registro y monitoreo, publicadores de eventos, etc.
  • proxies, bridges y adaptadores.
  • controladores, configuradores y actualizadores.

Los Pods individuales no están diseñados para ejecutar varias instancias de la misma aplicación, en general.

Para una explicación más detallada, ver El sistema distribuido ToolKit: Patrones para Contenedores multiaplicación.

Alternativas

¿Por qué simplemente no ejecutar múltiples programas en un solo contenedor de Docker?

  1. Transparencia. Hacer visibles los contenedores dentro del Pod a la infraestructura permite que esta brinde servicios, como gestión de procesos y monitoreo de recursos, a los contenedores, facilitando una serie de comodidades a los usuarios.
  2. Desacople de dependencias de software. Los contenedores individuales pueden ser versionados, reconstruidos y redistribuidos independientemente. Kubernetes podría incluso apoyar actualizaciones en vivo de contenedores individuales en un futuro.
  3. Facilidad de uso. Los usuarios no necesitan ejecutar sus propios administradores de procesos, para propagación de señales, códigos de salida, etc.
  4. Eficiencia. Debido a que la infraestructura asume más responsabilidad, los contenedores pueden ser más livianos.

¿Por qué no admitir la planificación conjunta de contenedores por afinidad?

Ese enfoque proporcionaría la ubicación conjunta, pero no la mayor parte de beneficios de los Pods, como compartir recursos, IPC, compartir el destino garantizado y gestión simplificada.

Durabilidad de pods (o su ausencia)

Los Pods no están destinados a ser tratados como entidades duraderas. No sobrevivirán a errores de planificación, caídas de nodo u otros desalojos, ya sea por falta de recursos o en el caso de mantenimiento de nodos.

En general, los usuarios no deberían necesitar crear Pods directamente, deberían usar siempre controladores incluso para Pods individuales, como por ejemplo, los Deployments. Los controladores proporcionan autorecuperación con un alcance de clúster, así como replicación y gestión de despliegue. Otros controladores como los StatefulSet pueden tambien proporcionar soporte para Pods que necesiten persistir el estado.

El uso de API colectivas como la principal primitiva de cara al usuario es relativamente común entre los sistemas de planificación de clúster, incluyendo Borg, Marathon, Aurora, y Tupperware.

El Pod se expone como primitiva para facilitar:

  • planificación y capacidad de conexión del controlador
  • soporte para operaciones a nivel de Pod sin la necesidad de "proxy" a través de las API del controlador
  • desacople de la vida útil del Pod de la vida útil del controlador, como para el arranque
  • desacople de controladores y servicios, el endpoint del controlador solo mira Pods
  • composición limpia de funcionalidad a nivel de Kubelet con funcionalidad a nivel de clúster, Kubelet es efectivamente el "controlador de Pod"
  • aplicaciones en alta disponibilidad, que esperan que los Pods sean reemplazados antes de su finalización y ciertamente antes de su eliminación, como en el caso de desalojos planificados o descarga previa de imágenes.

Finalización de Pods

Debido a que los Pods representan procesos en ejecución en los nodos del clúster, es importante permitir que esos procesos finalicen de forma correcta cuando ya no se necesiten (en lugar de ser detenidos bruscamente con una señal de KILL). Los usuarios deben poder solicitar la eliminación y saber cuándo finalizan los procesos, pero también deben poder asegurarse de que las eliminaciones finalmente se completen. Cuando un usuario solicita la eliminación de un Pod, el sistema registra el período de gracia previsto antes de que el Pod pueda ser eliminado de forma forzada, y se envía una señal TERM al proceso principal en cada contenedor. Una vez que el período de gracia ha expirado, la señal KILL se envía a esos procesos y el Pod se elimina del servidor API. Si se reinicia Kubelet o el administrador de contenedores mientras se espera que finalicen los procesos, la terminación se volverá a intentar con el período de gracia completo.

Un ejemplo del ciclo de terminación de un Pod:

  1. El usuario envía un comando para eliminar Pod, con un período de gracia predeterminado (30s)
  2. El Pod en el servidor API se actualiza con el tiempo a partir del cual el Pod se considera "muerto" junto con el período de gracia.
  3. El Pod aparece como "Terminando" cuando aparece en los comandos del cliente
  4. (simultáneo con 3) Cuando el Kubelet ve que un Pod se ha marcado como terminado porque se ha configurado el tiempo en 2, comienza el proceso de apagado del Pod.
    1. Si uno de los contenedores del Pod ha definido un preStop hook, se invoca dentro del contenedor. Si el hook preStop todavía se está ejecutando después de que expire el período de gracia, el paso 2 se invoca con un pequeño período de gracia extendido (2s).
    2. El contenedor recibe la señal TERM. Tenga en cuenta que no todos los contenedores en el Pod recibirán la señal TERM al mismo tiempo y cada uno puede requerir un hook preStop si el orden en el que se cierra es importante.
  5. (simultáneo con 3) Pod se elimina de la lista de endponts del servicio, y ya no se considera parte del conjunto de Pods en ejecución para controladores de replicación. Los Pods que se apagan lentamente no pueden continuar sirviendo el tráfico ya que los balanceadores de carga (como el proxy de servicio) los eliminan de sus rotaciones.
  6. Cuando expira el período de gracia, todos los procesos que todavía se ejecutan en el Pod se eliminan con SIGKILL.
  7. El Kubelet terminará de eliminar el Pod en el servidor API configurando el período de gracia 0 (eliminación inmediata). El Pod desaparece de la API y ya no es visible desde el cliente.

Por defecto, todas las eliminaciones se realizan correctamente en 30 segundos. El comando kubectl delete admite la opción--grace-period = <seconds>que permite al usuario anular el valor predeterminado y especificar su propio valor. El valor 0 forzar eliminación del Pod. Debe especificar un indicador adicional --force junto con --grace-period = 0 para realizar eliminaciones forzadas.

Forzar destrucción de Pods

La eliminación forzada de un Pod se define como la eliminación de un Pod del estado del clúster y etcd inmediatamente. Cuando se realiza una eliminación forzada, el apiserver no espera la confirmación del kubelet de que el Pod ha finalizado en el nodo en el que se estaba ejecutando. Elimina el Pod en la API inmediatamente para que se pueda crear un nuevo Pod con el mismo nombre. En el nodo, los Pods que están configurados para terminar de inmediato recibirán un pequeño período de gracia antes de ser forzadas a matar.

Estas eliminaciones pueden ser potencialmente peligrosas para algunos Pods y deben realizarse con precaución. En el caso de Pods de StatefulSets, consulte la documentación de la tarea para eliminando Pods de un StatefulSet.

Modo privilegiado para Pods

Cualquier contenedor en un Pod puede habilitar el modo privilegiado, utilizando el indicador privilegiado en el contexto de seguridad de la especificación del contenedor. Esto es útil para contenedores que desean usar capacidades de Linux como manipular la pila de red y acceder a dispositivos. Los procesos dentro del contenedor obtienen casi los mismos privilegios que están disponibles para los procesos fuera de un contenedor. Con el modo privilegiado, debería ser más fácil escribir complementos de red y volumen como Pods separados que no necesitan compilarse en el kubelet.

API

Pod es un recurso de nivel superior en la API REST de Kubernetes. La definición de objeto de API Pod describe el objeto en detalle.

4.1.2 - Ciclo de vida de un Pod

Esta página describe el ciclo de vida de un Pod. Los Pods siguen un ciclo de vida definido, comenzando en la fase Pending, y luego pasando a "en ejecución" Running si al menos uno de sus contenedores primarios se inicia correctamente, y luego pasando a "exitoso" (Succeeded) o "fallido" (Failed) si uno de los contenedores de un Pod termina en error.

Al igual que contenedores de aplicaciones, los Pods se consideran entidades relativamente efímeras. Los Pods se crean y se les asigna un identificador único (UID), y se programan para ejecutarse en nodos donde se mantienen hasta que se terminan (de acuerdo con las políticas de reinicio) o se eliminan.

Si un nodo muere, los Pods programados para ejecutarse en ese Nodo se programan para eliminarse. El plano de control marca los Pods para ser eliminados luego de un periodo de tiempo.

Ciclo de vida de un Pod

Mientras un Pod se está ejecutando, el kubelet puede reiniciar contenedores para manejar algunos tipos de fallos. Dentro de un Pod, Kubernetes rastrea distintos estados del contenedor y determina qué acción realizar para que el Pod esté sano nuevamente.

En la API de Kubernetes, los Pods tienen una especificación y un estatus actual. El estatus de un objeto Pod consiste en un conjunto de condiciones del Pod. También puedes inyectar información de readiness personalizada a los datos de condición de un Pod, si es útil para tu aplicación.

Los Pods solo se programan una vez en su ciclo de vida; asignar un Pod a un nodo específico se llama vincular (binding, en inglés), y el proceso de seleccionar cuál Pod usar se llama programar. Una vez que un Pod está vinculado a un nodo, Kubernetes intenta ejecutar el Pod en ese nodo. El Pod se ejecuta en ese nodo hasta que termina, o hasta que es terminado; si Kubernetes no es capaz de iniciar el Pod en el nodo seleccionado (por ejemplo, si el nodo falla antes que el Pod inicie), entonces ese Pod en particular nunca inicia.

Puedes usar readiness de programación del Pod para retrasar la programación de un Pod hasta que todas sus puertas de programación sean removidas. Por ejemplo, podrías querer definir un conjunto de Pods, pero solo lanzar la programación una vez que todos los Pods hayan sido creados.

Recuperación de fallos en los Pods

Si falla uno de los contenedores en el Pod, Kubernetes puede intentar reiniciar ese contenedor en específico. Para saber más, lea cómo los Pods manejan los errores del contenedor.

Sin embargo, los Pods pueden fallar de una manera que el clúster no puede recuperar, y en ese caso Kubernetes no intenta más sanar el Pod; en su lugar, Kubernetes elimina el Pod y confía en otros componentes para proporcionar una curación automática.

Si un Pod está programado para un nodo y ese nodo luego falla, el Pod se trata como no saludable y Kubernetes eventualmente elimina el Pod. Un Pod no sobrevivirá a una evacuación debido a la falta de recursos o al mantenimiento del Nodo.

Kubernetes utiliza una abstracción de nivel superior, llamada controlador, que maneja el trabajo de gestionar las instancias de Pods relativamente desechables.

Un Pod dado (como se define por un UID) nunca es "reprogramado" a un nodo diferente; en su lugar, ese Pod puede ser reemplazado por un nuevo Pod casi idéntico. Si hace un Pod de reemplazo, incluso puede tener el mismo nombre (como en .metadata.name) que tenía el Pod antiguo, pero el reemplazo tendría un .metadata.uid diferente del Pod antiguo.

Kubernetes no garantiza que un reemplazo de un Pod existente sea programado en el mismo nodo en el que el antiguo Pod estaba siendo reemplazado.

Ciclo de vida asociados

Cuando se dice que algo tiene la misma vida útil que un Pod, como un volúmen, eso significa que el objeto existe mientras ese Pod específico (con ese UID exacto) exista. Si ese Pod se elimina por cualquier razón, e incluso si se crea un reemplazo idéntico, el objeto relacionado (un volumen, en este ejemplo) también se destruye y se crea nuevamente.

Un Pod de varios contenedores que contiene un extractor de archivos sidecar y un servidor web. El Pod utiliza un volumen efímero emptyDir para almacenamiento compartido entre los contenedores.

Figura 1.

Un Pod de varios contenedores que contiene un extractor de archivos sidecar y un servidor web. El Pod utiliza un volumen efímero emptyDir para almacenamiento compartido entre los contenedores.

Fase del Pod

El campo status de un Pod es un objeto PodStatus de Kubernetes que tiene un campo phase.

La fase de un Pod es un resumen simple y de alto nivel de dónde se encuentra el Pod en su ciclo de vida. La fase no pretende ser un resumen completo de observaciones del estado del contenedor o Pod, ni tampoco pretende ser una máquina de estado completa.

El número y los significados de los valores de fase de un Pod están estrechamente guardados. Aparte de lo que se documenta aquí, no se debe asumir nada acerca de los Pods que tienen un valor de phase determinado.

Aquí están los posibles valores de phase:

Valor Descripción
Pending El clúster de Kubernetes aceptó el Pod, pero uno o más contenedores no se configuraron ni prepararon para ejecutarse. Esto incluye el tiempo que pasa un Pod esperando ser programado, así como el tiempo dedicado a descargar imágenes de contenedores a través de la red.
Running El Pod se vinculó a un nodo y se crearon todos los contenedores. Al menos un contenedor todavía se está ejecutando o está en proceso de iniciarse o reiniciarse.
Succeeded Todos los contenedores del Pod finalizaron con éxito y no se reiniciarán.
Failed Todos los contenedores del Pod han finalizado y al menos un contenedor ha finalizado con error. Es decir, el contenedor salió con un estado distinto de cero o el sistema lo canceló.
Unknown Por alguna razón no se pudo obtener el estado del Pod. Esta fase generalmente ocurre debido a un error en la comunicación con el nodo donde debería ejecutarse el Pod.

A partir de la versión 1.27 de Kubernetes, el kubelet aplica una transición de los Pods borrados, excepto por Pods estáticos y Pods borrados por la fuerza sin un finalizador, a una fase terminal (Failed o Succeeded dependiendo de los códigos de salida de los contenedores del Pod) antes de su eliminación del servidor API.

Si un Nodo muere o se desconecta del resto del clúster, Kubernetes aplica una política para establecer la phase de todos los Pods en Failed.

Estados del contenedor

Así como la fase del Pod en general, Kubernetes rastrea el estado de cada contenedor dentro de un Pod. Puedes usar hooks del ciclo de vida de un contenedor para lanzar eventos en ciertos puntos en el ciclo de vida de un contenedor.

Una vez que el programador asigna un Pod a un Nodo, el kubelet inicia creando los contenedores para ese Pod usando un runtime del contenedor. Hay 3 estados posibles para un contenedor: Waiting(esperando), Running(en ejecución), y Terminated(terminado).

Para revisar el estado de los contenedores de un Pod, puedes usar kubectl describe pod <name-of-pod>. La salida muestra el estado de cada contenedor dentro del Pod.

Cada estado tiene un significado específico:

Waiting

Si un contenedor no está en el estado Running o Terminated, está Waiting. Un contenedor en el estado Waiting aún está ejecutando las operaciones que requiere para completar su arranque: por ejemplo, descargar la imagen del contenedor de un registro de imágenes de un contenedor, o aplicando datos secretos.

Running

El estado Running indica que el contenedor se está ejecutando sin problemas. Si hay un hook postStart configurado, ya se ha ejecutado y finalizado. Cuando utilizas el comando kubectl para consultar un Pod con un contenedor que está Running, también puedes ver información sobre cuando el contenedor entró en estado Running.

Terminated

Un contenedor en el estado Terminated comenzó su ejecución y luego se terminó con éxito o falló por alguna razón. Cuando usas kubectl para consultar un Pod con un contenedor que está Terminated, puedes ver un motivo, y un código de salida, y la hora de inicio y de finalización del contenedor.

Si un contenedor tiene un hook preStop configurado, el hook se ejecuta antes de que el contenedor entre en estado Terminated.

Cómo los Pods manejan los problemas con los contenedores

Kubernetes maneja los fallos de los contenedores dentro de los Pods usando una política de reinicio, restartPolicy en inglés definida en la especificación spec del Pod. Esta política determina cómo reacciona Kubernetes cuando los contenedores salen debido a errores u otras razones, que sigue la siguiente secuencia:

  1. Fallo inicial: Kubernetes intenta un reinicio inmediato basado en la restartPolicy del Pod.

  2. Fallos repetidos: Después del fallo inicial, Kubernetes aplica un retraso exponencial para los reinicios subsiguientes, descrito en restartPolicy. Esto evita que los intentos de reinicio rápidos y repetidos sobrecarguen el sistema.

  3. Estado de CrashLoopBackOff: Esto indica que el mecanismo de retraso exponencial está actualmente en efecto para un contenedor dado que está en un bucle de fallos, fallando y reiniciando repetidamente.

  4. Reinicio del retraso: Si un contenedor funciona correctamente durante un cierto período (por ejemplo, 10 minutos), Kubernetes reinicia el retraso, tratando cualquier nuevo fallo como el primero.

  5. En la práctica, un CrashLoopBackOff es una condición o evento que podría verse como salida del comando kubectl, al describir o listar Pods, cuando un contenedor en el Pod no arranca correctamente y luego intenta y falla continuamente en un bucle.

En otras palabras, cuando un contenedor entra en el bucle de fallos, Kubernetes aplica el retraso exponencial mencionado en la Política de reinicio del contenedor. Este mecanismo evita que un contenedor defectuoso sobrecargue el sistema con intentos de inicio fallidos continuos.

El CrashLoopBackOff puede ser causado por problemas como los siguientes:

  • Errores de la aplicación que hacen que el contenedor salga.
  • Errores de configuración, como variables de entorno incorrectas o archivos de configuración faltantes.
  • Restricciones de recursos, donde el contenedor puede no tener suficiente memoria o CPU para arrancar correctamente.
  • Fallos en los chequeos de salud si la aplicación no comienza a servir dentro del tiempo esperado.
  • Las sondas de liveness o de arranque del contenedor devuelven un resultado de Failure como se menciona en la sección de sondas. Para investigar la causa raíz de un problema de CrashLoopBackOff, un usuario puede:
  1. Revisar los registros: Use kubectl logs <nombre-del-pod> para revisar los registros del contenedor. Esta es a menudo la forma más directa de diagnosticar el problema que causa los fallos.
  2. Inspeccionar eventos: Use kubectl describe pod <nombre-del-pod> para ver eventos para el Pod, lo que puede proporcionar pistas sobre problemas de configuración o recursos.
  3. Revisar la configuración: Asegúrese de que la configuración del Pod, incluidas las variables de entorno y los volúmenes montados, sea correcta y que todos los recursos externos necesarios estén disponibles.
  4. Verificar los límites de recursos: Asegúrese de que el contenedor tenga suficiente CPU y memoria asignada. A veces, aumentar los recursos en la definición del Pod puede resolver el problema.
  5. Depurar la aplicación: Pueden existir errores o configuraciones incorrectas en el código de la aplicación. Ejecutar esta imagen de contenedor localmente o en un entorno de desarrollo puede ayudar a diagnosticar problemas específicos de la aplicación.

Política de reinicio del contenedor

La especificación (spec en inglés) de un Pod tiene un campo restartPolicy con los posibles valores Always, OnFailure, y Never. El valor por defecto es Always.

La política de reinicio (restartPolicy en inglés) para un Pod aplica a contenedores de apps en el Pod para contenedores de inicialización regulares. Los contenedores sidecar ignoran el campo restartPolicy: en Kubernetes, un sidecar se define como una entrada dentro de initContainers que tiene su restartPolicy a nivel del contenedor establecido en Always. Para contenedores de inicio que finalizan con un error, el kubelet reinicia el contenedor de inicio si el nivel del Pod restartPolicy es OnFailure o Always:

  • Always: Automáticamente reinicia el contenedor luego de alguna terminación.
  • OnFailure: Solo reinicia el contenedor si finaliza con un error (estado de salida distinto de cero).
  • Never: No reinicia el contenedor automáticamente.

Cuando el kubelet está manejando el contenedor, se reinicia de acuerdo con la política de reinicio configurada, que solo se aplica a los reinicios que realizan contenedores de reemplazo dentro del mismo Pod y ejecutándose en el mismo nodo. Después de que los contenedores en un Pod terminan, el kubelet los reinicia con un retraso de retroceso exponencial (10s, 20s, 40s,...), que está limitado a cinco minutos. Una vez que un contenedor se ha ejecutado durante 10 minutos sin ningún problema, el kubelet restablece el temporizador de reinicio para ese contenedor. Ciclo de vida de contenedores Sidecar y el Pod explica el comportamiento de init containers cuando especifica una restartPolicy.

Condiciones del Pod

Un Pod tiene un PodStatus, que tiene un listado de PodConditions a través de los cuales el Pod ha pasado o no. El kubelet administra las siguientes condiciones del Pod:

  • PodScheduled: El Pod está programado para un nodo.
  • PodReadyToStartContainers: (característica beta; habilitada por defecto) La zona de pruebas del Pod se creó correctamente y se configuró la red.
  • ContainersReady: todos los contenedores en el Pod están listos.
  • Initialized: todos los contenedores de inicio han terminado exitosamente.
  • Ready: el Pod es capaz de recibir peticiones y debería ser agregado a los grupos de equilibrio de carga de todos los Services que coincidan.
Nombre del campo Descripción
type Nombre de esta condición del Pod.
status Indica si la condición es aplicable, con valores posibles "True", "False", ó "Unknown".
lastProbeTime Marca de tiempo de cuando se probó por última vez la condición del Pod.
lastTransitionTime Marca de tiempo de cuando el Pod hizo transición de un estado a otro.
reason Texto legible por máquina que indica el motivo de la última transición de la condición.
message Mensaje legible por humanos indicando detalles acerca de la última transición de estado.

Preparación del Pod

FEATURE STATE: Kubernetes v1.14 [stable]

Tu aplicación puede inyectar retroalimentación adicional o señales al PodStatus: Pod readiness. Para usar esto, establece readinessGates en la spec del Pod para especificar una lista de condiciones adicionales que el kubelet evalúa para la preparación del Pod.

Las condiciones de preparación están determinadas por el estado actual de los campos status.conditions de un Pod. Si Kubernetes no puede encontrar una condición en el campo status.conditions de un Pod, el estado de la condición se establece en "False".

Aquí hay un ejemplo:

kind: Pod
...
spec:
  readinessGates:
    - conditionType: "www.example.com/feature-1"
status:
  conditions:
    - type: Ready                              # una PodCondition construida
      status: "False"
      lastProbeTime: null
      lastTransitionTime: 2018-01-01T00:00:00Z
    - type: "www.example.com/feature-1"        # una PodCondition extra
      status: "False"
      lastProbeTime: null
      lastTransitionTime: 2018-01-01T00:00:00Z
  containerStatuses:
    - containerID: docker://abcd...
      ready: true
...

Las condiciones del Pod que incluyas deben tener nombres que sean válidos para los formatos de etiqueta de Kubernetes.

Estado de preparación del Pod

El comando kubectl patch no admite actualizar el estado del objeto. Para establecer estas status.conditions para el Pod, las aplicaciones y los operadores deberían utilizar la acción Patch.

Puedes utilizar una librería cliente de Kubernetes para escribir código que establece condiciones personalizadas de un Pod para su preparación.

Para los Pods que utilizan condiciones personalizadas, ese Pod es evaluado para estar listo solamente cuando ambas afirmaciones aplican:

  • Todos los contenedores del Pod están listos.
  • Todas las condiciones personalizadas especificadas en readinessGates están True.

Cuando los contenedores de un Pod están listos, pero al menos una condición personalizada está ausente o False, el kubelet establece la condición del Pod en ContainersReady.

Preparación de la red del Pod

FEATURE STATE: Kubernetes v1.29 [beta]

Después de que un Pod es programado en un nodo, necesita ser admitido por el kubelet y tener cualquier volumen de almacenamiento requerido montado. Una vez que estas fases se completan, el kubelet trabaja con un runtime de contenedores (usando Container runtime interface (CRI)) para configurar un sandbox de runtime y configurar la red para el Pod. Si la puerta de características PodReadyToStartContainersCondition está habilitada (está habilitada por defecto para Kubernetes 1.32), la condición PodReadyToStartContainers se agregará al campo status.conditions de un Pod.

La condición PodReadyToStartContainers se establece en False por el kubelet cuando detecta que un Pod no tiene un sandbox de runtime con red configurada. Esto ocurre en los siguientes escenarios:

  • Al principio del ciclo de vida del Pod, cuando el kubelet aún no ha comenzado a configurar un sandbox para el Pod usando el runtime de contenedores.
  • Más adelante en el ciclo de vida del Pod, cuando el sandbox del Pod ha sido destruido debido a:
    • el nodo reiniciándose, sin que el Pod sea desalojado.
    • para runtimes de contenedores que usan máquinas virtuales para aislamiento, la máquina virtual del sandbox del Pod reiniciándose, lo que luego requiere crear un nuevo sandbox y una nueva configuración de red para el contenedor.

La condición PodReadyToStartContainers se establece en True por el kubelet después de la finalización exitosa de la creación del sandbox y la configuración de la red para el Pod por el plugin de runtime. El kubelet puede comenzar a extraer imágenes de contenedores y crear contenedores después de que la condición PodReadyToStartContainers se haya establecido en True.

Para un Pod con contenedores de inicialización, el kubelet establece la condición Initialized en True después de que los contenedores de inicialización se hayan completado exitosamente (lo que ocurre después de la creación exitosa del sandbox y la configuración de la red por el plugin de runtime). Para un Pod sin contenedores de inicialización, el kubelet establece la condición Initialized en True antes de que comience la creación del sandbox y la configuración de la red.

Sondeos del contenedor

Una sonda es un diagnóstico realizado periódicamente por el kubelet en un contenedor. Para ejecutar este diagnóstico, el kubelet ejecuta código dentro del contenedor o realiza una solicitud de red.

Mecanismos de revisión

Existen cuatro maneras diferentes de revisar un contenedor usando una sonda. Cada sonda debe definir exactamente una de estas cuatro maneras:

exec: Ejecuta un comando especificado dentro del contenedor. El diagnóstico se considera exitoso si el comando termina con un código de estado 0.

grpc
Realiza una llamada de procedimiento remoto usando gRPC. El destino debe implementar revisión de estado de gRPC. El diagnóstico se considera exitoso si el status de la respuesta es SERVING.
httpGet
Realiza una petición HTTP GET contra la dirección IP en la ruta y puerto especificado. El diagnóstico se considera exitoso si la respuesta tiene un código de estado mayor o igual que 200 y menor que 400.
tcpSocket
Realiza una revisión TCP contra la dirección IP del Pod en un puerto específico. El diagnóstico se considera exitoso si el puerto está abierto. Si el sistema remoto (el contenedor) cierra la conexión inmediatamente después de abrir la conexión, el diagnóstico se considera exitoso.

Resultados de sondeos

Cada sondeo puede tener uno de tres resultados:

Success
El contenedor ha pasado el diagnóstico.
Failure
El contenedor ha fallado el diagnóstico.
Unknown
El diagnóstico ha fallado (no se debe tomar ninguna acción, y el kubelet hará más revisiones adicionales).

Tipos de sondeo

Opcionalmente, el kubelet puede ejecutar y reaccionar a tres tipos de sondeos en contenedores en ejecución:

livenessProbe
Indica si el contenedor se está ejecutando. Si el sondeo falla, el kubelet mata el contenedor, y el contenedor está sujeto a su política de reinicio. Si un contenedor no tiene un sondeo de liveness, el estado por defecto es Success.
readinessProbe
Indica si un contenedor está preparado para responder a peticiones. Si el sondeo falla, el controlador de endpoints elimina las direcciones IP del Pod de los endpoints de todos los Services que coinciden con el Pod. El estado por defecto de readiness antes del retraso inicial es Failure. Si un contenedor no tiene un sondeo de readiness, el estado por defecto es Success.
startupProbe
Indica si la aplicación dentro del contenedor ha iniciado. El resto de los sondeos están deshabilitados si un sondeo de inicio se proporciona, hasta que se complete. Si el sondeo falla, el kubelet mata el contenedor y el contenedor está sujeto a su política de reinicio. Si un contenedor no tiene un sondeo de inicio, el estado por defecto es Success.

Para mayor información sobre como configurar un sondeo liveness, readiness o de startup, mira la sección Configurar una sonda Liveness, Readiness y Startup.

¿Cuándo debería utilizar un sondeo liveness?

Si el proceso en tu contenedor es capaz de terminar por sí mismo cuando encuentra un error o deja de estar sano, no necesitas un sondeo liveness; el kubelet automáticamente realizará la acción adecuada de acuerdo con la política de reinicio restartPolicy del Pod.

Si te gustaría que tu contenedor fuese destruido y reiniciado si falla un sondeo, especifica un sondeo liveness y especifica una restartPolicy de Always o OnFailure.

¿Cuándo debería utilizar un sondeo readiness?

Se te gustaría enviar tráfico al Pod solo cuando una sonda sea exitosa, especifica un sondeo readiness. En este caso, el sondeo readiness podría ser el mismo que el liveness, pero la existencia del sondeo readines en la especificación significa que el Pod iniciará sin recibir ningún tráfico y solo iniciará cuando el sondeo readiness sea exitoso.

Si quieres que tu contenedor sea capaz de darse de baja por mantenimiento por sí mismo, puedes especificar un sondeo de readiness que revisa un endpoint específico de readiness que es distinto del sondeo liveness.

Si tu aplicación tiene una dependencia estricta con servicios de trasfondo, puedes implementar ambos sondeos de liveness y readiness. El sondeo de liveness pasa cuando la aplicación por sí misma está sana, pero el sondeo de readiness revisa adicionalmente que cada servicio de trasfondo está disponible. Esto ayuda a evitar enviar a Pods que solo pueden responder con errores.

Si tu contenedor necesita trabajar cargando grandes datos, ficheros de configuración, o migraciones durante el inicio, puedes usar un sondeo de inicio. Sin embargo, si quieres detectar la diferencia entre una aplicación que ha fallado y una aplicación que todavía está procesando datos de inicialización, puedes usar un sondeo de readiness.

¿Cuándo debería utilizar un sondeo de inicialización?

Los sondeos de inicialización son útiles para Pods que tienen contenedores que se toman un largo tiempo para estar en servicio. En lugar de especificar un intérvalo largo de liveness, puedes crear una configuración separada para sondear el contenedor en el inicio, permitiendo un tiempo mayor que el intervalo de liveness.

Si tu contenedor usualmente inicia en más de initialDelaySeconds + failureThreshold × periodSeconds, deberías especificar un sondeo de inicialización que revise el mismo endpoint que la sonda liveness. El periodo por defecto periodSeconds es de 10 segundos. Deberías especificar el campo failureThreshold lo suficientemente alto para permitir al contenedor arrancar, sin cambiar los valores por defecto de la sonda liveness. Esto ayuda a proteger contra puntos muertos.

Finalización de Pods

Ya que los Pods representan procesos ejecutándose en nodos de un clúster, es importante permitir que esos procesos terminen con gracia cuando no se necesitan (en lugar de detenerse abruptamente con una señal Kill y sin oportunidad de limpiarse).

El diseño está orientado a permitir que puedas solicitar la eliminación de un Pod y saber cuándo finalizan los procesos, pero también para asegurar que la eliminación se completa eventualmente. Cuando solicitas la eliminación de un Pod, el clúster registra y rastrea el periodo de gracia antes de que el Pod se elimine por la fuerza. Con este rastreo de detención forzada en marcha, el kubelet intenta pararlo con gracia.

Típicamente, con esta finalización con gracia del Pod, el kubelet hace peticiones al tiempo de ejecución del contenedor para intentar detener los contenedores en el Pod, primeramente enviando una señal Term (ej. SIGTERM), con un período de tiempo de gracia, al proceso principal de cada contenedor. Las peticiones para parar los contenedores se procesan de forma asíncrona en el tiempo de ejecución del contenedor. No hay garantía del orden de procesamiento de estas peticiones. Muchos contenedores respetan el valor STOPSIGNAL definido en la imagen del contenedor y, si es diferente, envían el valor de STOPSIGNAL en lugar de TERM.

Una vez que el período de gracia ha acabado, se envía la señal KILL a cualquier proceso restante, y luego el Pod se elimina del Servidor API. Si el kubelet o el tiempo de ejecución del contenedor del servicio que lo administra se reinicia mientras espera que los procesos terminen, el kubelet reintenta de nuevo el proceso incluyendo el periodo original de gracia.

Un flujo de finalización de un Pod, ilustrado con un ejemplo:

  1. Utilizas la herramienta kubectl para eliminar manualmente un Pod específico, con un periodo de gracia por defecto (30 segundos).

  2. El Pod en el servidor API se actualiza con el tiempo más allá del cual el Pod se considera "muerto" junto con el periodo de gracia. Si utilizas kubectl describe para revisar el Pod que estás borrando, ese Pod se mostrará como Terminating. En el nodo donde se ejecuta el Pod: tan pronto como el kubelet observa que el Pod se ha marcado como terminando (se ha definido una duración de parada con gracia), el kubelet comienza el proceso local de parar el Pod.

    1. Si uno de los contenedores del Pod tiene definido un hook preStop y el terminationGracePeriodSeconds en la especificación del Pod no está definido en 0, el kubelet ejecuta ese hook dentro del contenedor. El terminationGracePeriodSeconds por defecto es 30 segundos.

    Si el hook preStop todavía se está ejecutando luego de la expiración del período de gracia, el kubelet solicita una extensión 2 segundos del periodo de gracia.

    1. El kubelet lanza el tiempo de ejecución del contenedor para enviar una señal TERM al proceso 1 dentro de cada contenedor.
  3. Al mismo tiempo que el kubelet inicia la finalización con gracia del Pod, el panel de control evalúa si quitar este Pod en finalización de los objetos EndpointSlice (y Endpoints), donde aquellos objetos representan un Service con un selector configurado.

    Los ReplicaSets y otros recursos de carga de trabajo ya no consideran al Pod como réplica válida, en servicio.

    Los Pods que finalizan lentamente no servirían tráfico regular y debería iniciar la finalización de procesamiento de conexiones abiertas. Algunas aplicaciones necesitan ir más allá de finalizar las conexiones abiertas y necesitan finalización aún con más gracia, por ejemplo, drenar y completar una sesión.

    Cualquier endpoint que representa los Pods en finalización no son removidos inmediatamente de EndpointSlices y se expone un estatus indicando el estado de terminación de la API de EndpointSlice (y la API de Endpoint legada). Los endpoints que están terminando siempre tienen su estatus ready como false (para compatibilidad con versiones anteriores a 1.26), por lo que los balanceadores de carga no los usarán para tráfico regular.

    Si se necesita drenar el tráfico en un Pod que está terminando, el readiness se puede revisar con la condición serving. Puedes encontrar más detalles en cómo implementar drenado de conexiones en el tutorial Pods y flujo de terminación de Endpoints

  1. El kubelet se asegura que el Pod se ha apagado y terminado
    1. Cuando finaliza el tiempo de gracia, si aún existe algún contenedor ejecutándose en el Pod, el kubelet lanza un apagado forzado. El runtime del contenedor envía una señal SIGKILL a cualquier proceso ejecutándose en cualquier contenedor en el Pod. El kubelet también limpia un contenedor pause oculto si ese contenedor usa uno.
    2. El kubelet hace la transición del Pod a una fase terminal (Failed ó Succeeded dependiendo del estado final de sus contenedores).
    3. El Kubelet lanza la eliminación forzosa de los objetos del Pod del servidor API, estableciendo el periodo de gracia a 0 (detención inmediata).
    4. El servidor API elimina el objeto API del Pod, que ya no es visible desde ningún cliente.

Terminación Forzada del Pod

Por defecto, todas las eliminaciones tienen un tiempo de gracia de 30 segundos. El comando kubelet delete soporta la opción --grace-period=<segundos> que permite sobreescribir el valor por defecto y especificar tu propio valor.

Establecer el período de gracia a 0 elimina de forma forzada e inmediata el Pod del servidor API. Si el Pod aún se está ejecutando en un nodo, esa eliminación forzada hace que el kubelet inicie una limpieza inmediata.

Usando kubectl, debes especificar una opción adicional --force junto con --grace-period=0 para realizar eliminaciones forzadas.

Cuando se realiza una eliminación forzada, el servidor API no espera la confirmación del kubelet de que el Pod ha terminado en el nodo en que se está ejecutando. Este elimina el Pod en la API inmediatamente para que se pueda crear un Pod con el mismo nombre. En el nodo, los Pods que están por terminar inmediatamente aún pueden tener un pequeño período de gracia antes de ser eliminados de forma forzada.

Si necesitas eliminar Pods por la fuerza y son parte de un StatefulSet, mira la documentación para borrar Pods de un StatefulSet.

Terminación del Pod y contenedores sidecar

Si tus Pods incluyen uno o más contenedores sidecar (contenedores de inicialización con una política de reinicio Always), el kubelet retrasará el envío de la señal TERM a estos contenedores sidecar hasta que el último contenedor principal se haya terminado completamente. Los contenedores sidecar serán eliminados en orden inverso al que se han definido en la especificación del Pod. Esto asegura que los contenedores sidecar continúan sirviendo a los otros contenedores en el Pod hasta que ya no se necesiten.

Esto significa que la terminación lenta de un contenedor principal también retrasará la terminación de los contenedores sidecar.

Si el periodo de gracia expira antes que se complete el proceso de terminación, el Pod podría entrar en terminación forzada. En este caso, todos los contenedores restantes en el Pod serán terminados simultáneamente con un periodo de gracia corto.

De forma similar, si el Pod tiene un hook preStop que excede el periodo de gracia de finalización, puede ocurrir una terminación de emergencia. En general, si has usado hooks de preStop para controlar el orden de terminación sin contenedores sidecar, puedes quitarlos y permitir que el kubelet maneje la terminación de sidecars automáticamente.

Recolección de elementos no utilizados de los Pods

Cuando los Pods fallan, los objetos API permanecen en el clúster hasta que un humano o el proceso de controlador los elimine explícitamente.

El recolector de elementos no utilizados (PodGC en inglés) es un controlador en el plano de control que elimina los Pods que se han terminado (con una fase de Succeeded o Failed), cuando el número de Pods excede el umbral configurado (determinado por terminated-pod-gc-threshold en el controlador de kube-controller-manager). Esto evita la fuga de recursos mientras que los Pods se crean y se eliminan en el tiempo.

Adicionalmente, el PodGC limpia cualquier Pod que satisfaga cualquiera de las siguientes condiciones:

  1. Pods huérfanos - asociados a un Nodo que ya no existe,
  2. Pods que están finalizando y no están programados,
  3. Pods que están finalizando, asociados a un nodo que no está listo, contaminado con node.kubernetes.io/out-of-service, cuando la condición NodeOutOfServiceVolumeDetach está habilitada.

Cuando la condición PodDisruptionCondition está habilitada, además de limpiar los Pods, el PodGC también los marcará como fallidos si están en una fase no terminal. También, el PodGC agrega una condición de disrupción del Pod cuando realiza la limpieza de un Pod huérfano. Mira condiciones de disrupción del Pod para más detalles.

Siguientes pasos

4.1.3 - Contenedores de Inicialización

Esta página proporciona una descripción general de los contenedores de inicialización (init containers): contenedores especializados que se ejecutan antes de los contenedores de aplicación en un Pod. Los contenedores de inicialización pueden contener utilidades o scripts de instalación no presentes en una imagen de aplicación.

Tú puedes especificar contenedores de inicialización en la especificación del Pod junto con el arreglo de containers (el cual describe los contenedores de aplicación).

Entendiendo los contenedores de inicialización

Un Pod puede tener múltiples contenedores ejecutando aplicaciones dentro de él, pero también puede tener uno o más contenedores de inicialización que se ejecutan antes de que se inicien los contenedores de aplicación.

Los contenedores de inicialización son exactamente iguales a los contenedores regulares excepto por:

  • Los contenedores de inicialización siempre se ejecutan hasta su finalización.
  • Cada contenedor de inicialiación debe completarse correctamente antes de que comience el siguiente.

Si el contenedor de inicialización de un Pod falla, kubelet reinicia repetidamente ese contenedor de inicialización hasta que tenga éxito. Sin embargo, si el Pod tiene una restartPolicy de Never y un contenedor de inicialización falla durante el inicio de ese Pod, Kubernetes trata al Pod en general como fallido.

Para especificar un contenedor de inicialización para un Pod, agrega el campo initContainers en la especificación del Pod, como un arreglo de elementos container (similar al campo containers de aplicación y su contenido). Consulta Container en la referencia de API para más detalles.

El estado de los contenedores de inicialización se devuelve en el campo .status.initContainerStatuses como un arreglo de los estados del contenedor (similar al campo .status.containerStatuses).

Diferencias con los contenedores regulares

Los contenedores de inicialización admiten todos los campos y características de los contenedores de aplicaciones, incluidos los límites de recursos, los volúmenes y la configuración de seguridad. Sin embargo, las solicitudes de recursos y los límites para un contenedor de inicialización se manejan de manera diferente, como se documenta en Recursos.

Además, los contenedores de inicialización no admiten lifecycle, livenessProbe, readinessProbe o startupProbe porque deben de ejecutarse hasta su finalización antes de que el Pod pueda estar listo.

Si especificas varios contenedores de inicialización para un Pod, kubelet ejecuta cada contenedor de inicialización secuencialmente. Cada contenedor de inicialización debe tener éxito antes de que se pueda ejecutar el siguiente. Cuando todos los contenedores de inicialización se hayan ejecutado hasta su finalización, kubelet inicializa los contenedores de aplicación para el Pod y los ejecuta como de costumbre.

Usando contenedores de inicialización

Dado que los contenedores de inicialización tienen imágenes separadas de los contenedores de aplicaciones, estos tienen algunas ventajas sobre el código relacionado de inicio:

  • Los contenedores de inicialización pueden contener utilidades o código personalizado para la configuración que no están presentes en una imagen de aplicación. Por ejemplo, no hay necesidad de hacer una imagen FROM de otra imagen solo para usar una herramienta como sed, awk, python o dig durante la instalación.
  • Los roles de constructor e implementador de imágenes de aplicación pueden funcionar de forma independiente sin la necesidad de construir conjuntamente una sola imagen de aplicación.
  • Los contenedores de inicialización pueden ejecutarse con una vista diferente al sistema de archivos que los contenedores de aplicaciones en el mismo Pod. En consecuencia, se les puede dar acceso a Secrets a los que los contenedores de aplicaciones no pueden acceder.
  • Debido a que los contenedores de inicialización se ejecutan hasta su finalización antes de que se inicien los contenedores de aplicaciones, los contenedores de inicialización ofrecen un mecanismo para bloquear o retrasar el inicio del contenedor de aplicación hasta que se cumplan una serie de condiciones previas. Una vez que las condiciones previas se cumplen, todos los contenedores de aplicaciones de un Pod pueden iniciarse en paralelo.
  • Los contenedores de inicialización pueden ejecutar de forma segura utilidades o código personalizado que de otro modo harían a una imagen de aplicación de contenedor menos segura. Si mantiene separadas herramientas innecesarias, puede limitar la superficie de ataque a la imagen del contenedor de aplicación.

Ejemplos

A continuación, se muestran algunas ideas sobre cómo utilizar los contenedores de inicialización:

  • Esperar a que se cree un Service usando una sola linea de comando de shell:

    for i in {1..100}; do sleep 1; if nslookup myservice; then exit 0; fi; done; exit 1
    
  • Registrar este Pod con un servidor remoto desde la downward API con un comando como:

    curl -X POST http://$MANAGEMENT_SERVICE_HOST:$MANAGEMENT_SERVICE_PORT/register -d 'instance=$(<POD_NAME>)&ip=$(<POD_IP>)'
    
  • Esperar algo de tiempo antes de iniciar el contenedor de aplicación con un comando como:

    sleep 60
    
  • Clonar un repositorio de Git en un Volume

  • Colocar valores en un archivo de configuración y ejecutar una herramienta de plantilla para generar dinámicamente un archivo de configuración para el contenedor de aplicación principal. Por ejemplo, colocar el valor POD_IP en una configuración y generar el archivo de configuración de la aplicación principal usando Jinja.

Contenedores de inicialización en uso

Este ejemplo define un simple Pod que tiene dos contenedores de inicialización. El primero espera por myservice y el segundo espera por mydb. Una vez que ambos contenedores de inicialización se completen, el Pod ejecuta el contenedor de aplicación desde su sección spec.

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app.kubernetes.io/name: MyApp
spec:
  containers:
  - name: myapp-container
    image: busybox:1.28
    command: ['sh', '-c', 'echo ¡La aplicación se está ejecutando! && sleep 3600']
  initContainers:
  - name: init-myservice
    image: busybox:1.28
    command: ['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo esperando a myservice; sleep 2; done"]
  - name: init-mydb
    image: busybox:1.28
    command: ['sh', '-c', "until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo esperando a mydb; sleep 2; done"]

Puedes iniciar este Pod ejecutando:

kubectl apply -f myapp.yaml

El resultado es similar a esto:

pod/myapp-pod created

Y verificar su estado con:

kubectl get -f myapp.yaml

El resultado es similar a esto:

NAME        READY     STATUS     RESTARTS   AGE
myapp-pod   0/1       Init:0/2   0          6m

o para más detalles:

kubectl describe -f myapp.yaml

El resultado es similar a esto:

Name:          myapp-pod
Namespace:     default
[...]
Labels:        app.kubernetes.io/name=MyApp
Status:        Pending
[...]
Init Containers:
  init-myservice:
[...]
    State:         Running
[...]
  init-mydb:
[...]
    State:         Waiting
      Reason:      PodInitializing
    Ready:         False
[...]
Containers:
  myapp-container:
[...]
    State:         Waiting
      Reason:      PodInitializing
    Ready:         False
[...]
Events:
  FirstSeen    LastSeen    Count    From                      SubObjectPath                           Type          Reason        Message
  ---------    --------    -----    ----                      -------------                           --------      ------        -------
  16s          16s         1        {default-scheduler }                                              Normal        Scheduled     Successfully assigned myapp-pod to 172.17.4.201
  16s          16s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Pulling       pulling image "busybox"
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Pulled        Successfully pulled image "busybox"
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Created       Created container with docker id 5ced34a04634; Security:[seccomp=unconfined]
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Started       Started container with docker id 5ced34a04634

Para ver los logs de los contenedores de inicialización en este Pod ejecuta:

kubectl logs myapp-pod -c init-myservice # Inspecciona el primer contenedor de inicialización
kubectl logs myapp-pod -c init-mydb      # Inspecciona el segundo contenedor de inicialización

En este punto, estos contenedores de inicialización estarán esperando para descubrir los Servicios denominados mydb y myservice.

Aquí hay una configuración que puedes usar para que aparezcan esos Servicios:

---
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9376
---
apiVersion: v1
kind: Service
metadata:
  name: mydb
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9377

Para crear los servicios de mydb y myservice:

kubectl apply -f services.yaml

El resultado es similar a esto:

service/myservice created
service/mydb created

Luego verás que esos contenedores de inicialización se completan y que el Pod myapp-pod pasa al estado Running:

kubectl get -f myapp.yaml

El resultado es similar a esto:

NAME        READY     STATUS    RESTARTS   AGE
myapp-pod   1/1       Running   0          9m

Este sencillo ejemplo debería servirte de inspiración para crear tus propios contenedores de inicialización. ¿Qué es lo que sigue? contiene un enlace a un ejemplo más detallado.

Comportamiento detallado

Durante el inicio del Pod, kubelet retrasa la ejecución de contenedores de inicialización hasta que la red y el almacenamiento estén listos. Después, kubelet ejecuta los contenedores de inicialización del Pod en el orden que aparecen en la especificación del Pod.

Cada contenedor de inicialización debe salir correctamente antes de que comience el siguiente contenedor. Si un contenedor falla en iniciar debido al tiempo de ejecución o sale con una falla, se vuelve a intentar de acuerdo con el restartPolicy del Pod. Sin embargo, si el restartPolicy del Pod se establece en Always, los contenedores de inicialización usan el restartPolicy como OnFailure.

Un Pod no puede estar Ready sino hasta que todos los contenedores de inicialización hayan tenido éxito. Los puertos en un contenedor de inicialización no se agregan a un Servicio. Un Pod que se está inicializando, está en el estado de Pending, pero debe tener una condición Initialized configurada como falsa.

Si el Pod se reinicia o es reiniciado, todos los contenedores de inicialización deben ejecutarse de nuevo.

Los cambios en la especificación del contenedor de inicialización se limitan al campo de la imagen del contenedor. Alterar un campo de la imagen del contenedor de inicialización equivale a reiniciar el Pod.

Debido a que los contenedores de inicialización se pueden reiniciar, reintentar o volverse a ejecutar, el código del contenedor de inicialización debe ser idempotente. En particular, el código que escribe en archivos en EmptyDirs debe estar preparado para la posibilidad de que ya exista un archivo de salida.

Los contenedores de inicialización tienen todos los campos de un contenedor de aplicaciones. Sin embargo, Kubernetes prohíbe el uso de readinessProbe porque los contenedores de inicialización no pueden definir el readiness distinto de la finalización. Esto se aplica durante la validación.

Usa activeDeadlineSeconds en el Pod para prevenir que los contenedores de inicialización fallen por siempre. La fecha límite incluye contenedores de inicialización. Sin embargo, se recomienda utilizar activeDeadlineSeconds si el usuario implementa su aplicación como un Job porque activeDeadlineSeconds tiene un efecto incluso después de que initContainer finaliza. El Pod que ya se está ejecutando correctamente sería eliminado por activeDeadlineSeconds si lo estableces.

El nombre de cada aplicación y contenedor de inicialización en un Pod debe ser único; un error de validación es arrojado para cualquier contenedor que comparta un nombre con otro.

Recursos

Dado el orden y la ejecución de los contenedores de inicialización, las siguientes reglas para el uso de recursos se aplican:

  • La solicitud más alta de cualquier recurso o límite particular definido en todos los contenedores de inicialización es la solicitud/límite de inicialización efectiva. Si algún recurso no tiene un límite de recursos especificado éste se considera como el límite más alto.
  • La solicitud/límite efectiva para un recurso es la más alta entre:
    • la suma de todas las solicitudes/límites de los contenedores de aplicación, y
    • la solicitud/límite de inicialización efectiva para un recurso
  • La planificación es hecha con base en las solicitudes/límites efectivos, lo que significa que los contenedores de inicialización pueden reservar recursos para la inicialización que no se utilizan durante la vida del Pod.
  • El nivel de QoS (calidad de servicio) del nivel de QoS efectivo del Pod es el nivel de QoS tanto para los contenedores de inicialización como para los contenedores de aplicación.

La cuota y los límites son aplicados con base en la solicitud y límite efectivos de Pod.

Los grupos de control de nivel de Pod (cgroups) se basan en la solicitud y el límite de Pod efectivos, al igual que el planificador de Kubernetes (kube-scheduler).

Razones de reinicio del Pod

Un Pod puede reiniciarse, provocando la re-ejecución de los contenedores de inicialización por las siguientes razones:

  • Se reinicia el contenedor de infraestructura del Pod. Esto es poco común y debería hacerlo alguien con acceso de root a los nodos.
  • Todos los contenedores en un Pod son terminados mientras restartPolicy esté configurado en Always, forzando un reinicio y el registro de finalización del contenedor de inicialización se ha perdido debido a la recolección de basura.

El Pod no se reiniciará cuando se cambie la imagen del contenedor de inicialización o cuando se pierda el registro de finalización del contenedor de inicialización debido a la recolección de basura. Esto se aplica a Kubernetes v1.20 y posteriores. Si estás utilizando una versión anterior de Kubernetes, consulta la documentación de la versión que estás utilizando.

Siguientes pasos

4.1.4 - Interrupciones

Esta guía es para los dueños de aplicaciones que quieren crear aplicaciones con alta disponibilidad y que necesitan entender qué tipos de interrupciones pueden suceder en los Pods.

También es para los administradores de clústeres que quieren aplicar acciones automatizadas en sus clústeres, como actualizar o autoescalar los clústeres.

Interrupciones voluntarias e involuntarias

Los Pods no desaparecen hasta que algo (una persona o un controlador) los destruye o hay problemas de hardware o software que son inevitables.

Nosotros llamamos a esos casos inevitables interrupciones involuntarias de una aplicación. Algunos ejemplos:

  • Una falla en el hardware de la máquina física del nodo.
  • Un administrador del clúster borra una VM (instancia) por error.
  • El proveedor de la nube o el hipervisor falla y hace desaparecer la VM.
  • Un kernel panic.
  • El nodo desaparece del clúster por un problema de red que lo separa del clúster.
  • Una remoción del Pod porque el nodo no tiene recursos suficientes.

A excepción de la condición sin recursos suficientes, todas estas condiciones deben ser familiares para la mayoría de los usuarios, no son específicas de Kubernetes.

Nosotros llamamos a los otros casos interrupciones voluntarias. Estas incluyen las acciones iniciadas por el dueño de la aplicación y aquellas iniciadas por el Administrador del Clúster. Las acciones típicas de los dueños de la aplicación incluyen:

  • borrar el Deployment u otro controlador que maneja el Pod
  • actualizar el Deployment del Pod que causa un reinicio
  • borrar un Pod (por ejemplo, por accidente)

Las acciones del administrador del clúster incluyen:

  • Drenar un nodo para reparar o actualizar.
  • Drenar un nodo del clúster para reducir el clúster (aprenda acerca de Autoescalamiento de Clúster).
  • Remover un Pod de un nodo para permitir que otra cosa pueda ingresar a ese nodo.

Estas acciones pueden ser realizadas directamente por el administrador del clúster, por tareas automatizadas del administrador del clúster o por el proveedor del clúster.

Consulte al administrador de su clúster, a su proveedor de la nube o a la documentación de su distribución para determinar si alguna de estas interrupciones voluntarias está habilitada en su clúster. Si ninguna se encuentra habilitada, puede omitir la creación del presupuesto de Interrupción de Pods.

Tratando con las interrupciones

Estas son algunas de las maneras para mitigar las interrupciones involuntarias:

  • Asegurarse que el Pod solicite los recursos que necesita.
  • Replique su aplicación si usted necesita alta disponibilidad. (Aprenda sobre correr aplicaciones replicadas stateless y stateful
  • Incluso, para una alta disponibilidad mayor cuando se corren aplicaciones replicadas, propague las aplicaciones por varios racks (usando anti-affinity) o usando zonas (si usa un clúster multi-zona.)

La frecuencia de las interrupciones voluntarias varía. En un clúster basico de Kubernetes, no hay interrupciones voluntarias automáticas (solo el usuario las genera). Sin embargo, su administrador del clúster o proveedor de alojamiento puede correr algun servicio adicional que pueda causar estas interrupciones voluntarias. Por ejemplo, desplegando una actualización de software en los nodos puede causar interrupciones. También, algunas implementaciones de clústers con autoescalamiento de nodos puede causar interrupciones para defragmentar o compactar los nodos. Su administrador de clúster o proveedor de alojamiento debe tener documentado cuál es el nivel de interrupciones voluntarias esperadas, sí es que las hay. Ciertas opciones de configuración, como ser usar PriorityClasses en las especificaciones de su Pod pueden también causar interrupciones voluntarias (o involuntarias).

Presupuesto de Interrupción de Pods

FEATURE STATE: Kubernetes v1.21 [stable]

Kubernetes ofrece carácteristicas para ayudar a ejecutar aplicaciones con alta disponibliidad, incluso cuando usted introduce interrupciones voluntarias frecuentes.

Como dueño de la aplicación, usted puede crear un presupuesto de interrupción de Pods (PDB por sus siglas en inglés) para cada aplicación. Un PDB limita el numero de Pods de una aplicación replicada, que estan caídos de manera simultánea por interrupciones voluntarias. Por ejemplo, una aplicación basada en quórum puede asegurarse que el número de réplicas corriendo nunca es menor al número necesitado para obtener el quórum. Una web de tipo front end puede querer asegurarse que el número de réplicas atendiendo al tráfico nunca puede caer bajo un cierto porcentaje del total.

Los administradores del clúster y proveedores de hosting pueden usar herramientas que respeten el presupuesto de interrupción de Pods utilizando la API de Desalojo en vez de directamente borrar Pods o Deployments.

Por ejemplo, el subcomando kubectl drain le permite marcar un nodo a un modo fuera de servicio. Cuando se ejecuta kubectl drain, la herramienta trata de quitar a todos los Pods en el nodo que se esta dejando fuera de servicio. La petición de desalojo que kubectl solicita en su nombre puede ser temporalmente denegado, entonces la herramienta periodicamente reintenta todas las peticiones fallidas hasta que todos los Pods en el nodo afectado son terminados o hasta que el tiempo de espera, que puede ser configurado, es alcanzado.

Un PDB especifica el número de réplicas que una aplicación puede tolerar, relativo a cuantas se pretende tener. Por ejemplo, un Deployment que tiene un .spec.replicas: 5 se supone que tiene 5 Pods en cualquier momento. Si su PDB permite tener 4 a la vez, entonces la API de Desalojo va a permitir interrupciones voluntarias de uno (pero no de dos) Pod a la vez.

El grupo de Pods que comprende a la aplicación está especificado usando una etiqueta selectora, la misma que es usada por el controlador de aplicación (deployment, stateful-set, etc).

El número de Pods "deseado" es calculado a partir de .spec.replicas del recurso de Workload que es manejado para esos Pods. El plano de control descubre el recurso Workload perteneciente al examinar las .metadata.ownerReferences del Pod.

Las Interrupciones Involuntarias no pueden ser prevenidas por los PDB; pero si son contabilizadas a partir de este presupuesto.

Los Pods que son borrados o no están disponibles debido a una actualización continua de una aplicación forman parte del presupuesto de interrupciones, pero los recursos Workload (como los Deployments y StatefulSet) no están limitados por los PDBs cuando se hacen actualizaciones continuas. En cambio, la administración de fallas durante la actualización de la aplicación está configurada en la especificación para este recurso Workload específico.

Cuando un Pod es eliminado usando la API de desalojo, este es terminado correctamente, haciendo honor al terminationGracePeriodSeconds configurado en su PodSpec.

Ejemplo de Presupuesto de Interrupción de POD

Considere un clúster con 3 nodos, nodo-1 hasta nodo-3. El clúster está ejecutando varias aplicaciones. Uno de ellos tiene 3 replicas, que llamaremos pod-a, pod-b, y pod-c. Otro Pod no relacionado y sin PDB, llamado pod-x, también se muestra.

Inicialmente los Pods están distribuidos de esta manera:

nodo-1 nodo-2 nodo-3
pod-a available pod-b available pod-c available
pod-x available

Los 3 Pods son parte de un Deployment, ellos colectivamente tienen un PDB que requiere que por lo menos 2 de los 3 Pods estén disponibles en todo momento.

Por ejemplo, supongamos que el administrador del clúster quiere reiniciar para actualizar el kernel y solucionar un error. El administrador del clúster primero intenta drenar el nodo-1 usando el comando kubectl drain. La herramienta intenta drenar los Pods pod-a y pod-x. Esto tiene éxito inmediatamente. Ambos Pods pasan al estado terminating al mismo tiempo. Esto pone al clúster en el siguiente estado:

nodo-1 draining nodo-2 nodo-3
pod-a terminating pod-b available pod-c available
pod-x terminating

El Deployment detecta que uno de los Pods está terminando, entonces crea un reemplazo llamado pod-d. Dado que el nodo-1 está bloqueado, el pod se inicia en otro nodo. Además, se crea el pod pod-y como reemplazo de pod-x.

(Nota: para un StatefulSet, pod-a, que debería llamarse algo como pod-0, debe terminar completamente antes de su reemplazo, que también se llama pod-0 pero tiene un UID diferente, puede ser creado. De lo contrario, el ejemplo también se aplica a un StatefulSet).

Ahora el clúster está en este estado:

nodo-1 draining nodo-2 nodo-3
pod-a terminating pod-b available pod-c available
pod-x terminating pod-d starting pod-y starting

En algún momento, los Pods terminan y el clúster se ve así:

nodo-1 drained nodo-2 nodo-3
pod-b available pod-c available
pod-d starting pod-y starting

En este estado, si un administrador del clúster impaciente intenta drenar el nodo-2 o el nodo-3, el comando de drenado será bloqueado, porque solo hay 2 Pods disponibles para el Deployment y el PDB requiere al menos 2. Después de un tiempo, pod-d y pod-y están disponibles.

El estado del clúster ahora se ve así:

nodo-1 drained nodo-2 nodo-3
pod-b available pod-c available
pod-d available pod-y available

Ahora, el administrador del clúster drena el nodo-2. El comando de drenado intentará drenar los 2 Pods en algún orden, digamos primero pod-b y luego pod-d. Tendrá éxito en eliminar pod-b. Pero cuando intente drenar pod-d, será rechazado porque eso dejará solo un Pod disponible para el Deployment.

El Deployment crea un reemplazo para pod-b llamado pod-e. Dado que no hay suficientes recursos disponibles en el clúster para programar pod-e, el drenado será bloqueado nuevamente. El clúster terminará en este estado:

nodo-1 drained nodo-2 nodo-3 no node
pod-b terminating pod-c available pod-e pending
pod-d available pod-y available

Ahora, el administrador del clúster necesita agregar un nuevo nodo en el clúster para continuar con la actualización.

Usted puede ver cómo Kubernetes varía la tasa a la que ocurren las interrupciones, según:

  • cuántas réplicas necesita una aplicación
  • cuánto tiempo lleva apagar una instancia correctamente
  • cuánto tiempo lleva iniciar una nueva instancia
  • el tipo de controlador
  • la capacidad de recursos del clúster

Separación entre el dueño del Clúster y los roles de dueños de la Aplicación

Muchas veces es útil pensar en el Administrador del Clúster y al dueño de la aplicación como roles separados con conocimiento limitado el uno del otro. Esta separación de responsabilidades puede tener sentido en estos escenarios:

  • Cuando hay muchos equipos con aplicaciones compartiendo un clúster de Kubernetes y hay una especialización natural de roles
  • Cuando se usa una herramienta de terceros o un servicio para automatizar el control del clúster

El presupuesto de interrupción de Pods respalda esta separación de roles, proporcionando una interfaz entre los roles.

Si no hay tal separación de responsabilidades en la organización, es posible que no sea necesario el Presupuesto de Interrupción de Pods.

Cómo realizar Acciones Disruptivas en el Clúster

Si usted es el Administrador del Clúster y necesitas realizar una acción disruptiva en todos los nodos del clúster, como una actualización de nodo o de software, estas son algunas de las opciones:

  • Aceptar el tiempo de inactividad mientras dura la actualización.
  • Cambiar a otra réplica completa del clúster.
    • No hay tiempo de inactividad, pero puede ser costoso tener duplicados los nodos y también se requiere esfuerzo humano para orquestar dicho cambio.
  • Diseñar la tolerancia a fallas en la aplicación y usar PDBs.
    • No hay tiempo de inactividad.
    • Duplicación mínima de recursos.
    • Permite mucha más automatización en la administración del clúster.
    • Diseñar aplicaciones para tolerar fallas es complicado, pero el trabajo para tolerar interrupciones involuntarias a menudo vale la pena en comparación con el trabajo de admitir autoescalado y tolerar interrupciones involuntarias.

Siguientes pasos

4.1.5 - Containers Efímeros

FEATURE STATE: Kubernetes v1.32 [alpha]

Esta página proporciona una descripción general de los Containers efímeros: un tipo especial de Container que se ejecuta temporalmente en un Pod ya existente para cumplir las acciones iniciadas por el usuario, como por ejemplo, la solución de problemas. En vez de ser utilizadas para crear aplicaciones, los Containers efímeros se utilizan para examinar los servicios.

Entendiendo los Containers efímeros

Pods son el componente fundamental de las aplicaciones de Kubernetes. Puesto que los Pods están previstos para ser desechables y reemplazables, no se puede añadir un Container a un Pod una vez creado. Sin embargo, por lo general se eliminan y se reemplazan los Pods de manera controlada utilizando Deployments.

En ocasiones es necesario examinar el estado de un Pod existente, como por ejemplo, para poder solucionar un error difícil de reproducir. Puede ejecutar en estos casos un Container efímero en un Pod ya existente para examinar su estado y para ejecutar comandos de manera arbitraria.

Qué es un Container efímero?

Los Containers efímeros se diferencian de otros Containers en que no garantizan ni los recursos ni la ejecución, y en que nunca se reiniciarán automáticamente, de modo que no son aptos para la construcción de aplicaciones. Los Containers efímeros se describen usando la misma ContainerSpec que los Containers regulares, aunque muchos campos son incompatibles y no están habilitados para los Containers efímeros.

  • Los Containers efímeros no pueden tener puertos, por lo que campos como ports, livenessProbe, readinessProbe no están habilitados.
  • Las asignaciones de recursos del Pod son inmutables, por lo que no esta habilitado configurar "resources".
  • Para obtener una lista completa de los campos habilitados, consulte la documentación de referencia [EphemeralContainer] (/docs/reference/generated/kubernetes-api/v1.32/#ephemeralcontainer-v1-core).

En vez de añadirlos de forma directa al pod.spec, los Containers efímeros se crean usando un controlador especial de la API, ephemeralcontainers, por lo tanto no es posible añadir un Container efímero utilizando kubectl edit.

Al igual en el caso de los Containers regulares, no se puede modificar o remover un Container efímero después de haberlo agregado a un Pod.

Casos de uso para los Containers efímeros

Los Containers efímeros resultan útiles para la solución interactiva de incidencias cuando kubectl exec es insuficiente tanto porque un container se ha caído, como porque la imagen de un Container no incluye las utilidades de depuración.

En particular, las imágenes distroless le permiten desplegar imágenes de Containers mínimos que disminuyen la superficie de ataque y la exposición a errores y vulnerabilidades. Ya que las imágenes distroless no contienen un shell ni ninguna utilidad de depuración, resulta difícil solucionar los problemas de las imágenes distroless usando solamente kubectl exec.

Cuando utilice Containers efímeros, es conveniente habilitar el proceso Namespace de uso compartido para poder ver los procesos en otros containers.

Ejemplos

En los ejemplos de esta sección muestran la forma en que los Containers efímeros se presentan en la API. Los usuarios normalmente usarían un plugin kubectl para la solución de problemas que automatizaría estos pasos.

Los Containers efímeros son creados utilizando el subrecurso ephemeralcontainers del Pod, que puede ser visto utilizando kubectl --raw. En primer lugar describa el Container efímero a añadir como una lista de EphemeralContainers:

{
    "apiVersion": "v1",
    "kind": "EphemeralContainers",
    "metadata": {
        "name": "example-pod"
    },
    "ephemeralContainers": [{
        "command": [
            "sh"
        ],
        "image": "busybox",
        "imagePullPolicy": "IfNotPresent",
        "name": "debugger",
        "stdin": true,
        "tty": true,
        "terminationMessagePolicy": "File"
    }]
}

Para actualizar los Containers efímeros de los example-pod en ejecución:

kubectl replace --raw /api/v1/namespaces/default/pods/example-pod/ephemeralcontainers  -f ec.json

Esto devolverá una nueva lista de Containers efímeros:

{
   "kind":"EphemeralContainers",
   "apiVersion":"v1",
   "metadata":{
      "name":"example-pod",
      "namespace":"default",
      "selfLink":"/api/v1/namespaces/default/pods/example-pod/ephemeralcontainers",
      "uid":"a14a6d9b-62f2-4119-9d8e-e2ed6bc3a47c",
      "resourceVersion":"15886",
      "creationTimestamp":"2019-08-29T06:41:42Z"
   },
   "ephemeralContainers":[
      {
         "name":"debugger",
         "image":"busybox",
         "command":[
            "sh"
         ],
         "resources":{

         },
         "terminationMessagePolicy":"File",
         "imagePullPolicy":"IfNotPresent",
         "stdin":true,
         "tty":true
      }
   ]
}

Se puede ver el estado del Container efímero creado usando kubectl describe:

kubectl describe pod example-pod
...
Ephemeral Containers:
  debugger:
    Container ID:  docker://cf81908f149e7e9213d3c3644eda55c72efaff67652a2685c1146f0ce151e80f
    Image:         busybox
    Image ID:      docker-pullable://busybox@sha256:9f1003c480699be56815db0f8146ad2e22efea85129b5b5983d0e0fb52d9ab70
    Port:          <none>
    Host Port:     <none>
    Command:
      sh
    State:          Running
      Started:      Thu, 29 Aug 2019 06:42:21 +0000
    Ready:          False
    Restart Count:  0
    Environment:    <none>
    Mounts:         <none>
...

Se puede conectar al nuevo Container efímero usando kubectl attach:

kubectl attach -it example-pod -c debugger

Si el proceso Namespace de uso compartido está habilitado, se pueden visualizar los procesos de todos los Containers de ese Pod. Por ejemplo, después de haber conectado, ejecute ps en el debugger del container:

ps auxww

La respuesta es semejante a:

PID   USER     TIME  COMMAND
    1 root      0:00 /pause
    6 root      0:00 nginx: master process nginx -g daemon off;
   11 101       0:00 nginx: worker process
   12 101       0:00 nginx: worker process
   13 101       0:00 nginx: worker process
   14 101       0:00 nginx: worker process
   15 101       0:00 nginx: worker process
   16 101       0:00 nginx: worker process
   17 101       0:00 nginx: worker process
   18 101       0:00 nginx: worker process
   19 root      0:00 /pause
   24 root      0:00 sh
   29 root      0:00 ps auxww

4.2 - Controladores

4.2.1 - ReplicaSet

El objeto de un ReplicaSet es el de mantener un conjunto estable de réplicas de Pods ejecutándose en todo momento. Así, se usa en numerosas ocasiones para garantizar la disponibilidad de un número específico de Pods idénticos.

Cómo funciona un ReplicaSet

Un ReplicaSet se define con campos, incluyendo un selector que indica cómo identificar a los Pods que puede adquirir, un número de réplicas indicando cuántos Pods debería gestionar, y una plantilla pod especificando los datos de los nuevos Pods que debería crear para conseguir el número de réplicas esperado. Un ReplicaSet alcanza entonces su propósito mediante la creación y eliminación de los Pods que sea necesario para alcanzar el número esperado. Cuando un ReplicaSet necesita crear nuevos Pods, utiliza su plantilla Pod.

El enlace que un ReplicaSet tiene hacia sus Pods es a través del campo del Pod denominado metadata.ownerReferences, el cual indica qué recurso es el propietario del objeto actual. Todos los Pods adquiridos por un ReplicaSet tienen su propia información de identificación del ReplicaSet en su campo ownerReferences. Y es a través de este enlace cómo el ReplicaSet conoce el estado de los Pods que está gestionando y actúa en consecuencia.

Un ReplicaSet identifica los nuevos Pods a adquirir usando su selector. Si hay un Pod que no tiene OwnerReference o donde OwnerReference no es un controlador, pero coincide con el selector del ReplicaSet, este será inmediatamente adquirido por dicho ReplicaSet.

Cuándo usar un ReplicaSet

Un ReplicaSet garantiza que un número específico de réplicas de un pod se está ejecutando en todo momento. Sin embargo, un Deployment es un concepto de más alto nivel que gestiona ReplicaSets y proporciona actualizaciones de forma declarativa de los Pods junto con muchas otras características útiles. Por lo tanto, se recomienda el uso de Deployments en vez del uso directo de ReplicaSets, a no ser que se necesite una orquestración personalizada de actualización o no se necesite las actualizaciones en absoluto.

En realidad, esto quiere decir que puede que nunca necesites manipular los objetos ReplicaSet: en vez de ello, usa un Deployment, y define tu aplicación en la sección spec.

Ejemplo

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  # modifica las réplicas según tu caso de uso
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
      - name: php-redis
        image: gcr.io/google_samples/gb-frontend:v3

Si guardas este manifiesto en un archivo llamado frontend.yaml y lo lanzas en un clúster de Kubernetes, se creará el ReplicaSet definido y los Pods que maneja.

kubectl apply -f http://k8s.io/examples/controllers/frontend.yaml

Puedes ver los ReplicaSets actuales desplegados:

kubectl get rs

Y ver el frontend que has creado:

NAME       DESIRED   CURRENT   READY   AGE
frontend   3         3         3       6s

También puedes comprobar el estado del replicaset:

kubectl describe rs/frontend

Y verás una salida parecida a la siguiente:

Name:		frontend
Namespace:	default
Selector:	tier=frontend,tier in (frontend)
Labels:		app=guestbook
		tier=frontend
Annotations:	<none>
Replicas:	3 current / 3 desired
Pods Status:	3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:       app=guestbook
                tier=frontend
  Containers:
   php-redis:
    Image:      gcr.io/google_samples/gb-frontend:v3
    Port:       80/TCP
    Requests:
      cpu:      100m
      memory:   100Mi
    Environment:
      GET_HOSTS_FROM:   dns
    Mounts:             <none>
  Volumes:              <none>
Events:
  FirstSeen    LastSeen    Count    From                SubobjectPath    Type        Reason            Message
  ---------    --------    -----    ----                -------------    --------    ------            -------
  1m           1m          1        {replicaset-controller }             Normal      SuccessfulCreate  Created pod: frontend-qhloh
  1m           1m          1        {replicaset-controller }             Normal      SuccessfulCreate  Created pod: frontend-dnjpy
  1m           1m          1        {replicaset-controller }             Normal      SuccessfulCreate  Created pod: frontend-9si5l

Y por último, puedes comprobar los Pods que ha arrancado:

kubectl get Pods

Deberías ver la información de cada Pod similar a:

NAME             READY     STATUS    RESTARTS   AGE
frontend-9si5l   1/1       Running   0          1m
frontend-dnjpy   1/1       Running   0          1m
frontend-qhloh   1/1       Running   0          1m

También puedes verificar que la referencia de propietario de dichos pods está puesta al ReplicaSet frontend. Para ello, obtén el yaml de uno de los Pods ejecutándose:

kubectl get pods frontend-9si5l -o yaml

La salida será parecida a esta, donde la información sobre el ReplicaSet aparece en el campo ownerReferences de los metadatos:

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: 2019-01-31T17:20:41Z
  generateName: frontend-
  labels:
    tier: frontend
  name: frontend-9si5l
  namespace: default
  ownerReferences:
  - apiVersion: extensions/v1beta1
    blockOwnerDeletion: true
    controller: true
    kind: ReplicaSet
    name: frontend
    uid: 892a2330-257c-11e9-aecd-025000000001
...

Adquisiciones de Pods fuera de la plantilla

Aunque puedes crear Pods simples sin problemas, se recomienda encarecidamente asegurarse de que dichos Pods no tienen etiquetas que puedan coincidir con el selector de alguno de tus ReplicaSets. La razón de esta recomendación es que un ReplicaSet no se limita a poseer los Pods especificados en su plantilla -- sino que puede adquirir otros Pods como se explicó en secciones anteriores.

Toma el ejemplo anterior del ReplicaSet frontend, y los Pods especificados en el siguiente manifiesto:

apiVersion: v1
kind: Pod
metadata:
  name: pod1
  labels:
    tier: frontend
spec:
  containers:
  - name: hello1
    image: gcr.io/google-samples/hello-app:2.0

---

apiVersion: v1
kind: Pod
metadata:
  name: pod2
  labels:
    tier: frontend
spec:
  containers:
  - name: hello2
    image: gcr.io/google-samples/hello-app:1.0

Como estos Pods no tienen un Controlador (o cualquier otro objeto) como referencia de propietario y como además su selector coincide con el del ReplicaSet frontend, este último los terminará adquiriendo de forma inmediata.

Supón que creas los Pods después de que el ReplicaSet frontend haya desplegado los suyos para satisfacer su requisito de cuenta de réplicas:

kubectl apply -f http://k8s.io/examples/pods/pod-rs.yaml

Los nuevos Pods serán adquiridos por el ReplicaSet, e inmediatamente terminados ya que el ReplicaSet estaría por encima del número deseado.

Obtener los Pods:

kubectl get Pods

La salida muestra que los nuevos Pods se han terminado, o están en el proceso de terminarse:

NAME             READY   STATUS        RESTARTS   AGE
frontend-9si5l   1/1     Running       0          1m
frontend-dnjpy   1/1     Running       0          1m
frontend-qhloh   1/1     Running       0          1m
pod2             0/1     Terminating   0          4s

Si creas primero los Pods:

kubectl apply -f http://k8s.io/examples/pods/pod-rs.yaml

Y entonces creas el ReplicaSet:

kubectl apply -f http://k8s.io/examples/controllers/frontend.yaml

Verás que el ReplicaSet ha adquirido dichos Pods y simplemente ha creado tantos nuevos como necesarios para cumplir con su especificación hasta que el número de sus nuevos Pods y los originales coincidan con la cuenta deseado. Al obtener los Pods:

kubectl get Pods

Veremos su salida:

NAME             READY   STATUS    RESTARTS   AGE
frontend-pxj4r   1/1     Running   0          5s
pod1             1/1     Running   0          13s
pod2             1/1     Running   0          13s

De esta forma, un ReplicaSet puede poseer un conjunto no homogéneo de Pods

Escribir un manifiesto de ReplicaSet

Al igual que con el esto de los objeto de la API de Kubernetes, un ReplicaSet necesita los campos apiVersion, kind, y metadata. Para los ReplicaSets, el tipo es siempre ReplicaSet. En la versión 1.9 de Kubernetes, la versión apps/v1 de la API en un tipo ReplicaSet es la versión actual y está habilitada por defecto. La versión apps/v1beta2 de la API se ha desaprobado. Consulta las primeras líneas del ejemplo frontend.yaml como guía.

Un ReplicaSet también necesita una sección .spec.

Plantilla Pod

El campo .spec.template es una plantilla pod que es también necesita obligatoriamente tener etiquetas definidas. En nuestro ejemplo frontend.yaml teníamos una etiqueta: tier: frontend. Lleva cuidado de que no se entremezcle con los selectores de otros controladores, no sea que traten de adquirir este Pod.

Para el campo de regla de reinicio de la plantilla, .spec.template.spec.restartPolicy, el único valor permitido es Always, que es el valor predeterminado.

Selector de Pod

El campo .spec.selector es un selector de etiqueta. Como se explicó anteriormente, estas son las etiquetas que se usan para identificar los Pods potenciales a adquirir. En nuestro ejemplo frontend.yaml, el selector era:

matchLabels:
	tier: frontend

El el ReplicaSet, .spec.template.metadata.labels debe coincidir con spec.selector, o será rechazado por la API.

Réplicas

Puedes configurar cuántos Pods deberían ejecutarse de forma concurrente indicando el campo .spec.replicas. El ReplicaSet creará/eliminará sus Pods para alcanzar este número.

Si no indicas el valor del campo .spec.replicas, entonces por defecto se inicializa a 1.

Trabajar con ReplicaSets

Eliminar un ReplicaSet y sus Pods

Para eliminar un ReplicaSet y todos sus Pods, utiliza el comando kubectl delete. El Recolector de basura eliminará automáticamente todos los Pods subordinados por defecto.

Cuando se usa la API REST o la librería client-go, se debe poner el valor de propagationPolicy a Background o Foreground en la opción -d. Por ejemplo:

kubectl proxy --port=8080
curl -X DELETE  'localhost:8080/apis/extensions/v1beta1/namespaces/default/replicasets/frontend' \
> -d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Foreground"}' \
> -H "Content-Type: application/json"

Eliminar sólo un ReplicaSet

Se puede eliminar un ReplicaSet sin afectar a ninguno de sus Pods usando el comando kubectl delete con la opción --cascade=false. Cuando se usa la API REST o la librería client-go, se debe poner propagationPolicy a Orphan. Por ejemplo:

kubectl proxy --port=8080
curl -X DELETE  'localhost:8080/apis/extensions/v1beta1/namespaces/default/replicasets/frontend' \
> -d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Orphan"}' \
> -H "Content-Type: application/json"

Una vez que se ha eliminado el original, se puede crear un nuevo ReplicaSet para sustituirlo. Mientras el viejo y el nuevo .spec.selector sean el mismo, el nuevo adoptará a los viejos Pods. Sin embargo, no se esforzará en conseguir que los Pods existentes coincidan con una plantilla pod nueva, diferente. Para actualizar dichos Pods a la nueva especificación de forma controlada, usa una actualización en línea.

Aislar Pods de un ReplicaSet

Es posible aislar Pods de un ReplicaSet cambiando sus etiquetas. Esta técnica puede usarse para eliminar Pods de un servicio para poder depurar, recuperar datos, etc. Los Pods que se eliminar de esta forma serán sustituidos de forma automática (siempre que el número de réplicas no haya cambiado).

Escalar un ReplicaSet

Se puede aumentar o reducir fácilmente un ReplicaSet simplemente actualizando el campo .spec.replicas. El controlador del ReplicaSet se asegura de que el número deseado de Pods con un selector de etiquetas coincidente está disponible y operacional.

ReplicaSet como blanco de un Horizontal Pod Autoscaler

Un ReplicaSet puede también ser el blanco de un Horizontal Pod Autoscalers (HPA). Esto es, un ReplicaSet puede auto-escalarse mediante un HPA. Aquí se muestra un ejemplo de HPA dirigido al ReplicaSet que creamos en el ejemplo anterior.

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: frontend-scaler
spec:
  scaleTargetRef:
    kind: ReplicaSet
    name: frontend
  minReplicas: 3
  maxReplicas: 10
  targetCPUUtilizationPercentage: 50

Si guardas este manifiesto en un archivo hpa-rs.yaml y lo lanzas contra el clúster de Kubernetes, debería crear el HPA definido que auto-escala el ReplicaSet destino dependiendo del uso de CPU de los Pods replicados.

kubectl apply -f https://k8s.io/examples/controllers/hpa-rs.yaml

Alternativamente, puedes usar el comando kubectl autoscale para conseguir el mismo objetivo (¡y mucho más fácil!)

kubectl autoscale rs frontend --max=10

Alternativas al ReplicaSet

Deployment (recomendado)

UnDeployment es un objeto que puede poseer ReplicaSets y actualizar a estos y a sus Pods mediante actualizaciones en línea declarativas en el servidor. Aunque que los ReplicaSets puede usarse independientemente, hoy en día se usan principalmente a través de los Deployments como el mecanismo para orquestrar la creación, eliminación y actualización de los Pods. Cuando usas Deployments no tienes que preocuparte de gestionar los ReplicaSets que crean. Los Deployments poseen y gestionan sus ReplicaSets. Por tanto, se recomienda que se use Deployments cuando se quiera ReplicaSets.

Pods simples

A diferencia del caso en que un usuario creaba Pods de forma directa, un ReplicaSet sustituye los Pods que se eliminan o se terminan por la razón que sea, como en el caso de un fallo de un nodo o una intervención disruptiva de mantenimiento, como una actualización de kernel. Por esta razón, se recomienda que se use un ReplicaSet incluso cuando la aplicación sólo necesita un único Pod. Entiéndelo de forma similar a un proceso supervisor, donde se supervisa múltiples Pods entre múltiples nodos en vez de procesos individuales en un único nodo. Un ReplicaSet delega los reinicios del contenedor local a algún agente del nodo (por ejemplo, Kubelet o Docker).

Job

Usa un Job en vez de un ReplicaSet para aquellos Pods que se esperan que terminen por ellos mismos (esto es, trabajos por lotes).

DaemonSet

Usa un DaemonSet en vez de un ReplicaSet para aquellos Pods que proporcionan funcionalidad a nivel de servidor, como monitorización de servidor o logging de servidor. Estos Pods tienen un ciclo de vida asociado al del servidor mismo: el Pod necesita ejecutarse en el servidor antes de que los otros Pods comiencen, y es seguro que terminen cuando el servidor esté listo para ser reiniciado/apagado.

ReplicationController

Los ReplicaSets son los sucesores de los ReplicationControllers. Los dos sirven al mismo propósito, y se comportan de forma similar, excepto porque un ReplicationController no soporta los requisitos del selector basado en conjunto, como se describe en la guía de usuario de etiquetas. Por ello, se prefiere los ReplicaSets a los ReplicationControllers.

4.2.2 - ReplicationController

Un ReplicationController garantiza que un número determinado de réplicas se estén ejecutando en todo momento. En otras palabras, un ReplicationController se asegura que un pod o un conjunto homogéneo de pods siempre esté arriba y disponible.

Cómo Funciona un ReplicationController

Si hay muchos pods, el ReplicationController termina los pods extra. Si hay muy pocos, el ReplicationController arranca más pods. A difrencia de los pods creados manualmente, los pods mantenidos por un ReplicationController se sustituyen de forma automática si fallan, se borran, o se terminan. Por ejemplo, tus pods se re-crean en un nodo durante una intervención disruptiva de mantenimiento como una actualización del kernel. Por esta razón, deberías usar un ReplicationController incluso cuando tu aplicación sólo necesita un único pod. Un ReplicationController es parecido a un supervisor de procesos, pero en vez de supervisar procesos individuales en un único nodo, el ReplicationController supervisa múltiples pods entre múltiples nodos.

A menudo nos referimos a un ReplicationController de forma abreviada como "rc" o "rcs", así como atajo en los comandos de kubectl.

Un caso simple es crear un objeto ReplicationController para ejecutar de manera fiable una instancia de un Pod indefinidamente. Un caso de uso más complejo es ejecutar varias réplicas idénticas de un servicio replicado, como los servidores web.

Ejecutar un ejemplo de ReplicationController

Esta configuración de un ReplicationController de ejemplo ejecuta tres copias del servidor web nginx.

apiVersion: v1
kind: ReplicationController
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    app: nginx
  template:
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80

Ejecuta el ejemplo descargando el archivo de ejemplo y ejecutando este comando:

kubectl apply -f https://k8s.io/examples/controllers/replication.yaml
replicationcontroller/nginx created

Comprueba el estado del ReplicationController con este comando:

kubectl describe replicationcontrollers/nginx
Name:        nginx
Namespace:   default
Selector:    app=nginx
Labels:      app=nginx
Annotations:    <none>
Replicas:    3 current / 3 desired
Pods Status: 0 Running / 3 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:       app=nginx
  Containers:
   nginx:
    Image:              nginx
    Port:               80/TCP
    Environment:        <none>
    Mounts:             <none>
  Volumes:              <none>
Events:
  FirstSeen       LastSeen     Count    From                        SubobjectPath    Type      Reason              Message
  ---------       --------     -----    ----                        -------------    ----      ------              -------
  20s             20s          1        {replication-controller }                    Normal    SuccessfulCreate    Created pod: nginx-qrm3m
  20s             20s          1        {replication-controller }                    Normal    SuccessfulCreate    Created pod: nginx-3ntk0
  20s             20s          1        {replication-controller }                    Normal    SuccessfulCreate    Created pod: nginx-4ok8v

Como se puede observar, se han creado tres pods, pero ninguno se está ejecutándose todavía, puede que porque la imagen todavía se está descargando. Unos momentos después, el mismo comando puede que muestre:

Pods Status:    3 Running / 0 Waiting / 0 Succeeded / 0 Failed

Para listar todos los pods que pertenecen al ReplicationController de forma legible, puedes usar un comando como el siguiente:

pods=$(kubectl get pods --selector=app=nginx --output=jsonpath={.items..metadata.name})
echo $pods
nginx-3ntk0 nginx-4ok8v nginx-qrm3m

Como se puede ver, el selector es el mismo que el selector del ReplicationController (mostrado en la salida de kubectl describe), y con una forma diferente a lo definido en el archivo replication.yaml. La opción --output=jsonpath especifica una expresión que simplemente muestra el nombre de cada pod en la lista devuelta.

Escribir una especificación de ReplicationController

Al igual que con el resto de configuraciones de Kubernetes, un ReplicationController necesita los campos apiVersion, kind, y metadata. Para información general acerca del trabajo con archivos de configuración, ver la gestión de objetos.

Un ReplicationController también necesita un sección .spec.

Plantilla Pod

El campo .spec.template es el único campo requerido de .spec.

El campo .spec.template es una plantilla pod. Tiene exactamente el mismo esquema que un pod, excepto por el hecho de que está anidado y no tiene los campos apiVersion ni kind.

Además de los campos obligatorios de un Pod, una plantilla pod de un ReplicationController debe especificar las etiquetas apropiadas y la regla de reinicio apropiada. En el caso de las etiquetas, asegúrate que no se entremezclan con otros controladores. Ver el selector de pod.

Sólo se permite el valor Always para el campo .spec.template.spec.restartPolicy, que es el valor predeterminado si no se indica.

Para los reinicios locales de los contenedores, los ReplicationControllers delegan en los agentes del nodo, por ejmplo el Kubelet o Docker.

Etiquetas en los ReplicationController

los ReplicationController puede tener sus propias (.metadata.labels). Normalmente, se indicaría dichas etiquetas con los mismos valores que el campo .spec.template.metadata.labels; si el campo .metadata.labels no se indica, entonces se predetermina al valor de .spec.template.metadata.labels. Sin embargo, se permite que sean diferentes, y el valor de .metadata.labels no afecta al comportamiento del ReplicationController.

Selector de Pod

El campo .spec.selector es un selector de etiqueta. Un ReplicationController gestiona todos los pods con etiquetas que coinciden con el selector. No distingue entre pods que creó o eliminó, y pods que otra persona o proceso creó o eliminó. Esto permite sustituir al ReplicationController sin impactar a ninguno de sus pods que se esté ejecutando.

Si se indica, el valor de .spec.template.metadata.labels debe ser igual al de .spec.selector, o será rechazado por la API. Si no se indica el valor de .spec.selector, se tomará como predeterminado el de .spec.template.metadata.labels.

Tampoco deberías crear ningún pod cuyas etiquetas coincidan con las de este selector, ni directamente con otro ReplicationController, ni con otro controlador como un Job. Si lo haces, el ReplicationController piensa que el creó también los otros pods. Kubernetes no te impide hacerlo.

Si al final terminas con múltiples controladores que tienen selectores que se entremezclan, tendrás que gestionar la eliminación tú mismo (ver abajo).

Múltiples Réplicas

Puedes configurar cuántos pods deberían ejecutarse de forma concurrente poniendo el valor de .spec.replicas al número de pods que te gustaría tener ejecutándose a la vez. El número de ejecuciones en cualquier momento puede que sea superior o inferior, dependiendo de si las réplicas se han incrementado o decrementado, o si un pod se ha apagado de forma controlada, y su sustituto arranca más pronto.

Si no se indica el valor de .spec.replicas, entonces se predetermina a 1.

Trabajar con ReplicationControllers

Eliminar un ReplicationController y sus Pods

Para eliminar un ReplicationController y todos sus pods, usa el comando kubectl delete. Kubectl reducirá el ReplicationController a cero y esperará que elimine cada pod antes de eliminar al ReplicationController mismo. Si este comando kubectl se interrumpe, puede ser reiniciado.

Cuando uses la API REST o la librería Go, necesitas realizar los pasos de forma explícita (reducir las réplicas a cero, esperar a que se eliminen los pods, y entonces eliminar el ReplicationController).

Eliminar sólo el ReplicationController

Puedes eliminar un ReplicationController sin impactar a ninguno de sus Pods.

Usando kubectl, indica la opción --cascade=false en el comando kubectl delete.

Cuando uses la API REST o la librería Go, simplemente elimina objeto ReplicationController.

Una vez que el objeto original se ha eliminado, puedes crear un nuevo ReplicationController para sustituirlo. Mientras el viejo y el nuevo valor del .spec.selector sea el mismo, el nuevo adoptará a los viejos pods. Sin embargo, no se molestará en hacer que los pods actuales coincidan con una plantilla pod nueva, diferente. Para actualizar los pods con una nueva especificación de forma controlada, utiliza la actualización en línea.

Aislar pods de un ReplicationController

Se puede aislar Pods del conjunto destino de un ReplicationController cambiando sus etiquetas. Esta técnica puede usarse para eliminar pods de un servicio para poder depurarlos, recuperar datos, etc. Los Pods que se eliminan de esta forma serán sustituidos de forma automática (asumiendo que el número de réplicas no ha cambiado tampoco).

Patrones comunes de uso

Reprogramación

Como se comentó arriba, cuando tienes 1 pod que quieres mantener ejecutándose, o 1000, un ReplicationController se asegura de que el número indicado de pods exista, incluso si falla un nodo o se termina algún pod (por ejemplo, debido a alguna acción de otro agente de control).

Escalado

El ReplicationController facilita el escalado del número de réplicas tanto para su aumento como para su disminución, bien manualmente o mediante un agente de auto-escalado, simplemente actualizando el campo replicas.

Actualizaciones en línea

El ReplicationController se ha diseñado para facilitar las actualizaciones en línea de un servicio mediante la sustitución de sus pods uno por uno.

Cómo se explicó en #1353, la estrategia recomendada es crear un nuevo ReplicationController con 1 réplica, escalar el nuevo (+1) y el viejo (-1) controlador uno por uno, y entonces eliminar el viejo controlador una vez que alcanza las 0 réplicas. Esto actualiza de forma predecible el conjunto de pods independientemente de que se produzcan fallos inesperados.

De forma ideal, el controlador de actualización en línea tendrá en cuenta si la aplicación está lista, y se asegurará de que un número suficiente de pods está en servicio en todo momento.

Los dos ReplicationControllers necesitarán crear pods con al menos una etiqueta diferenciadora, como la etiqueta de imagen del contenedor primario del pod, ya que las actualizaciones de imagen son las que normalmente desencadenan las actualizaciones en línea.

La actualización en línea se implementa a través de la herramienta cliente mediante kubectl rolling-update. Echa un vistazo a la tarea kubectl rolling-update para más ejemplos concretos.

Múltiples operaciones de despliegue

Además de llevar a cabo múltiples despliegues de una aplicación cuando una actualización en línea está en progreso, es común ejecutar varios despliegues durante un período extendido de tiempo, o incluso de forma contínua, usando múltiples operaciones de despliegue. Dichas operaciones se diferenciarían por etiquetas.

Por ejemplo, un servicio puede que exponga todos los pods con etiquetas tier in (frontend), environment in (prod). Ahora digamos que tenemos 10 pods replicados que forman este grupo. Pero queremos poder desplegar una nueva versión 'canary' de este component. Se podría configurar un ReplicationController con el valor de replicas puesto a 9 para la mayor parte de las réplicas, con etiquetas tier=frontend, environment=prod, track=stable, y otro ReplicationController con el valor de replicas puesto a 1 para el 'canary', con las etiquetas tier=frontend, environment=prod, track=canary. Así el servicio cubriría tanto los pods canary como el resto. Pero también es posible trastear con los ReplicationControllers de forma separada para probar cosas, monitorizar los resultados, etc.

Usar ReplicationControllers con servicios

Un único servicio puede exponer múltiples ReplicationControllers, de forma que, por ejemplo, algo de tráfico vaya a la versión vieja, y otro tanto vaya a la versión nueva.

Un ReplicationController nunca se terminará por sí mismo, pero tampoco se espera que se ejecute permanentemente como los servicios. Los servicios puede que estén compuestos de pods controlados por múltiples ReplicationControllers, y se espera que muchos ReplicationControllers se creen y se destruyan durante el ciclo de vida de un servicio (por ejemplo, para realizar una actualización de los pods que ejecutan el servicio). Ambos servicios mismos y sus clientes deberían permanecer ajenos a los ReplicationControllers que mantienen los pods que proporcionan los servicios.

Escribir aplicaciones que se repliquen

Los Pods creados por un ReplicationController están pensados para que sean intercambiables y semánticamente idénticos, aunque sus configuraciones puede que sean heterogéneas a lo largo del tiempo. Este es un ajuste obvio para los servidores sin estado replicados, pero los ReplicationControllers también pueden utilizarse para mantener la disponibilidad de aplicaciones que se elijen por un maestro, las particionadas, y las de grupos de trabajadores. Dichas aplicaciones deberían usar los mecanismos de asignación dinámica de trabajo, como las colas de trabajo RabbitMQ, en vez de la personalización estática/de una sola vez en la configuración de cada pod, ya que se considera un anti-patrón. Cualquier personalización de pod que se haga, como el calibrado vertical automático de recursos (por ejemplo, cpu o memoria), debería realizarse a través de otro proceso de controlador en línea, no con el mismo ReplicationController.

Responsabilidades de un ReplicationController

El ReplicationController simplemente garantiza que el número deseado de pods coincide con su selector de etiqueta y que son operacionales. Actualmente, sólo los pods que han terminado se excluyen de la cuenta. En el futuro, la disponibilidad y otra información disponible en el sistema se tendrá en cuenta, se añadirá más controles sobre la regla de sussitución, y se está planificando emitir eventos que podrían ser aprovechados por clientes externos para implementar reglas complejas de sustitución y escalado de forma arbitraria.

El ReplicationController está siempre condicionado a esta reducida responsabilidad. Él mismo no llevará a cabo ni pruebas de estar listo ni vivo. En vez de aplicar el auto-escalado, se pretende que este sea realizado por un auto-escalador externo (como se vio en #492), que sería el encargado de cambiar su campo replicas. No se añadirá reglas de programación (por ejemplo, propagación) al ReplicationController. Ni se debería validar que los pods controlados coincidan con la plantilla actual especificada, ya que eso obstruiría el auto-calibrado y otros procesos automáticos. De forma similar, los vencimientos de término, las dependencias de orden, la extensión de la configuración, y otras características se aplican en otro lado. Incluso se plantea excluir el mecanismo de creación de pods a granel (#170).

El ReplicationController está pensado para ser una primitiva de bloques is intended to be a composable building-block primitive. We expect higher-level APIs and/or tools to be built on top of it and other complementary primitives for user convenience in the future. The "macro" operations currently supported by kubectl (run, scale, rolling-update) are proof-of-concept examples of this. For instance, we could imagine something like Asgard managing ReplicationControllers, auto-scalers, services, scheduling policies, canaries, etc.

Objeto API

El ReplicationController es un recurso de alto nivel en la API REST de Kubernetes. Más detalles acerca del objeto API se pueden encontrar aquí: Objeto API ReplicationController.

Alternativas al ReplicationController

ReplicaSet

El ReplicaSet es el ReplicationController de nueva generación que soporta el nuevo selector de etiqueta basado en conjunto. Se usa principalmente por el Deployment como un mecanismo para orquestrar la creación de pods, la eliminación y las actualizaciones. Nótese que se recomienda usar Deployments en vez de directamente usar los ReplicaSets, a menos que necesites una orquestración personalizada de actualizaciones o no quieras actualizaciones en absoluto.

Deployment (Recomendado)

El Deployment es un objeto de alto nivel de la API que actualiza sus ReplicaSets subyacenetes y sus Pods de forma similar a cómo lo hace el comando kubectl rolling-update. Se recomienda el uso de Deployments si se quiere esta functionalidad de actualización en línea, porque a diferencia del comando kubectl rolling-update, son declarativos, se ejecutan del lado del servidor, y tienen características adicionales.

Pods simples

A diferencia del caso en que un usuario ha creado directamente pods, un ReplicationController sustituye los pods que han sido eliminador o terminados por cualquier motivo, como en el caso de un fallo de un nodo o una intervención disruptiva de mantenimiento, como la actualización del kernel. Por esta razón, se recomienda que se usa un ReplicationController incluso si tu aplicación sólo necesita un único pod. Piensa que es similar a un supervisor de proceso, sólo que supervisa múltiples pods entre múltiples nodos en vez de procesos individuales en un único nodo. Un ReplicationController delega los reinicios locales de los contenedores a algún agente del nodo (por ejemplo, Kubelet o Docker).

Job

Usa un Job en vez de un ReplicationController para aquellos pods que se espera que terminen por sí mismos (esto es, trabajos por lotes).

DaemonSet

Usa un DaemonSet en vez de un ReplicationController para aquellos pods que proporcionan una función a nivel de servidor, como la monitorización o el loggin de servidor. Estos pods tienen un ciclo de vida que está asociado al del servidor: el pod necesita ejecutarse en el servidor antes que los otros pods arranquen, y es seguro terminarlo cuando el servidor está listo para reiniciarse/apagarse.

Para más información

Lee Ejecutar Aplicaciones sin Estado con un ReplicationController.

4.2.3 - Deployment

Un controlador de Deployment proporciona actualizaciones declarativas para los Pods y los ReplicaSets.

Cuando describes el estado deseado en un objeto Deployment, el controlador del Deployment se encarga de cambiar el estado actual al estado deseado de forma controlada. Puedes definir Deployments para crear nuevos ReplicaSets, o eliminar Deployments existentes y adoptar todos sus recursos con nuevos Deployments.

Casos de uso

A continuación se presentan los casos de uso típicos de los Deployments:

Crear un Deployment

El siguiente ejemplo de un Deployment crea un ReplicaSet para arrancar tres Pods con nginx:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

En este ejemplo:

  • Se crea un Deployment denominado nginx-deployment, indicado a través del campo .metadata.name.

  • El Deployment crea tres Pods replicados, indicado a través del campo replicas.

  • El campo selector define cómo el Deployment identifica los Pods que debe gestionar. En este caso, simplemente seleccionas una etiqueta que se define en la plantilla Pod (app: nginx). Sin embargo, es posible definir reglas de selección más sofisticadas, siempre que la plantilla Pod misma satisfaga la regla.

  • El campo template contiene los siguientes sub-campos:

    • Los Pods se etiquetan como app: nginx usando el campo labels.
    • La especificación de la plantilla Pod, o el campo .template.spec, indica que los Pods ejecutan un contenedor, nginx, que utiliza la versión 1.7.9 de la imagen de nginx de Docker Hub.
    • Crea un contenedor y lo llamar nginx usando el campo name.
    • Ejecuta la imagen nginx en su versión 1.7.9.
    • Abre el puerto 80 para que el contenedor pueda enviar y recibir tráfico.

Para crear este Deployment, ejecuta el siguiente comando:

kubectl apply -f https://k8s.io/examples/controllers/nginx-deployment.yaml

A continuación, ejecuta el comando kubectl get deployments. La salida debe ser parecida a la siguiente:

NAME               READY   UP-TO-DATE   AVAILABLE   AGE 
nginx-deployment   3/3     3            3           1s  

Cuando inspeccionas los Deployments de tu clúster, se muestran los siguientes campos:

  • NAME enumera los nombre de los Deployments del clúster.
  • READY muestra cuántas réplicas de la aplicación están disponibles para sus usuarios. Sigue el patrón número de réplicas listas/deseadas.
  • UP-TO-DATE muestra el número de réplicas que se ha actualizado para alcanzar el estado deseado.
  • AVAILABLE muestra cuántas réplicas de la aplicación están disponibles para los usuarios.
  • AGE muestra la cantidad de tiempo que la aplicación lleva ejecutándose.

Nótese cómo los valores de cada campo corresponden a los valores de la especificación del Deployment:

  • El número de réplicas deseadas es 3 de acuerdo con el campo .spec.replicas.
  • El número de réplicas actuales es 0 de acuerdo con el campo .status.replicas.
  • El número de réplicas actualizadas es 0 de acuerdo con el campo .status.updatedReplicas.
  • El número de réplicas disponibles es 0 de acuerdo con el campo .status.availableReplicas.

Si deseamos obtener más información del Deployment utilice el parámetro '-o wide', ejecutando el comando 'kubectl get deployments -o wide'. La salida será parecida a la siguiente:

NAME               READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES         SELECTOR
nginx-deployment   3/3     3            3           10s   nginx        nginx:1.7.9   app=nginx

Ejecutando el comando anterior se muestran los siguientes campos adicionales:

  • CONTAINERS muestra los nombres de los contenedores declarados en .spec.template.spec.containers.[name].
  • IMAGES muestra los nombres de las imágenes declaradas en .spec.template.spec.containers.[image].
  • 'SELECTOR' muestra el Label selector que se declaró en matchLabels o matchExpressions.

Para ver el estado del Deployment, ejecuta el comando kubectl rollout status deployment.v1.apps/nginx-deployment. Este comando devuelve el siguiente resultado:

Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
deployment "nginx-deployment" successfully rolled out

Ejecuta de nuevo el comando kubectl get deployments unos segundos más tarde:

NAME               READY   UP-TO-DATE   AVAILABLE   AGE 
nginx-deployment   3/3     3            3           18s  

Fíjate que el Deployment ha creado todas las tres réplicas, y que todas las réplicas están actualizadas (contienen la última plantilla Pod) y están disponibles (el estado del Pod tiene el valor Ready al menos para el campo .spec.minReadySeconds del Deployment).

Para ver el ReplicaSet (rs) creado por el Deployment, ejecuta el comando kubectl get rs:

NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-75675f5897   3         3         3       18s

Fíjate que el nombre del ReplicaSet siempre se formatea con el patrón [DEPLOYMENT-NAME]-[RANDOM-STRING]. La cadena aleatoria se genera de forma aleatoria y usa el pod-template-hash como semilla.

Para ver las etiquetas generadas automáticamente en cada pod, ejecuta el comando kubectl get pods --show-labels. Se devuelve la siguiente salida:

NAME                                READY     STATUS    RESTARTS   AGE       LABELS
nginx-deployment-75675f5897-7ci7o   1/1       Running   0          18s       app=nginx,pod-template-hash=75675f5897
nginx-deployment-75675f5897-kzszj   1/1       Running   0          18s       app=nginx,pod-template-hash=75675f5897
nginx-deployment-75675f5897-qqcnn   1/1       Running   0          18s       app=nginx,pod-template-hash=75675f5897

El ReplicaSet creado garantiza que hay tres Pods de nginx ejecutándose en todo momento.

Etiqueta pod-template-hash

La etiqueta pod-template-hash es añadida por el controlador del Deployment a cada ReplicaSet que el Deployment crea o adopta.

Esta etiqueta garantiza que todos los hijos ReplicaSets de un Deployment no se entremezclan. Se genera mediante una función hash aplicada al PodTemplate del ReplicaSet y usando el resultado de la función hash como el valor de la etiqueta que se añade al selector del ReplicaSet, en las etiquetas de la plantilla Pod, y en cualquier Pod existente que el ReplicaSet tenga.

Actualizar un Deployment

Asumiendo que ahora quieres actualizar los Pods nginx para que usen la imagen nginx:1.9.1 en vez de la imagen nginx:1.7.9.

kubectl --record deployment.apps/nginx-deployment set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1
image updated

De forma alternativa, puedes editar el Deployment y cambiar el valor del campo .spec.template.spec.containers[0].image de nginx:1.7.9 a nginx:1.9.1:

kubectl edit deployment.v1.apps/nginx-deployment
deployment.apps/nginx-deployment edited

Para ver el estado del despliegue, ejecuta:

kubectl rollout status deployment.v1.apps/nginx-deployment
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
deployment "nginx-deployment" successfully rolled out

Cuando el despliegue funciona, puede que quieras obtener el Deployment:

kubectl get deployments
NAME               READY   UP-TO-DATE   AVAILABLE   AGE 
nginx-deployment   3/3     3            3           36s 

El número de réplicas actualizadas indica que el Deployment ha actualizado las réplicas según la última configuración. Las réplicas actuales indican el total de réplicas que gestiona este Deployment, y las réplicas disponibles indican el número de réplicas actuales que están disponibles.

Puedes ejecutar el comando kubectl get rs para ver que el Deployment actualizó los Pods creando un nuevo ReplicaSet y escalándolo hasta las 3 réplicas, así como escalando el viejo ReplicaSet a 0 réplicas.

kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-1564180365   3         3         3       6s
nginx-deployment-2035384211   0         0         0       36s

Si ejecutas el comando get pods deberías ver los nuevos Pods:

kubectl get pods
NAME                                READY     STATUS    RESTARTS   AGE
nginx-deployment-1564180365-khku8   1/1       Running   0          14s
nginx-deployment-1564180365-nacti   1/1       Running   0          14s
nginx-deployment-1564180365-z9gth   1/1       Running   0          14s

La próxima vez que quieras actualizar estos Pods, sólo necesitas actualizar la plantilla Pod del Deployment otra vez.

El Deployment permite garantizar que sólo un número determinado de Pods puede eliminarse mientras se están actualizando. Por defecto, garantiza que al menos el 25% menos del número deseado de Pods se está ejecutando (máx. 25% no disponible).

El Deployment también permite garantizar que sólo un número determinado de Pods puede crearse por encima del número deseado de Pods. Por defecto, garantiza que al menos el 25% más del número deseado de Pods se está ejecutando (máx. 25% de aumento).

Por ejemplo, si miras detenidamente el Deployment de arriba, verás que primero creó un Pod, luego eliminó algunos viejos Pods y creó otros nuevos. No elimina los viejos Pods hasta que un número suficiente de nuevos Pods han arrancado, y no crea nuevos Pods hasta que un número suficiente de viejos Pods se han eliminado. De esta forma, asegura que el número de Pods disponibles siempre es al menos 2, y el número de Pods totales es cómo máximo 4.

kubectl describe deployments
Name:                   nginx-deployment
Namespace:              default
CreationTimestamp:      Thu, 30 Nov 2017 10:56:25 +0000
Labels:                 app=nginx
Annotations:            deployment.kubernetes.io/revision=2
Selector:               app=nginx
Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=nginx
  Containers:
   nginx:
    Image:        nginx:1.9.1
    Port:         80/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   nginx-deployment-1564180365 (3/3 replicas created)
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  2m    deployment-controller  Scaled up replica set nginx-deployment-2035384211 to 3
  Normal  ScalingReplicaSet  24s   deployment-controller  Scaled up replica set nginx-deployment-1564180365 to 1
  Normal  ScalingReplicaSet  22s   deployment-controller  Scaled down replica set nginx-deployment-2035384211 to 2
  Normal  ScalingReplicaSet  22s   deployment-controller  Scaled up replica set nginx-deployment-1564180365 to 2
  Normal  ScalingReplicaSet  19s   deployment-controller  Scaled down replica set nginx-deployment-2035384211 to 1
  Normal  ScalingReplicaSet  19s   deployment-controller  Scaled up replica set nginx-deployment-1564180365 to 3
  Normal  ScalingReplicaSet  14s   deployment-controller  Scaled down replica set nginx-deployment-2035384211 to 0

Aquí puedes ver que cuando creaste por primera vez el Deployment, este creó un ReplicaSet (nginx-deployment-2035384211) y lo escaló a 3 réplicas directamente. Cuando actualizaste el Deployment, creó un nuevo ReplicaSet (nginx-deployment-1564180365) y lo escaló a 1 y entonces escaló el viejo ReplicaSet a 2, de forma que al menos hubiera 2 Pods disponibles y como mucho 4 Pods en total en todo momento. Entonces, continuó escalando el nuevo y el viejo ReplicaSet con la misma estrategia de actualización continua. Finalmente, el nuevo ReplicaSet acaba con 3 réplicas disponibles, y el viejo ReplicaSet se escala a 0.

Sobrescritura (o sea, múltiples actualizaciones a la vez)

Cada vez que el controlador del Deployment observa un nuevo objeto de despliegue, se crea un ReplicaSet para arrancar los Pods deseados si es que no existe otro ReplicaSet haciéndolo. Los ReplicaSet existentes que controlan los Pods cuyas etiquetas coinciden con el valor del campo .spec.selector, pero cuya plantilla no coincide con el valor del campo .spec.template se reducen. Al final, el nuevo ReplicaSet se escala hasta el valor del campo .spec.replicas y todos los viejos ReplicaSets se escalan a 0.

Si actualizas un Deployment mientras otro despliegue está en curso, el Deployment creará un nuevo ReplicaSet como consecuencia de la actualización y comenzará a escalarlo, y sobrescribirá al ReplicaSet que estaba escalando anteriormente -- lo añadirá a su lista de viejos ReplicaSets y comenzará a reducirlos.

Por ejemplo, supongamos que creamos un Deployment para crear 5 réplicas de nginx:1.7.9, pero entonces actualizamos el Deployment para crear 5 réplicas de nginx:1.9.1 cuando sólo se ha creado 3 réplicas de nginx:1.7.9. En este caso, el Deployment comenzará automáticamente a matar los 3 Pods de nginx:1.7.9 que había creado, y empezará a crear los Pods de nginx:1.9.1. Es decir, no esperará a que se creen las 5 réplicas de nginx:1.7.9 antes de aplicar la nueva configuración.

Actualizaciones del selector de etiquetas

No se recomienda hacer cambios al selector del etiquetas y, por ello, se aconseja encarecidamente planificar el valor de dichos selectores por adelantado. En cualquier caso, si necesitas cambiar un selector de etiquetas, hazlo con mucho cuidado y asegúrate que entiendes todas sus implicaciones.

  • Las adiciones posteriores al selector obligan también a actualizar las etiquetas de la plantilla Pod en la especificación del Deployment con los nuevos valores, ya que de lo contrario se devolvería un error. Este cambio no es de superposición, es decir, que el nuevo selector no selecciona los ReplicaSets y Pods creados con el viejo selector, lo que provoca que todos los viejos ReplicaSets se marquen como huérfanos y la creación de un nuevo ReplicaSet.
  • Las actualizaciones de selector -- esto es, cambiar el valor actual en una clave de selector -- provocan el mismo comportamiento que las adiciones.
  • Las eliminaciones de selector -- esto es, eliminar una clave actual del selector del Deployment -- no necesitan de cambios en las etiquetas de la plantilla Pod. No se marca ningún ReplicaSet existente como huérfano, y no se crea ningún ReplicaSet nuevo, pero debe tenerse en cuenta que la etiqueta eliminada todavía existe en los Pods y ReplicaSets que se están ejecutando.

Revertir un Deployment

En ocasiones necesitas revertir un Deployment; por ejemplo, cuando el Deployment no es estable, como cuando no para de reiniciarse. Por defecto, toda la historia de despliegue del Deployment se mantiene en el sistema de forma que puedes revertir en cualquier momento (se puede modificar este comportamiento cambiando el límite de la historia de revisiones de modificaciones).

Vamos a suponer que hemos cometido un error al actualizar el Deployment, poniendo como nombre de imagen nginx:1.91 en vez de nginx:1.9.1:

kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.91 --record=true
deployment.apps/nginx-deployment image updated

El despliegue se atasca y no progresa.

kubectl rollout status deployment.v1.apps/nginx-deployment
Waiting for rollout to finish: 1 out of 3 new replicas have been updated...

Presiona Ctrl-C para detener la monitorización del despliegue de arriba. Para obtener más información sobre despliegues atascados, lee más aquí.

Verás que el número de réplicas viejas (nginx-deployment-1564180365 y nginx-deployment-2035384211) es 2, y el número de nuevas réplicas (nginx-deployment-3066724191) es 1.

kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-1564180365   3         3         3       25s
nginx-deployment-2035384211   0         0         0       36s
nginx-deployment-3066724191   1         1         0       6s

Echando un vistazo a los Pods creados, verás que uno de los Pods creados por el nuevo ReplicaSet está atascado en un bucle intentando bajar la imagen:

kubectl get pods
NAME                                READY     STATUS             RESTARTS   AGE
nginx-deployment-1564180365-70iae   1/1       Running            0          25s
nginx-deployment-1564180365-jbqqo   1/1       Running            0          25s
nginx-deployment-1564180365-hysrc   1/1       Running            0          25s
nginx-deployment-3066724191-08mng   0/1       ImagePullBackOff   0          6s
kubectl describe deployment
Name:           nginx-deployment
Namespace:      default
CreationTimestamp:  Tue, 15 Mar 2016 14:48:04 -0700
Labels:         app=nginx
Selector:       app=nginx
Replicas:       3 desired | 1 updated | 4 total | 3 available | 1 unavailable
StrategyType:       RollingUpdate
MinReadySeconds:    0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=nginx
  Containers:
   nginx:
    Image:        nginx:1.91
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    ReplicaSetUpdated
OldReplicaSets:     nginx-deployment-1564180365 (3/3 replicas created)
NewReplicaSet:      nginx-deployment-3066724191 (1/1 replicas created)
Events:
  FirstSeen LastSeen    Count   From                    SubobjectPath   Type        Reason              Message
  --------- --------    -----   ----                    -------------   --------    ------              -------
  1m        1m          1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-2035384211 to 3
  22s       22s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 1
  22s       22s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 2
  22s       22s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 2
  21s       21s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 1
  21s       21s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 3
  13s       13s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 0
  13s       13s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-3066724191 to 1

Para arreglar este problema, necesitas volver a una revisión previa del Deployment que sea estable.

Comprobar la Historia de Despliegues de un Deployment

Primero, comprobemos las revisiones de este despliegue:

kubectl rollout history deployment.v1.apps/nginx-deployment
deployments "nginx-deployment"
REVISION    CHANGE-CAUSE
1           kubectl apply --filename=https://k8s.io/examples/controllers/nginx-deployment.yaml --record=true
2           kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1 --record=true
3           kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.91 --record=true

En el momento de la creación, el mensaje en CHANGE-CAUSE se copia de la anotación kubernetes.io/change-cause del Deployment a sus revisiones. Podrías indicar el mensaje CHANGE-CAUSE:

  • Anotando el Deployment con el comando kubectl annotate deployment.v1.apps/nginx-deployment kubernetes.io/change-cause="image updated to 1.9.1"
  • Añadiendo el parámetro --record para registrar el comando kubectl que está haciendo cambios en el recurso.
  • Manualmente editando el manifiesto del recursos.

Para ver más detalles de cada revisión, ejecuta:

kubectl rollout history deployment.v1.apps/nginx-deployment --revision=2
deployments "nginx-deployment" revision 2
  Labels:       app=nginx
          pod-template-hash=1159050644
  Annotations:  kubernetes.io/change-cause=kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1 --record=true
  Containers:
   nginx:
    Image:      nginx:1.9.1
    Port:       80/TCP
     QoS Tier:
        cpu:      BestEffort
        memory:   BestEffort
    Environment Variables:      <none>
  No volumes.

Retroceder a una Revisión Previa

Ahora has decidido que quieres deshacer el despliegue actual y retrocederlo a la revisión previa:

kubectl rollout undo deployment.v1.apps/nginx-deployment
deployment.apps/nginx-deployment

Alternativamente, puedes retroceder a una revisión específica con el parámetro --to-revision:

kubectl rollout undo deployment.v1.apps/nginx-deployment --to-revision=2
deployment.apps/nginx-deployment

Para más detalles acerca de los comandos relacionados con las revisiones de un Deployment, echa un vistazo a kubectl rollout.

El Deployment se ha revertido ahora a una revisión previa estable. Como se puede comprobar, el controlador del Deployment genera un evento DeploymentRollback al retroceder a la revisión 2.

kubectl get deployment nginx-deployment
NAME               READY   UP-TO-DATE   AVAILABLE   AGE 
nginx-deployment   3/3     3            3           30m 
kubectl describe deployment nginx-deployment
Name:                   nginx-deployment
Namespace:              default
CreationTimestamp:      Sun, 02 Sep 2018 18:17:55 -0500
Labels:                 app=nginx
Annotations:            deployment.kubernetes.io/revision=4
                        kubernetes.io/change-cause=kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1 --record=true
Selector:               app=nginx
Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=nginx
  Containers:
   nginx:
    Image:        nginx:1.9.1
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   nginx-deployment-c4747d96c (3/3 replicas created)
Events:
  Type    Reason              Age   From                   Message
  ----    ------              ----  ----                   -------
  Normal  ScalingReplicaSet   12m   deployment-controller  Scaled up replica set nginx-deployment-75675f5897 to 3
  Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-c4747d96c to 1
  Normal  ScalingReplicaSet   11m   deployment-controller  Scaled down replica set nginx-deployment-75675f5897 to 2
  Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-c4747d96c to 2
  Normal  ScalingReplicaSet   11m   deployment-controller  Scaled down replica set nginx-deployment-75675f5897 to 1
  Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-c4747d96c to 3
  Normal  ScalingReplicaSet   11m   deployment-controller  Scaled down replica set nginx-deployment-75675f5897 to 0
  Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-595696685f to 1
  Normal  DeploymentRollback  15s   deployment-controller  Rolled back deployment "nginx-deployment" to revision 2
  Normal  ScalingReplicaSet   15s   deployment-controller  Scaled down replica set nginx-deployment-595696685f to 0

Escalar un Deployment

Puedes escalar un Deployment usando el siguiente comando:

kubectl scale deployment.v1.apps/nginx-deployment --replicas=10
deployment.apps/nginx-deployment scaled

Asumiendo que se ha habilitado el escalado horizontal de pod en tu clúster, puedes configurar un auto-escalado para tu Deployment y elegir el mínimo y máximo número de Pods que quieres ejecutar en base al uso de CPU de tus Pods actuales.

kubectl autoscale deployment.v1.apps/nginx-deployment --min=10 --max=15 --cpu-percent=80
deployment.apps/nginx-deployment scaled

Escalado proporcional

La actualización continua de los Deployments permite la ejecución de múltiples versiones de una aplicación al mismo tiempo. Cuando tú o un auto-escalado escala un Deployment con actualización continua que está en medio de otro despliegue (bien en curso o pausado), entonces el controlador del Deployment balanceará las réplicas adicionales de los ReplicaSets activos (ReplicaSets con Pods) para así poder mitigar el riesgo. Esto se conoce como escalado proporcional.

Por ejemplo, imagina que estás ejecutando un Deployment con 10 réplicas, donde maxSurge=3, y maxUnavailable=2.

kubectl get deploy
NAME               READY   UP-TO-DATE   AVAILABLE   AGE 
nginx-deployment   10/10   10           10          50s 

Si actualizas a una nueva imagen que no puede descargarse desde el clúster:

kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:sometag
deployment.apps/nginx-deployment image updated

La actualización de la imagen arranca un nuevo despliegue con el ReplicaSet nginx-deployment-1989198191, pero se bloquea debido al requisito maxUnavailable indicado arriba:

kubectl get rs
NAME                          DESIRED   CURRENT   READY     AGE
nginx-deployment-1989198191   5         5         0         9s
nginx-deployment-618515232    8         8         8         1m

Y entonces se origina una nueva petición de escalado para el Deployment. El auto-escalado incrementa las réplicas del Deployment a 15. El controlador del Deployment necesita ahora decidir dónde añadir esas nuevas 5 réplicas. Si no estuvieras usando el escalado proporcional, las 5 se añadirían al nuevo ReplicaSet. Pero con el escalado proporcional, las réplicas adicionales se distribuyen entre todos los ReplicaSets. Las partes más grandes van a los ReplicaSets con el mayor número de réplicas y las partes más pequeñas van a los ReplicaSets con menos réplicas. Cualquier resto sobrante se añade al ReplicaSet con mayor número de réplicas. Aquellos ReplicaSets con 0 réplicas no se escalan.

En nuestro ejemplo anterior, se añadirán 3 réplicas al viejo ReplicaSet y 2 réplicas al nuevo ReplicaSet. EL proceso de despliegue debería al final mover todas las réplicas al nuevo ReplicaSet, siempre que las nuevas réplicas arranquen positivamente.

kubectl get deploy
NAME               READY   UP-TO-DATE   AVAILABLE   AGE 
nginx-deployment   18/15   7            8           7m 
kubectl get rs
NAME                          DESIRED   CURRENT   READY     AGE
nginx-deployment-1989198191   7         7         0         7m
nginx-deployment-618515232    11        11        11        7m

Pausar y Reanudar un Deployment

Puedes pausar un Deployment antes de arrancar una o más modificaciones y luego reanudarlo. Esto te permite aplicar múltiples arreglos entre la pausa y la reanudación sin necesidad de arrancar despliegues innecesarios.

Por ejemplo, con un Deployment que acaba de crearse:

kubectl get deploy
NAME               READY   UP-TO-DATE   AVAILABLE   AGE 
nginx-deployment   3/3     3            3           1m 
kubectl get rs
NAME               DESIRED   CURRENT   READY     AGE
nginx-2142116321   3         3         3         1m

Lo pausamos ejecutando el siguiente comando:

kubectl rollout pause deployment.v1.apps/nginx-deployment
deployment.apps/nginx-deployment paused

Y luego actualizamos la imagen del Deployment:

kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1
deployment.apps/nginx-deployment image updated

Nótese que no se arranca ningún despliegue nuevo:

kubectl rollout history deployment.v1.apps/nginx-deployment
deployments "nginx"
REVISION  CHANGE-CAUSE
1   <none>
kubectl get rs
NAME               DESIRED   CURRENT   READY     AGE
nginx-2142116321   3         3         3         2m

Puedes realizar tantas modificaciones como quieras, por ejemplo, para actualizar los recursos a utilizar:

kubectl set resources deployment.v1.apps/nginx-deployment -c=nginx --limits=cpu=200m,memory=512Mi
deployment.apps/nginx-deployment resource requirements updated

El estado inicial del Deployment anterior a la pausa continuará su función, pero las nuevas modificaciones del Deployment no tendrán efecto ya que el Deployment está pausado.

Al final, reanuda el Deployment y observa cómo se genera un nuevo ReplicaSet con todos los cambios:

kubectl rollout resume deployment.v1.apps/nginx-deployment
deployment.apps/nginx-deployment resumed
kubectl get rs -w
NAME               DESIRED   CURRENT   READY     AGE
nginx-2142116321   2         2         2         2m
nginx-3926361531   2         2         0         6s
nginx-3926361531   2         2         1         18s
nginx-2142116321   1         2         2         2m
nginx-2142116321   1         2         2         2m
nginx-3926361531   3         2         1         18s
nginx-3926361531   3         2         1         18s
nginx-2142116321   1         1         1         2m
nginx-3926361531   3         3         1         18s
nginx-3926361531   3         3         2         19s
nginx-2142116321   0         1         1         2m
nginx-2142116321   0         1         1         2m
nginx-2142116321   0         0         0         2m
nginx-3926361531   3         3         3         20s
kubectl get rs
NAME               DESIRED   CURRENT   READY     AGE
nginx-2142116321   0         0         0         2m
nginx-3926361531   3         3         3         28s

Estado del Deployment

Un Deployment pasa por varios estados a lo largo de su ciclo de vida. Así, puede estar progresando mientras se despliega un nuevo ReplicaSet, puede estar completo, o puede quedar en estado fallido.

Progresar un Deployment

Kubernetes marca un Deployment como progresando cuando se realiza cualquiera de las siguientes tareas:

  • El Deployment crea un nuevo ReplicaSet.
  • El Deployment está escalando su ReplicaSet más nuevo.
  • El Deployment está reduciendo su(s) ReplicaSet(s) más antiguo(s).
  • Hay nuevos Pods disponibles y listos (listo por lo menos MinReadySeconds).

Puedes monitorizar el progreso de un Deployment usando el comando kubectl rollout status.

Completar un Deployment

Kubernetes marca un Deployment como completado cuando presenta las siguientes características:

  • Todas las réplicas asociadas con el Deployment han sido actualizadas a la última versión indicada, lo cual quiere decir que todas las actualizaciones se han completado.
  • Todas las réplicas asociadas con el Deployment están disponibles.
  • No están ejecutándose viejas réplicas del Deployment.

Puedes comprobar si un Deployment se ha completado usando el comando kubectl rollout status. Si el despliegue se ha completado de forma satisfactoria, el comando kubectl rollout status devuelve un código 0 de salida.

kubectl rollout status deployment.v1.apps/nginx-deployment
Waiting for rollout to finish: 2 of 3 updated replicas are available...
deployment "nginx-deployment" successfully rolled out
$ echo $?
0

Deployment fallido

Tu Deployment puede quedarse bloqueado intentando desplegar su nuevo ReplicaSet sin nunca completarse. Esto puede ocurrir debido a algunos de los factores siguientes:

  • Cuota insuficiente
  • Fallos en la prueba de estar listo
  • Errores en la descarga de imágenes
  • Permisos insuficientes
  • Rangos de límites de recursos
  • Mala configuración del motor de ejecución de la aplicación

Una forma de detectar este tipo de situación es especificar un parámetro de vencimiento en la especificación de tu Deployment: (.spec.progressDeadlineSeconds). .spec.progressDeadlineSeconds denota el número de segundos que el controlador del Deployment debe esperar antes de indicar (en el estado del Deployment) que el Deployment no avanza.

El siguiente comando kubectl configura el campo progressDeadlineSeconds para forzar al controlador a informar de la falta de avance de un Deployment después de 10 minutos:

kubectl patch deployment.v1.apps/nginx-deployment -p '{"spec":{"progressDeadlineSeconds":600}}'
deployment.apps/nginx-deployment patched

Una vez que se ha excedido el vencimiento, el controlador del Deployment añade una DeploymentCondition con los siguientes atributos al campo .status.conditions del Deployment:

  • Type=Progressing
  • Status=False
  • Reason=ProgressDeadlineExceeded

Ver las convenciones de la API de Kubernetes para más información acerca de las condiciones de estado.

Puede que notes errores transitorios en tus Deployments, bien debido a un tiempo de vencimiento muy pequeño que hayas configurado o bien a cualquier otro tipo de error que puede considerarse como transitorio. Por ejemplo, supongamos que no tienes suficiente cuota. Si describes el Deployment, te darás cuenta de la sección siguiente:

kubectl describe deployment nginx-deployment
<...>
Conditions:
  Type            Status  Reason
  ----            ------  ------
  Available       True    MinimumReplicasAvailable
  Progressing     True    ReplicaSetUpdated
  ReplicaFailure  True    FailedCreate
<...>

Si ejecutas el comando kubectl get deployment nginx-deployment -o yaml, el estado del Deployment puede parecerse a:

status:
  availableReplicas: 2
  conditions:
  - lastTransitionTime: 2016-10-04T12:25:39Z
    lastUpdateTime: 2016-10-04T12:25:39Z
    message: Replica set "nginx-deployment-4262182780" is progressing.
    reason: ReplicaSetUpdated
    status: "True"
    type: Progressing
  - lastTransitionTime: 2016-10-04T12:25:42Z
    lastUpdateTime: 2016-10-04T12:25:42Z
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  - lastTransitionTime: 2016-10-04T12:25:39Z
    lastUpdateTime: 2016-10-04T12:25:39Z
    message: 'Error creating: pods "nginx-deployment-4262182780-" is forbidden: exceeded quota:
      object-counts, requested: pods=1, used: pods=3, limited: pods=2'
    reason: FailedCreate
    status: "True"
    type: ReplicaFailure
  observedGeneration: 3
  replicas: 2
  unavailableReplicas: 2

Al final, una vez que se supera el vencimiento del progreso del Deployment, Kubernetes actualiza el estado y la razón de el estado de progreso:

Conditions:
  Type            Status  Reason
  ----            ------  ------
  Available       True    MinimumReplicasAvailable
  Progressing     False   ProgressDeadlineExceeded
  ReplicaFailure  True    FailedCreate

Puedes solucionar un problema de cuota insuficiente simplemente reduciendo el número de réplicas de tu Deployment, reduciendo otros controladores que puedas estar ejecutando, o incrementando la cuota en tu espacio de nombres. Si una vez satisfechas las condiciones de tu cuota, el controlador del Deployment completa el despliegue, entonces verás que el estado del Deployment se actualiza al estado satisfactorio (Status=True y Reason=NewReplicaSetAvailable).

Conditions:
  Type          Status  Reason
  ----          ------  ------
  Available     True    MinimumReplicasAvailable
  Progressing   True    NewReplicaSetAvailable

Type=Available con Status=True significa que tu Deployment tiene disponibilidad mínima. La disponibilidad mínima se prescribe mediante los parámetros indicados en la estrategia de despligue. Type=Progressing con Status=True significa que tu Deployment está bien en medio de un despliegue y está progresando o bien que se ha completado de forma satisfactoria y el número mínimo requerido de nuevas réplicas ya está disponible (ver la Razón del estado para cada caso particular - en nuestro caso Reason=NewReplicaSetAvailable significa que el Deployment se ha completado).

Puedes comprobar si un Deployment ha fallado en su progreso usando el comando kubectl rollout status. kubectl rollout status devuelve un código de salida distinto de 0 si el Deployment ha excedido su tiempo de vencimiento.

kubectl rollout status deployment.v1.apps/nginx-deployment
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
error: deployment "nginx" exceeded its progress deadline
$ echo $?
1

Actuar ante un despliegue fallido

Todas las acciones que aplican a un Deployment completado también aplican a un Deployment fallido. Puedes escalarlo/reducirlo, retrocederlo a una revisión previa, o incluso pausarlo si necesitas realizar múltiples cambios a la plantilla Pod del Deployment.

Regla de Limpieza

Puedes configurar el campo .spec.revisionHistoryLimit de un Deployment para especificar cuántos ReplicaSets viejos quieres conservar para este Deployment. El resto será eliminado en segundo plano. Por defecto, es 10.

Casos de Uso

Despligue Canary

Si quieres desplegar nuevas versiones a un sub-conjunto de usuarios o servidores usando el Deployment, puedes hacerlo creando múltiples Deployments, uno para cada versión nueva, siguiendo el patrón canary descrito en gestionar recursos.

Escribir una especificación de Deployment

Al igual que con el resto de configuraciones de Kubernetes, un Deployment requiere los campos apiVersion, kind, y metadata. Para información general acerca de cómo trabajar con ficheros de configuración, ver los documentos acerca de desplegar aplicaciones, configurar contenedores, y usar kubectl para gestionar recursos.

Un Deployment también necesita una sección .spec.

Plantilla Pod

Tanto .spec.template como .spec.selector sin campos obligatorios dentro de .spec.

El campo .spec.template es una plantilla Pod. Tiene exactamente el mismo esquema que un Pod, excepto por el hecho de que está anidado y no tiene apiVersion ni kind.

Junto con los campos obligatorios de un Pod, una plantilla Pod de un Deployment debe indicar las etiquetas y las reglas de reinicio apropiadas. Para el caso de las etiquetas, asegúrate que no se entremezclan con otros controladores. Ver selector).

Únicamente se permite una .spec.template.spec.restartPolicy igual a Always, que es el valor por defecto si no se indica.

Réplicas

.spec.replicas es un campo opcional que indica el número de Pods deseados. Su valor por defecto es 1.

Selector

.spec.selector es un campo opcional que indica un selector de etiquetas para los Pods objetivo del deployment.

.spec.selector debe coincidir con .spec.template.metadata.labels, o será descartado por la API.

A partir de la versión apps/v1 de la API, .spec.selector y .metadata.labels no toman como valor por defecto el valor de .spec.template.metadata.labels si no se indica. Por ello, debe especificarse de forma explícita. Además hay que mencionar que .spec.selector es inmutable tras la creación del Deployment en apps/v1.

Un Deployment puede finalizar aquellos Pods cuyas etiquetas coincidan con el selector si su plantilla es diferente de .spec.template o si el número total de dichos Pods excede .spec.replicas. Arranca nuevos Pods con .spec.template si el número de Pods es menor que el número deseado.

Si tienes múltiples controladores que entremezclan sus selectores, dichos controladores competirán entre ellos y no se comportarán de forma correcta.

Estrategia

.spec.strategy especifica la estrategia usada para remplazar los Pods viejos con los nuevos. .spec.strategy.type puede tener el valor "Recreate" o "RollingUpdate". "RollingUpdate" el valor predeterminado.

Despliegue mediante recreación

Todos los Pods actuales se eliminan antes de que los nuevos se creen cuando .spec.strategy.type==Recreate.

Despliegue mediante actualización continua

El Deployment actualiza los Pods en modo de actualización continua cuando .spec.strategy.type==RollingUpdate. Puedes configurar los valores de maxUnavailable y maxSurge para controlar el proceso de actualización continua.

Número máximo de pods no disponibles

.spec.strategy.rollingUpdate.maxUnavailable es un campo opcional que indica el número máximo de Pods que pueden no estar disponibles durante el proceso de actualización. El valor puede ser un número absoluto (por ejemplo, 5) o un porcentaje de los Pods deseados (por ejemplo, 10%). El número absoluto se calcula a partir del porcentaje con redondeo a la baja. El valor no puede ser 0 si .spec.strategy.rollingUpdate.maxSurge es 0. El valor predeterminado es 25%.

Por ejemplo, cuando este valor es 30%, el ReplicaSet viejo puede escalarse al 70% de los Pods deseados de forma inmediata tras comenzar el proceso de actualización. Una vez que los Pods están listos, el ReplicaSet viejo puede reducirse aún mas, seguido de un escalado del nuevo ReplicaSet, asegurándose que el número total de Pods disponibles en todo momento durante la actualización es de al menos el 70% de los Pods deseados.

Número máximo de pods por encima del número deseado

.spec.strategy.rollingUpdate.maxSurge es un campo opcional que indica el número máximo de Pods que puede crearse por encima del número deseado de Pods. El valor puede ser un número absoluto (por ejemplo, 5) o un porcentaje de los Pods deseados (por ejemplo, 10%). El valor no puede ser 0 si MaxUnavailable es 0. El número absoluto se calcula a partir del porcentaje con redondeo al alza. El valor predeterminado es 25%.

Por ejemplo, cuando este valor es 30%, el nuevo ReplicaSet puede escalarse inmediatamente cuando comienza la actualización continua, de forma que el número total de Pods viejos y nuevos no excede el 130% de los Pods deseados. Una vez que los viejos Pods se han eliminado, el nuevo ReplicaSet puede seguir escalándose, asegurándose que el número total de Pods ejecutándose en todo momento durante la actualización es como mucho del 130% de los Pods deseados.

Segundos para vencimiento del progreso

.spec.progressDeadlineSeconds es un campo opcional que indica el número de segundos que quieres esperar a que tu Deployment avance antes de que el sistema reporte que dicho Deployment ha fallado en su avance - expresado como un estado con Type=Progressing, Status=False. y Reason=ProgressDeadlineExceeded en el recurso. El controlador del Deployment seguirá intentando el despliegue. En el futuro, una vez que se implemente el retroceso automático, el controlador del Deployment retrocederá el despliegue en cuanto detecte ese estado.

Si se especifica, este campo debe ser mayor que .spec.minReadySeconds.

Tiempo mínimo para considerar el Pod disponible

.spec.minReadySeconds es un campo opcional que indica el número mínimo de segundos en que un Pod recién creado debería estar listo sin que falle ninguno de sus contenedores, para que se considere disponible. Por defecto su valor es 0 (el Pod se considera disponible en el momento que está listo). Para aprender más acerca de cuándo un Pod se considera que está listo, ver las pruebas de contenedor.

Vuelta atrás

El campo .spec.rollbackTo se ha quitado de las versiones extensions/v1beta1 y apps/v1beta1 de la API, y ya no se permite en las versiones de la API a partir de apps/v1beta2. En su caso, se debería usar kubectl rollout undo, tal y como se explicó en Retroceder a una Revisión Previa.

Límite del histórico de revisiones

La historia de revisiones de un Deployment se almacena en los ReplicaSets que este controla.

.spec.revisionHistoryLimit es un campo opcional que indica el número de ReplicaSets viejos a retener para permitir los retrocesos. Estos ReplicaSets viejos consumen recursos en etcd y rebosan la salida de kubectl get rs. La configuración de cada revisión de Deployment se almacena en sus ReplicaSets; por lo tanto, una vez que se elimina el ReplicaSet viejo, se pierde la posibilidad de retroceder a dicha revisión del Deployment. Por defecto, se retienen hasta 10 ReplicaSets viejos; pero su valor ideal depende de la frecuencia y la estabilidad de los nuevos Deployments.

De forma más específica, si ponemos este campo a cero quiere decir que todos los ReplicaSets viejos con 0 réplicas se limpiarán. En este caso, el nuevo despliegue del Deployment no se puede deshacer, ya que su historia de revisiones se habrá limpiado.

Pausa

.spec.paused es un campo booleano opcional para pausar y reanudar un Deployment. La única diferencia entre un Deployment pausado y otro que no lo está es que cualquier cambio al PodTemplateSpec del Deployment pausado no generará nuevos despliegues mientras esté pausado. Un Deployment se pausa de forma predeterminada cuando se crea.

Alternativa a los Deployments

kubectl rolling update

kubectl rolling update actualiza los Pods y los ReplicationControllers de forma similar. Pero se recomienda el uso de Deployments porque se declaran del lado del servidor, y proporcionan características adicionales como la posibilidad de retroceder a revisiones anteriores incluso después de haber terminado una actualización continua.

4.2.4 - StatefulSets

Un StatefulSet es el objeto de la API workload que se usa para gestionar aplicaciones con estado.

Gestiona el despliegue y escalado de un conjunto de Pods, y garantiza el orden y unicidad de dichos Pods.

Al igual que un Deployment, un StatefulSet gestiona Pods que se basan en una especificación idéntica de contenedor. A diferencia de un Deployment, un StatefulSet mantiene una identidad asociada a sus Pods. Estos pods se crean a partir de la misma especificación, pero no pueden intercambiarse; cada uno tiene su propio identificador persistente que mantiene a lo largo de cualquier re-programación.

Un StatefulSet opera bajo el mismo patrón que cualquier otro controlador. Se define el estado deseado en un objeto StatefulSet, y el controlador del StatefulSet efectúa las actualizaciones que sean necesarias para alcanzarlo a partir del estado actual.

Usar StatefulSets

Los StatefulSets son valiosos para aquellas aplicaciones que necesitan uno o más de los siguientes:

  • Identificadores de red estables, únicos.
  • Almacenamiento estable, persistente.
  • Despliegue y escalado ordenado, controlado.
  • Actualizaciones en línea ordenadas, automatizadas.

De los de arriba, estable es sinónimo de persistencia entre (re)programaciones de Pods. Si una aplicación no necesita ningún identificador estable o despliegue, eliminación, o escalado ordenado, deberías desplegar tu aplicación con un controlador que proporcione un conjunto de réplicas sin estado, como un Deployment o un ReplicaSet, ya que están mejor preparados para tus necesidades sin estado.

Limitaciones

  • El almacenamiento de un determinado Pod debe provisionarse por un Provisionador de PersistentVolume basado en la storage class requerida, o pre-provisionarse por un administrador.
  • Eliminar y/o reducir un StatefulSet no eliminará los volúmenes asociados con el StatefulSet. Este comportamiento es intencional y sirve para garantizar la seguridad de los datos, que da más valor que la purga automática de los recursos relacionados del StatefulSet.
  • Los StatefulSets actualmente necesitan un Servicio Headless como responsable de la identidad de red de los Pods. Es tu responsabilidad crear este Service.
  • Los StatefulSets no proporcionan ninguna garantía de la terminación de los pods cuando se elimina un StatefulSet. Para conseguir un término de los pods ordenado y controlado en el StatefulSet, es posible reducir el StatefulSet a 0 réplicas justo antes de eliminarlo.
  • Cuando se usan las Actualizaciones en línea con la Regla de Gestión de Pod (OrderedReady) por defecto, es posible entrar en un estado inconsistente que requiere de una intervención manual para su reparación.

Componentes

El ejemplo de abajo demuestra los componentes de un StatefulSet:

  • Un servicio Headless, llamado nginx, se usa para controlar el dominio de red.
  • Un StatefulSet, llamado web, que tiene una especificación que indica que se lanzarán 3 réplicas del contenedor nginx en Pods únicos.
  • Un volumeClaimTemplate que proporciona almacenamiento estable por medio de PersistentVolumes provisionados por un provisionador de tipo PersistentVolume.
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx # tiene que coincidir con .spec.template.metadata.labels
  serviceName: "nginx"
  replicas: 3 # por defecto es 1
  template:
    metadata:
      labels:
        app: nginx # tiene que coincidir con .spec.selector.matchLabels
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: registry.k8s.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "my-storage-class"
      resources:
        requests:
          storage: 1Gi

Selector de Pod

Debes poner el valor del campo .spec.selector de un StatefulSet para que coincida con las etiquetas de su campo .spec.template.metadata.labels. Antes de Kubernetes 1.8, el campo .spec.selector se predeterminaba cuando se omitía. A partir de la versión 1.8, si no se especifica un selector de coincidencia de Pods, se produce un error de validación durante la creación del StatefulSet.

Identidad de Pod

Los Pods de un StatefulSet tienen una identidad única que está formada por un ordinal, una identidad estable de red, y almacenamiento estable. La identidad se asocia al Pod, independientemente del nodo en que haya sido (re)programado.

Índice Ordinal

Para un StatefulSet con N réplicas, a cada Pod del StatefulSet se le asignará un número entero ordinal, desde 0 hasta N-1, y que es único para el conjunto.

ID estable de Red

El nombre de anfitrión (hostname) de cada Pod de un StatefulSet se deriva del nombre del StatefulSet y del número ordinal del Pod. El patrón para construir dicho hostname es $(statefulset name)-$(ordinal). Así, el ejemplo de arriba creará tres Pods denominados web-0,web-1,web-2. Un StatefulSet puede usar un Servicio Headless para controlar el nombre de dominio de sus Pods. El nombre de dominio gestionado por este Service tiene la forma: $(service name).$(namespace).svc.cluster.local, donde "cluster.local" es el nombre de dominio del clúster. Conforme se crea cada Pod, se le asigna un nombre DNS correspondiente de subdominio, que tiene la forma: $(podname).$(governing service domain), donde el servicio en funciones se define por el campo serviceName del StatefulSet.

Como se indicó en la sección limitaciones, la creación del Servicio Headless encargado de la identidad de red de los pods es enteramente tu responsabilidad.

Aquí se muestran algunos ejemplos de elecciones de nombres de Cluster Domain, nombres de Service, nombres de StatefulSet, y cómo impactan en los nombres DNS de los Pods del StatefulSet:

Cluster Domain Service (ns/nombre) StatefulSet (ns/nombre) StatefulSet Domain Pod DNS Pod Hostname
cluster.local default/nginx default/web nginx.default.svc.cluster.local web-{0..N-1}.nginx.default.svc.cluster.local web-{0..N-1}
cluster.local foo/nginx foo/web nginx.foo.svc.cluster.local web-{0..N-1}.nginx.foo.svc.cluster.local web-{0..N-1}
kube.local foo/nginx foo/web nginx.foo.svc.kube.local web-{0..N-1}.nginx.foo.svc.kube.local web-{0..N-1}

Almacenamiento estable

Kubernetes crea un PersistentVolume para cada VolumeClaimTemplate. En el ejemplo de nginx de arriba, cada Pod recibirá un único PersistentVolume con una StorageClass igual a my-storage-class y 1 GiB de almacenamiento provisionado. Si no se indica ninguna StorageClass, entonces se usa la StorageClass por defecto. Cuando un Pod se (re)programa en un nodo, sus volumeMounts montan los PersistentVolumes asociados con sus PersistentVolume Claims. Nótese que los PersistentVolumes asociados con los PersistentVolume Claims de los Pods no se eliminan cuando los Pods, o los StatefulSet se eliminan. Esto debe realizarse manualmente.

Etiqueta de Nombre de Pod

Cuando el controlador del StatefulSet crea un Pod, añade una etiqueta, statefulset.kubernetes.io/pod-name, que toma el valor del nombre del Pod. Esta etiqueta te permite enlazar un Service a un Pod específico en el StatefulSet.

Garantías de Despliegue y Escalado

  • Para un StatefulSet con N réplicas, cuando los Pods se despliegan, se crean secuencialmente, en orden de {0..N-1}.
  • Cuando se eliminan los Pods, se terminan en orden opuesto, de {N-1..0}.
  • Antes de que una operación de escalado se aplique a un Pod, todos sus predecesores deben estar Running y Ready.
  • Antes de que se termine un Pod, todos sus sucesores deben haberse apagado completamente.

El StatefulSet no debería tener que indicar un valor 0 para el campo pod.Spec.TerminationGracePeriodSeconds. Esta práctica no es segura y se aconseja no hacerlo. Para una explicación más detallada, por favor echa un vistazo a cómo forzar la eliminación de Pods de un StatefulSet.

Cuando el ejemplo nginx de arriba se crea, se despliegan tres Pods en el orden web-0, web-1, web-2. web-1 no se desplegará hasta que web-0 no esté Running y Ready, y web-2 no se desplegará hasta que web-1 esté Running y Ready. En caso de que web-0 fallase, después de que web-1 estuviera Running y Ready, pero antes de que se desplegara web-2, web-2 no se desplegaría hasta que web-0 se redesplegase con éxito y estuviera Running y Ready.

Si un usuario fuera a escalar el ejemplo desplegado parcheando el StatefulSet de forma que replicas=1, web-2 se terminaría primero. web-1 no se terminaría hasta que web-2 no se hubiera apagado y eliminado por completo. Si web-0 fallase después de que web-2 se hubiera terminado y apagado completamente, pero antes del término de web-1, entonces web-1 no se terminaría hasta que web-0 estuviera Running y Ready.

Reglas de Gestión de Pods

En Kubernetes 1.7 y versiones posteriores, el StatefulSet permite flexibilizar sus garantías de ordenación al mismo tiempo que preservar su garantía de singularidad e identidad a través del campo .spec.podManagementPolicy.

Gestión de tipo OrderedReady de Pods

La gestión de tipo OrderedReady de pods es la predeterminada para los StatefulSets. Implementa el comportamiento descrito arriba.

Gestión de tipo Parallel de Pods

La gestión de tipo Parallel de pods le dice al controlador del StatefulSet que lance y termine todos los Pods en paralelo, y que no espere a que los Pods estén Running y Ready o completamente terminados antes de lanzar o terminar otro Pod.

Estrategias de Actualización

En Kubernetes 1.7 y a posteriori, el campo .spec.updateStrategy del StatefulSet permite configurar y deshabilitar las actualizaciones automátizadas en línea para los contenedores, etiquetas, peticiones/límites de recursos, y anotaciones de los Pods del StatefulSet.

On Delete

La estrategia de actualización OnDelete implementa el funcionamiento tradicional (1.6 y previo). Cuando el campo .spec.updateStrategy.type de un StatefulSet se pone al valor OnDelete, el controlador del StatefulSet no actualizará automáticamente los Pods del StatefulSet. Los usuarios deben eliminar manualmente los Pods para forzar al controlador a crear nuevos Pods que reflejen las modificaciones hechas al campo .spec.template del StatefulSet.

Rolling Updates

La estrategia de actualización RollingUpdate implementa una actualización automatizada en línea de los Pods del StatefulSet. Es la estrategia por defecto cuando el campo .spec.updateStrategy se deja sin valor. Cuando el campo .spec.updateStrategy.type de un StatefulSet se pone al valor RollingUpdate, el controlador del StatefulSet lo eliminará y recreará cada Pod en el StatefulSet. Procederá en el mismo orden en que ha terminado los Pod (del número ordinal más grande al más pequeño), actualizando cada Pod uno por uno. Esperará a que el Pod actualizado esté Running y Ready antes de actualizar su predecesor.

Particiones

La estrategia de actualización RollingUpdate puede particionarse, indicando el valor del campo .spec.updateStrategy.rollingUpdate.partition. Si se indica una partición, todos los Pods con un número ordinal mayor o igual que el de la partición serán actualizados cuando el campo .spec.template del StatefulSet se actualice. Todos los Pods con un número ordinal que sea menor que el de la partición no serán actualizados, e incluso si son eliminados, serán recreados con la versión anterior. Si el campo .spec.updateStrategy.rollingUpdate.partition de un StatefulSet es mayor que el valor del campo .spec.replicas, las modificaciones al campo .spec.template no se propagarán a sus Pods. En la mayoría de ocasiones, no necesitarás usar una partición, pero pueden resultar útiles si quieres preparar una actualización, realizar un despliegue tipo canary, o llevar a cabo un despliegue en fases.

Retroceso Forzado

Cuando se usa Actualizaciones en línea con el valor de la Regla de Gestión de Pod (OrderedReady) por defecto, es posible acabar en un estado inconsistente que requiera de una intervención manual para arreglarlo.

Si actualizas la plantilla Pod a una configuración que nunca llega a Running y Ready (por ejemplo, debido a un binario incorrecto o un error de configuración a nivel de aplicación), el StatefulSet detendrá el despliegue y esperará.

En este estado, no es suficiente con revertir la plantilla Pod a la configuración buena. Debido a un problema conocido, el StatefulSet seguirá esperando a que los Pod estropeados se pongan en Ready (lo que nunca ocurre) antes de intentar revertirla a la configuración que funcionaba.

Antes de revertir la plantilla, debes también eliminar cualquier Pod que el StatefulSet haya intentando ejecutar con la configuración incorrecta. El StatefulSet comenzará entonces a recrear los Pods usando la plantilla revertida.

Siguientes pasos

4.2.5 - DaemonSet

Un DaemonSet garantiza que todos (o algunos) de los nodos ejecuten una copia de un Pod. Conforme se añade más nodos al clúster, nuevos Pods son añadidos a los mismos. Conforme se elimina nodos del clúster, dichos Pods se destruyen. Al eliminar un DaemonSet se limpian todos los Pods que han sido creados.

Algunos casos de uso típicos de un DaemonSet son:

  • Ejecutar un proceso de almacenamiento en el clúster.
  • Ejecutar un proceso de recolección de logs en cada nodo.
  • Ejecutar un proceso de monitorización de nodos en cada nodo.

De forma básica, se debería usar un DaemonSet, cubriendo todos los nodos, por cada tipo de proceso. En configuraciones más complejas se podría usar múltiples DaemonSets para un único tipo de proceso, pero con diferentes parámetros y/o diferentes peticiones de CPU y memoria según el tipo de hardware.

Escribir una especificación de DaemonSet

Crear un DaemonSet

Un DaemonSet se describe por medio de un archivo YAML. Por ejemplo, el archivo daemonset.yaml de abajo describe un DaemonSet que ejecuta la imagen Docker de fluentd-elasticsearch:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd-elasticsearch
  namespace: kube-system
  labels:
    k8s-app: fluentd-logging
spec:
  selector:
    matchLabels:
      name: fluentd-elasticsearch
  template:
    metadata:
      labels:
        name: fluentd-elasticsearch
    spec:
      tolerations:
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      containers:
      - name: fluentd-elasticsearch
        image: gcr.io/fluentd-elasticsearch/fluentd:v2.5.1
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
  • Crear un DaemonSet basado en el archivo YAML:
kubectl apply -f https://k8s.io/examples/controllers/daemonset.yaml

Campos requeridos

Como con cualquier otra configuración de Kubernetes, un DaemonSet requiere los campos apiVersion, kind, y metadata. Para información general acerca de cómo trabajar con ficheros de configuración, ver los documentos desplegar aplicaciones, configurar contenedores, y gestión de objetos usando kubectl.

Un DaemonSet también necesita un sección .spec.

Plantilla Pod

El campo .spec.template es uno de los campos obligatorios de la sección .spec.

El campo .spec.template es una plantilla Pod. Tiene exactamente el mismo esquema que un Pod, excepto por el hecho de que está anidado y no tiene los campos apiVersion o kind.

Además de los campos obligatorios de un Pod, la plantilla Pod para un DaemonSet debe especificar las etiquetas apropiadas (ver selector de pod).

Una plantilla Pod para un DaemonSet debe tener una RestartPolicy igual a Always, o no indicarse, lo cual asume por defecto el valor Always.

Selector de Pod

El campo .spec.selector es un selector de pod. Funciona igual que el campo .spec.selector de un Job.

A partir de Kubernetes 1.8, se debe configurar un selector de pod que coincida con las etiquetas definidas en el .spec.template. Así, el selector de pod ya no asume valores por defecto cuando no se indica. Dichos valores por defecto no eran compatibles con kubectl apply. Además, una vez que se ha creado el DaemonSet, su campo .spec.selector no puede alterarse porque, si fuera el caso, ello podría resultar en Pods huérfanos, lo cual confundiría a los usuarios.

El campo .spec.selector es un objeto que, a su vez, consiste en dos campos:

  • matchLabels - funciona igual que el campo .spec.selector de un ReplicationController.
  • matchExpressions - permite construir selectores más sofisticados indicando la clave, la lista de valores y un operador para relacionar la clave y los valores.

Cuando se configura ambos campos, el resultado es conjuntivo (AND).

Si se especifica el campo .spec.selector, entonces debe coincidir con el campo .spec.template.metadata.labels. Aquellas configuraciones que no coinciden, son rechazadas por la API.

Además, normalmente no se debería crear ningún Pod con etiquetas que coincidan con el selector, bien sea de forma directa, via otro DaemonSet, o via otro controlador como un ReplicaSet. De ser así, el controlador del DaemonSet pensará que dichos Pods fueron en realidad creados por él mismo. Kubernetes, en cualquier caso, no te impide realizar esta operación. Un caso donde puede que necesites hacer esto es cuando quieres crear manualmente un Pod con un valor diferente en un nodo para pruebas.

Ejecutar Pods sólo en Nodos seleccionados

Si se configura un .spec.template.spec.nodeSelector, entonces el controlador del DaemonSet creará los Pods en aquellos nodos que coincidan con el selector de nodo indicado. De forma similar, si se configura una .spec.template.spec.affinity, entonces el controlador del DaemonSet creará los Pods en aquellos nodos que coincidan con la afinidad de nodo indicada. Si no se configura ninguno de los dos, entonces el controlador del DaemonSet creará los Pods en todos los nodos.

Cómo se planifican los Pods procesos

Planificados por el controlador del DaemonSet (deshabilitado por defecto a partir de 1.12)

Normalmente, el planificador de Kubernetes determina la máquina donde se ejecuta un Pod. Sin embargo, los Pods creados por el controlador del DaemonSet ya tienen la máquina seleccionada (puesto que cuando se crea el Pod, se indica el campo .spec.nodeName, y por ello el planificador los ignora). Por lo tanto:

  • El controlador del DaemonSet no tiene en cuenta el campo unschedulable de un nodo.
  • El controlador del DaemonSet puede crear Pods incluso cuando el planificador no ha arrancado, lo cual puede ayudar en el arranque del propio clúster.

Planificados por el planificador por defecto de Kubernetes (habilitado por defecto desde 1.12)

FEATURE STATE: Kubernetes v1.32 [beta]

Un DaemonSet garantiza que todos los nodos elegibles ejecuten una copia de un Pod. Normalmente, es el planificador de Kubernetes quien determina el nodo donde se ejecuta un Pod. Sin embargo, los pods del DaemonSet son creados y planificados por el mismo controlador del DaemonSet. Esto introduce los siguientes inconvenientes:

  • Comportamiento inconsistente de los Pods: Los Pods normales que están esperando a ser creados, se encuentran en estado Pending, pero los pods del DaemonSet no pasan por el estado Pending. Esto confunde a los usuarios.
  • La prioridad y el comportamiento de apropiación de Pods se maneja por el planificador por defecto. Cuando se habilita la contaminación, el controlador del DaemonSet tomará la decisiones de planificación sin considerar ni la prioridad ni la contaminación del pod.

ScheduleDaemonSetPods permite planificar DaemonSets usando el planificador por defecto en vez del controlador del DaemonSet, añadiendo la condición NodeAffinity a los pods del DaemonSet, en vez de la condición .spec.nodeName. El planificador por defecto se usa entonces para asociar el pod a su servidor destino. Si la afinidad de nodo del pod del DaemonSet ya existe, se sustituye. El controlador del DaemonSet sólo realiza estas operaciones cuando crea o modifica los pods del DaemonSet, y no se realizan cambios al spec.template del DaemonSet.

nodeAffinity:
  requiredDuringSchedulingIgnoredDuringExecution:
    nodeSelectorTerms:
    - matchFields:
      - key: metadata.name
        operator: In
        values:
        - target-host-name

Adicionalmente, se añade de forma automática la tolerancia node.kubernetes.io/unschedulable:NoSchedule a los Pods del DaemonSet. Así, el planificador por defecto ignora los nodos unschedulable cuando planifica los Pods del DaemonSet.

Contaminaciones (taints) y Tolerancias (tolerations)

A pesar de que los Pods de proceso respetan las contaminaciones y tolerancias, la siguientes tolerancias son añadidas a los Pods del DaemonSet de forma automática según las siguientes características:

Clave de tolerancia Efecto Versión Descripción
node.kubernetes.io/not-ready NoExecute 1.13+ Los pods del DaemonSet no son expulsados cuando hay problemas de nodo como una partición de red.
node.kubernetes.io/unreachable NoExecute 1.13+ Los pods del DaemonSet no son expulsados cuando hay problemas de nodo como una partición de red.
node.kubernetes.io/disk-pressure NoSchedule 1.8+ Los pods del DaemonSet no son expulsados cuando hay problemas de nodo como la falta de espacio en disco.
node.kubernetes.io/memory-pressure NoSchedule 1.8+ Los pods del DaemonSet no son expulsados cuando hay problemas de nodo como la falta de memoria.
node.kubernetes.io/unschedulable NoSchedule 1.12+ Los pods del DaemonSet toleran los atributos unschedulable del planificador por defecto.
node.kubernetes.io/network-unavailable NoSchedule 1.12+ Los pods del DaemonSet, que usan la red del servidor anfitrión, toleran los atributos network-unavailable del planificador por defecto.

Comunicarse con los Pods de los DaemonSets

Algunos patrones posibles para la comunicación con los Pods de un DaemonSet son:

  • Push: Los Pods del DaemonSet se configuran para enviar actualizaciones a otro servicio, como una base de datos de estadísticas. No tienen clientes.
  • NodeIP y Known Port: Los Pods del DaemonSet pueden usar un hostPort, de forma que se les puede alcanzar via las IPs del nodo. Los clientes conocen la lista de IPs del nodo de algún modo, y conocen el puerto acordado.
  • DNS: Se crea un servicio headless con el mismo selector de pod, y entonces se descubre a los DaemonSets usando los recursos endpoints o mediante múltiples registros de tipo A en el DNS.
  • Service: Se crea un servicio con el mismo selector de Pod, y se usa el servicio para llegar al proceso de uno de los nodos. (No hay forma de determinar el nodo exacto.)

Actualizar un DaemonSet

Si se cambian las etiquetas de nodo, el DaemonSet comenzará de forma inmediata a añadir Pods a los nuevos nodos que coincidan y a eliminar los Pods de aquellos nuevos nodos donde no coincidan.

Puedes modificar los Pods que crea un DaemonSet. Sin embargo, no se permite actualizar todos los campos de los Pods. Además, el controlador del DaemonSet utilizará la plantilla original la próxima vez que se cree un nodo (incluso con el mismo nombre).

Puedes eliminar un DaemonSet. Si indicas el parámetro --cascade=false al usar kubectl, entonces los Pods continuarán ejecutándose en los nodos. Así, puedes crear entonces un nuevo DaemonSet con una plantilla diferente. El nuevo DaemonSet con la plantilla diferente reconocerá a todos los Pods existentes que tengan etiquetas coincidentes y no modificará o eliminará ningún Pod aunque la plantilla no coincida con los Pods desplegados. Entonces, deberás forzar la creación del nuevo Pod eliminando el Pod mismo o el nodo.

A partir de las versión 1.6 de Kubernetes, puedes llevar a cabo una actualización continua en un DaemonSet.

Alternativas al DaemonSet

Secuencias de comandos de inicialización

Aunque es perfectamente posible ejecutar procesos arrancándolos directamente en un nodo (ej. usando init, upstartd, o systemd), existen numerosas ventajas si se realiza via un DaemonSet:

  • Capacidad de monitorizar y gestionar los logs de los procesos del mismo modo que para las aplicaciones.
  • Mismo lenguaje y herramientas de configuración (ej. plantillas de Pod, kubectl) tanto para los procesos como para las aplicaciones.
  • Los procesos que se ejecutan en contenedores con límitaciones de recursos aumentan el aislamiento entre dichos procesos y el resto de contenedores de aplicaciones. Sin embargo, esto también se podría conseguir ejecutando los procesos en un contenedor en vez de un Pod (ej. arrancarlos directamente via Docker).

Pods individuales

Es posible crear Pods directamente sin indicar el nodo donde ejecutarse. Sin embargo, la ventaja del DaemonSet es que sustituye los Pods que se eliminan o terminan por cualquier razón, como en el caso de un fallo del nodo o una intervención disruptiva de mantenimiento del nodo, como la actualización del kernel. Por esta razón, deberías siempre utilizar un DaemonSet en vez de crear Pods individuales.

Pods estáticos

Es posible crear Pods a partir de archivos en el directorio donde está escuchando el proceso Kubelet. Este tipo de Pods se denomina pods estáticos. A diferencia del DaemonSet, los Pods estáticos no se pueden gestionar con kubectl o cualquier otro cliente de la API de Kubernetes. Los Pods estáticos no dependen del apiserver, lo cual los hace convenientes para el arranque inicial del clúster. Además, puede que los Pods estáticos se deprecien en el futuro.

Deployments

Los DaemonSets son similares a los Deployments en el sentido que ambos crean Pods, y que dichos Pods tienen procesos que no se espera que terminen (ej. servidores web, servidores de almacenamiento).

Utiliza un Deployment para definir servicios sin estado, como las interfaces de usuario, donde el escalado vertical y horizontal del número de réplicas y las actualizaciones continuas son mucho más importantes que el control exacto del servidor donde se ejecuta el Pod. Utiliza un DaemonSet cuando es importante que una copia de un Pod siempre se ejecute en cada uno de los nodos, y cuando se necesite que arranque antes que el resto de Pods.

4.2.6 - Recolección de Basura

El papel del recolector de basura de Kubernetes es el de eliminar determinados objetos que en algún momento tuvieron un propietario, pero que ahora ya no.

Propietarios y subordinados

Algunos objetos de Kubernetes son propietarios de otros objetos. Por ejemplo, un ReplicaSet es el propietario de un conjunto de Pods. Los objetos que se poseen se denominan subordinados del objeto propietario. Cada objeto subordinado tiene un campo metadata.ownerReferences que apunta al objeto propietario.

En ocasiones, Kubernetes pone el valor del campo ownerReference automáticamente. Por ejemplo, cuando creas un ReplicaSet, Kubernetes automáticamente pone el valor del campo ownerReference de cada Pod en el ReplicaSet. A partir de la versión 1.8, Kubernetes automáticamente pone el valor de ownerReference para los objetos creados o adoptados por un ReplicationController, ReplicaSet, StatefulSet, DaemonSet, Deployment, Job y CronJob.

También puedes configurar las relaciones entre los propietarios y sus subordinados de forma manual indicando el valor del campo ownerReference.

Aquí se muestra un archivo de configuración para un ReplicaSet que tiene tres Pods:

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: my-repset
spec:
  replicas: 3
  selector:
    matchLabels:
      pod-is-for: garbage-collection-example
  template:
    metadata:
      labels:
        pod-is-for: garbage-collection-example
    spec:
      containers:
      - name: nginx
        image: nginx

Si se crea el ReplicaSet y entonces se muestra los metadatos del Pod, se puede observar el campo OwnerReferences:

kubectl apply -f https://k8s.io/examples/controllers/replicaset.yaml
kubectl get pods --output=yaml

La salida muestra que el propietario del Pod es el ReplicaSet denominado my-repset:

apiVersion: v1
kind: Pod
metadata:
  ...
  ownerReferences:
  - apiVersion: apps/v1
    controller: true
    blockOwnerDeletion: true
    kind: ReplicaSet
    name: my-repset
    uid: d9607e19-f88f-11e6-a518-42010a800195
  ...

Controlar cómo el recolector de basura elimina los subordinados

Cuando eliminas un objeto, puedes indicar si sus subordinados deben eliminarse también de forma automática. Eliminar los subordinados automáticamente se denomina borrado en cascada.
Hay dos modos de borrado en cascada: en segundo plano y en primer plano.

Si eliminas un objeto sin borrar sus subordinados de forma automática, dichos subordinados se convierten en huérfanos.

Borrado en cascada en primer plano

En el borrado en cascada en primer plano, el objeto raíz primero entra en un estado llamado "deletion in progress". En este estado "deletion in progress", se cumplen las siguientes premisas:

  • El objeto todavía es visible a través de la API REST
  • Se pone el valor del campo deletionTimestamp del objeto
  • El campo metadata.finalizers del objeto contiene el valor "foregroundDeletion".

Una vez que se pone el estado "deletion in progress", el recolector de basura elimina los subordinados del objeto. Una vez que el recolector de basura ha eliminado todos los subordinados "bloqueantes" (los objetos con ownerReference.blockOwnerDeletion=true), elimina el objeto propietario.

Cabe mencionar que usando "foregroundDeletion", sólo los subordinados con valor en ownerReference.blockOwnerDeletion bloquean la eliminación del objeto propietario. A partir de la versión 1.7, Kubernetes añadió un controlador de admisión que controla el acceso de usuario cuando se intenta poner el campo blockOwnerDeletion a true con base a los permisos de borrado del objeto propietario, de forma que aquellos subordinados no autorizados no puedan retrasar la eliminación del objeto propietario.

Si un controlador (como un Deployment o un ReplicaSet) establece el valor del campo ownerReferences de un objeto, se pone blockOwnerDeletion automáticamente y no se necesita modificar de forma manual este campo.

Borrado en cascada en segundo plano

En el borrado en cascada en segundo plano, Kubernetes elimina el objeto propietario inmediatamente y es el recolector de basura quien se encarga de eliminar los subordinados en segundo plano.

Configurar la regla de borrado en cascada

Para controlar la regla de borrado en cascada, configura el campo propagationPolicy del parámetro deleteOptions cuando elimines un objeto. Los valores posibles incluyen "Orphan", "Foreground", o "Background".

Antes de la versión 1.9 de Kubernetes, la regla predeterminada del recolector de basura para la mayoría de controladores era orphan. Esto incluía al ReplicationController, ReplicaSet, StatefulSet, DaemonSet, y al Deployment. Para los tipos dentro de las versiones de grupo extensions/v1beta1, apps/v1beta1, y apps/v1beta2, a menos que se indique de otra manera, los objetos subordinados se quedan huérfanos por defecto. En Kubernetes 1.9, para todos los tipos de la versión de grupo apps/v1, los objetos subordinados se eliminan por defecto.

Aquí se muestra un ejemplo que elimina los subordinados en segundo plano:

kubectl proxy --port=8080
curl -X DELETE localhost:8080/apis/apps/v1/namespaces/default/replicasets/my-repset \
-d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Background"}' \
-H "Content-Type: application/json"

Aquí se muestra un ejemplo que elimina los subordinados en primer plano:

kubectl proxy --port=8080
curl -X DELETE localhost:8080/apis/apps/v1/namespaces/default/replicasets/my-repset \
-d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Foreground"}' \
-H "Content-Type: application/json"

Aquí se muestra un ejemplo de subordinados huérfanos:

kubectl proxy --port=8080
curl -X DELETE localhost:8080/apis/apps/v1/namespaces/default/replicasets/my-repset \
-d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Orphan"}' \
-H "Content-Type: application/json"

kubectl también permite el borrado en cascada. Para eliminar los subordinados automáticamente, utiliza el parámetro --cascade a true. Usa false para subordinados huérfanos. Por defecto, el valor de --cascade es true.

Aquí se muestra un ejemplo de huérfanos de subordinados de un ReplicaSet:

kubectl delete replicaset my-repset --cascade=false

Nota adicional sobre los Deployments

Antes de la versión 1.7, cuando se usaba el borrado en cascada con Deployments se debía usar propagationPolicy: Foreground para eliminar no sólo los ReplicaSets creados, sino también sus Pods correspondientes. Si este tipo de propagationPolicy no se usa, solo se elimina los ReplicaSets, y los Pods se quedan huérfanos. Ver kubeadm/#149 para más información.

Problemas conocidos

Seguimiento en #26120

Siguientes pasos

Documento de Diseño 1

Documento de Diseño 2

4.2.7 - Controlador TTL para Recursos Finalizados

FEATURE STATE: Kubernetes v1.12 [alpha]

El controlador TTL proporciona un mecanismo TTL para limitar el tiempo de vida de los objetos de recurso que ya han terminado su ejecución. El controlador TTL sólo se ocupa de los Jobs por el momento, y puede que se extienda para gestionar otros recursos que terminen su ejecución, como los Pods y los recursos personalizados.

Descargo de responsabilidad Alpha: esta característica está actualmente en versión alpha, y puede habilitarse mediante el feature gate TTLAfterFinished.

Controlador TTL

El controlador TTL sólo soporta los Jobs por ahora. Un operador del clúster puede usar esta funcionalidad para limpiar los Jobs terminados (bien Complete o Failed) automáticamente especificando el valor del campo .spec.ttlSecondsAfterFinished del Job, como en este ejemplo. El controlador TTL asumirá que un recurso es candidato a ser limpiado TTL segundos después de que el recurso haya terminado; dicho de otra forma, cuando el TTL haya expirado. Cuando el controlador TTL limpia un recursos, lo elimina en cascada, esto es, borra sus objetos subordinados juntos. Nótese que cuando se elimina un recurso, se respetan las garantías de su ciclo de vida, como con los finalizadores.

Los segundos TTL pueden ser configurados en cualquier momento. Aquí se muestran algunos ejemplos para poner valores al campo .spec.ttlSecondsAfterFinished de un Job:

  • Indicando este campo en el manifiesto de los recursos, de forma que se pueda limpiar un Job automáticamente un tiempo después de que haya finalizado.
  • Haciendo que el campo de los recursos existentes, ya finalizados, adopte esta nueva característica.
  • Usando un mutating admission webhook para poner el valor de este campo dinámicamente en el momento de la creación del recursos. Los administradores del clúster pueden usar este enfoque para forzar una regla TTL para los recursos terminados.
  • Usando un mutating admission webhook para poner el valor de este campo dinámicamente después de que el recurso haya terminado, y eligiendo diferentes valores TTL basados en los estados de los recursos, etiquetas, etc.

Advertencia

Actualizar los segundos TTL

Cabe señalar que el período TTL , ej. campo .spec.ttlSecondsAfterFinished de los Jobs, puede modificarse después de que el recurso haya sido creado o terminado. Sin embargo, una vez que el Job se convierte en candidato para ser eliminado (cuando el TTL ha expirado), el sistema no garantiza que se mantendrán los Jobs, incluso si una modificación para extender el TTL devuelve una respuesta API satisfactoria.

Diferencia horaria

Como el controlador TTL usa marcas de fecha/hora almacenadas en los recursos de Kubernetes para determinar si el TTL ha expirado o no, esta funcionalidad es sensible a las diferencias horarias del clúster, lo que puede provocar que el controlador TTL limpie recursos en momentos equivocados.

En Kubernetes, se necesita ejecutar NTP en todos los nodos (ver #6159) para evitar este problema. Los relojes no siempre son correctos, pero la diferencia debería ser muy pequeña. Ten presente este riesgo cuando pongas un valor distinto de cero para el TTL.

Siguientes pasos

Limpiar Jobs automáticamente

Documento de diseño

4.2.8 - Jobs - Ejecución hasta el final

Un Job crea uno o más Pods y se asegura de que un número específico de ellos termina de forma satisfactoria. Conforme los pods terminan satisfactoriamente, el Job realiza el seguimiento de las ejecuciones satisfactorias. Cuando se alcanza un número específico de ejecuciones satisfactorias, la tarea (esto es, el Job) se completa. Al eliminar un Job se eliminan los Pods que haya creado.

Un caso simple de uso es crear un objeto Job para que se ejecute un Pod de manera fiable hasta el final. El objeto Job arrancará un nuevo Pod si el primer Pod falla o se elimina (por ejemplo como consecuencia de un fallo de hardware o un reinicio en un nodo).

También se puede usar un Job para ejecutar múltiples Pods en paralelo.

Ejecutar un Job de ejemplo

Aquí se muestra un ejemplo de configuración de Job. Este ejemplo calcula los primeros 2000 decimales de π y los imprime por pantalla. Tarda unos 10s en completarse.

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4

Puedes ejecutar el ejemplo con este comando:

kubectl apply -f https://k8s.io/examples/controllers/job.yaml
job "pi" created

Comprueba el estado del Job con kubectl:

kubectl describe jobs/pi
Name:             pi
Namespace:        default
Selector:         controller-uid=b1db589a-2c8d-11e6-b324-0209dc45a495
Labels:           controller-uid=b1db589a-2c8d-11e6-b324-0209dc45a495
                  job-name=pi
Annotations:      <none>
Parallelism:      1
Completions:      1
Start Time:       Tue, 07 Jun 2016 10:56:16 +0200
Pods Statuses:    0 Running / 1 Succeeded / 0 Failed
Pod Template:
  Labels:       controller-uid=b1db589a-2c8d-11e6-b324-0209dc45a495
                job-name=pi
  Containers:
   pi:
    Image:      perl
    Port:
    Command:
      perl
      -Mbignum=bpi
      -wle
      print bpi(2000)
    Environment:        <none>
    Mounts:             <none>
  Volumes:              <none>
Events:
  FirstSeen    LastSeen    Count    From            SubobjectPath    Type        Reason            Message
  ---------    --------    -----    ----            -------------    --------    ------            -------
  1m           1m          1        {job-controller }                Normal      SuccessfulCreate  Created pod: pi-dtn4q

Para ver los Pods de un Job que se han completado, usa kubectl get pods.

Para listar todos los Pods que pertenecen a un Job de forma que sea legible, puedes usar un comando como:

pods=$(kubectl get pods --selector=job-name=pi --output=jsonpath='{.items[*].metadata.name}')
echo $pods
pi-aiw0a

En este caso, el selector es el mismo que el selector del Job. La opción --output=jsonpath indica un expresión que simplemente obtiene el nombre de cada Pod en la lista devuelta.

Mira la salida estándar de uno de los Pods:

$ kubectl logs $pods
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000816470600161452491921732172147723501414419735685481613611573525521334757418494684385233239073941433345477624168625189835694855620992192221842725502542568876717904946016534668049886272327917860857843838279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863067442786220391949450471237137869609563643719172874677646575739624138908658326459958133904780275901

Escribir una especificación de Job

Como con el resto de configuraciones de Kubernetes, un Job necesita los campos apiVersion, kind, y metadata.

Un Job también necesita la sección .spec.

Plantilla Pod

El campo .spec.template es el único campo obligatorio de .spec.

El campo .spec.template es una plantilla Pod. Tiene exactamente el mismo esquema que un pod, excepto por el hecho de que está anidado y no tiene el campo apiVersion o kind.

Además de los campos olbigatorios de un Pod, una plantilla Pod de un Job debe indicar las etiquetas apropiadas (ver selector de pod) y una regla de reinicio apropiada.

Sólo se permite los valores Never o OnFailure para RestartPolicy.

Selector de Pod

El campo .spec.selector es opcional. En la práctica mayoría de los casos no deberías configurarlo. Mira la sección sobre configurar tu propio selector de pod.

Jobs en paralelo

Hay tres tipos principales de tarea aptos para ejecutarse como un Job:

  1. Jobs no paralelos
  • normalmente, sólo se arranca un Pod, a menos que el Pod falle.
  • el Job se completa tan pronto como su Pod termine de forma satisfactoria.
  1. Jobs en paralelo con un cupo fijo de terminación:
  • se configura un valor positivo distinto de cero para el campo .spec.completions.
  • el Job representa la tarea en general, y se completa cuando hay una ejecución satisfactoria de un Pod por cada valor dentro del rango de 1 a .spec.completions.
  • no implementado todavía: A cada Pod se le pasa un índice diferenente dentro del rango de 1 a .spec.completions.
  1. Jobs en paralelo con una cola de trabajo:
  • no se especifica el campo .spec.completions, por defecto .spec.parallelism.
  • los Pods deben coordinarse entre ellos mismos o a través de un servicio externo que determine quién debe trabajar en qué. Por ejemplo, un Pod podría ir a buscar un lote de hasta N ítems de una cola de trabajo.
  • cada Pod es capaz de forma independiente de determinar si sus compañeros han terminado o no, y como consecuencia el Job entero ha terminado.
  • cuando cualquier Pod del Job termina con éxito, no se crean nuevos Pods.
  • una vez que al menos uno de los Pods ha terminado con éxito y todos los Pods han terminado, entonces el Job termina con éxito.
  • una vez que cualquier Pod ha terminado con éxito, ningún otro Pod debería continuar trabajando en la misma tarea o escribiendo ningún resultado. Todos ellos deberían estar en proceso de terminarse.

En un Job no paralelo, no debes indicar el valor de .spec.completions ni .spec.parallelism. Cuando ambos se dejan sin valor, ambos se predeterminan a 1.

En un Job con cupo fijo de terminación, deberías poner el valor de .spec.completions al número de terminaciones que se necesiten. Puedes dar un valor a .spec.parallelism, o dejarlo sin valor, en cuyo caso se predetermina a 1.

En un Job con cola de trabajo, no debes indicar el valor de .spec.completions, y poner el valor de .spec.parallelism a un entero no negativo.

Para más información acerca de cómo usar los distintos tipos de Job, ver la sección de patrones de job.

Controlar el paralelismo

El paralelismo solicitado (.spec.parallelism) puede usar cualquier valor no negativo. Si no se indica, se predeterminad a 1. Si se indica como 0, entonces el Job se pausa de forma efectiva hasta que se incremente.

El paralelismo actual (número de pods ejecutándose en cada momento) puede que sea mayor o menor que el solicitado, por los siguientes motivos:

  • Para los Jobs con cupo fijo de terminaciones, el número actual de pods ejecutándose en paralelo no excede el número de terminaciones pendientes. Los valores superiores de .spec.parallelism se ignoran.
  • Para los Jobs con cola de trabajo, no se arranca nuevos Pods después de que cualquier Pod se haya completado -- sin embargo, se permite que se completen los Pods pendientes.
  • Cuando el controlador no ha tenido tiempo para reaccionar.
  • Cuando el controlador no pudo crear los Pods por el motivo que fuera (falta de ResourceQuota, falta de permisos, etc.), entonces puede que haya menos pods que los solicitados.
  • El controlador puede que regule la creación de nuevos Pods debido al excesivo número de fallos anteriores en el mismo Job.
  • Cuando un Pod se para de forma controlada, lleva tiempo pararlo.

Gestionar Fallos de Pod y Contenedor

Un contenedor de un Pod puede fallar por cualquier motivo, como porque el proceso que se estaba ejecutando termina con un código de salida distinto de cero, o porque se mató el contenedor por exceder un límite de memoria, etc. Si esto ocurre, y se tiene .spec.template.spec.restartPolicy = "OnFailure", entonces el Pod permance en el nodo, pero el contenedor se vuelve a ejecutar. Por lo tanto, tu aplicación debe poder gestionar el caso en que se reinicia de forma local, o bien especificar .spec.template.spec.restartPolicy = "Never". Ver el ciclo de vida de un pod para más información sobre restartPolicy.

Un Pod entero puede también fallar por cualquier motivo, como cuando se expulsa al Pod del nodo (porque el nodo se actualiza, reinicia, elimina, etc.), o si un contenedor del Pod falla cuando .spec.template.spec.restartPolicy = "Never". Cuando un Pod falla, entonces el controlador del Job arranca un nuevo Pod. Esto quiere decir que tu aplicación debe ser capaz de gestionar el caso en que se reinicia en un nuevo pod. En particular, debe ser capaz de gestionar los ficheros temporales, los bloqueos, los resultados incompletos, y cualquier otra dependencia de ejecuciones previas.

Nótese que incluso si se configura .spec.parallelism = 1 y .spec.completions = 1 y .spec.template.spec.restartPolicy = "Never", el mismo programa puede arrancarse dos veces.

Si se especifica .spec.parallelism y .spec.completions con valores mayores que 1, entonces puede que haya múltiples pods ejecutándose a la vez. Por ello, tus pods deben tolerar la concurrencia.

Regla de retroceso de Pod por fallo

Hay situaciones en que quieres que el Job falle después de intentar ejecutarlo unas cuantas veces debido a un error lógico en la configuración, etc. Para hacerlo, pon el valor de .spec.backoffLimit al número de reintentos que quieres antes de considerar el Job como fallido. El límite de retroceso se predetermina a 6. Los Pods fallidos asociados al Job son recreados por el controlador del Job con un retroceso exponencial (10s, 20s, 40s ...) limitado a seis minutos. El contador de retroceso se resetea si no aparecen Pods fallidos antes del siguiente chequeo de estado del Job.

Terminación y Limpieza de un Job

Cuando un Job se completa, ya no se crea ningún Pod, pero tampoco se elimina los Pods. Guardarlos permite ver todavía los logs de los pods acabados para comprobar errores, avisos, o cualquier otro resultado de diagnóstico. El objeto job también se conserva una vez que se ha completado para que se pueda ver su estado. Es decisión del usuario si elimina los viejos jobs después de comprobar su estado. Eliminar el job con el comando kubectl (ej. kubectl delete jobs/pi o kubectl delete -f ./job.yaml). Cuando eliminas un job usando el comando kubectl, todos los pods que creó se eliminan también.

Por defecto, un Job se ejecutará de forma ininterrumpida a menos que uno de los Pods falle, en cuyo caso el Job se fija en el valor de .spec.backoffLimit descrito arriba. Otra forma de acabar un Job es poniéndole un vencimiento activo. Haz esto poniendo el valor del campo .spec.activeDeadlineSeconds del Job a un número de segundos.

El campo activeDeadlineSeconds se aplica a la duración del job, independientemente de cuántos Pods se hayan creado. Una vez que el Job alcanza activeDeadlineSeconds, se terminan todos sus Pods y el estado del Job se pone como type: Failed con reason: DeadlineExceeded.

Fíjate que el campo .spec.activeDeadlineSeconds de un Job tiene precedencia sobre el campo .spec.backoffLimit. Por lo tanto, un Job que está reintentando uno o más Pods fallidos no desplegará nuevos Pods una vez que alcance el límite de tiempo especificado por activeDeadlineSeconds, incluso si todavía no se ha alcanzado el backoffLimit.

Ejemplo:

apiVersion: batch/v1
kind: Job
metadata:
  name: pi-with-timeout
spec:
  backoffLimit: 5
  activeDeadlineSeconds: 100
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never

Fíjate que tanto la especificación del Job como la especificación de la plantilla Pod dentro del Job tienen un campo activeDeadlineSeconds. Asegúrate que pones el valor de este campo de forma adecuada.

Limpiar los Jobs terminados automáticamente

Normalmente, los Jobs que han terminado ya no se necesitan en el sistema. Conservarlos sólo añade más presión al servidor API. Si dichos Jobs no se gestionan de forma directa por un controlador de más alto nivel, como los CronJobs, los Jobs pueden limpiarse por medio de CronJobs en base a la regla de limpieza basada en capacidad que se haya especificado.

Mecanismo TTL para Jobs terminados

FEATURE STATE: Kubernetes v1.12 [alpha]

Otra forma de limpiar los Jobs terminados (bien Complete o Failed) de forma automática es usando un mecanismo TTL proporcionado por un controlador TTL de recursos finalizados, indicando el valor .spec.ttlSecondsAfterFinished del Job.

Cuando el controlador TTL limpia el Job, lo eliminará en cascada, esto es, eliminará sus objetos subordinados, como Pods, junto con el Job. Nótese que cuando se elimina el Job, sus garantías de ciclo de vida, como los finalizadores, se tendrán en cuenta.

Por ejemplo:

apiVersion: batch/v1
kind: Job
metadata:
  name: pi-with-ttl
spec:
  ttlSecondsAfterFinished: 100
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never

Aquí el Job pi-with-ttl será candidato a ser automáticamente eliminado, 100 segundos después de que termine.

Si el campo se pone a 0, el Job será candidato a ser automáticamente eliminado inmediatamente después de haber terminado. Si no se pone valor al campo, este Job no será eliminado por el controlador TTL una vez concluya.

Nótese que este mecanismo TTL está todavía en alpha, a través de la característica denominada TTLAfterFinished. Para más información, ver la documentación del controlador TTL para recursos terminados.

Patrones de Job

El objeto Job puede usarse para dar soporte a la ejecución fiable de Pods en paralelo. El objeto Job no se diseñó para dar soporte a procesos paralelos estrechamente comunicados, como los que comúnmente se encuentran en la computación científica. Eso sí, permite el proceso paralelo de un conjunto de ítems de trabajo independientes, pero relacionados entre sí. Estos pueden ser correos a enviar, marcos a renderizar, archivos a codificar, rangos de claves en una base de datos NoSQL a escanear, y demás.

En un sistema complejo, puede haber múltiples diferentes conjuntos de ítems de trabajo. Aquí sólo se está considerando un conjunto de ítems de trabajo que el usuario quiere gestionar de forma conjunta — un proceso por lotes.

Hay varios patrones diferentes para computación en paralelo, cada uno con sus fortalezas y sus debilidades. Los sacrificios a tener en cuenta son:

  • Un objeto Job para cada ítem de trabajo vs. un objeto Job simple para todos los ítems de trabajo. El último es mejor para grandes números de ítems de trabajo. El primero añade sobrecarga para el usuario y para el sistema al tener que gestionar grandes números de objetos Job.
  • El número de pods creados es igual al número de ítems de trabajo vs. cada Pod puede procesar múltiplese ítems de trabajo. El primero típicamente requiere menos modificaciones al código existente y a los contenedores. El último es mejor cuanto mayor sea el número de ítems de trabajo, por las mismas razones que antes..
  • Varios enfoques usan una cola de trabajo. Ello requiere ejecutar un servicio de colas, y modificaciones a las aplicaciones o contenedores existentes para que hagan uso de la cola de trabajo. Otras estrategias son más fáciles de adaptar a una aplicación ya usando contenedores.

Los sacrificios a tener en cuenta se indican a continuación, donde las columnas 2 a 4 representan los sacrificios de arriba. Los nombres de los patrones son también enlaces a ejemplos e información más detallada.

Patrón Objeto Job simple ¿Menos pods que ítems de trabajo? ¿No modificar la aplicación? ¿Funciona en Kube 1.1?
Extensión de la Plantilla Job
Cola con Pod por Ítem de Trabajo a veces
Cola con Cuenta Variable de Pods
Job simple con Asignación Estática de Trabajo

Cuando se especifican terminaciones con .spec.completions, cada Pod creado por el controlado del Job tiene un specidéntico. Esto significa que todos los pods de una tarea tendrán la misma línea de comandos y la misma imagne, los mismo volúmenes, y (casi) las mismas variables de entorno. Estos patrones otorgan diferentes formas de organizar los pods para que trabajen en cosas distintas.

Esta tabla muestra la configuración necesaria para .spec.parallelism y .spec.completions para cada uno de los patrones. Aquí, T es el número de ítems de trabajo.

Patrón .spec.completions .spec.parallelism
Extensión de la Plantilla Job 1 debería ser 1
Cola con Pod por Ítem de Trabajo T cualquiera
Cola con Cuenta Variable de Pods 1 cualquiera
Job simple con Asignación Estática de Trabajo T cualquiera

Uso Avanzado

Especificar tu propio selector de pod

Normalmente, cuando creas un objeto Job, no especificas el campo .spec.selector. La lógica por defecto del sistema añade este campo cuando se crea el Job. Se elige un valor de selector que no se entremezcle con otras tareas.

Sin embargo, en algunos casos, puede que necesites sobreescribir este selector que se configura de forma automática. Para ello, puedes indicar el valor de .spec.selector en el Job.

Pero ten mucho cuidado cuando lo hagas. Si configuras un selector de etiquta que no es único para los pods de ese Job, y que selecciona Pods que no tienen que ver, entonces estos últimos pueden ser eliminados, o este Job puede contar los otros Pods para terminarse, o uno o ambos Jobs pueden negarse a crear Pods o ejecutarse hasta el final. Si se elige un selector que no es único, entonces otros controladores (ej. ReplicationController) y sus Pods puede comportarse de forma impredecibles también. Kubernetes no te impide cometer un error especificando el .spec.selector.

Aquí se muestra un ejemplo de un caso en que puede que necesites usar esta característica.

Digamos que el Job viejo todavía está ejeuctándose. Quieres que los Pods existentes sigan corriendo, pero quieres que el resto de los Pods que se creen usen una plantilla pod diferente y que el Job tenga un nombre nuevo. Como no puedes modificar el Job porque esos campos no son modificables, eliminas el Job old, pero dejas sus pods ejecutándose mediante el comando kubectl delete jobs/old --cascade=false. Antes de eliminarlo, apúntate el selector actual que está usando:

kind: Job
metadata:
  name: viejo
  ...
spec:
  selector:
    matchLabels:
      job-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
  ...

Entonces, creas un nuevo Job con el nombre nuevo y le configuras explícitamente el mismo selector. Puesto que los Pods existentes tienen la etiqueta job-uid=a8f3d00d-c6d2-11e5-9f87-42010af00002, son controlados por el Job nuevo igualmente.

Necesitas configurar manualSelector: true en el nuevo Job, ya qye no estás usando el selector que normalmente se genera de forma automática por el sistema.

kind: Job
metadata:
  name: nuevo
  ...
spec:
  manualSelector: true
  selector:
    matchLabels:
      job-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
  ...

El mismo Job nuevo tendrá un uid distinto a a8f3d00d-c6d2-11e5-9f87-42010af00002. Poniendo manualSelector: true le dice al sistema que sabes lo que estás haciendo y que te permita hacer este desajuste.

Alternativas

Pods simples

Cuando el nodo donde un Pod simple se estaba ejecutando se reinicia o falla, dicho pod se termina y no será reinicado. Sin embargo, un Job creará nuevos Pods para sustituir a los que se han terminando. Por esta razón, se recomienda que se use un Job en vez de un Pod simple, incluso si tu aplicación sólo necesita un único Pod.

Replication Controller

Los Jobs son complementarios a los Replication Controllers. Un Replication Controller gestiona aquellos Pods que se espera que no terminen (ej. servidores web), y un Job gestiona aquellos Pods que se espera que terminen (ej. tareas por lotes).

Como se discutió en el Ciclo de vida de un Pod, un Job sólo es apropiado para aquellos pods con RestartPolicy igual a OnFailure o Never. (Nota: Si RestartPolicy no se pone, el valor predeterminado es Always.)

Job simple arranca que arranca un controlador de Pod

Otro patrón es aquel donde un Job simple crea un Pod que, a su vez, crea otros Pods, actuando como una especie de controlador personalizado para esos Pods. Esto da la máxima flexibilidad, pero puede que cueste un poco más de entender y ofrece menos integración con Kubernetes.

Un ejemplo de este patrón sería un Job que arranca un Pod que ejecuta una secuencia de comandos que, a su vez, arranca un controlador maestro de Spark (ver el ejemplo de spark), ejecuta un manejador de spark, y a continuación lo limpia todo.

Una ventaja de este enfoque es que el proceso general obtiene la garantía del objeto Job, además del control completo de los Pods que se crean y cómo se les asigna trabajo.

Cron Jobs

Puedes utilizar un CronJob para crear un Job que se ejecute en una hora/fecha determinadas, de forma similar a la herramienta cron de Unix.

4.2.9 - CronJob

Un Cron Job ejecuta tareas, Jobs, a intervalos regulares.

Un objeto CronJob es como una línea de un archivo crontab (tabla cron). Ejecuta un trabajo de forma periódica según un horario programado escrito en formato Cron.

Para instrucciones sobre cómo crear y trabajar con trabajos programados, incluyendo definiciones de ejemplo, puedes consultar Ejecutar tareas automatizadas con trabajos programados.

Limitaciones de las tareas programados

Un trabajo programado crea un objeto job como mínimo una vez por cada ejecución de su programación. Decimos "como mínimo" porque hay determinadas circunstancias bajo las cuales dos trabajos pueden crearse, o ninguno de ellos se crea. Se intenta que estos casos sean residuales, pero no pueden evitarse completamente. Por lo tanto, los trabajos deberían ser idempotentes, es decir, que se pueden ejecutar más de una vez con el mismo resultado.

Si el valor de startingDeadlineSeconds se establece a un valor grande o se deja sin especificar (por defecto) y si el valor de concurrencyPolicy se establece a Allow, los trabajos siempre se ejecutarán por lo menos una vez.

Para cada CronJob, el controlador de CronJob verifica cuántas programaciones se han perdido desde la última programación hasta el momento actual. Si hay más de 100 programaciones perdidas, entonces ya no vuelve a ejecutar el trabajo y registra el error:

Cannot determine if job needs to be started. Too many missed start time (> 100). Set or decrease .spec.startingDeadlineSeconds or check clock skew.

Es importante destacar que si el campo startingDeadlineSeconds está configurado, es decir, no es nulo (nil), el controlador cuenta cuántos trabajos perdidos se produjeron desde el valor de startingDeadlineSeconds hasta el momento actual, en vez de la última programación. Por ejemplo, si startingDeadlineSeconds es 200, el controlador cuenta cuántos trabajos perdidos se produjeron en los últimos 200 segundos.

Se cuenta un CronJob como perdido si no se ha podido crear a la hora programada. Por ejemplo, si establecemos el valor de concurrencyPolicy a Forbid y se intentó programar un CronJob cuando otro previamente programado estaba todavía ejecutándose, entonces contará como perdido.

Por ejemplo, imagina que un CronJob se configura para programar un nuevo Job cada minuto a partir de las 08:30:00, y su campo startingDeadlineSeconds no se configura. Si el controlador del CronJob no estuviera disponible de 08:29:00 a 10:21:00, el trabajo no comenzaría porque el número de trabajos perdidos que se habría perdido en su programación sería superior a 100.

Para ilustrar este concepto mejor, vamos a suponer que programamos un CronJob para que ejecute un nuevo Job cada minuto comenzando a las 08:30:00, y establecemos el valor del campo startingDeadlineSeconds a 200 segundos. Si el controlador del CronJob no se encuentra disponible durante el mismo período que en el ejemplo anterior (08:29:00 a 10:21:00,) aún así el Job comenzará a las 10:22:00. Esto ocurre porque el controlador en este caso comprueba cuántas programaciones perdidas ha habido en los últimos 200 segundos (esto es, 3 programaciones que no se han ejecutado), en vez de comprobarlo a partir de la última programación hasta el momento actual.

El CronJob es únicamente responsable de crear los Jobs que coinciden con su programación, y el Job por otro lado es el responsable de gestionar los Pods que representa.

5 - Servicios, balanceo de carga y redes

5.1 - Service

Un Service, servicio en castellano, es el objeto de la API de Kubernetes que describe cómo se accede a las aplicaciones, tal como un conjunto de Pods, y que puede describir puertos y balanceadores de carga.

Con Kubernetes no necesitas modificar tu aplicación para que utilice un mecanismo de descubrimiento de servicios desconocido. Kubernetes le otorga a sus Pods su propia dirección IP y un nombre DNS para un conjunto de Pods, y puede balancear la carga entre ellos.

Motivación

Los Pods de Kubernetes son creados y destruidos para coincidir con el estado de tu clúster. Los Pods son recursos no permanentes. Si utilizas un Deployment para correr tu aplicación, puede crear y destruir los Pods dinámicamente.

Cada Pod obtiene su propia dirección IP, sin embargo, en un Deployment, el conjunto de Pods corriendo en un momento dado puede ser diferente al conjunto de Pods corriendo esa aplicación un momento después.

Esto conlleva un problema: si un conjunto de Pods (llamémoslos "backends") provee funcionalidad a otros Pods (llamémoslos "frontends") dentro de tu clúster, ¿de qué manera los frontends encuentran y tienen seguimiento de cuál dirección IP conectarse, para que el frontend pueda usar la parte del backend de la carga de trabajo?

Entran los Services.

Recursos Service

En Kubernetes, un Service es una abstracción que define un conjunto lógico de Pods y una política por la cual acceder a ellos (algunas veces este patrón es llamado micro-servicio). El conjunto de Pods a los que apunta un Servicio se determina usualmente por un Selector. Para aprender más sobre otras maneras de definir Endpoints para un Service, mira Services sin selectores.

Por ejemplo, considera un backend sin estado para procesar imágenes que está corriendo con 3 réplicas. Estas réplicas son fungibles; a los frontends no les importa cuál backend usar. Mientras que los Pods actuales que componen el backend pueden cambiar, los clientes del frontend no deberían estar al tanto de ello, ni deberían llevar un seguimiento del conjunto de backends en sí mismos.

La abstracción del Service habilita este desacoplamiento.

Descubrimiento de servicios nativos en la nube

Si eres capaz de usar la API de Kubernetes para descubrir servicios en tu aplicación, puedes hacer una búsqueda en el servidor API para los Endpoints, que se actualizan cuando cambian el conjunto de Pods en el servicio.

Para aplicaciones no nativas, Kubernetes ofrece una manera de colocar un puerto de red o un balanceador de carga entre tu aplicación y los Pods del backend.

Definiendo un Service

Un Service en Kubernetes es un objeto REST, similar a un Pod. Como todos los objetos REST, puedes hacer un Post a una definición de un Service al servidor API para crear una nueva instancia. EL nombre de un objeto Service debe ser un nombre RFC 1035 válido.

Por ejemplo, supongamos que tienes un conjunto de Pods en el que cada uno escucha el puerto TCP 9376 y contiene la etiqueta app.kubernetes.io/name=MyApp:

apiVersion: v1
kind: Service
metadata:
  name: mi-servicio
spec:
  selector:
    app.kubernetes.io/name: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376

Esta especificación crea un nuevo objeto Service llamado "mi-servicio", que apunta via TCP al puerto 9376 de cualquier Pod con la etiqueta app.kubernetes.io/name=MyApp.

Kubernetes asigna una dirección IP a este Service (Algunas veces llamado "Cluster IP"), la cual es usada por los proxies de los Services (mira IPs Virtuales y proxies de servicios abajo).

El controlador para el selector del Service escanea continuamente a los Pods que coincidan con este selector, y luego hace un Post de cualquier actualización a un objeto Endpoint llamado también "mi-servicio".

Las definiciones de puerto en los Pods tienen nombres, y puedes hacer referencia a estos nombres en el atributo targetPort del Service. Esto funciona incluso si existe una mezcla de Pods en el Service usando un único nombre configurado, con el mismo protocolo de red disponible via diferentes números de puerto. Esto ofrece mucha flexibilidad para desplegar y evolucionar tus Services. Por ejemplo, puedes cambiar los números de puertos que los Pods exponen en la siguiente versión de tu software backend, sin romper los clientes.

El protocolo por defecto para los Services is TCP; también puedes usar cualquier otro protocolo soportado.

Como muchos Services necesitan exponer más de un puerto, Kubernetes soporta múltiples definiciones de puertos en un único objeto Service. Cada definición de un puerto puede tener el mismo protocolo, o uno diferente

Services sin selectores

Los Services comúnmente abstraen el acceso a los Pods de Kubernetes, pero también pueden abstraer otros tipos de backends.

Por ejemplo:

  • Quieres tener un clúster de base de datos externo en producción, pero en el entorno de pruebas quieres usar tus propias bases de datos.
  • Quieres apuntar tu Service a un Service en un Namespace o en un clúster diferente.
  • Estás migrando tu carga de trabajo a Kubernetes. Mientras evalúas la aproximación, corres solo una porción de tus backends en Kubernetes.

En cualquiera de estos escenarios puedes definir un Service sin un selector de Pod.

Por ejemplo:

apiVersion: v1
kind: Service
metadata:
  name: mi-servicio
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376

Debido a que este Service no tiene un selector, el objeto Endpoints no se crea de forma automática. Puedes mapear manualmente el Service a la dirección de red y puerto donde está corriendo, añadiendo el objeto Endpoints manualmente:

apiVersion: v1
kind: Endpoints
metadata:
  name: mi-servicio
subsets:
  - addresses:
      - ip: 192.0.2.42
    ports:
      - port: 9376

El nombre del objeto Endpoints debe ser un nombre de subdominio DNS válido.

Acceder a un Service sin un selector funciona de la misma manera que si tuviese un selector. En el ejemplo de abajo, el tráfico se dirige al único Endpoint definido en el YAML: 192.0.2.42:9376 (TCP).

Un Service ExternalName es un caso especial de Service que no tiene selectores y usa nombres DNS en su lugar. Para más información, mira la sección ExternalName en este documento.

Endpoints de sobrecapacidad

Si un recurso Endpoint tiene más de 1000 endpoints entonces un clúster de Kubernetes v1.22 (o posterior) anota los Endpoints con endpoints.kubernetes.io/over-capacity: truncated. Esta anotación indica que el objeto Endpoints afectado está por encima de la capacidad y que el controlador de endpoints ha truncado el número de endpoints a 1000.

EndpointSlices

FEATURE STATE: Kubernetes v1.21 [stable]

Los EndpointSlices son un recurso de la API que pueden proveer una alternativa más escalable a los Endpoints. Aunque conceptualmente es muy similar a los Endpoints, los EndpointSlices permiten distribuir los endpoints de red a través de múltiples recursos. Por defecto, un EndpointSlice se considera "full" una vez que alcanza 100 endpoints, punto en el cual un EndpointSlice se creará para almacenar cualquier endpoint adicional.

Los EndpointSlices proveen atributos adicionales y funcionalidad que se describe en detalle en EndpointSlices.

Protocolo de aplicación

FEATURE STATE: Kubernetes v1.20 [stable]

El campo appProtocol provee una manera de especificar un protocolo de aplicación para cada puerto de un Service. El valor de este campo es reflejado por el Endpoint correspondiente y los objetos EndpointSlices.

Este campo sigue una sintaxis estándar de etiquetas de Kubernetes. Los valores deberían ser nombres de servicio IANA estándar o nombres de dominio con prefijos tales como mycompany.com/my-custom-protocol.

IPS Virtuales proxies de servicio

Cada nodo en un clúster de Kubernetes ejecuta un kube-proxy. El kube-proxyes el responsable de implementar una forma de IP virtual para los Services de un tipo distinto al de ExternalName.

Por qué no usar DNS round-robin?

Una pregunta que surge algunas veces es por qué Kubernetes depende de proxies para redirigir el tráfico de entrada a los backends. ¿Qué hay de otros enfoques? Por ejemplo, ¿sería posible configurar registros DNS que tengan múltiples valores A (o AAA para IPv6), y depender en la resolución de nombres round-robin?

Existen algunas razones para usar proxies en los Services:

  • Hay una larga historia de implementaciones DNS que no respetan los registros TTLs, y cachean los resultados de la búsqueda de nombres luego de que deberían haber expirado.
  • Algunas aplicaciones realizan la búsqueda de DNS solo una vez y almacenan en caché los resultados indefinidamente.
  • Incluso si las aplicaciones y las librerías hicieran una resolución apropiada, los TTLs bajos o nulos en los registros DNS podrían imponer una carga alta en los DNS que luego se volvería difícil de manejar.

Más adelante en esta página puedes leer acerca del trabajo de varias implementaciones de kube-proxy. En general, deberías notar que, cuando ejecutas kube-proxy, los niveles de reglas del kernel podrían modificarse (por ejemplo, podrían crearse reglas iptables), que luego no son limpiados, en algunos casos hasta que reinicias. Por tanto, ejecutar kube-proxy es algo que solo debería hacer un administrador que entienda las consecuencias de tener un servicio de bajo nivel privilegiado de proxy de red en un computador. Aunque el ejecutable de kube-proxy soporta una función de cleanup, esta función no es una característica oficial y solo está disponible para usarse como está.

Configuración

Ten en cuenta que el kube-proxy inicia en diferentes modos, los cuales están determinados por su configuración.

  • La configuración del kube-proxy se hace via un ConfigMap, y el ConfigMap para el kube-proxy remplaza efectivamente el comportamiento de casi todas las banderas para el kube-proxy.
  • La configuración del ConfigMap no soporta la recarga en vivo de la configuración.
  • Los parámetros del ConfigMap para el kube-proxy no se pueden validar y verificar en el arranque. Por ejemplo, si tu sistema operativo no permite ejecutar comandos iptables, el kube-proxy del kernel estándar no funcionará. De igual forma, si tienes un sistema operativo que no soporta netsh, no se ejecutará en modo userspace en Windows.

Modo proxy userspace

En este modo, el kube-proxy observa la adición y eliminación de objetos Endpoint Service del plano de control de Kubernetes. Para cada Service se abre un puerto (elegido al azar) en el nodo local. Cualquier conexión a este "puerto proxy" es dirigido a uno de los Pods backend del Servicio (como se reporta via Endpoints). El kube-proxy toma el valor sessionAffinity del Service en cuenta cuando decide cuál Pod del backend utilizar.

Finalmente, el proxy del userspace instala reglas de iptables que capturan el tráfico al clusterIP (que es virtual) del servicio y el port. Las reglas redirigen el tráfico al puerto proxy que redirige al Pod del backend.

Por defecto, el kube-proxy en modo userspace elige un backend con un algoritmo round-robin.

Diagrama de descripción general de los Services para el proxy userspace

Modo proxy iptables

En este modo, el kube-proxy observa la adición y eliminación de objetos Endpoint Service del panel de control de Kubernetes. Para Service, instala reglas iptables, las cuales capturan el tráfico al clusterIP y el port del Service, y redirige este tráfico a uno de los conjuntos del backend. Para cada objeto Endpoint, instala reglas de iptables que seleccionan un Pod del backend.

Por defecto, el kube-proxy en modo iptables elige un backend al azar.

Usar iptables para manejar tráfico tiene una sobrecarga más baja del sistema, porque el tráfico es manejado por el netfilter de Linux sin la necesidad de cambiar entre userspace y el espacio del kernel. Esta aproximación puede llegar a ser más confiable.

Si el kube-proxy está corriendo en modo iptables y el primer Pod seleccionado no responde, la conexión falla. Esto es diferente del modo userspace: en ese escenario, el kube-proxy detectaría que la conexión al primer Pod ha fallado e intentaría automáticamente con otro Pod del backend.

Puedes usar readiness probes para verificar que los Pods del backend están funcionando correctamente, para que kube-proxy en modo iptables solo vea los backends que han sido comprobados como sanos. Hacer esto significa que evitas enviar tráfico via kube-proxy a un Pod que se sabe que ha fallado.

Diagrama de descripción general de los Services para el proxy iptables

Modo Proxy IPVS

FEATURE STATE: Kubernetes v1.11 [stable]

En el modo ipvs, el kube-proxy observa los Services de Kubernetes y los Endpoints, llama la interfaz netlink para crear reglas IPVS respectivamente y sincroniza las reglas IPVS con los Services de Kubernetes y los Endpoints periódicamente. Este ciclo de control asegura que los estados del IPVS coincidan con el estado deseado.

Cuando accede a un Service, IPVS dirige el tráfico a uno de estos Pods del backend.

El modo proxy IPVS está basado en la función de enlace netfilter que es similar al modo iptables, pero usa una tabla hash como estructura de datos subyacente y opera en el espacio del kernel.

Esto significa que el kube-proxy en modo IPVS redirige el tráfico como menor latencia que el kube-proxy en modo iptables, con mejor desempeño cuando sincroniza las reglas proxy. Comparado con otros modos proxy, el modo IPVS también soporta un rendimiento más alto de tráfico de red.

IPVS provee más opciones para balancear el tráfico a los Pods del backend; estas son:

  • rr: round-robin
  • lc: menor conexión (el número más pequeño de conexiones abiertas)
  • dh: hash de destino
  • sh: hash de origen
  • sed: retraso esperado más corto
  • nq: nunca hacer cola

Diagrama de descripción general de los Services para el proxy IPVS

En estos modelos de proxy, el tráfico enlazado para la IP:Port del Service es redirigido al backend apropiado sin que el cliente sepa nada de Kubernetes, Services o Pods.

Si quieres asegurarte que las conexiones desde un cliente en particular se pasen al mismo Pod cada vez, puedes seleccionar la afinidad de sesión basada en la dirección IP del cliente al establecer service.spec.sessionAffinity a "ClientIP" (por defecto es "None").

Puedes establecer también el número máximo de tiempo al establecer service.spec.sessionAffinityConfig.clientIP.timeoutSeconds apropiadamente. (El valor por defecto es 10800, que resulta ser unas 3 horas).

Services multi puerto

Para algunos servicios, necesitas exponer más de un puerto. Kubernetes te permite configurar múltiples definiciones puertos en un objeto Service. Cuando usas múltiples puertos para un Service, debes nombrar todos tus puertos para que no sean ambiguos. Por ejemplo:

apiVersion: v1
kind: Service
metadata:
  name: mi-servicio
spec:
  selector:
    app: MiApp
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 9376
    - name: https
      protocol: TCP
      port: 443
      targetPort: 9377

Eligiendo tu propia dirección IP

Puedes especificar tu propia dirección IP para el clúster como parte de la petición de creación de un Service. Para hacer esto, establece el campo .spec.clusterIP. Por ejemplo, si ya tienes una entrada DNS existente que quieres reutilizar, o sistemas legacy que están configurados para direcciones IP específicas que son difíciles de reconfigurar.

La dirección IP que elijas debe ser una dirección IPV4 o IPV6 válida dentro del rango CIDR service-cluster-ip-range que está configurado para el servidor API. Si intentas crear un Service con una dirección clusterIP inválida, el servidor API devolverá un código de estado 422 para indicar que hay un problema.

Políticas de tráfico

Política de tráfico externa

Puedes establecer el campo spec.externalTrafficPolicy para controlar cómo se enruta el tráfico de fuentes externas. Los valores válidos son Clustery Local. Establece el campo a Cluster para enrutar tráfico externo a todos los endpoints listos y Local para enrutar solamente a los endpoints locales del nodo. Si la política de tráfico es Local y no hay endpoints de nodos locales, kube-proxy no redirige ningún tráfico al Service relevante.

Política de tráfico interna

FEATURE STATE: Kubernetes v1.22 [beta]
Puedes establecer el campo spec.internalTrafficPolicy para controlar como se enruta el tráfico desde las fuentes internas. Los valores válidos son Cluster y Local. Establece el campo a Cluster para enrutar el tráfico interno a todos los endpoints listos y Local para enrutar solo los endpoints locales del nodo. Si la política de tráfico es Local y no hay endpoints locales de nodo, el tráfico es terminado por el kube-proxy.

Descubriendo servicios

Kubernetes soporta 2 modos primarios para encontrar un Service - variables de entorno y DNS

Variables de entorno

Cuando un Pod está corriendo en un Node, kubelet añade un conjunto de variables de entorno para cada Service activo. Soporta tanto variables Docker links compatible como variables más sencillas {SVCNAME}_SERVICE_HOST and {SVCNAME}_SERVICE_PORT, donde el nombre del Service está en mayúsculas y los guiones medios se convierten en guiones bajos.

Por ejemplo, el Service redis-master que expone el puerto TCP 6739 y se le ha asignado una dirección IP de clúster 10.0.0.11, produce las siguientes variables de entorno:

REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11

DNS

Puedes (y casi siempre deberías) configurar un servicio DNS para tu clúster de Kubernetes usando un add-on.

Un servidor DNS consciente del clúster, como CoreDNS, observa la API de Kubernetes por nuevos Services y crea un conjunto de registros DNS para cada uno. Si DNS ha sido habilitado a través de tu clúster entonces todos los Pods automáticamente serán capaces de resolver los Services por su nombre DNS.

Por ejemplo, si tienes un Service llamado mi-servicio en un namespace mi-ns, el plano de control y el Service DNS crean un registro DNS para mi-servicio.mi-ns conjuntamente. Los Pods en el namespace mi-ns deberían ser capaces de encontrar el Service haciendo una búsqueda de nombre por mi-servicio (mi-servicio.mi-ns también funcionaría)

Los Pods en otros namespaces deben calificar el nombre como my-service.my-ns. Estos nombres resolverán la clúster IP asignada para el Service.

Kubernetes también soporta registros DNS SRV (Service) para los puertos nombrados. Si el Service mi-servicio.mi-ns tiene un puerto llamado http con el protocolo fijado a TCP, puedes hacer una consulta DNS SRV a _http._tcp.mi-servicio.mi-ns para descubrir el número de puerto para http así como la dirección IP.

El servidor DNS de Kubernetes es la única manera de acceder a los Services ExternalName. Puedes encontrar más información sobre la resolución ExternalName en Pods y Services DNS.

Servicios Headless

Algunas veces no necesitas balancear cargas y una IP única. En este caso, puedes crear lo que llamamos Services "headless", especificando "None" para el clúster IP (.spec.clusterIP).

Puedes usar un Service headless para hacer una interfaz con otros mecanismos de descubrimiento de servicios, sin estar atado a la implementación de Kubernetes.

Para los Services headless, no se asigna una clúster IP, kube-proxy no maneja estos Services, y no hay balanceo de cargas o redirección por la plataforma para ellos. Cómo se configura el DNS automáticamente depende de si el Service tiene selectores definidos:

Con selectores

Para los Services headless que definen selectores, el controlador de endpoints crea registros Endpoints en la API, y modifica la configuración DNS para devolver registros A (direcciones IP) que apuntan directamente a los Pods que respaldan el Service.

Sin selectores

Para Services headless que no definen selectores, el controlador de endpoints no crea registros Endpoints. Sin embargo, el sistema DNS busca y configura:

  • Registros CNAME para Services del tipo ExternalName.
  • Registros A para cualquier Endpoints que comparten un nombre con el Service, para todos los otros tipos.

Publicar Services (ServiceTypes)

En algunas partes de tu aplicación (por ejemplo, frontends) puede que necesites exponer un Service a una dirección IP externa, que está fuera de tu clúster local

Los ServiceTypes de Kubernetes permiten especificar qué tipo de Service quieres. El valor por defecto es ClusterIP

Los valores Type y sus comportamientos son:

  • ClusterIP: Expone el Service en una dirección IP interna del clúster. Al escoger este valor el Service solo es alcanzable desde el clúster. Este es el ServiceType por defecto.

  • NodePort: Expone el Service en cada IP del nodo en un puerto estático (el NodePort). Automáticamente se crea un Service ClusterIP, al cual enruta el NodePortdel Service. Podrás alcanzar el Service NodePort desde fuera del clúster, haciendo una petición a <NodeIP>:<NodePort>.

  • LoadBalancer: Expone el Service externamente usando el balanceador de carga del proveedor de la nube. Son creados automáticamente Services NodePorty ClusterIP, a los cuales el apuntará el balanceador externo.

  • ExternalName: Mapea el Service al contenido del campo externalName (ej. foo.bar.example.com), al devolver un registro CNAME con su valor. No se configura ningún tipo de proxy.

También puedes usar un Ingress para exponer tu Service. Ingress no es un tipo de Service, pero actúa como el punto de entrada de tu clúster. Te permite consolidar tus reglas de enrutamiento en un único recurso, ya que puede exponer múltiples servicios bajo la misma dirección IP.

Tipo NodePort

Si estableces el campo type a NodePort, el plano de control de Kubernetes asigna un puerto desde un rango especificado por la bandera --service-node-port-range (por defecto: 30000-32767). Cada nodo es un proxy de ese puerto (el mismo número de puerto en cada nodo) hacia tu Service. Tu Service reporta al puerto asignado en el campo .spec.ports[*].nodePort

Si quieres especificar una(s) IP(s) particular(es) para hacer proxy del puerto, puedes establecer la bandera --nodeport-addresses para el kube-proxy o el campo equivalente nodePortAddresses del fichero de configuración de kube-proxy para ese bloque particular de IP(s).

Esta bandera recibe un listado de bloques de IP separados por coma (ej. 10.0.0.0/8, 192.0.2.0/25) para especificar rangos de direcciones IP que el kube-proxy debería considerar como local para este nodo.

Por ejemplo, si arrancas el kube-proxy con la bandera --nodeport-addresses=127.0.0.0/8, el kube-proxy solo selecciona la interfaz loopback para los Services NodePort. El valor por defecto es --nodeport-addresses es una lista vacía. Esto significa que el kube-proxy considera todas las interfaces de red disponibles para el NodePort. (Esto es compatible también con versiones más antiguas de Kubernetes).

Si quieres un número de puerto específico, puedes especificar un valor en el campo nodePort. El plano de control te asignará ese puerto o reportará que la transacción API ha fallado. Esto significa que necesitas prestar atención a posibles colisiones de puerto por tu cuenta. También tienes que usar un número de puerto válido, uno que esté dentro del rango configurado para uso del NodePort.

Usar un NodePort te da libertad para configurar tu propia solución de balanceo de cargas, para configurar entornos que no soportan Kubernetes del todo, o para exponer uno o más IPs del nodo directamente.

Ten en cuenta que este Service es visible como <NodeIP>:spec.ports[*].nodePort y .spec.clusterIP:spec.ports[*].port. Si la bandera --nodeport-addresses está configurada para el kube-proxy o para el campo equivalente en el fichero de configuración, <NodeIP> sería IP filtrada del nodo. Si

Por ejemplo:

apiVersion: v1
kind: Service
metadata:
  name: mi-servicio
spec:
  type: NodePort
  selector:
    app: MiApp
  ports:
    # Por defecto y por comodidad, el `TargetPort` tiene el mismo valor que el campo `port.
    - port: 80
      targetPort: 80
      # Campo opcional
      # Por defecto y por comodidad, el plano de control de Kubernetes asignará el puerto desde un rango (por defecto: 30000-32767)
      nodePort: 30007

Tipo LoadBalancer

En proveedores de la nube que soportan balanceadores de carga externos, establecer el campo type a LoadBalancer aprovisiona un balanceador de carga para tu Service. La creación del balanceador de carga ocurre de forma asíncrona, y la información sobre el balanceador de carga provisto se publica en el campo .status.loadBalancer del Service.

Por ejemplo:

apiVersion: v1
kind: Service
metadata:
  name: mi-servicio
spec:
  selector:
    app: MiApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
  clusterIP: 10.0.171.239
  type: LoadBalancer
status:
  loadBalancer:
    ingress:
      - ip: 192.0.2.127

El tráfico desde el balanceador de carga externo es dirigido a los Pods del backend. El proveedor de la nube decide cómo balancear la carga.

Algunos proveedores de la nube te permiten especificar la IP loadBalancerIP. En esos caso, el balanceador de carga es creado con la loadBalancerIP especificada por el usuario. Si el campo loadBalancerIP no se especifica, el balanceador de carga se configura con una dirección IP efímera. Si especificas una loadBalancerIP pero tu proveedor de la nube no soporta esta característica, se ignora el campo loadBalancerIP que has configurado.

Balanceadores de carga con tipos de protocolo mixtos

FEATURE STATE: Kubernetes v1.20 [alpha]
Por defecto, para los tipos de Service LoadBalancer, cuando hay más de un puerto definido, todos los puertos deben tener el mismo protocolo, y el protocolo debe estar soportado por el proveedor de la nube.

Si la feature gate MixedProtocolLBService está habilitada para el kube-apiserver se permiten usar diferentes protocolos cuando hay más de un puerto definido.

Deshabilitar la asignación NodePort del balanceador de carga

FEATURE STATE: Kubernetes v1.20 [alpha]

A partir de v1.20, puedes deshabilitar opcionalmente la asignación del puerto del nodo para un Service de tipo LoadBalancer estableciendo el campo spec.allocateLoadBalancerNodePorts a false. Esto debería ser usado solo para implementaciones de balanceadores de carga que enrutan el tráfico directamente a los Pods al contrario de usar puertos del nodo. Por defecto, spec.allocateLoadBalancerNodePorts es true y los Services de tipo LoadBalancer continuarán asignando puertos. Si spec.allocateLoadBalancerNodePorts es false en un Service existente con puertos asignado, esos puertos del nodo no serán desasignados automáticamente. Debes quitar explícitamente la entrada nodePortsen cada puerto del Service para desasignar esos puertos del nodo. Debes habilitar la feature gate ServiceLBNodePortControl para usar este campo.

Especificar la clase de implementación del balanceador de carga

FEATURE STATE: Kubernetes v1.22 [beta]

spec.loadBalancerClass te permite usar una implementación del balanceador de carga distinta que la que viene por defecto para el proveedor de la nube. Esta característica está disponible desde v1.21, debes habilitar la feature gate ServiceLoadBalancerClass para usar este campo en v1.21, y la feature gate está habilitada por defecto desde v1.22 en adelante.

Por defecto, spec.loadBalancerClass es nil y un tipo de Service LoadBalancer usa la implementación por defecto del proveedor de la nube si el clúster está configurado con un proveedor de nube usando la bandera de componente --cloud-provider.

Si spec.loadBalancerClass está especificado, se asume que una implementación de un balanceador de carga que coincida con la clase especificada está observando los Services. Cualquier implementación por defecto del balanceador de carga (por ejemplo, la que es provista por el proveedor de la nube) ignorará los Services que tienen este campo establecido. spec.loadBalancerClass se puede establecer en cualquier Service de tipo LoadBalancer únicamente. Una vez hecho, no se puede cambiar. El valor de spec.loadBalancerClass debe ser un identificador de etiqueta, con un prefijo opcional como "internal-vip" o "example.com/internal-vip". Los nombres sin prefijo están reservados para usuarios finales.

Balanceador de carga interno

En un entorno mixto algunas veces es necesario enrutar el tráfico desde Services dentro del mismo bloque (virtual) de direcciones de red.

En un entorno de split-horizon DNS necesitarías dos Services para ser capaz de enrutar tanto el tráfico externo como el interno a tus Endpoints.

Para establecer un balanceador de carga interno, agrega una de las siguientes anotaciones a tu Service dependiendo del proveedor de Service en la nube que estás usando.

Selecciona una de las pestañas.

[...]
metadata:
    name: my-service
    annotations:
        networking.gke.io/load-balancer-type: "Internal"
[...]

[...]
metadata:
    name: my-service
    annotations:
        service.beta.kubernetes.io/aws-load-balancer-internal: "true"
[...]

[...]
metadata:
    name: my-service
    annotations:
        service.beta.kubernetes.io/azure-load-balancer-internal: "true"
[...]

[...]
metadata:
    name: my-service
    annotations:
        service.kubernetes.io/ibm-load-balancer-cloud-provider-ip-type: "private"
[...]

[...]
metadata:
    name: my-service
    annotations:
        service.beta.kubernetes.io/openstack-internal-load-balancer: "true"
[...]

[...]
metadata:
    name: my-service
    annotations:
        service.beta.kubernetes.io/cce-load-balancer-internal-vpc: "true"
[...]

[...]
metadata:
  annotations:
    service.kubernetes.io/qcloud-loadbalancer-internal-subnetid: subnet-xxxxx
[...]

[...]
metadata:
  annotations:
    service.beta.kubernetes.io/alibaba-cloud-loadbalancer-address-type: "intranet"
[...]

Soporte para TLS en AWS

Para soporte parcial de TLS/SSL en clústeres corriendo en AWS, puedes agregar tres anotaciones al servicio LoadBalancer:

metadata:
  name: mi-servicio
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012

El primero especifica el ARN del certificado a usar. Este puede ser un certificado de un emisor de un tercero que fue subido en IAM o uno creado dentro del Administrador de Certificados de AWS.

metadata:
  name: mi-servicio
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-backend-protocol: (https|http|ssl|tcp)

La segunda anotación especifica cuál protocolo habla el Pod. Para HTTPS y SSL, el ELB espera que el Pod se autentique a sí mismo sobre una conexión encriptada, usando un certificado.

HTTP y HTTPS seleccionan un proxy de capa 7: el ELB termina la conexión con el usuario, interpreta los encabezados, e inyecta el encabezado X-Forwared-For con la dirección IP del usuario (los Pods solo ven la dirección IP del ELB del otro lado de su conexión) cuando reenvía las peticiones.

TCP y SSL seleccionan un proxy de capa 4: el ELB reenvía el tráfico sin modificar los encabezados.

En un entorno mixto donde algunos puertos están asegurados y otros se dejan sin encriptar, puedes usar una de las siguientes anotaciones:

metadata:
  name: mi-servicio
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http
    service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443,8443"

En el ejemplo de arriba, si el Service contenía tres puertos, 80, 443 y 8443 entonces 443 y 8443 usarían el certificado SSL, pero 80sería HTTP proxy.

A partir de Kubernetes v1.9 en adelante puedes usar políticas predefinidas de AWS SSL con listeners HTTPS o SSL para tus Services. Para ver cuáles políticas están disponibles para usar, puedes usar la herramienta de línea de comandos aws:

aws elb describe-load-balancer-policies --query 'PolicyDescriptions[].PolicyName'

Puedes especificar cualquiera de estas políticas usando la anotación "service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy", por ejemplo:

metadata:
  name: mi-servicio
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy: "ELBSecurityPolicy-TLS-1-2-2017-01"

Soporte de Protocolo PROXY en AWS

Para habilitar el soporte para el protocolo PROXY en clústeres corriendo en AWS, puedes usar la siguiente anotación para el servicio:

metadata:
  name: mi-servicio
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"

A partir de la versión 1.3.0, el uso de esta anotación aplica para todos los puertos proxy del ELB y no puede ser configurado de otra manera.

Acceso a los logs ELB en AWS

Existen algunas anotaciones para administrar el acceso a los logs para Services ELB en AWS.

La anotación service.beta.kubernetes.io/aws-load-balancer-access-log-enabled controla si el acceso a los logs están habilitados.

La anotación service.beta.kubernetes.io/aws-load-balancer-access-log-emit-interval controla el intervalo en minutos para publicar los logs de acceso. Puedes especificar un intervalo de 5 0 60 minutos.

La anotación service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-name controla el nombre del bucket de Amazon S3 donde se almacenan los logs del balanceador de carga.

La anotación service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-prefix especifica la jerarquía lógica que has creado para tu bucket de Amazon S3.

metadata:
  name: mi-servicio
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-access-log-enabled: "true"
    # Especifica si está habilitado el acceso a los logs
    service.beta.kubernetes.io/aws-load-balancer-access-log-emit-interval: "60"
    # EL intervalo para publicar los logs de acceso. Puedes especificar un intervalo de 5 o 60 (minutos)
    service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-name: "my-bucket"
    # El nombre del bucket S· de Amazon donde se almacenan los logs de acceso
    service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-prefix: "my-bucket-prefix/prod"
    # La jerarquía lógica que has creado para tu bucket S3 de Amazon, por ejemplo `my-bucket-prefix/prod`

Drenaje de conexión en AWS

Drenaje de conexión para ELBs clásicos se puede administrar con la anotación service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled fijada al valor "true". La anotación service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout puede ser usada también para establecer el tiempo máximo, en segundos, para mantener las conexiones existentes antes de dar de baja las instancias.

metadata:
  name: mi-servicio
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled: "true"
    service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout: "60"

Otras anotaciones ELB

Existen otras anotaciones para administrar Classic Elastic Load Balancers que se describen abajo.

metadata:
  name: mi-servicio
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: "60"
    # El tiempo, en segundos, que se permite a una conexión estar en reposo (no se han enviado datos sobre la conexión) antes que sea cerrada por el balanceador de carga

    service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
    # Especifica si el balanceo de cargas entre zonas está habilitado para el balanceador de carga

    service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags: "environment=prod,owner=devops"
    # Un listado separado por comas de valores de clave-valor que será guardados como etiquetas adicionales en el ELB.

    service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold: ""
    # El número de comprobaciones de estado exitosas sucesivas requeridas para considerar sano para el tráfico a un backend.
    # Por defecto es 2, debe ser entre 2 y 10

    service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold: "3"
    # El número de comprobaciones de estado fallidas requeridas para considerar a un backend no apto para el tráfico.
    # Por defecto es 6, debe ser entre 2 y 10

    service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "20"
    # EL intervalo aproximado, en segundos, entre comprobaciones de estados de una instancia individual.
    # Por defecto es 10, debe ser entre 5 y 300.

    service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: "5"
    # La cantidad de tiempo, en segundos, durante el cual no recibir respuesta significa una comprobación de estado fallida.
    # Este valor debe ser menor que el valor de service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval
    # Por defecto es 5, debe estar entre 2 y 60

    service.beta.kubernetes.io/aws-load-balancer-security-groups: "sg-53fae93f"
    # Un listado de grupos de seguridad existentes para configurar en el ELB creado. A diferencia de la anotación
    # service.beta.kubernetes.io/aws-load-balancer-extra-security-groups, esta reemplaza todos los grupos de seguridad previamente asignados al ELB y también sobreescribe la creación de un grupo de seguridad creado únicamente para este ELB.
    # El primer ID grupo de seguridad en esta lista se utiliza para permitir tráfico de entrada a los nodos workers objetivo (tráfico de servicio y comprobaciones de estados).
    # Si se configuran múltiples ELBs con el mismo grupo de seguridad, solo una única línea de permisos será añadida a los grupos de seguridad del nodo worker, lo que significa que si eliminas cualquiera de esos ELBs removerá la línea de permisos y bloqueará el acceso para todos los ELBs que comparten el mismo ID de seguridad de grupo.
    # Esto puede ocasionar cortes entre servicios si no se usa apropiadamente

    service.beta.kubernetes.io/aws-load-balancer-extra-security-groups: "sg-53fae93f,sg-42efd82e"
    # Un listado adicional de grupos de seguridad para añadir al ELB creado, esto deja un grupo de seguridad creado únicamente, asegurando que cada ELB tiene un ID de grupo de seguridad único que coincide con la línea de permiso para permitir tráfico a los nodos worker objetivo (tráfico de servicio y comprobaciones de estados)
    # Grupos de seguridad definidos se pueden compartir entre servicios.

    service.beta.kubernetes.io/aws-load-balancer-target-node-labels: "ingress-gw,gw-name=public-api"
    # Un listado separado por comas de clave-valor que se utilizan para seleccionar los nodos objetivos para el balanceador de carga

Soporte para Balanceador de Carga de Red (NLB) en AWS

FEATURE STATE: Kubernetes v1.15 [beta]
Para usar un balanceador de carga de Red en AWS, usa la anotación service.beta.kubernetes.io/aws-load-balancer-type con el valor fijado a nlb.

metadata:
  name: mi-servicio
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"

A diferencia de los balanceadores de cargas, el balanceador de carga de red (NLB) reenvía la dirección IP del cliente a través del nodo. Si el campo .spec.externalTrafficPolicy está fijado a clúster, la dirección IP del cliente no es propagada a los Pods finales.

Al fijar .spec.externalTrafficPolicy en Local, la dirección IP del cliente se propaga a los Pods finales, pero esto puede resultar a una distribución de tráfico desigual. Los nodos sin ningún Pod para un Service particular de tipo LoadBalancer fallarán en la comprobación de estado del grupo objetivo del NLB en el puerto .spec.healthCheckNodePort y no recibirán ningún tráfico.

Para conseguir trafico equilibrado, usa un DaemonSet o especifica pod anti-affinity para no localizar en el mismo nodo.

También puedes usar Services NLB con la anotación del balanceador de carga interno

Para permitir que el tráfico del cliente alcance las instancias detrás del NLB, los grupos de seguridad del Nodo se modifican con las siguientes reglas de IP:

Regla Protocolo Puerto(s) Rango de IP(s) Descripción del Rango de IP
Health Check TCP NodePort(s) (.spec.healthCheckNodePort para .spec.externalTrafficPolicy = Local) Subnet CIDR kubernetes.io/rule/nlb/health=<loadBalancerName>
Tráfico del Cliente TCP NodePort(s) .spec.loadBalancerSourceRanges (por defecto en 0.0.0.0/0) kubernetes.io/rule/nlb/client=<loadBalancerName>
MTU Discovery ICMP 3,4 .spec.loadBalancerSourceRanges (por defecto en 0.0.0.0/0) kubernetes.io/rule/nlb/mtu=<loadBalancerName>

Para limitar cuáles IPs del cliente pueden acceder al balanceador de carga de red, especifica loadBalancerSourceRanges.

spec:
  loadBalancerSourceRanges:
    - "143.231.0.0/16"

Otras anotaciones CLS en Tencent Kubernetes Engine (TKE)

Hay otras anotaciones para administrar balanceadores de carga en la nube en TKE como se muestra abajo.

    metadata:
      name: mi-servicio
      annotations:
        # Enlaza Loadbalancers con nodos específicos
        service.kubernetes.io/qcloud-loadbalancer-backends-label: key in (value1, value2)

        # Identificador de un balanceador de carga existente
        service.kubernetes.io/tke-existed-lbid:lb-6swtxxxx

        #Parámetros personalizados para el balanceador de cargas (LB), no soporta la modificación del tipo de LB todavía
        service.kubernetes.io/service.extensiveParameters: ""

        # Parámetros personalizados para el listener LB
        service.kubernetes.io/service.listenerParameters: ""

        # Especifica el tipo de balanceador de carga;
        # valores válidos: clásico (Balanceador de Carga clásico) o aplicación (Balanceador de Carga de aplicación de la nube)
        service.kubernetes.io/loadbalance-type: xxxxx

        # Especifica método de pago el ancho de banda de la red pública;
        # valores válidos: TRAFFIC_POSTPAID_BY_HOUR(bill-by-traffic) y BANDWIDTH_POSTPAID_BY_HOUR (bill-by-bandwidth).
        service.kubernetes.io/qcloud-loadbalancer-internet-charge-type: xxxxxx

        # Especifica el valor del ancho de banda (rango valor: [1,2000] Mbps).
        service.kubernetes.io/qcloud-loadbalancer-internet-max-bandwidth-out: "10"

        # Cuando se fija esta anotación, los balanceadores de carga solo registrarán nodos con Pods corriendo en él, de lo contrario todos los nodos serán registrados.
        service.kubernetes.io/local-svc-only-bind-node-with-pod: true

Tipo ExternalName

Los Services de tipo ExternalName mapean un Service a un nombre DNS, no a un selector típico como mi-servicio o cassandra. Estos Services se especifican con el parámetro spec.externalName.

Esta definición de Service, por ejemplo, mapea el Service mi-Servicio en el namespace prod a my.database.example.com:

apiVersion: v1
kind: Service
metadata:
  name: mi-servicio
  namespace: prod
spec:
  type: ExternalName
  externalName: my.database.example.com

Cuando busca el host mi-servicio.prod.svc.cluster.local, el Service DNS del clúster devuelve un registro CNAME con el valor my.database.example.com. Acceder a mi-servicio funciona de la misma manera que otros Services, pero con la diferencia crucial de que la redirección ocurre a nivel del DNS en lugar reenviarlo o redirigirlo. Si posteriormente decides mover tu base de datos al clúster, puedes iniciar sus Pods, agregar selectores apropiados o endpoints, y cambiar el type del Service.

IPs Externas

Si existen IPs externas que enrutan hacia uno o más nodos del clúster, los Services de Kubernetes pueden ser expuestos en esas externalIPs. El tráfico que ingresa al clúster con la IP externa (como IP de destino), en el puerto del Service, será enrutado a uno de estos endpoints del Service. Las externalIPs no son administradas por Kubernetes y son responsabilidad del administrador del clúster.

En la especificación del Service, las externalIPs se pueden especificar junto con cualquiera de los ServiceTypes. En el ejemplo de abajo, "mi-servicio" puede ser accedido por clientes en "198.51.100.32:80" (externalIP:port)

apiVersion: v1
kind: Service
metadata:
  name: mi-servicio
spec:
  selector:
    app.kubernetes.io/name: MyApp
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 9376
  externalIPs:
    - 80.11.12.10

Limitaciones

Usar el proxy del userspace for VIPs funciona en pequeña y mediana escala, pero no escalará a clústeres muy grandes con miles de Services. El tópico original design proposal for portals tiene más detalles sobre esto.

Usar el proxy del userspace oculta la dirección IP de origen de un paquete que accede al Service. Esto hace que algún tipo de filtrado (firewalling) sea imposible. El modo proxy iptables no oculta IPs de origen en el clúster, pero aún tiene impacto en clientes que vienen desde un balanceador de carga o un node-port.

El campo Type está diseñado como una funcionalidad anidada - cada nivel se agrega al anterior. Esto no es estrictamente requerido en todos los proveedores de la nube (ej. Google Compute Engine no necesita asignar un NodePort para que funcione el LoadBalancer, pero AWS si) pero la API actual lo requiere.

Implementación de IP Virtual

La información previa sería suficiente para muchas personas que quieren usar Services. Sin embargo, ocurren muchas cosas detrás de bastidores que valdría la pena entender.

Evitar colisiones

Una de las principales filosofías de Kubernetes es que no debe estar expuesto a situaciones que podrían hacer que sus acciones fracasen por su propia culpa. Para el diseño del recurso de Service, esto significa no obligarlo a elegir su propio número de puerto si esa elección puede colisionar con la de otra persona. Eso es un fracaso de aislamiento.

Para permitirte elegir un número de puerto en tus Services, debemos asegurarnos que dos Services no puedan colisionar. Kubernetes lo hace asignando a cada Service su propia dirección IP.

Para asegurarse que cada Service recibe una IP única, un asignador interno actualiza atómicamente el mapa global de asignaciones en etcd antes de crear cada Service. El objeto mapa debe existir en el registro para que los Services obtengan asignaciones de dirección IP, de lo contrario las creaciones fallarán con un mensaje indicando que la dirección IP no pudo ser asignada.

En el plano de control, un controlador de trasfondo es responsable de crear el mapa (requerido para soportar la migración desde versiones más antiguas de Kubernetes que usaban bloqueo en memoria). Kubernetes también utiliza controladores para revisar asignaciones inválidas (ej. debido a la intervención de un administrador) y para limpiar las direcciones IP que ya no son usadas por ningún Service.

Direcciones IP del Service

A diferencia de direcciones IP del Pod, que enrutan a un destino fijo, las IPs del Service no son respondidas por ningún host. En lugar de ello, El kube-proxy usa iptables (lógica de procesamiento de paquetes en Linux) para definir direcciones IP virtuales que se redirigen de forma transparente cuando se necesita. Cuando el cliente se conecta con la VIP, su tráfico es transportado automáticamente al endpoint apropiado. Las variables de entorno y DNS para los Services son pobladas en términos de la dirección IP virtual del Service (y el puerto).

Kube-proxy soporta tres modos — userspace, iptables e IPVS — los cuales operan ligeramente diferente cada uno.

Userspace

Por ejemplo, considera la aplicación de procesamiento de imágenes descrita arriba. Cuando el Service del backend es creado, el nodo maestro de Kubernetes asigna una dirección IP virtual, por ejemplo 10.0.0.1. Asumiendo que el puerto del Service es 1234, el Service es observado por todas las instancias del kube-proxy en el clúster. Cuando un proxy mira un nuevo Service, abre un puerto al azar, establece una redirección iptables desde la dirección IP virtual a este nuevo puerto, y comienza a aceptar conexiones a este.

Cuando un cliente se conecta a la dirección IP virtual del Service, la regla de iptables entra en acción, y redirige los paquetes al propio puerto del proxy. El "proxy del Service" elige un backend, y comienza a redirigir el tráfico desde el cliente al backend.

Esto quiere decir que los dueños del Service pueden elegir cualquier puerto que quieran sin riesgo de colisión. Los clientes pueden conectarse a una IP y un puerto, sin estar conscientes de a cuáles Pods están accediendo.

iptables

Nuevamente, considera la aplicación de procesamiento de imágenes descrita arriba. Cuando se crea el Service Backend, el plano de control de Kubernetes asigna una dirección IP virtual, por ejemplo 10.0.0.1. Asumiendo que el puerto del servicio es 1234, el Service es observado por todas las instancias del kube-proxy en el clúster. Cuando un proxy mira un nuevo Service, instala una serie de reglas de iptables que redirigen desde la dirección IP virtual a las reglas del Service. Las reglas del Service enlazan a las reglas del Endpoint que redirigen el tráfico (usando NAT de destino) a los backends.

Cuando un cliente se conecta a la dirección IP virtual del Service la regla de iptables son aplicadas. A diferencia del modo proxy userspace, el kube-proxy no tiene que estar corriendo para que funcione la dirección IP virtual, y los nodos observan el tráfico que viene desde la dirección IP del cliente sin alteraciones.

El mismo flujo básico se ejecuta cuando el tráfico viene a través de un node-port o de un balanceador de carga, aunque en estos casos la IP del cliente es alterada.

IPVS

Las operaciones iptables ralentizan dramáticamente en un clúster a gran escala, ej. 10.000 Services. IPVS está diseñado para balancear cargas y está basado en tablas hash dentro del kernel. De esta manera puedes alcanzar consistencia en el desempeño en un número grande de Services de un kube-proxy basado en IPVS. Mientras tanto, el kube-proxy basado en IPVS tiene algoritmos de balanceo de cargas más sofisticados (least conns, locality, weighted, persistence).

Objeto API

El Service es un recurso de alto nivel en la API REST de Kubernetes. Puedes encontrar más detalles sobre el objeto API en: Objeto API Service API.

Protocolos soportados

TCP

Puedes usar TPC para cualquier tipo de Service, y es el protocolo de red por defecto.

UDP

Puedes usar UDP para la mayoría de los Services. Para Services type=LoadBalancer, el soporte UDP depende del proveedor de la nube que ofrece esta facilidad.

SCTP

FEATURE STATE: Kubernetes v1.20 [stable]
Cuando usas un plugin de red que soporta tráfico SCTP, puedes usar SCTP para la mayoría de los Services. Para Services type=LoadBalancer, el soporte SCTP depende del proveedor de la nube que ofrece esta facilidad. (La mayoría no lo hace)

Advertencias

Soporte para asociaciones SCTP multihomed
Windows
Userspace kube-proxy

HTTP

Si tu proveedor de la nube lo soporta, puedes usar un Service en modo LoadBalancer para configurar un proxy invertido HTTP/HTTPS, redirigido a los Endpoints del Service.

Protocolo PROXY

Si tu proveedor de la nube lo soporta, puedes usar un Service en modo LoadBalancer para configurar un balanceador de carga fuera de Kubernetes mismo, que redirigirá las conexiones prefijadas con protocolo PROXY.

El balanceador de carga enviará una serie inicial de octetos describiendo la conexión entrante, similar a este ejemplo

PROXY TCP4 192.0.2.202 10.0.42.7 12345 7\r\n

Seguido de la data del cliente.

Siguientes pasos

5.2 - Ingress

Permite que sean accesibles los servicios de red HTTP (o HTTPS) usando un mecanismo de configuración consciente del protocolo, que entiende conceptos como URIs, nombres de host, rutas y más. El concepto de Ingress te permite mapear el tráfico a diferentes backend basado en las reglas que defines a través de la API de Kubernetes.

FEATURE STATE: Kubernetes v1.19 [stable]

Un objeto de la API que administra el acceso externo a los servicios en un clúster, típicamente HTTP.

Un Ingress podría proveer balanceo de cargas, terminación SSL y hosting virtual basado en nombres.

Terminología

Para mayor claridad, esta guía define los siguientes términos:

  • Nodo: Una máquina worker en Kubernetes, parte de un clúster.
  • Clúster: Un conjunto de Nodos que ejecutan aplicaciones en contenedores, administrados por Kubernetes. Para este ejemplo, y para los despliegues más comunes de Kubernetes, los nodos en el clúster no son parte del internet público.
  • Enrutador Edge: un enrutador que refuerza la política de seguridad del cortafuegos para tu clúster. Esto podría ser una puerta de entrada administrada por un proveedor de la nube o una pieza física de hardware.
  • Red del clúster: un conjunto de enlaces, lógicos o físicos, que facilitan la comunicación dentro de un clúster de acuerdo con el modelo de redes de Kubernetes.
  • Service: Un Service que identifica un conjunto de Pods que utilizan selectores de label. A menos que se indique de otra manera, los Services se asumen que tienen IPs virtuales que solo se pueden enrutar dentro de la red del clúster.

¿Qué es un Ingress?

Un Ingress expone rutas HTTP y HTTPS desde el exterior del clúster a los servicios dentro del clúster. El control del tráfico es controlado por las reglas definidas en el recurso Ingress.

Aquí tienes un ejemplo simple de un Ingress que envía todo su tráfico a un Service:

ingress-diagram

Figure. Ingress

Un Ingress se puede configurar para otorgar URLs a los Services que son accesibles desde el exterior, para hacer balance de cargas del tráfico, finalizar SSL/TLS y ofrecer alojamiento virtual basado en nombres.

Un controlador de Ingress es responsable de complementar el Ingress, comúnmente con un balanceador de cargas, aunque también puedes configurar tu enrutador edge con frontends adicionales para ayudar a manejar el tráfico.

Un Ingress no expone puertos o protocolos arbitrariamente. Exponer servicios distintos de HTTP o HTTPS al internet usa un servicio de tipo Service.Type=NodePort o Service.Type=LoadBalancer.

Prerrequisitos

Debes tener un controlador de Ingress para satisfacer a un Ingress. Crear únicamente un recurso Ingress no tiene ningún efecto.

Puede que necesites desplegar un controlador Ingress controller tal como el ingress-nginx. Puedes elegir de un número de controladores de Ingress.

Idealmente, todos los controladores de Ingress deberían encajar con la especificación de referencia. En realidad, los distintos controladores de Ingress operan ligeramente diferente.

El recurso Ingress

Un ejemplo mínimo de un recurso Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: minimal-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx-example
  rules:
  - http:
      paths:
      - path: /testpath
        pathType: Prefix
        backend:
          service:
            name: test
            port:
              number: 80

Un Ingress necesita los campos apiVersion, kind, metadata y spec. El nombre del objeto Ingress debe ser un nombre de subdominio DNS válido. Para información general sobre cómo trabajar con archivos de configuración, mira desplegando aplicaciones, configurando contenedores, administrando recursos.

Los Ingress usan anotaciones frecuentemente para configurar algunas opciones dependiendo del controlador de Ingress, un ejemplo de ello es la anotación rewrite-target. Distintos controladores de Ingress soportan anotaciones diferentes. Revisa la documentación para tu elección del controlador de Ingress para saber qué anotaciones son soportadas.

La especificación Ingress tiene toda la información que necesitas para configurar un balanceador de cargas o un servidor proxy. Mucho más importante, contiene un listado de reglas que emparejan contra todas las peticiones entrantes. El recurso Ingress solo soporta reglas para dirigir el tráfico HTTP(S).

Si se omite la ingressClassName, se define una clase Ingress por defecto.

Existen algunos controladores de Ingress, que trabajan sin la definición de una IngressClass por defecto. Por ejemplo, el controlador Ingress-NGINX se puede configurar con una opción --watch-ingress-without-class. Sin embargo, se recomienda especificar el IngressClass por defecto como se muestra abajo.

Reglas del Ingress

Cada regla HTTP contiene la siguiente información:

  • Un host opcional. En este ejemplo, no se define un host así que la regla se aplica a todo el tráfico de entrada HTTP a través de la dirección IP especificada. Cuando se proporciona un host (por ejemplo, foo.bar.com), las reglas se aplican a ese host.
  • Un listado de rutas (por ejemplo, /testpath), cada una de las cuales tiene un backend asociado con un service.name y un service.port.name o un service.port.number. Tanto el host como la ruta deben coincidir con el contenido de una petición de entrada antes que el balanceador de cargas dirija el tráfico al Service referenciado.
  • Un backend es una combinación de un Service y un puerto como se describe en la documentación del Service o un recurso personalizado backend a través de un CRD. Las peticiones HTTP (y HTTPS) al Ingress que coinciden con el host y la ruta de la regla se envían al backend del listado.

Un defaultBackend se configura frecuentemente en un controlador de Ingress para dar servicio a cualquier petición que no coincide con una ruta en la especificación.

DefaultBackend

Un Ingress sin reglas envía todo el tráfico a un único backend y .spec.defaultBackend está en el backend que debería manejar las peticiones en ese caso. El defaultBackend es una opción de configuración convencional del controlador Ingress y no se especifica en tus recursos del Ingress. Si no se especifican reglas .spec.rules, se debe especificar .spec.defaultBackend. Si no se establece un defaultBackend, las peticiones que no coincidan con ninguna de las reglas las decidirá el controlador ingress (consulta la documentación de tu controlador de ingress para saber cómo maneja este caso).

Si ninguno de los hosts o rutas coincide con la petición HTTP en los objetos Ingress, el tráfico será enrutado a tu backend predeterminado.

Resource backends

Un Resource backend es una referencia de objeto (ObjectRef en inglés) a otro recurso de Kubernetes dentro del mismo espacio de nombres que el objeto Ingress. Este Resource es mutuamente exclusivo con el Service, y la validación fallará si ambos se especifican. Un uso común para un Resource backend es para ingresar datos a un backend de almacenamiento de datos con activos estáticos.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-resource-backend
spec:
  defaultBackend:
    resource:
      apiGroup: k8s.example.com
      kind: StorageBucket
      name: static-assets
  rules:
    - http:
        paths:
          - path: /icons
            pathType: ImplementationSpecific
            backend:
              resource:
                apiGroup: k8s.example.com
                kind: StorageBucket
                name: icon-assets

Luego de crear el Ingress mencionado arriba, puedes verlo con el siguiente comando:

kubectl describe ingress ingress-resource-backend
Name:             ingress-resource-backend
Namespace:        default
Address:
Default backend:  APIGroup: k8s.example.com, Kind: StorageBucket, Name: static-assets
Rules:
  Host        Path  Backends
  ----        ----  --------
  *
              /icons   APIGroup: k8s.example.com, Kind: StorageBucket, Name: icon-assets
Annotations:  <none>
Events:       <none>

Tipos de ruta

Se requiere que cada ruta de un Ingress tenga un tipo de ruta correspondiente. Las Rutas que no incluyen un pathType explícito fallarán la validación. Hay 3 tipos de rutas soportadas:

  • ImplementationSpecific: Con este tipo de ruta, la coincidencia depende de la IngressClass. Las implementaciones pueden tratar esto como un pathType separado o tratarlas de forma idéntica a los tipos de ruta Prefix o Exact.

  • Exact: Coincide la ruta de la URL exactamente con sensibilidad a mayúsculas y minúsculas.

  • Prefix: Coincide basado en el prefijo de una ruta URL dividida por /. La coincidencia es sensible a mayúsculas y minúsculas, y hecha en un elemento de la ruta por elemento. Un elemento de la ruta refiere a la lista de etiquetas en la ruta dividida por el separador /. Una petición es una coincidencia para la ruta p si cada p es un elemento prefijo de p de la ruta requerida.

Ejemplos

Tipo Ruta(s) Ruta de la(s) peticion(es) ¿Coincide?
Prefijo / (todas las rutas)
Exacto /foo /foo Si
Exacto /foo /bar No
Exacto /foo /foo/ No
Exacto /foo/ /foo No
Prefijo /foo /foo, /foo/ Si
Prefijo /foo/ /foo, /foo/ Si
Prefijo /aaa/bb /aaa/bbb No
Prefijo /aaa/bbb /aaa/bbb Si
Prefijo /aaa/bbb/ /aaa/bbb Si, ignora la barra diagonal
Prefijo /aaa/bbb /aaa/bbb/ Si, coincide con barra diagonal
Prefijo /aaa/bbb /aaa/bbb/ccc Si, coincide con la subruta
Prefijo /aaa/bbb /aaa/bbbxyz No, no coincide con el prefijo de cadena
Prefijo /, /aaa /aaa/ccc Si, coincide con el prefijo /aaa
Prefijo /, /aaa, /aaa/bbb /aaa/bbb Si, coincide con el prefijo /aaa/bbb
Prefijo /, /aaa, /aaa/bbb /ccc Si, coincide con el prefijo/
Prefijo /aaa /ccc No, usa el backend predeterminado
Mezclado /foo (Prefijo), /foo (Exacto) /foo Si, prefiere la coincidencia exacta

Múltiples coincidencias

En algunos casos, muchas rutas dentro de un Ingress coincidirán con una petición. En esos casos, la precedencia se le dará al primero con la ruta más larga que coincide. Si dos rutas todavía coinciden por igual, se le dará precedencia a las rutas con una coincidencia de ruta exacta sobre las rutas que contienen prefijos.

Comodines Hostname

Los hosts pueden ser coincidencias exactas (por ejemplo “foo.bar.com”) o un comodín (por ejemplo “*.foo.com”). Las coincidencias precisas requieren que el encabezado host coincida con el campo host. Las coincidencias de comodín requieren que el encabezado host sea igual al sufijo de la regla del comodín.

Host Encabezado Host ¿Coincidencia?
*.foo.com bar.foo.com Coincide basado en el sufijo común
*.foo.com baz.bar.foo.com No coincide, el comodín solo cubre una etiqueta DNS
*.foo.com foo.com No coincide, el comodín solo cubre una etiqueta DNS
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-wildcard-host
spec:
  rules:
  - host: "foo.bar.com"
    http:
      paths:
      - pathType: Prefix
        path: "/bar"
        backend:
          service:
            name: service1
            port:
              number: 80
  - host: "*.foo.com"
    http:
      paths:
      - pathType: Prefix
        path: "/foo"
        backend:
          service:
            name: service2
            port:
              number: 80

La Clase Ingress

Los Ingress pueden ser implementados por distintos controladores, comúnmente con una configuración distinta. Cada Ingress debería especificar una clase, una referencia a un recurso IngressClass que contiene información adicional incluyendo el nombre del controlador que debería implementar la clase.

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: external-lb
spec:
  controller: example.com/ingress-controller
  parameters:
    apiGroup: k8s.example.com
    kind: IngressParameters
    name: external-lb

El campo .spec.parameters de un IngressClass te permite hacer referencia a otro recurso que proporciona la configuración relacionada con esa IngressClass.

El tipo específico de parámetros a usar depende del controlador de Ingress que especificas en el campo spec.controller de la IngressClass.

Alcance de IngressClass

Dependiendo de tu controlador de ingress, podrías ser capaz de usar parámetros que se establecen en todo el clúster, o solamente para un namespace.

El alcance por defecto de los parámetros IngressClass es para todo el clúster.

Si estableces el campo spec.parameters y no estableces el campo spec.parameters.scope, entonces el IngressClass se refiere al recurso cuyo alcance es todo el clúster. El atributo kind (en combinación con el apiGroup) de los parámetros se refiere a la API con alcance a todo el clúster (posiblemente un recurso personalizado), y el name de los parámetros identifica al recurso del clúster específico para esa API.

Por ejemplo:

---
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: external-lb-1
spec:
  controller: example.com/ingress-controller
  parameters:
    # Los parámetros para este IngressClass se especifican en un
    # ClusterIngressParameter (API group k8s.example.net) llamado
    # "external-config-1". Esta definición le indica a Kubernetes 
    # de buscar por un parámetro de recurso con alcance a todo el clúster.
    scope: Cluster
    apiGroup: k8s.example.net
    kind: ClusterIngressParameter
    name: external-config-1

FEATURE STATE: Kubernetes v1.23 [stable]

Si estableces el campo spec.parameters y el spec.parameters.scope al Namespace, entonces el IngressClass se refiere al recurso cuyo alcance es el namespace. También debes establecer el campo namespace dentro de spec.parameters con el Namespace que contiene los parámetros que quieres usar.

El atributo kind (en combinación con apiGroup) de los parámetros se refiere a la API restringida por un Namespace (por ejemplo: ConfigMap), y el name de los parámetros identifica al recurso específico en el namespace que has especificado en namespace.

Los parámetros con alcance al Namespace ayudan al operador del clúster a delegar el control sobre la configuración (por ejemplo, ajustes del balanceador de cargas, definición de una API gateway) que se usa para una carga de trabajo. Si utilizas un parámetro con alcance al Namespace entonces:

  • El equipo operador del clúster necesita aprobar los cambios de un equipo distinto cada vez que se aplica un nuevo cambio a la configuración.
  • O el equipo operador del clúster debe definir específicamente estos controles de acceso, tales como asociaciones de roles RBAC y mapeos, que permitan a la aplicación hacer cambios al recurso de parámetros con alcance al clúster.

La API de la IngressClass por sí misma siempre tiene alcance al clúster.

Aquí hay un ejemplo de una IngressClass que hace referencia a parámetros que están restringidos por un Namespace:

---
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: external-lb-2
spec:
  controller: example.com/ingress-controller
  parameters:
    # Los parámetros para esta IngressClass se especifican en un 
    # IngressParameter (API group k8s.example.com) llamado "external-config",
    # que está en el namespace "external-configuration".
    scope: Namespace
    apiGroup: k8s.example.com
    kind: IngressParameter
    namespace: external-configuration
    name: external-config

Anotación deprecada

Antes que el recurso IngressClass y el campo ingressClassName se añadieran a Kubernetes 1.18, las clases Ingress se especificaban con una anotación kubernetes.io/ingress.class en el Ingress. Esta anotación nunca se definió formalmente, pero era ampliamente soportada por los controladores de Ingress.

El nuevo campo ingressClassName en los recursos Ingress es un reemplazo para esa anotación, pero no es un equivalente directo. Mientras que la anotación se utilizaba generalmente para hacer referencia al nombre del controlador de Ingress que debería implementar el Ingress, el campo es una referencia a un recurso IngressClass que contiene configuración adicional del Ingress, incluyendo el nombre del controlador Ingress.

IngressClass por defecto

Puedes marcar un ingressClass en particular por defecto para tu clúster. Establecer la anotación ingressclass.kubernetes.io/is-default-class a true en un recurso IngressClass asegurará que los nuevos Ingress sin un campo ingressClassName especificado sean asignados a esta IngressClass por defecto.

Existen algunos controladores de ingress, que funcionan sin una definición de una ingressClass. Por ejemplo, el controlador Ingress-NGINX se puede configurar con una opción --watch-ingress-without-class. Sin embargo, se recomienda especificar el IngressClass predeterminado:

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  labels:
    app.kubernetes.io/component: controller
  name: nginx-example
  annotations:
    ingressclass.kubernetes.io/is-default-class: "true"
spec:
  controller: k8s.io/ingress-nginx

Tipos de Ingress

Ingress respaldado por un único servicio

Hay conceptos existentes de Kubernetes que te permiten exponer un Service único (mirar alternativas). También puedes hacerlo con un Ingress especificando un backend predeterminado sin reglas.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress
spec:
  defaultBackend:
    service:
      name: test
      port:
        number: 80

Si lo creas usando kubectl apply -f podrías mirar el estado del Ingress que has creado:

kubectl get ingress test-ingress
NAME           CLASS         HOSTS   ADDRESS         PORTS   AGE
test-ingress   external-lb   *       203.0.113.123   80      59s

Donde 203.0.113.123 es la IP asignada por el controlador Ingress para satisfacer este Ingress.

Abanico Simple

Una configuración de abanico enruta el tráfico de una única dirección IP a más de un Service, basado en la URI HTTP solicitada. Un Ingress te permite tener el número de balanceadores de carga al mínimo. Por ejemplo, una configuración como:

ingress-fanout-diagram

Figure. Ingress Fan Out

Requeriría un Ingress como este:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: simple-fanout-example
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - path: /foo
        pathType: Prefix
        backend:
          service:
            name: service1
            port:
              number: 4200
      - path: /bar
        pathType: Prefix
        backend:
          service:
            name: service2
            port:
              number: 8080

Cuando creas el Ingress con kubectl apply -f:

kubectl describe ingress simple-fanout-example
Name:             simple-fanout-example
Namespace:        default
Address:          178.91.123.132
Default backend:  default-http-backend:80 (10.8.2.3:8080)
Rules:
  Host         Path  Backends
  ----         ----  --------
  foo.bar.com
               /foo   service1:4200 (10.8.0.90:4200)
               /bar   service2:8080 (10.8.0.91:8080)
Events:
  Type     Reason  Age                From                     Message
  ----     ------  ----               ----                     -------
  Normal   ADD     22s                loadbalancer-controller  default/test

El controlador de Ingress aprovisiona un balanceador de cargas específico para la implementación que satisface al Ingress, mientras los Services (service1, service2) existan.

Cuando sea así, podrás ver la dirección del balanceador de cargas en el campo de dirección.

Hosting virtual basado en nombre

Los hostings virtuales basados en el nombre soportan enrutado de tráfico HTTP a nombres hosts múltiples con la misma dirección IP.

ingress-namebase-diagram

Figure. Ingress Name Based Virtual hosting

El siguiente Ingress le dice al balanceador de cargas de respaldo de enrutar las peticiones basadas en el encabezado del Host .

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: name-virtual-host-ingress
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: service1
            port:
              number: 80
  - host: bar.foo.com
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: service2
            port:
              number: 80

Si creas un recurso Ingress sin ningún host definido en las reglas, luego cualquier tráfico web a la dirección IP de tu controlador Ingress puede coincidir sin requerir un host virtual basado en el nombre.

Por ejemplo, el siguiente Ingress enruta el tráfico solicitado para first.bar.com a service1, second.bar.com a service2, y cualquier tráfico cuyo encabezado de petición del host no coincida con first.bar.com y second.bar.com a service3.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: name-virtual-host-ingress-no-third-host
spec:
  rules:
  - host: first.bar.com
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: service1
            port:
              number: 80
  - host: second.bar.com
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: service2
            port:
              number: 80
  - http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: service3
            port:
              number: 80

TLS

Puedes segurizar un Ingress especificando un Secret que contiene una clave privada TLS y un certificado. El recurso Ingress solo soporta un puerto TLS, el 443, y asume la terminación TLS en el punto del ingress (El tráfico al Service y sus Pods es en texto plano). Si la sección de configuración TLS especifica hosts diferentes, se multiplexan en el mismo puerto de acuerdo con el hostname especificado a través de la extensión TLS SNI (teniendo el cuenta que el controlador de Ingress soporte SNI). El secreto TLS debe contener claves llamadas tls.crt y tls.key que contiene el certificado y llave privad para usar TLS. Por ejemplo:

apiVersion: v1
kind: Secret
metadata:
  name: testsecret-tls
  namespace: default
data:
  tls.crt: base64 encoded cert
  tls.key: base64 encoded key
type: kubernetes.io/tls

Al hacer referencia a este secreto en un Ingress le indica al controlador Ingress de segurizar el canal desde el cliente al balanceador de cargas usando TLS. Necesitas asegurarte que el secreto TLS que has creado viene de un certificado que contiene un nombre común (CN), también conocido como Nombre de dominio calificado (FQDN en inglés) para https-example.foo.com.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tls-example-ingress
spec:
  tls:
  - hosts:
      - https-example.foo.com
    secretName: testsecret-tls
  rules:
  - host: https-example.foo.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: service1
            port:
              number: 80

Balanceo de cargas

Un controlador de Ingress está configurado por defecto con algunos ajustes de política de balanceo de cargas que aplica a todos los Ingress, como los algoritmos de balanceo de cargas, esquema de pesos del backend y otros. Otros conceptos más avanzados de balanceo de cargas (ej., sesiones persistentes, pesos dinámicos) no están expuestos todavía a través del Ingress. En su lugar, obtienes estas características a través del balanceador de cargas usado por un Service.

Vale la pena apuntar que aunque las revisiones de salud no se exponen directamente a través del Ingress, existen conceptos paralelos en Kubernetes tales como readiness probes que permiten lograr el mismo resultado final. Revisa la documentación específica del controlador para conocer cómo manejar estas revisiones de salud (por ejemplo: nginx, o GCE).

Actualizando un Ingress

Para actualizar un Ingress existente a un nuevo Host, puedes actualizarlo editando el recurso:

kubectl describe ingress test
Name:             test
Namespace:        default
Address:          178.91.123.132
Default backend:  default-http-backend:80 (10.8.2.3:8080)
Rules:
  Host         Path  Backends
  ----         ----  --------
  foo.bar.com
               /foo   service1:80 (10.8.0.90:80)
Annotations:
  nginx.ingress.kubernetes.io/rewrite-target:  /
Events:
  Type     Reason  Age                From                     Message
  ----     ------  ----               ----                     -------
  Normal   ADD     35s                loadbalancer-controller  default/test
kubectl edit ingress test

Esto muestra un editor con la configuración existente en formato YAML. Modifícalo para incluir el nuevo Host:

spec:
  rules:
    - host: foo.bar.com
      http:
        paths:
          - backend:
              service:
                name: service1
                port:
                  number: 80
            path: /foo
            pathType: Prefix
    - host: bar.baz.com
      http:
        paths:
          - backend:
              service:
                name: service2
                port:
                  number: 80
            path: /foo
            pathType: Prefix
  #..

Luego de guardar tus cambios, kubectl actualiza el recurso en el servidor API, que le indica al controlador Ingress de reconfigurar el balanceador de cargas.

Verifica esto:

kubectl describe ingress test
Name:             test
Namespace:        default
Address:          178.91.123.132
Default backend:  default-http-backend:80 (10.8.2.3:8080)
Rules:
  Host         Path  Backends
  ----         ----  --------
  foo.bar.com
               /foo   service1:80 (10.8.0.90:80)
  bar.baz.com
               /foo   service2:80 (10.8.0.91:80)
Annotations:
  nginx.ingress.kubernetes.io/rewrite-target:  /
Events:
  Type     Reason  Age                From                     Message
  ----     ------  ----               ----                     -------
  Normal   ADD     45s                loadbalancer-controller  default/test

Puedes lograr el mismo resultado invocando kubectl replace -f en un fichero YAML de Ingress.

Fallos a través de zonas de disponibilidad

Las técnicas para distribuir el tráfico entre dominios de falla difieren entre los proveedores de la nube. Revisa la documentación del Ingress controller relevante para detalles.

Alternativas

Puedes exponer un Service de muchas maneras que no involucran directamente el recurso Ingress:

Siguientes pasos

5.3 - Controladores Ingress

Para que un Ingress funcione en tu clúster, debe haber un ingress controller en ejecución. Debes seleccionar al menos un controlador Ingress y asegurarte de que está configurado en tu clúster. En esta página se enumeran los controladores Ingress más comunes que se pueden implementar.

Para que el recurso Ingress funcione, el clúster necesita tener un controlador Ingress corriendo.

Mientras otro tipo de controladores que corren como parte del binario de kube-controller-manager, los controladores Ingress no son automaticamente iniciados dentro del clúster. Usa esta página para elegir la mejor implementación de controlador Ingress que funcione mejor para tu clúster.

Kubernetes es un proyecto que soporta y mantiene los controladores Ingress de AWS, GCE y nginx.

Controladores adicionales

Uso de varios controladores Ingress

Puedes desplegar cualquier número de controladores Ingress utilizando clase ingress dentro de un clúster. Ten en cuenta el .metadata.name de tu recurso de clase Ingress. Cuando creas un Ingress, necesitarás ese nombre para especificar el campo ingressClassName de su objeto Ingress (consulta referencia IngressSpec v1). ingressClassName sustituye el antiguo método de anotación.

Si no especificas una IngressClass para un Ingress, y tu clúster tiene exactamente una IngressClass marcada como predeterminada, Kubernetes aplica la IngressClass predeterminada del clúster al Ingress. Se marca una IngressClass como predeterminada estableciendo la anotación ingressclass.kubernetes.io/is-default-class en esa IngressClass, con el valor de cadena "true".

Lo ideal sería que todos los controladores Ingress cumplieran esta especificación, pero los distintos controladores Ingress funcionan de forma ligeramente diferente.

Siguientes pasos

5.4 - Políticas de red (Network Policies)

Si quieres controlar el tráfico de red a nivel de dirección IP o puerto (capa OSI 3 o 4), puedes considerar el uso de Kubernetes NetworkPolicies para las aplicaciones que corren en tu clúster. Las NetworkPolicies son una estructura enfocada en las aplicaciones que permite establecer cómo un Pod puede comunicarse con otras "entidades" (utilizamos la palabra "entidad" para evitar sobrecargar términos más comunes como "Endpoint" o "Service", que tienen connotaciones específicas de Kubernetes) a través de la red. Las NetworkPolicies se aplican a uno o ambos extremos de la conexión a un Pod, sin afectar a otras conexiones.

Las entidades con las que un Pod puede comunicarse son una combinación de estos 3 tipos:

  1. Otros Pods permitidos (excepción: un Pod no puede bloquear el acceso a sí mismo)
  2. Namespaces permitidos
  3. Bloqueos de IP (excepción: el tráfico hacia y desde el nodo donde se ejecuta un Pod siempre está permitido, independientemente de la dirección IP del Pod o del nodo)

Cuando se define una NetworkPolicy basada en Pods o Namespaces, se utiliza un Selector para especificar qué tráfico se permite desde y hacia los Pod(s) que coinciden con el selector.

Por otro lado, cuando se crean NetworkPolicies basadas en IP, se definen políticas basadas en bloques de IP (rangos CIDR).

Prerrequisitos

Las políticas de red son implementadas por el plugin de red. Para usar políticas de red, debes estar utilizando una solución de red que soporte NetworkPolicy. Crear un recurso NetworkPolicy sin un controlador que lo habilite no tendrá efecto alguno.

Dos Tipos de Aislamiento de Pod

Hay dos tipos de aislamiento para un Pod: el aislamiento para la salida y el aislamiento para la entrada. Estos se refieren a las conexiones que pueden establecerse. El término "Aislamiento" en el contexto de este documento no es absoluto, sino que significa "se aplican algunas restricciones". La alternativa, "no aislado para $dirección", significa que no se aplican restricciones en la dirección descrita. Los dos tipos de aislamiento (o no) se declaran independientemente, y ambos son relevantes para una conexión de un Pod a otro.

Por defecto, un Pod no está aislado para la salida; todas las conexiones salientes están permitidas. Un Pod está aislado para la salida si hay alguna NetworkPolicy con "Egress" en su policyTypes que seleccione el Pod; decimos que tal política se aplica al Pod para la salida. Cuando un Pod está aislado para la salida, las únicas conexiones permitidas desde el Pod son las permitidas por la lista egress de las NetworkPolicy que se apliquen al Pod para la salida. Los valores de esas listas egress se combinan de forma aditiva.

Por defecto, un Pod no está aislado para la entrada; todas las conexiones entrantes están permitidas. Un Pod está aislado para la entrada si hay alguna NetworkPolicy con "Ingress" en su policyTypes que seleccione el Pod; decimos que tal política se aplica al Pod para la entrada. Cuando un Pod está aislado para la entrada, las únicas conexiones permitidas en el Pod son las del nodo del Pod y las permitidas por la lista ingress de alguna NetworkPolicy que se apliquen al Pod para la entrada. Los valores de esas listas de direcciones se combinan de forma aditiva.

Las políticas de red no entran en conflicto; son aditivas. Si alguna política(s) se aplica a un Pod para una dirección determinada, las conexiones permitidas en esa dirección desde ese Pod son la unión de lo que permiten las políticas aplicables. Por lo tanto, el orden de evaluación no afecta al resultado de la política.

Para que se permita una conexión desde un Pod de origen a un Pod de destino, tanto la política de salida del Pod de origen como la de entrada del Pod de destino deben permitir la conexión. Si cualquiera de los dos lados no permite la conexión, ésta no se producirá.

El Recurso NetworkPolicy

Ver la referencia NetworkPolicy para una definición completa del recurso.

Un ejemplo de NetworkPolicy podría ser este:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: test-network-policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      role: db
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - ipBlock:
            cidr: 172.17.0.0/16
            except:
              - 172.17.1.0/24
        - namespaceSelector:
            matchLabels:
              project: myproject
        - podSelector:
            matchLabels:
              role: frontend
      ports:
        - protocol: TCP
          port: 6379
  egress:
    - to:
        - ipBlock:
            cidr: 10.0.0.0/24
      ports:
        - protocol: TCP
          port: 5978

Campos Obligatorios: Como con todas las otras configuraciones de Kubernetes, una NetworkPolicy necesita los campos apiVersion, kind, y metadata. Para obtener información general sobre cómo funcionan esos archivos de configuración, puedes consultar Configurar un Pod para usar un ConfigMap, y Gestión de Objetos.

spec: NetworkPolicy spec contiene toda la información necesaria para definir una política de red dado un Namespace.

podSelector: Cada NetworkPolicy incluye un podSelector el cual selecciona el grupo de Pods en los cuales aplica la política. La política de ejemplo selecciona Pods con la etiqueta "role=db". Un podSelector vacío selecciona todos los Pods en un Namespace.

policyTypes: Cada NetworkPolicy incluye una lista de policyTypes la cual puede incluir Ingress, Egress, o ambas. Los campos policyTypes indican si la política aplica o no al tráfico de entrada hacia el Pod seleccionado, el tráfico de salida desde el Pod seleccionado, o ambos. Si no se especifican policyTypes en una NetworkPolicy, el valor Ingress se aplicará siempre por defecto y Egress se aplicará si la NetworkPolicy contiene alguna regla de salida.

ingress: Cada NetworkPolicy puede incluir una lista de reglas ingress permitidas. Cada regla permite el tráfico relacionado con los valores de las secciones from y ports. La política de ejemplo contiene una única regla, la cual se relaciona con el tráfico sobre un solo puerto, desde uno de los tres orígenes definidos, el primero especificado por el valor ipBlock, el segundo especificado por el valor namespaceSelector y el tercero especificado por el podSelector.

egress: Cada NetworkPolicy puede incluir una lista de reglas de egress permitidas. Cada regla permite el tráfico relacionado con los valores de las secciones to y ports. La política de ejemplo contiene una única regla, la cual se relaciona con el tráfico en un único puerto para cualquier destino en el rango de IPs 10.0.0.0/24.

Por lo tanto, la NetworkPolicy de ejemplo:

  1. Aísla los Pods "role=db" en el Namespace "default" para ambos tipos de tráfico ingress y egress (si aún no están aislados).
  2. (Reglas Ingress) permite la conexión hacia todos los Pods en el Namespace "default" con la etiqueta "role=db" en el puerto TCP 6379 desde los siguientes orígenes:
  • cualquier Pod en el Namespace "default" con la etiqueta "role=frontend"
  • cualquier Pod en un Namespace con la etiqueta "project=myproject"
  • La dirección IP en los rangos 172.17.0.0–172.17.0.255 y 172.17.2.0–172.17.255.255 (por ejemplo, todo el rango de IPs de 172.17.0.0/16 con excepción del 172.17.1.0/24)
  1. (Reglas de Egress) permite la conexión desde cualquier Pod en el Namespace "default" con la etiqueta "role=db" hacia CIDR 10.0.0.0/24 en el puerto TCP 5978

Ver el artículo de Declarar Network Policy para más ejemplos.

Comportamiento de los selectores to y from

Existen cuatro tipos de selectores que pueden ser especificados en una sección ingress from o en una sección egress to:

podSelector: Este selector selecciona Pods específicos en el mismo Namespace que la NetworkPolicy para permitir el tráfico como origen de entrada o destino de salida.

namespaceSelector: Este selector selecciona Namespaces específicos para permitir el tráfico como origen de entrada o destino de salida.

namespaceSelector y podSelector: Una única entrada to/from que especifica tanto namespaceSelector como podSelector selecciona Pods específicos dentro de Namespaces específicos. Es importante revisar que se utiliza la sintaxis de YAML correcta. A continuación se muestra un ejemplo de esta política:

  ...
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          user: alice
      podSelector:
        matchLabels:
          role: client
  ...

contiene un elemento from permitiendo conexiones desde los Pods con el label role=client en Namespaces con el label user=alice. Por el contrario, esta política:

  ...
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          user: alice
    - podSelector:
        matchLabels:
          role: client
  ...

contiene dos elementos en el array from, y permite conexiones desde Pods en los Namespaces con el label role=client, o desde cualquier Pod en cualquier Namespace con el label user=alice.

En caso de duda, utilice kubectl describe para ver cómo Kubernetes ha interpretado la política.

ipBlock: Este selector selecciona rangos CIDR de IP específicos para permitirlas como origen de entrada o destino de salida. Estas IPs deben ser externas al clúster, ya que las IPs de Pod son efímeras e impredecibles.

Los mecanismos de entrada y salida del clúster a menudo requieren reescribir la IP de origen o destino de los paquetes. En los casos en los que esto ocurre, no está definido si esto ocurre antes o después del procesamiento de NetworkPolicy, y el comportamiento puede ser diferente para diferentes combinaciones de plugin de red, proveedor de nube, implementación de Service, etc.

En el caso de la entrada, esto significa que en algunos casos se pueden filtrar paquetes entrantes basándose en la IP de origen real, mientras que en otros casos, la "IP de origen" sobre la que actúa la NetworkPolicy puede ser la IP de un LoadBalancer o la IP del Nodo donde está el Pod involucrado, etc.

Para la salida, esto significa que las conexiones de los Pods a las IPs de Service que se reescriben a IPs externas al clúster pueden o no estar sujetas a políticas basadas en ipBlock.

Políticas por defecto

Por defecto, si no existen políticas en un Namespace, se permite todo el tráfico de entrada y salida hacia y desde los Pods de ese Namespace. Los siguientes ejemplos muestran cómo cambiar el comportamiento por defecto en ese Namespace.

Denegar todo el tráfico de entrada por defecto

Puedes crear una política que "por defecto" aisle a un Namespace del tráfico de entrada con la creación de una política que seleccione todos los Pods del Namespace pero no permite ningún tráfico de entrada en esos Pods.

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
spec:
  podSelector: {}
  policyTypes:
  - Ingress

Esto asegura que incluso los Pods que no están seleccionados por ninguna otra NetworkPolicy también serán aislados del tráfico de entrada. Esta política no afecta el aislamiento en el tráfico de salida desde cualquier Pod.

Permitir todo el tráfico de entrada

Si quieres permitir todo el tráfico de entrada a todos los Pods en un Namespace, puedes crear una política que explícitamente permita eso.

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all-ingress
spec:
  podSelector: {}
  ingress:
  - {}
  policyTypes:
  - Ingress

Con esta política en curso, ninguna política(s) adicional puede hacer que se niegue cualquier conexión entrante a esos Pods. Esta política no tiene efecto sobre el aislamiento del tráfico de salida de cualquier Pod.

Denegar por defecto todo el tráfico de salida

Puedes crear una política que "por defecto" aisle el tráfico de salida para un Namespace, creando una NetworkPolicy que seleccione todos los Pods pero que no permita ningún tráfico de salida desde esos Pods.

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
spec:
  podSelector: {}
  policyTypes:
  - Egress

Esto asegura que incluso los Pods que no son seleccionados por ninguna otra NetworkPolicy no tengan permitido el tráfico de salida. Esta política no cambia el comportamiento de aislamiento para el tráfico de entrada de ningún Pod.

Permitir todo el tráfico de salida

Si quieres permitir todas las conexiones desde todos los Pods de un Namespace, puedes crear una política que permita explícitamente todas las conexiones salientes de los Pods de ese Namespace.

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all-egress
spec:
  podSelector: {}
  egress:
  - {}
  policyTypes:
  - Egress

Con esta política en vigor, ninguna política(s) adicional puede hacer que se niegue cualquier conexión de salida desde esos Pods. Esta política no tiene efecto sobre el aislamiento para el tráfico de entrada a cualquier Pod.

Denegar por defecto todo el tráfico de entrada y de salida

Puedes crear una política que "por defecto" en un Namespace impida todo el tráfico de entrada y de salida creando la siguiente NetworkPolicy en ese Namespace.

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

Esto asegura que incluso los Pods que no son seleccionados por ninguna otra NetworkPolicy no tendrán permitido el tráfico de entrada o salida.

Soporte a SCTP

FEATURE STATE: Kubernetes v1.20 [stable]

Como característica estable, está activada por defecto. Para deshabilitar SCTP a nivel de clúster, usted (o el administrador de su clúster) tiene que deshabilitar la feature gate SCTPSupport para el API Server con el flag --feature-gates=SCTPSupport=false,.... Cuando esta feature gate está habilitada, puede establecer el campo protocol de una NetworkPolicy como SCTP.

Apuntar a un rango de puertos

FEATURE STATE: Kubernetes v1.22 [beta]

Cuando se escribe una NetworkPolicy, se puede apuntar a un rango de puertos en lugar de un solo puerto.

Esto se puede lograr con el uso del campo endPort, como en el siguiente ejemplo:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: multi-port-egress
  namespace: default
spec:
  podSelector:
    matchLabels:
      role: db
  policyTypes:
  - Egress
  egress:
  - to:
    - ipBlock:
        cidr: 10.0.0.0/24
    ports:
    - protocol: TCP
      port: 32000
      endPort: 32768

La regla anterior permite que cualquier Pod con la etiqueta role=db en el Namespace default se comunique con cualquier IP dentro del rango 10.0.0.0/24 sobre el protocolo TCP, siempre que el puerto esté entre el rango 32000 y 32768.

Se aplican las siguientes restricciones al utilizar este campo:

  • Como característica en estado beta, está activada por defecto. Para desactivar el campo endPort a nivel de clúster, usted (o su administrador de clúster) debe desactivar la feature gate NetworkPolicyEndPort
    en el API Server con el flag --feature-gates=NetworkPolicyEndPort=false,....
  • El campo endPort debe ser igual o mayor que el campo port.
  • Solo se puede definir endPort si también se define port.
  • Ambos puertos deben ser numéricos.

Cómo apuntar a un Namespace usando su nombre

FEATURE STATE: Kubernetes 1.22 [stable]

El plano de control de Kubernetes establece una etiqueta inmutable kubernetes.io/metadata.name en todos los Namespaces, siempre que se haya habilitado la feature gate NamespaceDefaultLabelName. El valor de la etiqueta es el nombre del Namespace.

Aunque NetworkPolicy no puede apuntar a un Namespace por su nombre con algún campo de objeto, puede utilizar la etiqueta estandarizada para apuntar a un Namespace específico.

Qué no puedes hacer con políticas de red (al menos, aún no)

Actualmente, en Kubernetes 1.32, la siguiente funcionalidad no existe en la API de NetworkPolicy, pero es posible que se puedan implementar soluciones mediante componentes del sistema operativo (como SELinux, OpenVSwitch, IPTables, etc.) o tecnologías de capa 7 (controladores Ingress, implementaciones de Service Mesh) o controladores de admisión. En caso de que seas nuevo en la seguridad de la red en Kubernetes, vale la pena señalar que las siguientes historias de usuario no pueden (todavía) ser implementadas usando la API NetworkPolicy.

  • Forzar que el tráfico interno del clúster pase por una puerta de enlace común (esto se puede implementar con una malla de servicios u otro proxy).
  • Cualquier cosa relacionada con TLS (se puede implementar con una malla de servicios o un controlador Ingress para esto).
  • Políticas específicas de los nodos (se puede utilizar la notación CIDR para esto, pero no se puede apuntar a los nodos por sus identidades Kubernetes específicamente).
  • Apuntar Services por nombre (sin embargo, se pueden orientar los Pods o los Namespaces por sus labels, lo que suele ser una solución viable).
  • Creación o gestión de "solicitudes de políticas" que son atendidas por un tercero.
  • Políticas que por defecto son aplicadas a todos los Namespaces o Pods (hay algunas distribuciones y proyectos de Kubernetes de terceros que pueden hacer esto).
  • Consulta avanzada de políticas y herramientas de accesibilidad.
  • La capacidad de registrar los eventos de seguridad de la red (por ejemplo, las conexiones bloqueadas o aceptadas).
  • La capacidad de negar explícitamente las políticas (actualmente el modelo para NetworkPolicies es negar por defecto, con solo la capacidad de añadir reglas de permitir).
  • La capacidad de impedir el tráfico entrante de Loopback o de Host (actualmente los Pods no pueden bloquear el acceso al host local, ni tienen la capacidad de bloquear el acceso desde su nodo residente).

Siguientes pasos

5.5 - EndpointSlices

La API de EndpointSlice es el mecanismo que Kubernetes utiliza para permitir que tu Servicio escale para manejar un gran número de backends, y permite que el clúster actualice tu lista de backends saludables eficientemente.
FEATURE STATE: Kubernetes v1.21 [stable]

La API de EndpointSlice de Kubernetes proporciona una forma de rastrear los endpoints de red dentro de un clúster Kubernetes. EndpointSlices ofrece una alternativa más escalable y extensible a Endpoints.

EndpointSlice API

En Kubernetes, un EndpointSlice contiene referencias a un conjunto de endpoints de red. El plano de control crea automáticamente EndpointSlices para cualquier Servicio de Kubernetes que tenga especificado un selector. Estos EndpointSlices incluyen referencias a todos los Pods que coinciden con el selector de Servicio. Los EndpointSlices agrupan los endpoints de la red mediante combinaciones únicas de protocolo, número de puerto y nombre de Servicio.

El nombre de un objeto EndpointSlice debe ser un nombre de subdominio DNS válido.

A modo de ejemplo, a continuación se muestra un objeto EndpointSlice de ejemplo, propiedad del Servicio example de Kubernetes.

apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
  name: example-abc
  labels:
    kubernetes.io/service-name: example
addressType: IPv4
ports:
  - name: http
    protocol: TCP
    port: 80
endpoints:
  - addresses:
      - "10.1.2.3"
    conditions:
      ready: true
    hostname: pod-1
    nodeName: node-1
    zone: us-west2-a

Por defecto, el plano de control crea y gestiona EndpointSlices para que no tengan más de 100 endpoints cada una. Puedes configurar esto con la bandera de funcionalidad --max-endpoints-per-slice kube-controller-manager hasta un máximo de 1000.

EndpointSlices puede actuar como la fuente de verdad kube-proxy sobre cómo enrutar el tráfico interno.

Tipos de dirección

EndpointSlices admite tres tipos de direcciones:

  • IPv4
  • IPv6
  • FQDN (Fully Qualified Domain Name)

Cada objeto EndpointSlice representa un tipo de dirección IP específico. Si tienes un servicio disponible a través de IPv4 e IPv6, habrá al menos dos objetos EndpointSlice (uno para IPv4 y otro para IPv6).

Condiciones

La API EndpointSlice almacena condiciones sobre los endpoints que pueden ser útiles para los consumidores. Las tres condiciones son ready, serving y terminating.

Ready

ready es una condición que corresponde a la condición Ready de un Pod. Un Pod en ejecución con la condición Ready establecida a True debería tener esta condición EndpointSlice también establecida a true. Por razones de compatibilidad, ready NUNCA es true cuando un Pod está terminando. Los consumidores deben referirse a la condición serving para inspeccionar la disponibilidad de los Pods que están terminando. La única excepción a esta regla son los servicios con spec.publishNotReadyAddresses a true. Los endpoints de estos servicios siempre tendrán la condición ready a true.

Serving

FEATURE STATE: Kubernetes v1.26 [stable]

La condición serving es casi idéntica a la condición ready. La diferencia es que los consumidores de la API EndpointSlice deben comprobar la condición serving si se preocupan por la disponibilidad del pod mientras el pod también está terminando.

Terminating

FEATURE STATE: Kubernetes v1.22 [beta]

Terminating es una condición que indica si un endpoint está terminando. En el caso de los pods, se trata de cualquier pod que tenga establecida una marca de tiempo de borrado.

Información sobre topología

Cada endpoint dentro de un EndpointSlice puede contener información topológica relevante. La información de topología incluye la ubicación del endpoint e información sobre el Nodo y la zona correspondientes. Estos están disponibles en los siguientes campos por endpoint en EndpointSlices:

  • nodeName - El nombre del Nodo en el que se encuentra este endpoint.
  • zone - La zona en la que se encuentra este endpoint.

Administración

En la mayoría de los casos, el plano de control (concretamente, el endpoint slice controller) crea y gestiona objetos EndpointSlice. Existe una variedad de otros casos de uso para EndpointSlices, como implementaciones de servicios Mesh, que podrían dar lugar a que otras entidades o controladores gestionen conjuntos adicionales de EndpointSlices.

Para garantizar que varias entidades puedan gestionar EndpointSlices sin interferir unas con otras, Kubernetes define el parámetro label endpointslice.kubernetes.io/managed-by, que indica la entidad que gestiona un EndpointSlice. El controlador de endpoint slice establece endpointslice-controller.k8s.io como valor para esta etiqueta en todos los EndpointSlices que gestiona. Otras entidades que gestionen EndpointSlices también deben establecer un valor único para esta etiqueta.

Propiedad

En la mayoría de los casos de uso, los EndpointSlices son propiedad del Servicio para el que el objeto EndpointSlices rastree los endpoints. Esta propiedad se indica mediante una referencia de propietario en cada EndpointSlice, así como una etiqueta kubernetes.io/service-name que permite búsquedas sencillas de todos los EndpointSlices que pertenecen a un Servicio.

Replicación de EndpointSlice

En algunos casos, las aplicaciones crean recursos Endpoints personalizados. Para garantizar que estas aplicaciones no tengan que escribir simultáneamente en recursos Endpoints y EndpointSlice, el plano de control del clúster refleja la mayoría de los recursos Endpoints en los EndpointSlices correspondientes.

El plano de control refleja los recursos de los Endpoints a menos que:

  • El recurso Endpoints tenga una etiqueta endpointslice.kubernetes.io/skip-mirror con el valor en true.
  • El recurso Endpoints tenga una anotación control-plane.alpha.kubernetes.io/leader.
  • El recurso Service correspondiente no exista.
  • El recurso Service correspondiente tiene un selector no nulo.

Los recursos Endpoints individuales pueden traducirse en múltiples EndpointSlices. Esto ocurrirá si un recurso Endpoints tiene múltiples subconjuntos o incluye endpoints con múltiples familias IP (IPv4 e IPv6). Se reflejará un máximo de 1000 direcciones por subconjunto en EndpointSlices.

Distribución de EndpointSlices

Cada EndpointSlice tiene un conjunto de puertos que se aplica a todos los endpoints dentro del recurso. Cuando se utilizan puertos con nombre para un Servicio, los Pods pueden terminar con diferentes números de puerto de destino para el mismo puerto con nombre, requiriendo diferentes EndpointSlices. Esto es similar a la lógica detrás de cómo se agrupan los subconjuntos con Endpoints.

El plano de control intenta llenar los EndpointSlices tanto como sea posible, pero no los reequilibra activamente. La lógica es bastante sencilla:

  1. Iterar a través de los EndpointSlices existentes, eliminar los endpoints que ya no se deseen y actualizar los endpoints coincidentes que hayan cambiado.
  2. Recorrer los EndpointSlices que han sido modificados en el primer paso y rellenarlos con los nuevos endpoints necesarios.
  3. Si aún quedan nuevos endpoints por añadir, intente encajarlos en un slice que no se haya modificado previamente y/o cree otros nuevos.

Es importante destacar que el tercer paso prioriza limitar las actualizaciones de EndpointSlice sobre una distribución perfectamente completa de EndpointSlices. Por ejemplo, si hay 10 nuevos endpoints que añadir y 2 EndpointSlices con espacio para 5 endpoints más cada uno, este enfoque creará un nuevo EndpointSlice en lugar de llenar los 2 EndpointSlices existentes. En otras palabras, es preferible una única creación de EndpointSlice que múltiples actualizaciones de EndpointSlice.

Con kube-proxy ejecutándose en cada Nodo y vigilando los EndpointSlices, cada cambio en un EndpointSlice se vuelve relativamente caro ya que será transmitido a cada Nodo del clúster. Este enfoque pretende limitar el número de cambios que necesitan ser enviados a cada Nodo, incluso si puede resultar con múltiples EndpointSlices que no están llenos.

En la práctica, esta distribución menos que ideal debería ser poco frecuente. La mayoría de los cambios procesados por el controlador EndpointSlice serán lo suficientemente pequeños como para caber en un EndpointSlice existente, y si no, es probable que pronto sea necesario un nuevo EndpointSlice de todos modos. Las actualizaciones continuas de los Deployments también proporcionan un reempaquetado natural de los EndpointSlices con todos los Pods y sus correspondientes endpoints siendo reemplazados.

Endpoints duplicados

Debido a la naturaleza de los cambios de EndpointSlice, los endpoints pueden estar representados en más de un EndpointSlice al mismo tiempo. Esto ocurre de forma natural, ya que los cambios en diferentes objetos EndpointSlice pueden llegar a la vigilancia / caché del cliente de Kubernetes en diferentes momentos.

Comparación con endpoints

La API Endpoints original proporcionaba una forma simple y directa de rastrear los endpoints de red en Kubernetes. A medida que los clústeres de Kubernetes y los Services crecían para manejar más tráfico y enviar más tráfico a más Pods backend, las limitaciones de la API original se hicieron más visibles. Más notablemente, estos incluyen desafíos con la ampliación a un mayor número de endpoints de red.

Dado que todos los endpoints de red para un Servicio se almacenaban en un único objeto Endpoint, esos objetos Endpoints podían llegar a ser bastante grandes. Para los Services que permanecían estables (el mismo conjunto de endpoints durante un largo período de tiempo), el impacto era menos notable; incluso entonces, algunos casos de uso de Kubernetes no estaban bien servidos.

Cuando un Service tenía muchos Endpoints de backend y la carga de trabajo se escalaba con frecuencia o se introducían nuevos cambios con frecuencia, cada actualización del objeto Endpoint para ese Service suponía mucho tráfico entre los componentes del clúster de Kubernetes (dentro del plano de control y también entre los nodos y el servidor de API). Este tráfico adicional también tenía un coste en términos de uso de la CPU.

Con EndpointSlices, la adición o eliminación de un único Pod desencadena el mismo número de actualizaciones a los clientes que están pendientes de los cambios, pero el tamaño de esos mensajes de actualización es mucho menor a gran escala.

EndpointSlices también ha permitido innovar en torno a nuevas funciones, como las redes de doble pila y el enrutamiento con conocimiento de la topología.

Siguientes pasos

6 - Almacenamiento

6.1 - Volumes

Los archivos localizados dentro de un contenedor son efímeros, lo cual presenta problemas para aplicaciones no triviales cuando se ejecutan en contenedores. Un problema es la pérdida de archivos cuando el contenedor termina. Kubelet reinicia el contenedor con un estado limpio. Un segundo problema ocurre cuando compartimos ficheros entre contenedores corriendo juntos dentro de un Pod. La abstracción volume de Kubernetes resuelve ambos problemas. Se sugiere familiaridad con Pods

Trasfondo

Docker tiene el concepto de volúmenes, aunque es algo más flojo y menos controlado. Un volumen de Docker es un directorio en disco o en otro contenedor. Docker provee controladores de volúmenes, pero la funcionalidad es algo limitada.

Kubernetes soporta muchos tipos de volúmenes. Un Pod puede utilizar cualquier número de tipos de volúmenes simultáneamente. Los tipos de volúmenes efímeros tienen el mismo tiempo de vida que un Pod, pero los volúmenes persistentes existen más allá del tiempo de vida de un Pod. Cuando un Pod deja de existir, Kubernetes destruye los volúmenes efímeros; sin embargo, Kubernetes no destruye los volúmenes persistentes. Para cualquier tipo de volumen en un Pod dado, los datos son preservados a lo largo de los reinicios del contenedor.

En su núcleo, un volumen es un directorio, posiblemente con algunos datos en este, que puede ser accesible para los contenedores en un Pod. Cómo ese directorio llega a crearse, el medio que lo respalda, y el contenido de este se determinan por el tipo de volumen usado.

Para usar un volumen, especifica los volúmenes a proveer al por en .spec.volumes y declara dónde montar estos volúmenes dentro de los contenedores en .spec.containers[*].volumeMounts. Un proceso en el contenedor observa una vista del sistema de archivos compuesta por la imagen Docker y volúmenes. La imagen Docker está en la raíz de la jerarquía del sistema de archivos. Los volúmenes se montan en las rutas especificadas dentro de la imagen. Los volúmenes no se pueden montar en otros volúmenes o tener enlaces duros a otros volúmenes. Cada contenedor en la configuración del Pod debe especificar de forma independiente donde montar cada volumen.

Tipos de volúmenes

Kubernetes soporta varios tipos de volúmenes

awsElasticBlockStore

Un volumen awsElasticBlockStore monta un volumen EBS de Amazon Web Services (AWS) en tu Pod. A diferencia de emptyDir, que se borra cuando se quita un Pod, el contenido de un volumen EBS es persistido cuando se desmonta el volumen. Esto significa que un volumen EBS puede ser pre-poblado con datos, y que los datos puedes ser compartidos entre pods.

Existen algunas restricciones cuando usas un volumen awsElasticBlockStore:

  • Los nodos en los que corren los pods deben ser instances AWS EC2.
  • Estas instancias deben estar en la misma región y zona de disponibilidad que el volumen EBS
  • EBS solo soporta una única instancia EC2 montando un volumen

Creando un volumen AWS EBS

Antes poder usar un volumen EBS en un Pod, necesitas crearlo.

aws ec2 create-volume --availability-zone=eu-west-1a --size=10 --volume-type=gp2

Asegúrate de que la zona coincide con la zona en que has creado el clúster. Revisa que el tamaño y el tipo de volumen EBS son compatibles para tu uso.

Ejemplo de configuración AWS EBS

apiVersion: v1
kind: Pod
metadata:
  name: test-ebs
spec:
  containers:
    - image: registry.k8s.io/test-webserver
      name: test-container
      volumeMounts:
        - mountPath: /test-ebs
          name: test-volume
  volumes:
    - name: test-volume
      # Este volumen EBS debe existir anteriormente.
      awsElasticBlockStore:
        volumeID: "<volume id>"
        fsType: ext4

Si el volumen EBS está particionado, puedes suministrar el campo opcional partition: "<partition number>" para especificar cuál partición montar.

Migración CSI AWS EBS CSI

FEATURE STATE: Kubernetes v1.17 [beta]

La función CSIMigration para awsElasticBlockStore, cuando se habilita, redirige todas las operaciones de complemento desde el complemento existente dentro del árbol existente al controlador de Interfaz de Almacenamiento del Contenedor (CSI) de ebs.csi.aws.com. Para utilizar esta función, el controlador AWS EBS CSI debe ser instalado en el clúster y las características beta CSIMigration y CSIMigrationAWS deben estar habilitadas.

Migración CSI AWS EBS CSI completa

FEATURE STATE: Kubernetes v1.17 [alpha]

Para desactivar el complemento de almacenamiento awsElasticBlockStore de ser cargado por el administrador de controladores y el kubelet, establece el atributo CSIMigrationAWSComplete a true. Esta función requiere tener instalado el controlador de interfaz de almacenamiento del contenedor (CSI) en todos los nodos en obreros.

azureDisk

El tipo de volumen azureDisk monta un Data Disk de Microsoft Azure en el Pod.

Para más detalles, mira el azureDisk volume plugin.

Migración CSI azureDisk

FEATURE STATE: Kubernetes v1.19 [beta]

La función CSIMigration para azureDisk, cuando se habilita, redirige todas las operaciones de complemento desde el complemento existente dentro del árbol existente al controlador de Interfaz de Almacenamiento del Contenedor (CSI) de disk.csi.azure.com. Para utilizar esta función, el controlador Azure Disk CSI debe ser instalado en el clúster y las características beta CSIMigration y CSIMigrationAzureDisk deben estar habilitadas.

azureFile

El tipo de volumen azureFile monta un volumen de ficheros de Microsoft Azure (SMB 2.1 and 3.0) en un Pod.

Para más detalles, mira el azureFile volume plugin.

Migración CSI azureFile CSI

FEATURE STATE: Kubernetes v1.21 [beta]

La función CSIMigration para azureFile, cuando se habilita, redirige todas las operaciones de complemento desde el complemento existente dentro del árbol existente al controlador de Interfaz de Almacenamiento del Contenedor (CSI) de file.csi.azure.com. Para utilizar esta función, el controlador Azure File CSI Driver debe ser instalado en el clúster y las feature gates CSIMigration y CSIMigrationAzureFile deben estar habilitadas.

El controlador Azure File CSI no soporta usar el mismo volumen con fsgroups diferentes, si está habilitadla migración CSI Azurefile, usar el mismo volumen con fsgorups diferentes no será compatible en absoluto.

cephfs

Un volumen cephfs permite montar un volumen CephFS existente en tu Pod. A diferencia de emptydir, que es borrado cuando se remueve el Pod, el contenido de un volumen cephfs es preservado y el volumen es meramente desmontado. Esto significa que un volumen cephfspuede ser pre-poblado por múltiples escritores simultáneamente.

Mira el CephFS example para más detalles.

cinder

El tipo de volumen cinder se usa para montar un volumen Cinder de OpenStack en tu Pod.

Cinder volume configuration example

apiVersion: v1
kind: Pod
metadata:
  name: test-cinder
spec:
  containers:
    - image: registry.k8s.io/test-webserver
      name: test-cinder-container
      volumeMounts:
        - mountPath: /test-cinder
          name: test-volume
  volumes:
    - name: test-volume
      # Este volumen de  OpenStack debe existir anteriormente.
      cinder:
        volumeID: "<volume id>"
        fsType: ext4

Migración CSI OpenStack

FEATURE STATE: Kubernetes v1.21 [beta]

La función CSIMigration para Cinder está habilitada por defecto en Kubernetes 1.21. Esta redirige todas las operaciones de complemento desde el complemento existente dentro del árbol existente al controlador de Interfaz de Almacenamiento del Contenedor (CSI) de cinder.csi.openstack.org. El controlador OpenStack Cinder CSI Driver debe estar instalado en el clúster.

Puedes deshabilitar la migración CSI para tu clúster estableciendo el feature gate CSIMigrationOpenStack a false. Si deshabilitas la función CSIMigrationOpenStack, el complemento del volumen Cinder dentro del árbol toma la responsabilidad para todos los aspectos de la administración del almacenamiento del volumen Cinder.

configMap

Un ConfigMap provee una manera de inyectar datos de configuración a los pods. Los datos almacenados en un ConfigMap se pueden referenciar en un volumen de tipo configMap y luego ser consumidos por aplicaciones contenerizadas corriendo en un Pod.

Cuando haces referencia a un ConfigMap, provees el nombre del ConfigMap en el volumen. Puedes personalizar la ruta para una entrada específica en el ConfigMap. La siguiente configuración muestra cómo montar un ConfigMap log-config en un Pod llamado configmap-pod:

apiVersion: v1
kind: Pod
metadata:
  name: configmap-pod
spec:
  containers:
    - name: test
      image: busybox:1.28
      command: ['sh', '-c', 'echo "The app is running!" && tail -f /dev/null']
      volumeMounts:
        - name: config-vol
          mountPath: /etc/config
  volumes:
    - name: config-vol
      configMap:
        name: log-config
        items:
          - key: log_level
            path: log_level

El ConfigMap log-config es montado como un volumen, y todo el contenido almacenado en su entrada log_level es montado en el Pod en la ruta /etc/config/log_level. Ten en cuenta que esta ruta se deriva del mountPathdel volumen y el path cuya clave es log_level.

downwardAPI

Un volumen de downwardAPI hace que los datos API descendentes estén disponibles para las aplicaciones. Monta un directorio y escribe los datos solicitados en archivos de texto sin formato.

Mira el downward API example para mayores detalles.

emptyDir

Un volumen emptyDires creado primero cuando se asigna un Pod a un nodo, y existe mientras el Pod está corriendo en el nodo. Como su nombre lo indica un volumen emptydirestá inicialmente vacío. Todos los contenedores en el Pod pueden leer y escribir los archivos en el volumen emptyDir, aunque ese volumen se puede montar en la misma o diferente ruta en cada contenedor. Cuando un Pod es removido del nodo por alguna razón, los datos en emptydir se borran permanentemente.

Algunos usos para un emptyDir son:

  • Espacio temporal, como para una clasificación de combinación basada en disco
  • Marcar un largo cálculo para la recuperación de fallos
  • Contener archivos que un contenedor de administrador de contenido recupera mientras un contenedor de servidor web sirve los datos

Dependiendo de tu entorno, los volúmenes emptydir se almacenan en cualquier medio que respalde el nodo tales como disco SSD, o almacenamiento de red. Sin embargo, si se establece el campo emptydir.medium a Memory, Kubernetes monta en su lugar un tmpfs (sistema de ficheros respaldado por la RAM). Mientras que tmpfs es muy rápido, ten en cuenta que a diferencia de los discos, tmpfs se limpia cuando el nodo reinicia y cualquier archivo que escribas cuenta con el límite de memoria del contenedor.

Ejemplo de configuración de emptyDir

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
    - image: registry.k8s.io/test-webserver
      name: test-container
      volumeMounts:
        - mountPath: /cache
          name: cache-volume
  volumes:
    - name: cache-volume
      emptyDir: {}

fc (canal de fibra)

Un tipo de volumen fc permite que un volumen de almacenamiento de bloque de canal de fibra existente se monte en un Pod. Puede especificar nombres mundiales de destino únicos o múltiples (WWN) utilizando el parámetro targetWWNs en su configuración de volumen. Si se especifican varios WWN, targettWWNs esperan que esos WWN sean de conexiones de múltiples rutas.

Revisa el ejemplo de canal de fibra para más detalles.

flocker (deprecado)

Flocker es un administrador open-source de volúmenes de contenedor agrupado por clúster. Flocker proporciona administración y orquestación de volúmenes de datos respaldados por una variedad de backends de almacenamiento.

Un volumen flocker permite montar un conjunto de datos Flocker en un Pod. Si el conjunto de datos no existe en Flocker, necesita ser creado primero con el CLI de Flocker o usando la API de Flocker. Si el conjunto de datos existe será adjuntado de nuevo por Flocker al nodo donde el Pod está programado. Esto significa que los datos pueden ser compartidos entre pods como sea necesario.

Mira el ejemplo de Flocker para más detalles.

gcePersistentDisk

Un volumen gcePersistentDisk monta un volumen de Google Compute Engine (GCE) de disco persistente (DP) en tu Pod. A diferencia de emptyDir, que se borra cuando el Pod es removido, el contenido de un disco persistente es preservado y el volumen solamente se desmonta. Esto significa que un disco persistente puede ser pre-poblado con datos, y que esos datos se pueden compartir entre pods.

Existen algunas restricciones cuando usas gcePersistentDisk:

  • Los nodos en los que se ejecutan los pods deben ser máquinas virtuales GCE.
  • Esas máquinas virtuales deben estar en el mismo proyecto GCE y zona que el disco persistente se encuentra.

Una de las características del disco persistente CGE es acceso concurrente de solo lectura al disco persistente. Un volumen gcePersistentDisk permite montar simultáneamente un disco de solo lectura a múltiples consumidores. Esto significa que puedes pre-poblar un DP con tu conjunto de datos y luego servirlo en paralelo desde tantos pods como necesites. Desafortunadamente, los DPs solo se pueden montar por un único consumidor en modo lectura-escritura. No están permitidos escritores simultáneos.

Usar un disco persistente GCE con un Pod controlado por un ReplicaSet fallará a manos que el DP sea de solo lectura o el número de réplicas sea 0 o 1.

Creando un disco persistente GCE

Antes de poder usar un disco persistente GCE en un Pod, necesitas crearlo.

gcloud compute disks create --size=500GB --zone=us-central1-a my-data-disk

Ejemplo de configuración de un disco persistente GCE

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
    - image: registry.k8s.io/test-webserver
      name: test-container
      volumeMounts:
        - mountPath: /test-pd
          name: test-volume
  volumes:
    - name: test-volume
      # Este PD GCE debe existir con anterioridad.
      gcePersistentDisk:
        pdName: my-data-disk
        fsType: ext4

Discos regionales persistentes

La función de discos regionales persistentes permite la creación de discos persistentes que están disponibles en dos zonas dentro de la misma región. Para usar esta función, el volumen debe ser provisto como un PersistentVolumen; referenciar el volumen directamente desde un Pod no está soportado.

Aprovisionamiento manual de un PD PersistentVolume Regional

El aprovisionamiento dinámico es posible usando un StorageClass para el DP GCE. Antes de crear un PersistentVolume, debes crear el disco persistente:

gcloud compute disks create --size=500GB my-data-disk
  --region us-central1
  --replica-zones us-central1-a,us-central1-b

Ejemplo de configuración de un disco persistente regional

apiVersion: v1
kind: PersistentVolume
metadata:
  name: test-volume
spec:
  capacity:
    storage: 400Gi
  accessModes:
    - ReadWriteOnce
  gcePersistentDisk:
    pdName: my-data-disk
    fsType: ext4
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: failure-domain.beta.kubernetes.io/zone
              operator: In
              values:
                - us-central1-a
                - us-central1-b

Migración CSI GCE

FEATURE STATE: Kubernetes v1.17 [beta]

La función CSIMigration para el DP GCE, cuando se habilita, redirige todas las operaciones de complemento desde el complemento existente dentro del árbol existente al controlador de Interfaz de Almacenamiento del Contenedor (CSI) de pd.csi.storage.gke.io. Para poder usar esta función, el controlador PD GCE debe ser instalado en el clúster y habilitar las funciones beta CSIMigration y CSIMigrationGCE.

gitRepo (deprecado)

Un volumen gitRepo es un ejemplo de un complemento de volumen. Este complemento monta un directorio vacío y clona un repositorio git en este directorio para que tu Pod pueda usarlo.

Aquí un ejemplo de un volumen gitrepo:

apiVersion: v1
kind: Pod
metadata:
  name: server
spec:
  containers:
    - image: nginx
      name: nginx
      volumeMounts:
        - mountPath: /mypath
          name: git-volume
  volumes:
    - name: git-volume
      gitRepo:
        repository: "git@somewhere:me/my-git-repository.git"
        revision: "22f1d8406d464b0c0874075539c1f2e96c253775"

glusterfs

Un volumen glusterfs permite montar un volumen Glusterfs en tu Pod. A diferencia de emptyDir, que se borra cuando se remueve un Pod, el contenido de un volumen glusterfs es preservado y el volumen solamente se desmonta. Esto significa que un volumen glusterfs puede ser pre-poblado con datos, y que los datos pueden ser compartidos entre pods. GlusterFS puede ser montado por múltiples escritores simultáneamente.

Mira el ejemplo de GlusterFS para más detalles.

hostPath

Un volumen hostPath monta un archivo o un directorio del sistema de archivos del nodo host a tu Pod. Esto no es algo de muchos Pods necesiten, pero ofrece una trampa de escape poderosa para algunas aplicaciones.

Por ejemplo, algunos usos de un hostPath son:

  • ejecutar un contenedor que necesita acceso a los directorios internos de Docker, usa un hostPath de /var/lib/docker
  • ejecutar un cAdvisor en un contenedor; usa un hostPath de /sys
  • permitir a un Pod especificar si un hostPath dado debería existir ante de correr el Pod, si debe crearse, cómo debe existir

Además de la propiedad requerida path, puedes especificar opcionalmente un tipopara un volumen hostPath.

Los valores soportados para el campo tipo son:

Valor Comportamiento
Una cadena vacía (por defecto) es para compatibilidad con versiones anteriores, lo que significa que no se harán revisiones antes de montar el volumen hostPath.
DirectoryOrCreate Si no hay nada en la ruta dada, se creará un directorio vacío como es requerido con los permisos a 0755, teniendo el mismo grupo y propiedad que el Kubelet.
Directory Un directorio debe existir en la ruta dada
FileOrCreate Si no hay nada en la ruta dada, se creará un archivo vacío como es requerido con los permisos a 0644, teniendo el mismo grupo y propiedad que el Kubelet.
File Un archivo debe existir en la ruta dada
Socket Un socket de UNIX debe existir en la ruta dada
CharDevice Un dispositivo de caracteres debe existir en la ruta data
BlockDevice Un dispositivo de bloques dbe existir en la ruta dada

Ten cuidado cuando uses este tipo de volumen, porque:

  • Los Pods con configuración idéntica (tales como los creados por un PodTemplate) pueden comportarse de forma distinta en nodos distintos debido a diferentes ficheros en los nodos.
  • Los ficheros o directorios creados en los hosts subyacentes son modificables solo por root. Debes ejecutar tu proceso como root en un Contenedor privilegiado o modificar los permisos de archivo en el host para escribir a un volumen hostPath

Ejemplo de configuración hostPath

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
    - image: registry.k8s.io/test-webserver
      name: test-container
      volumeMounts:
        - mountPath: /test-pd
          name: test-volume
  volumes:
    - name: test-volume
      hostPath:
        # localización del directorio en el host
        path: /data
        # este campo es opcional
        type: Directory

ejemplo de configuración hostPath FileOrCreate

apiVersion: v1
kind: Pod
metadata:
  name: test-webserver
spec:
  containers:
    - name: test-webserver
      image: registry.k8s.io/test-webserver:latest
      volumeMounts:
        - mountPath: /var/local/aaa
          name: mydir
        - mountPath: /var/local/aaa/1.txt
          name: myfile
  volumes:
    - name: mydir
      hostPath:
        # Asegúrate que el directorio del archivo es creado.
        path: /var/local/aaa
        type: DirectoryOrCreate
    - name: myfile
      hostPath:
        path: /var/local/aaa/1.txt
        type: FileOrCreate

iscsi

Un volumen iscsi permite que se monte un volumen ISCSI (SCSI sobre IP) existente en tu Pod. A diferencia de emptydir, que es removido cuando se remueve un Pod, el contenido de un volumen iscsi es preservado y el volumen solamente se desmonta. Esto significa que un volumen iscsi puede ser pre-poblado con datos, y que estos datos se pueden compartir entre pods.

Una función de SCSI es que puede ser montado como de solo lectura por múltiples consumidores simultáneamente. Esto significa que puedes pre-poblar un volumen con tu conjunto de datos y servirlo en paralelo para tantos Pods como necesites. Desafortunadamente, los volúmenes ISCSI solo se pueden montar por un único consumidor en modo lectura-escritura. Escritores simultáneos no está permitido.

Mira el ejemplo iSCSI para más detalles.

local

Un volumen local representa un dispositivo de almacenamiento local como un disco, una partición o un directorio.

Los volúmenes locales solo se pueden usar como un PersistenVolume creado estáticamente. El aprovisionamiento dinámico no está soportado.

Comparados con volúmenes hostPath, los volúmenes local se usan de manera duradera y portátil sin programar pods manualmente a los nodos. El sistema está consciente de las limitaciones del nodo del volumen al mirar la afinidad del nodo en el PersistenVolumen.

Sin embargo, los volúmenes localestán sujetos a la disponibilidad del nodo subyacente y no son compatibles para todas las aplicaciones.

Si un nodo deja de estar sano, entonces el volumen local se vuelve inaccesible al Pod. El Pod que utiliza este volumen no se puede ejecutar. Las aplicaciones que usan volúmenes local deben ser capaces de tolerar esta disponibilidad reducida, así como la pérdida potencial de datos, dependiendo de las características de durabilidad del disco subyacente.

El siguiente ejemplo muestra un PersistentVolume usando un volumen localy nodeAffinity:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: example-pv
spec:
  capacity:
    storage: 100Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  storageClassName: local-storage
  local:
    path: /mnt/disks/ssd1
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - example-node

Debes establecer un valor de nodeAffinity del PersistenVolume cuando uses volúmenes local. El Scheduler de Kubernetes usa nodeaffinity del PersistenVolume para programar estos Pods al nodo correcto.

El volumeMode del PersistentVolume se puede establecer en "Block" (en lugar del valor por defecto, "Filesystem") para exponer el volumen local como un dispositivo de bloque sin formato.

Cuando usas volúmenes locales, se recomienda crear un StorageClass con volumeBindingMode en WaitForFirstConsumer. Para más detalles, mira el ejemplo de StorageClass. Retrasar el enlace con el volumen asegura que la decisión del PersistenVolumeClaim sea evaluada con otras limitaciones que el Pod pueda tener, tales como requisitos de recursos del nodo, selectores de nodo, afinidad del Pod, y anti-afinidad del Pod.

Se puede ejecutar un aprovisionador estático externo para un manejo mejorado del ciclo de vida del volumen local. Ten en cuenta que este aprovisionador no soporta aprovisionamiento dinámico todavía. Para un ejemplo de un aprovisionador local externo, mira la guía de usuario de aprovisionador de volumen local

nfs

Un volumen nfs permite montar un NFS (Sistema de Ficheros de Red) compartido en tu Pod. A diferencia de emptyDir que se borra cuando el Pod es removido, el contenido de un volumen nfs solamente se desmonta. Esto significa que un volumen NFS puede ser pre-poblado con datos, y que estos datos puedes ser compartidos entre pods. NFS puede ser montado por múltiples escritores simultáneamente.

Mira el ejemplo NFS para más información.

persistentVolumeClaim

Un volumen persistenceVolumeClain se utiliza para montar un PersistentVolume en tu Pod. PersistentVolumeClaims son una forma en que el usuario "reclama" almacenamiento duradero (como un PersistentDisk GCE o un volumen ISCSI) sin conocer los detalles del entorno de la nube en particular.

Mira la información spbre PersistentVolumes para más detalles.

portworxVolume

Un portworxVolume es un almacenamiento de bloque elástico que corre hiperconvergido con Kubernetes. Almacenamiento de huellas de Portworx en un servidor, niveles basados en capacidades y capacidad agregada en múltiples servidores. Portworx se ejecuta como invitado en máquinas virtuales o en nodos Linux nativos.

Un portworxVolume puede ser creado dinámicamente a través de Kubernetes o puede ser pre-aprovisionado y referido dentro de un Pod. Aquí un Pod de ejemplo refiriendo a un volumen Portworx pre-aprovisionado:

apiVersion: v1
kind: Pod
metadata:
  name: test-portworx-volume-pod
spec:
  containers:
    - image: registry.k8s.io/test-webserver
      name: test-container
      volumeMounts:
        - mountPath: /mnt
          name: pxvol
  volumes:
    - name: pxvol
      # Este volumen portworx debe sxistir con anterioridad.
      portworxVolume:
        volumeID: "pxvol"
        fsType: "<fs-type>"

Para más detalles, mira los ejemplos de volumen Portworx.

projected

Un volumen projected mapea distintas fuentes de volúmenes existentes en un mismo directorio.

Actualmente, se pueden los siguientes tipos de volúmenes:

Se requiere que todas las fuentes estén en el mismo namespace que el Pod. Para más detalles mira el all-in-one volume design document.

Configuración de ejemplo con un secret, un downwardAPI, y un configMap

apiVersion: v1
kind: Pod
metadata:
  name: volume-test
spec:
  containers:
    - name: container-test
      image: busybox
      volumeMounts:
        - name: all-in-one
          mountPath: "/projected-volume"
          readOnly: true
  volumes:
    - name: all-in-one
      projected:
        sources:
          - secret:
              name: mysecret
              items:
                - key: username
                  path: my-group/my-username
          - downwardAPI:
              items:
                - path: "labels"
                  fieldRef:
                    fieldPath: metadata.labels
                - path: "cpu_limit"
                  resourceFieldRef:
                    containerName: container-test
                    resource: limits.cpu
          - configMap:
              name: myconfigmap
              items:
                - key: config
                  path: my-group/my-config

Configuración de ejemplo: secrets con un modo de permisos no predeterminados

apiVersion: v1
kind: Pod
metadata:
  name: volume-test
spec:
  containers:
    - name: container-test
      image: busybox
      volumeMounts:
        - name: all-in-one
          mountPath: "/projected-volume"
          readOnly: true
  volumes:
    - name: all-in-one
      projected:
        sources:
          - secret:
              name: mysecret
              items:
                - key: username
                  path: my-group/my-username
          - secret:
              name: mysecret2
              items:
                - key: password
                  path: my-group/my-password
                  mode: 511

Cada volumen proyectado está listado en spec bajo sources. Los parámetros son casi los mismos salvo dos excepciones:

  • Para los secrets, el campo secretName ha sido cambiado a name para ser consistente con el nombre del configMap.
  • El defaultMode solo se puede especificar en el nivel proyectado y no para cada fuente de volumen. Sin, como se muestra arriba, puedes establecer explícitamente el mode para cada proyección individual.

Cuando la función TokenRequestProjection está habilitada, puedes inyectar el token para el service account actual en un Pod en la ruta especificada. Por ejemplo:

apiVersion: v1
kind: Pod
metadata:
  name: sa-token-test
spec:
  containers:
    - name: container-test
      image: busybox
      volumeMounts:
        - name: token-vol
          mountPath: "/service-account"
          readOnly: true
  volumes:
    - name: token-vol
      projected:
        sources:
          - serviceAccountToken:
              audience: api
              expirationSeconds: 3600
              path: token

El Pod de ejemplo tiene un volumen proyectado que contiene el token del serviceAccount inyectado. Este token se puede usar por el contenedor de un Pod para acceder a la API del servidor de Kubernetes. El audience contiene la audiencia dirigida del token. Un recipiente del token debe identificarse a sí mismo con un identificador especificado en la audiencia del token, de lo contrario debería rechazar el token. Este campo es opcional y por defecto tiene el valor del identificador del servidor API.

EL campo expirationSeconds es la duración esperada de la validez del token del serviceAccount. Su valor por defecto es 1 hora y debe ser al menos 10 minutos (600 segundos). Un administrador puede limitar su valor máximo al especificar la opción --service-account-max-token-expiration para el servidor API. El campo path especifica una ruta relativa al punto de montaje del volumen proyectado.

quobyte

Un volumen quobyte permite montar un volumen Quobyte en tu Pod.

Quobyte soporta el Container Storage Interface. CSI es el complemento recomendado para usar Quobyte dentro de Kubernetes. El proyecto Github de Quobyte tiene instrucciones para desplegar usando CSI, junto con ejemplos.

rbd

Un volumen rbd permite montar un volumen Rados Block Device (RBD) en tu Pod. A diferencia de emptyDir, que se borra cuando el Pod es removido, el contenido de un volumen rbd es preservado y el volumen se desmonta. Esto significa que un volumen RBD puede ser pre-poblado con datos, y que estos datos pueden ser compartidos entre pods.

Una función de RBD es que solo se puede montar como de solo lectura por múltiples consumidores simultáneamente. Esto significa que puedes pre-poblar un volumen con tu conjunto de datos y luego servirlo en paralelo desde tantos pods como necesites. Desafortunadamente, los volúmenes RBD solo se pueden montar por un único consumidor en modo lectura-escritura. No se permiten escritores simultáneos.

Mira el ejemplo RBD para más detalles.

scaleIO (deprecado)

ScaleIO es una plataforma de almacenamiento basada en software que usa el hardware existente para crear clústeres de almacenamiento en red de bloques compartidos escalables. El complemento de volumen scaleIO permite a los pods desplegados acceder a volúmenes existentes ScaleIO. Para información acerca de aprovisionamiento dinámico de nuevos persistence volumen claims, mira ScaleIO persistent volumes

El siguiente ejemplo es una configuración de un Pod con ScaleIO:

apiVersion: v1
kind: Pod
metadata:
  name: pod-0
spec:
  containers:
    - image: registry.k8s.io/test-webserver
      name: pod-0
      volumeMounts:
        - mountPath: /test-pd
          name: vol-0
  volumes:
    - name: vol-0
      scaleIO:
        gateway: https://localhost:443/api
        system: scaleio
        protectionDomain: sd0
        storagePool: sp1
        volumeName: vol-0
        secretRef:
          name: sio-secret
        fsType: xfs

Para más detalles, mira los ejemplos de ScaleIO

secret

Un volumen seret se utiliza para pasar información sensible, como contraseñas, a los Pods. Puedes guardar secrets en la API de Kubernetes y montarlos como ficheros para usarlos con los pods sin acoplarlos con Kubernetes directamente. Los volúmenes secret son respaldados por tmpfs (un sistema de ficheros respaldado por la RAM) así que nunca se escriben en un almacenamiento no volátil.

Para más detalles, mira Configurando Secrets.

storageOS

Un volumen storageos permite montar un volumen existente StorageOS en tu Pod.

StorageOS corre como un contenedor dentro de tu contenedor Kubernetes, haciendo accesible el almacenamiento local o adjunto desde cualquier node dentro del cluster de Kubernetes. Los datos pueden ser replicados para protegerlos contra fallos del nodo. Este aprovisionamiento y compresión pueden mejorar el uso y reducir costes.

El contenedor StorageOs requiere Linux de 64 bits y no tiene dependencias adicionales. Una licencia gratuita para desarrolladores está disponible.

El siguiente ejemplo es una configuración de un Pod con Storage OS:

apiVersion: v1
kind: Pod
metadata:
  labels:
    name: redis
    role: master
  name: test-storageos-redis
spec:
  containers:
    - name: master
      image: kubernetes/redis:v1
      env:
        - name: MASTER
          value: "true"
      ports:
        - containerPort: 6379
      volumeMounts:
        - mountPath: /redis-master-data
          name: redis-data
  volumes:
    - name: redis-data
      storageos:
        # El volumen `redis-vol01` debe existir dentro de StorageOS en el namespace `default`.
        volumeName: redis-vol01
        fsType: ext4

Para más información sobre StorageOS, aprovisionamiento dinámico, y PersistentVolumeClaims, mira los ejemplos de StorageOS examples.

vsphereVolume

Un volumen vsphereVolume se usa para montar un volumen VMDK vSphere en tu Pod. El contenido de un volumen es preservado cuando se desmonta. Tiene soporte para almacén de datos VMFS y VSAN.

Creando un volumen VMDK

Elige uno de los siguientes métodos para crear un VMDK.

Primero entra mediante ssh en ESX, luego usa uno de los siguientes comandos para crear un VMDK:

vmkfstools -c 2G /vmfs/volumes/DatastoreName/volumes/myDisk.vmdk

Usa el siguiente comando para crear un VMDK:

vmware-vdiskmanager -c -t 0 -s 40GB -a lsilogic myDisk.vmdk

Ejemplo de configuración vSphere VMDK

apiVersion: v1
kind: Pod
metadata:
  name: test-vmdk
spec:
  containers:
    - image: registry.k8s.io/test-webserver
      name: test-container
      volumeMounts:
        - mountPath: /test-vmdk
          name: test-volume
  volumes:
    - name: test-volume
      # Este volumen VMDK ya debe existir.
      vsphereVolume:
        volumePath: "[DatastoreName] volumes/myDisk"
        fsType: ext4

Para mayor información, mira el ejemplo de vSphere volume.

Migración CSI vSphere

FEATURE STATE: Kubernetes v1.19 [beta]
Cuando la función CSIMigration está habilitada, redirige todas las operaciones de complemento desde el complemento existente en el árbol al controlador CSI csi.vsphere.vmware.com. Para usar esta función, el controlador vSphere CSI debe estar instalado en el clúster y las feature gates CSIMigration y CSIMigrationvSphere deben estar habilitadas.

Esto también requiere que la versión de vSphere vCenter/ESXi sea la 7.0u1 y la versión mínima de HW version sea VM versión 15.

migración completa de vSphere CSI

FEATURE STATE: Kubernetes v1.19 [beta]
Para apagar el complemento vsphereVolume y no cargarlo por el administrador del controlador y el kubelet, necesitas establecer eta función a true. Debes instalar un controlador de tipo csi.vsphere.vmware.com en todos los nodos worker.

Uso de subPath

Algunas veces es útil compartir un volumen para múltiples usos en un único Pod. La propiedad volumeMounts[*].subPath especifica una sub-ruta dentro del volumen referenciado en lugar de su raíz.

El siguiente ejemplo muestra cómo configurar un Pod con la pila LAMP (Linux Apache MySQL PHP) usando un único volumen compartido. Esta configuración de ejemplo usando subPath no se recomienda para su uso en producción.

El código de la aplicación PHP y los recursos apuntan al directorio html del volumen y la base de datos MySQL se almacena en el directorio mysql. Por ejemplo: The PHP application's code and assets map to the volume's html folder and the MySQL database is stored in the volume's mysql folder. For example:

apiVersion: v1
kind: Pod
metadata:
  name: my-lamp-site
spec:
  containers:
    - name: mysql
      image: mysql
      env:
        - name: MYSQL_ROOT_PASSWORD
          value: "rootpasswd"
      volumeMounts:
        - mountPath: /var/lib/mysql
          name: site-data
          subPath: mysql
    - name: php
      image: php:7.0-apache
      volumeMounts:
        - mountPath: /var/www/html
          name: site-data
          subPath: html
  volumes:
    - name: site-data
      persistentVolumeClaim:
        claimName: my-lamp-site-data

Uso de subPath con variables de entorno expandidas

FEATURE STATE: Kubernetes v1.17 [stable]
Usa el campo subPathExpr para construir un nombre de directorio subPath desde variables de entorno de la API. Las propiedades subPath y subPathExpr son mutuamente exclusivas.

En este ejemplo, un Pod usa subPathExpr para crear un directorio pod1 dentro del volumen hostPath var/logs/pods. El volumen hostPath toma el nombre del Pod desde la downwardAPI. El directorio anfitrión var/log/pods/pod1 se monta en /logs en el contenedor.

apiVersion: v1
kind: Pod
metadata:
  name: pod1
spec:
  containers:
    - name: container1
      env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: metadata.name
      image: busybox
      command:
        [
          "sh",
          "-c",
          "while [ true ]; do echo 'Hello'; sleep 10; done | tee -a /logs/hello.txt",
        ]
      volumeMounts:
        - name: workdir1
          mountPath: /logs
          subPathExpr: $(POD_NAME)
  restartPolicy: Never
  volumes:
    - name: workdir1
      hostPath:
        path: /var/log/pods

Recursos

El medio de almacenamiento (como un disco o un SSD) de un volumen emptyDir se determina por el medio del sistema de archivos que contiene el directorio raíz del kubelet (típicamente /var/lib/kubelet). No hay límite de cuánto espacio puede consumir un volumen emptydir o hostPath, y no hay aislamiento entre contenedores o entre pods.

Para aprender más sobre requerir espacio usando una espacificación de recurso, mira cómo administrar recursos.

Complementos de volúmenes fuera del árbol

Los complementos de volumen fuera del árbol incluyen Container Storage Interface (CSI) y FlexVolume. Estos complementos permiten a los proveedores de almacenamiento crear complementos de almacenamiento personalizados sin añadir su código fuente al repositorio de Kubernetes.

Anteriormente, todos los complementos de volumen estaban "en el árbol". Los complementos "en el árbol" se construían, enlazaban, compilaban y enviaban con los binarios del núcleo de Kubernetes. Esto significaba que agregar un nuevo sistema de almacenamiento a Kubernetes (un complemento de volumen) requería verificar el código en el repositorio de código del núcleo de Kubernetes.

Tanto CSI como FlexVolume permiten que se desarrollen complementos de volúmenes independientemente del código base de Kubernetes, y se desplieguen (instalen) en los clústeres de Kubernetes como extensiones.

Para los proveedores de almacenamiento que buscan crear un complemento de volumen fuera del árbol, por favor refiéranse a Preguntas frecuentees de complementos de volumen.

csi

La interfaz de almacenamiento del contenedor (CSI) define una interfaz estándar para sistemas de orquestación del contenedor (como Kubernetes) para exponer sistemas de almacenamiento arbitrario a sus cargas de trabajo del contenedor.

Por favor, lee la propuesta de diseño CSI para más información.

Una vez que se despliega un controlador de volumen CSI compatible, los usuarios pueden usar el tipo de volumen csi para adjuntar o montar los volúmenes expuestos por el controlador CSI.

Un volumen csi puede ser usado en un Pod en tres maneras distintas:

Los siguientes campos están disponibles para que los administradores de almacenamiento configuren el volumen persistente CSI

  • driver: Un valor de cadena de caracteres que especifica el nombre del controlador de volumen a usar. Este valor debe corresponder al valor de respuesta en el GetPluginInfoResponse por el controlador CSI tal como se define en la especificación CSI. Es usado por Kubernetes para identificar cuál controlador llamar, y por los componentes del controlador CSI para identificar cuáles objetos PV pertenecen al controlador CSI.
  • volumenHandle: Un valor de cadena de caracteres que identifica el volumen unívocamente. Este valor debe corresponder al valor en el campo volumen.id del CreateVolumeResponse por el controlador CSI como se define en la especificación CSI spec. El valor es pasado como volume.id en todas las llamadas al controlador de volumen CSI cuando referencias el volumen.
  • readOnly: Un valor booleano opcional que indica si el volumen es para ser "ControllerPublished" (adjuntado) como solo lectura. Por defecto es falso. Este valor es pasado el controlador CSI en el campo readOnly en el ControllerPublishVolumeRequest.
  • fsType: Si el VolumeModedel PV es Filesystem entonces este campo se puede usar para especificar el sistema de archivos que debería usarse para montar el volumen. Si el volumen no ha sido formateado y soportado, este valor se utilizará para formatear el volumen. Este valor se para al controlador CSI con el campo VolumeCapability de ControllerPublishVolumeRequest, NodeStageVolumeRequest, y NodePublishVolumeRequest.
  • volumeAttributes: Un mapa de cadena de caracteres que especifica las propiedades estáticas de un volumen. Este mapa debe corresponder al map devuelto por el campo volume.attributes del CreateVolumeResponse por el controlador CSI tal como se define en la especificación CSI spec. El mapa es pasado al controlador CSI con el campo volume.context en el ControllerPublishVolumeRequest, NodeStageVolumeRequest, y NodePublishVolumeRequest.
  • controllerPublishSecretRef: Una referencia al objeto secret que contiene información sensible para pasar al controlador CSI para completar las llamadas CSI ControllerPublishVolume y ControllerUnpublishVolume. Este campo es opcional, y puede estar vacío si no se requiere un secret. Si el Secret contiene más de un secret, se pasan todos los secrets.
  • nodeStageSecretRef: Una referencia al objeto secret que contiene información sensible a pasar al controlador CSI para completar la llamada CSI NodeStageVolume. Este ampo es opcional, y puede estar vacío si no se requiere un secret. Si el Secret contiene más de un secret, todos los secrets son pasados.
  • nodePublishSecretRef: Una referencia al objeto que contiene información sensible a pasar al controlador CSI para completar la llamada CSI NodePublishVolume. Este ampo es opcional, y puede estar vacío si no se requiere un secret. Si el Secret contiene más de un secret, todos los secrets son pasados.

Soporte de volumen CSI de fila de bloques

FEATURE STATE: Kubernetes v1.18 [stable]

Los proveedores con controladores CSI externos pueden implementar soporte de volumen de bloques sin procesar en cargas de trabajo de Kubernetes.

Puedes configurar tu You can set up your PersistentVolume/PersistentVolumeClaim with raw block volume support como de costumbre, sin ningún cambio específico CSI.

Volúmenes efímeros CSI

FEATURE STATE: Kubernetes v1.16 [beta]

Puedes configurar directamente volúmenes CSI dentro de la especificación del Pod. Los volúmenes especificados de esta manera son efímeros y no se persisten entre reinicios del Pod. Mira Volúmenes efímeros para más información.

Para más información de cómo desarrollador un controlador CSI, mira la documentación kubernetes-csi

Migrando a controladores CSI desde complementos en el árbol.

FEATURE STATE: Kubernetes v1.17 [beta]
La función CSIMigration, cuando está habilitada, dirige todas las operaciones hacia complementos existentes en el árbol a complementos CSI correspondientes (que se espera que estén instalados y configurados). Como resultado, los operadores no tienen que hacer ningún cambio de configuración a las clases de almacenamiento, PersistentVolumes o PersistentVolumeClaims (refiriéndose a complementos en el árbol) cuando haces la transición a un controlador CSI que un reemplaza complemento en el árbol.

Las operaciones y funciones que están soportadas incluye: aprovisionamiento/borrado, adjuntar/separar, montar/desmontar y redimensionar volúmenes.

In-tree plugins that support CSIMigration and have a corresponding CSI driver implemented are listed in Types of Volumes.

flexVolume

FlexVolume is an out-of-tree plugin interface that has existed in Kubernetes since version 1.2 (before CSI). It uses an exec-based model to interface with drivers. The FlexVolume driver binaries must be installed in a pre-defined volume plugin path on each node and in some cases the control plane nodes as well.

Pods interact with FlexVolume drivers through the flexvolume in-tree volume plugin. For more details, see the FlexVolume examples.

Propagación del montaje

La propagación del montaje permite compartir volúmenes montados por un contenedor para otros contenedores en el mismo Pod, o aun para otros pods en el mismo nodo.

La propagación del montaje de un volumen es controlada por el campo mountPropagation en containers[*].volumeMounts. Sus valores son:

  • None - Este montaje de volumen no recibirá ningún montaje posterior que el host haya montado en este volumen o en cualquiera de sus subdirectorios. De manera similar, los montajes creados por el contenedor no serán visibles en el host. Este es el modo por defecto.

    Este modo es igual la propagación del montaje private tal como se describe en la documentación Linux kernel documentation

  • HostToContainer - Este montaje de volumen recibirá todos los montajes subsecuentes que son montados a este volumen o cualquiera de sus subdirectorios.

    En otras palabras, si el host monta algo dentro del montaje del volumen, el contenedor lo verá montado allí.

    De manera similar, si cualquier Pod con propagación de montaje Bidirectional al mismo volumen monta algo allí, el contenedor con propagación de montaje HostToContainer lo verá.

    Este modo es igual a la propagación de montaje rslave tal como se describe en la documentación del kernel de Linux

  • Bidirectional- Este montaje de volumen se comporta de la misma manera de el montajeHostToContainer. Adicionalmente, todos los montajes de volúmenes creados por el contenedor serán propagados de vuelta al host y a todos los contenedores de todos los pods que usan el mismo volumen.

    Un uso típico para este modo es un Pod con un FlexVolumen o un controlador CSI o un Pod que necesita montar al en el host usando un volumen hostPath.

    Este modo es igual a la propagación de montaje rshared tal como se describe en la documentación del kernel de Linux documentación

Configuration

Before mount propagation can work properly on some deployments (CoreOS, RedHat/Centos, Ubuntu) mount share must be configured correctly in Docker as shown below.

Edit your Docker's systemd service file. Set MountFlags as follows:

MountFlags=shared

Or, remove MountFlags=slave if present. Then restart the Docker daemon:

sudo systemctl daemon-reload
sudo systemctl restart docker

Siguientes pasos

Sigue un ejemplo de desplegar WordPrss y MySQL con volúmenes persistentes.

6.2 - Snapshots de Volúmenes

En Kubernetes, un VolumeSnapshot representa un Snapshot de un volumen en un sistema de almacenamiento. Este documento asume que está familiarizado con volúmenes persistentes de Kubernetes.

Introducción

Al igual que los recursos de API PersistentVolume y PersistentVolumeClaim se utilizan para aprovisionar volúmenes para usuarios y administradores, VolumeSnapshotContent y VolumeSnapshot se proporcionan para crear Snapshots de volumen para usuarios y administradores.

Un VolumeSnapshotContent es un Snapshot tomado de un volumen en el clúster que ha sido aprovisionado por un administrador. Es un recurso en el clúster al igual que un PersistentVolume es un recurso de clúster.

Un VolumeSnapshot es una solicitud de Snapshot de un volumen por parte del usuario. Es similar a un PersistentVolumeClaim.

VolumeSnapshotClass permite especificar diferentes atributos que pertenecen a un VolumeSnapshot. Estos atributos pueden diferir entre Snapshots tomados del mismo volumen en el sistema de almacenamiento y, por lo tanto, no se pueden expresar mediante el mismo StorageClass de un PersistentVolumeClaim.

Los Snapshots de volumen brindan a los usuarios de Kubernetes una forma estandarizada de copiar el contenido de un volumen en un momento determinado, sin crear uno completamente nuevo. Esta funcionalidad permite, por ejemplo, a los administradores de bases de datos realizar copias de seguridad de las bases de datos antes de realizar una edición o eliminar modificaciones.

Cuando utilicen esta función los usuarios deben tener en cuenta lo siguiente:

  • Los objetos de API VolumeSnapshot, VolumeSnapshotContent, y VolumeSnapshotClass son CRDs, y no forman parte de la API principal.
  • La compatibilidad con VolumeSnapshot solo está disponible para controladores CSI.
  • Como parte del proceso de implementación de VolumeSnapshot, el equipo de Kubernetes proporciona un controlador de Snapshot para implementar en el plano de control y un sidecar auxiliar llamado csi-snapshotter para implementar junto con el controlador CSI. El controlador de Snapshot observa los objetos VolumeSnapshot y VolumeSnapshotContent y es responsable de la creación y eliminación del objeto VolumeSnapshotContent. El sidecar csi-snapshotter observa los objetos VolumeSnapshotContent y activa las operaciones CreateSnapshot y DeleteSnapshot en un punto final CSI.
  • También hay un servidor webhook de validación que proporciona una validación más estricta en los objetos Snapshot. Esto debe ser instalado por las distribuciones de Kubernetes junto con el controlador de Snapshots y los CRDs, no los controladores CSI. Debe instalarse en todos los clústeres de Kubernetes que tengan habilitada la función de Snapshot.
  • Los controladores CSI pueden haber implementado o no la funcionalidad de Snapshot de volumen. Los controladores CSI que han proporcionado soporte para Snapshot de volumen probablemente usarán csi-snapshotter. Consulte CSI Driver documentation para obtener más detalles.
  • Los CRDs y las instalaciones del controlador de Snapshot son responsabilidad de la distribución de Kubernetes.

Ciclo de vida de un Snapshot de volumen y el contenido de un Snapshot de volumen.

VolumeSnapshotContents son recursos en el clúster. VolumeSnapshots son solicitudes de esos recursos. La interacción entre VolumeSnapshotContents y VolumeSnapshots sigue este ciclo de vida:

Snapshot del volumen de aprovisionamiento

Hay dos formas de aprovisionar los Snapshots: aprovisionadas previamente o aprovisionadas dinámicamente.

Pre-aprovisionado

Un administrador de clúster crea una serie de VolumeSnapshotContents. Llevan los detalles del Snapshot del volumen real en el sistema de almacenamiento que está disponible para que lo utilicen los usuarios del clúster. Existen en la API de Kubernetes y están disponibles para su consumo.

Dinámica

En lugar de utilizar un Snapshot preexistente, puede solicitar que se tome una Snapshot dinámicamente de un PersistentVolumeClaim. El VolumeSnapshotClass especifica los parámetros específicos del proveedor de almacenamiento para usar al tomar una Snapshot.

Vinculante

El controlador de Snapshots maneja el enlace de un objeto VolumeSnapshot con un objeto VolumeSnapshotContent apropiado, tanto en escenarios de aprovisionamiento previo como de aprovisionamiento dinámico. El enlace es un mapeo uno a uno.

En el caso de un enlace aprovisionado previamente, el VolumeSnapshot permanecerá sin enlazar hasta que se cree el objeto VolumeSnapshotContent solicitado.

Persistent Volume Claim como Snapshot Source Protection

El propósito de esta protección es garantizar que los objetos de la API PersistentVolumeClaim en uso, no se eliminen del sistema mientras se toma un Snapshot (ya que esto puede resultar en la pérdida de datos).

Mientras se toma un Snapshot de un PersistentVolumeClaim, ese PersistentVolumeClaim está en uso. Si elimina un objeto de la API PersistentVolumeClaim en uso activo como fuente de Snapshot, el objeto PersistentVolumeClaim no se elimina de inmediato. En cambio, la eliminación del objeto PersistentVolumeClaim se pospone hasta que el Snapshot esté readyToUse o se cancele.

Borrar

La eliminación se activa al eliminar el objeto VolumeSnapshot, y se seguirá la DeletionPolicy. Sí DeletionPolicy es Delete, entonces el Snapshot de almacenamiento subyacente se eliminará junto con el objeto VolumeSnapshotContent. Sí DeletionPolicy es Retain, tanto el Snapshot subyacente como el VolumeSnapshotContent permanecen.

VolumeSnapshots

Cada VolumeSnapshot contiene una especificación y un estado.

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: new-snapshot-test
spec:
  volumeSnapshotClassName: csi-hostpath-snapclass
  source:
    persistentVolumeClaimName: pvc-test

persistentVolumeClaimName es el nombre de la fuente de datos PersistentVolumeClaim para el Snapshot. Este campo es obligatorio para aprovisionar dinámicamente un Snapshot.

Un Snapshot de volumen puede solicitar una clase particular especificando el nombre de un VolumeSnapshotClass utilizando el atributo volumeSnapshotClassName. Si no se establece nada, se usa la clase predeterminada si está disponible.

Para los Snapshots aprovisionadas previamente, debe especificar un volumeSnapshotContentName como el origen del Snapshot, como se muestra en el siguiente ejemplo. El campo de origen volumeSnapshotContentName es obligatorio para los Snapshots aprovisionados previamente.

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: test-snapshot
spec:
  source:
    volumeSnapshotContentName: test-content

Contenido del Snapshot de volumen

Cada VolumeSnapshotContent contiene una especificación y un estado. En el aprovisionamiento dinámico, el controlador común de Snapshots crea objetos VolumeSnapshotContent. Aquí hay un ejemplo:

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotContent
metadata:
  name: snapcontent-72d9a349-aacd-42d2-a240-d775650d2455
spec:
  deletionPolicy: Delete
  driver: hostpath.csi.k8s.io
  source:
    volumeHandle: ee0cfb94-f8d4-11e9-b2d8-0242ac110002
  volumeSnapshotClassName: csi-hostpath-snapclass
  volumeSnapshotRef:
    name: new-snapshot-test
    namespace: default
    uid: 72d9a349-aacd-42d2-a240-d775650d2455

volumeHandle es el identificador único del volumen creado en el backend de almacenamiento y devuelto por el controlador CSI durante la creación del volumen. Este campo es obligatorio para aprovisionar dinámicamente un Snapshot. Especifica el origen del volumen del Snapshot.

Para los Snapshots aprovisionados previamente, usted (como administrador del clúster) es responsable de crear el objeto VolumeSnapshotContent de la siguiente manera.

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotContent
metadata:
  name: new-snapshot-content-test
spec:
  deletionPolicy: Delete
  driver: hostpath.csi.k8s.io
  source:
    snapshotHandle: 7bdd0de3-aaeb-11e8-9aae-0242ac110002
  volumeSnapshotRef:
    name: new-snapshot-test
    namespace: default

snapshotHandle es el identificador único del Snapshot de volumen creado en el backend de almacenamiento. Este campo es obligatorio para las Snapshots aprovisionadas previamente. Especifica el ID del Snapshot CSI en el sistema de almacenamiento que representa el VolumeSnapshotContent.

Aprovisionamiento de Volúmenes a partir de Snapshots

Puede aprovisionar un nuevo volumen, rellenado previamente con datos de una Snapshot, mediante el campo dataSource en el objeto PersistentVolumeClaim.

Para obtener más detalles, consulte Volume Snapshot and Restore Volume from Snapshot.

6.3 - Volúmenes Persistentes

Este documento describe volúmenes persistentes en Kubernetes. Se sugiere familiarizarse con volumes, StorageClasses y VolumeAttributesClasses.

Introducción

La gestión del almacenamiento es un problema distinto al de la gestión de instancias de cómputo. El subsistema PersistentVolume proporciona una API para usuarios y administradores que abstrae los detalles de cómo se proporciona el almacenamiento de cómo se consume. Para hacer esto, introducimos dos nuevos recursos de API: PersistentVolume y PersistentVolumeClaim.

Un PersistentVolume (PV) es una pieza de almacenamiento en el clúster que ha sido provisionada por un administrador o provisionada dinámicamente usando Storage Classes. Es un recurso en el clúster al igual que un nodo es un recurso del clúster. Los PVs son plugins de volumen como Volúmenes, que tienen un ciclo de vida independiente de cualquier Pod individual que use el PV. Este objeto API captura los detalles de la implementación del almacenamiento, sea NFS, iSCSI o un sistema de almacenamiento específico de un proveedor de nube.

Un PersistentVolumeClaim (PVC) es una solicitud de almacenamiento por parte de un usuario. Es similar a un Pod. Los Pods consumen recursos de nodos y los PVCs consumen recursos de PVs. Los Pods pueden solicitar niveles específicos de recursos (CPU y Memoria). Las solicitudes pueden pedir tamaños específicos y modos de acceso (por ejemplo, pueden montarse como ReadWriteOnce, ReadOnlyMany, ReadWriteMany o ReadWriteOncePod, ver Modos de Acceso).

Aunque los PersistentVolumeClaims permiten a un usuario consumir recursos de almacenamiento abstractos, es común que los usuarios necesiten PersistentVolumes con propiedades variadas, tales como rendimiento, para diferentes problemas. Los administradores del clúster necesitan poder ofrecer una variedad de PersistentVolumes que difieran en más aspectos que el tamaño y los modos de acceso, sin exponer a los usuarios a los detalles de cómo se implementan esos volúmenes. Para estas necesidades, existe el recurso StorageClass.

Vea el tutorial detallado con ejemplos prácticos.

Ciclo de vida de un volumen y una solicitud

Los PVs son recursos en el clúster. Los PVCs son solicitudes para esos recursos y también actúan como comprobantes de reclamo al recurso. La interacción entre PVs y PVCs sigue este ciclo de vida:

Aprovisionamiento

Hay dos formas en que los PVs pueden ser aprovisionados: estáticamente o dinámicamente.

Estático

Un administrador del clúster crea un número de PVs. Llevan los detalles del almacenamiento real, que está disponible para uso de los usuarios del clúster. Existen en la API de Kubernetes y están disponibles para su consumo.

Dinámico

Cuando ninguno de los PVs estáticos creados por el administrador coincide con un PersistentVolumeClaim de un usuario, el clúster puede intentar aprovisionar dinámicamente un volumen especialmente para el PVC. Este aprovisionamiento se basa en StorageClasses: el PVC debe solicitar una clase de almacenamiento y el administrador debe haber creado y configurado esa clase para que ocurra el aprovisionamiento dinámico. Las solicitudes que piden la clase "" efectivamente desactivan el aprovisionamiento dinámico para sí mismas.

Para habilitar el aprovisionamiento dinámico de almacenamiento basado en clases de almacenamiento, el administrador del clúster necesita habilitar el controlador de admisión DefaultStorageClass admission controller en el servidor API. Esto se puede hacer, por ejemplo, asegurándose de que DefaultStorageClass esté entre la lista de valores delimitados por comas y ordenados para la bandera --enable-admission-plugins del componente del servidor API. Para más información sobre las opciones de línea de comandos del servidor API, consulta la documentación de kube-apiserver.

Binding

Un usuario crea, o en el caso de aprovisionamiento dinámico, ya ha creado, un PersistentVolumeClaim con una cantidad específica de almacenamiento solicitado y con ciertos modos de acceso. Un bucle de control en el plano de control observa los nuevos PVCs, encuentra un PV coincidente (si es posible) y los une. Si un PV fue aprovisionado dinámicamente para un nuevo PVC, el bucle siempre unirá ese PV al PVC. De lo contrario, el usuario siempre obtendrá al menos lo que pidió, pero el volumen puede ser superior a lo solicitado. Una vez unidos, los enlaces PersistentVolumeClaim son exclusivos, independientemente de cómo se hayan unido. Una unión PVC a PV es un mapeo uno a uno, utilizando un ClaimRef que es una unión bidireccional entre el PersistentVolume y el PersistentVolumeClaim.

Las solicitudes permanecerán sin unir indefinidamente si no existe un volumen coincidente. Las solicitudes se unirán a medida que los volúmenes coincidentes estén disponibles. Por ejemplo, un clúster aprovisionado con muchos PVs de 50Gi no coincidirá con un PVC que solicite 100Gi. El PVC puede unirse cuando se agregue un PV de 100Gi al clúster.

Uso

Los Pods usan las solicitudes como volúmenes. El clúster inspecciona la solicitud para encontrar el volumen unido y monta ese volumen para un Pod. Para los volúmenes que admiten múltiples modos de acceso, el usuario especifica qué modo desea cuando utiliza su solicitud como volumen en un Pod.

Una vez que un usuario tiene una solicitud y esa solicitud está unida, el PV unido pertenece al usuario mientras lo necesiten. Los usuarios programan Pods y acceden a sus PVs reclamados incluyendo una sección de persistentVolumeClaim en el bloque volumes de un Pod. Consulta Solicitudes como Volúmenes para más detalles sobre esto.

Protección de Objeto de Almacenamiento en Uso

El propósito de la función de Protección de Objeto de Almacenamiento en Uso es asegurar que las PersistentVolumeClaims (PVCs) en uso activo por un Pod y los PersistentVolumes (PVs) que están unidos a PVCs no sean eliminados del sistema, ya que esto podría resultar en pérdida de datos.

Si un usuario elimina un PVC en uso activo por un Pod, el PVC no se elimina inmediatamente. La eliminación del PVC se pospone hasta que el PVC ya no esté en uso activo por ningún Pod. Además, si un administrador elimina un PV que está unido a un PVC, el PV no se elimina inmediatamente. La eliminación del PV se pospone hasta que el PV ya no esté unido a ningún PVC.

Puedes ver que un PVC está protegido cuando el estado del PVC es Terminating y la lista de Finalizers incluye kubernetes.io/pvc-protection:

kubectl describe pvc hostpath
Name:          hostpath
Namespace:     default
StorageClass:  example-hostpath
Status:        Terminating
Volume:
Labels:        <none>
Annotations:   volume.beta.kubernetes.io/storage-class=example-hostpath
               volume.beta.kubernetes.io/storage-provisioner=example.com/hostpath
Finalizers:    [kubernetes.io/pvc-protection]
...

Puedes ver que un PV está protegido cuando el estado del PV es Terminating y la lista de Finalizers incluye también kubernetes.io/pv-protection:

kubectl describe pv task-pv-volume
Name:            task-pv-volume
Labels:          type=local
Annotations:     <none>
Finalizers:      [kubernetes.io/pv-protection]
StorageClass:    standard
Status:          Terminating
Claim:
Reclaim Policy:  Delete
Access Modes:    RWO
Capacity:        1Gi
Message:
Source:
    Type:          HostPath (bare host directory volume)
    Path:          /tmp/data
    HostPathType:
Events:            <none>

Reclamación

Cuando un usuario ha concluido con el uso de su volumen, puede eliminar los objetos PVC de la API que permite la recuperación del recurso. La política de reclamación para un PersistentVolume le dice al clúster que hacer con el volumen después de que ha sido liberado de su solicitud. Actualmente, los volúmenes pueden ser Retenidos, Reciclados o Eliminados.

Retener

La política de reclamación Retain permite la recuperación manual del recurso. Cuando se elimina el PersistentVolumeClaim, el PersistentVolume todavía existe y el volumen se considera "liberado". Pero aún no está disponible para otra solicitud porque los datos del reclamante anterior permanecen en el volumen. Un administrador puede reclamar manualmente el volumen con los siguientes pasos.

  1. Eliminar el PersistentVolume. El recurso de almacenamiento asociado en la infraestructura externa todavía existe después de que se haya eliminado el PV.
  2. Limpiar manualmente los datos en el recurso de almacenamiento asociado en consecuencia.
  3. Eliminar manualmente el recurso de almacenamiento asociado.

Si deseas reutilizar el mismo recurso de almacenamiento, crea un nuevo PersistentVolume con la misma definición de recurso de almacenamiento.

Eliminar

Para los plugins de volumen que soportan la política de reclamación Delete, la eliminación remueve tanto el objeto PersistentVolume de Kubernetes, como el recurso de almacenamiento asociado en la infraestructura externa. Los volúmenes que fueron aprovisionados dinámicamente heredan la política de reclamación de su StorageClass, que por defecto es Delete. El administrador debe configurar la StorageClass de acuerdo con las expectativas de los usuarios; de lo contrario, el PV debe ser editado o modificado después de que se haya creado. Consulta Cambiar la Política de Reclamación de un PersistentVolume.

Reciclar

Si es compatible con el plugin de volumen subyacente, la política de reclamación Recycle realiza un borrado básico (rm -rf /elvolumen/*) en el volumen y lo hace disponible nuevamente para una nueva solicitud.

Sin embargo, un administrador puede configurar una plantilla de Pod reciclador personalizada usando los argumentos de línea de comandos del administrador del controlador de Kubernetes como se describe en la referencia. La plantilla de Pod reciclador personalizada debe contener una especificación de volumes, como se muestra en el ejemplo a continuación:

apiVersion: v1
kind: Pod
metadata:
  name: pv-recycler
  namespace: default
spec:
  restartPolicy: Never
  volumes:
    - name: vol
      hostPath:
        path: /any/path/it/will/be/replaced
  containers:
    - name: pv-recycler
      image: "registry.k8s.io/busybox"
      command:
        [
          "/bin/sh",
          "-c",
          'test -e /scrub && rm -rf /scrub/..?* /scrub/.[!.]* /scrub/*  && test -z "$(ls -A /scrub)" || exit 1',
        ]
      volumeMounts:
        - name: vol
          mountPath: /scrub

Sin embargo, la ruta particular especificada en la plantilla de Pod reciclador personalizado en la parte de volumes se reemplaza con la ruta particular del volumen que está siendo reciclado.

Finalizador de protección de eliminación de PersistentVolume

FEATURE STATE: Kubernetes v1.23 [alpha]

Los finalizadores pueden agregarse en un PersistentVolume para asegurar que los PersistentVolumes con política de reclamación Delete solo se eliminen después de que se eliminen los almacenamientos subyacentes.

Los finalizadores recién introducidos kubernetes.io/pv-controller y external-provisioner.volume.kubernetes.io/finalizer solo se agregan a los volúmenes provisionados dinámicamente.

El finalizador kubernetes.io/pv-controller se agrega a los volúmenes de plugins integrados. El siguiente es un ejemplo.

kubectl describe pv pvc-74a498d6-3929-47e8-8c02-078c1ece4d78
Name:            pvc-74a498d6-3929-47e8-8c02-078c1ece4d78
Labels:          <none>
Annotations:     kubernetes.io/createdby: vsphere-volume-dynamic-provisioner
                 pv.kubernetes.io/bound-by-controller: yes
                 pv.kubernetes.io/provisioned-by: kubernetes.io/vsphere-volume
Finalizers:      [kubernetes.io/pv-protection kubernetes.io/pv-controller]
StorageClass:    vcp-sc
Status:          Bound
Claim:           default/vcp-pvc-1
Reclaim Policy:  Delete
Access Modes:    RWO
VolumeMode:      Filesystem
Capacity:        1Gi
Node Affinity:   <none>
Message:
Source:
    Type:               vSphereVolume (a Persistent Disk resource in vSphere)
    VolumePath:         [vsanDatastore] d49c4a62-166f-ce12-c464-020077ba5d46/kubernetes-dynamic-pvc-74a498d6-3929-47e8-8c02-078c1ece4d78.vmdk
    FSType:             ext4
    StoragePolicyName:  vSAN Default Storage Policy
Events:                 <none>

El finalizador external-provisioner.volume.kubernetes.io/finalizer se agrega para los volúmenes CSI. El siguiente es un ejemplo:

Name:            pvc-2f0bab97-85a8-4552-8044-eb8be45cf48d
Labels:          <none>
Annotations:     pv.kubernetes.io/provisioned-by: csi.vsphere.vmware.com
Finalizers:      [kubernetes.io/pv-protection external-provisioner.volume.kubernetes.io/finalizer]
StorageClass:    fast
Status:          Bound
Claim:           demo-app/nginx-logs
Reclaim Policy:  Delete
Access Modes:    RWO
VolumeMode:      Filesystem
Capacity:        200Mi
Node Affinity:   <none>
Message:
Source:
    Type:              CSI (a Container Storage Interface (CSI) volume source)
    Driver:            csi.vsphere.vmware.com
    FSType:            ext4
    VolumeHandle:      44830fa8-79b4-406b-8b58-621ba25353fd
    ReadOnly:          false
    VolumeAttributes:      storage.kubernetes.io/csiProvisionerIdentity=1648442357185-8081-csi.vsphere.vmware.com
                           type=vSphere CNS Block Volume
Events:                <none>

Cuando la bandera de funcionalidad CSIMigration{provider} está habilitada para un plugin de volumen integrado específico, el finalizador kubernetes.io/pv-controller se reemplaza por el finalizador external-provisioner.volume.kubernetes.io/finalizer.

Reservando un PersistentVolume

El plano de control puede unir PersistentVolumeClaims a PersistentVolumes correspondientes en el clúster. Sin embargo, si quieres que un PVC se una a un PV específico, necesitas pre-unirlos.

Al especificar un PersistentVolume en un PersistentVolumeClaim, declaras una unión entre ese PV y PVC específicos. Si el PersistentVolume existe y no ha reservado PersistentVolumeClaims a través de su campo claimRef, entonces el PersistentVolume y el PersistentVolumeClaim se unirán.

La unión ocurre independientemente de algunos criterios de coincidencia de volumen, incluida la afinidad de nodo. El plano de control aún verifica que la clase de almacenamiento, los modos de acceso y el tamaño de almacenamiento solicitado sean válidos.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: foo-pvc
  namespace: foo
spec:
  storageClassName: "" # La cadena vacía debe establecerse explícitamente; de lo contrario, se establecerá la StorageClass predeterminada
  volumeName: foo-pv
  ...

Este método no garantiza ningún privilegio de unión al PersistentVolume. Si otros PersistentVolumeClaims pudieran usar el PV que especificas, primero necesitas reservar ese volumen de almacenamiento. Especifica el PersistentVolumeClaim relevante en el campo claimRef del PV para que otros PVCs no puedan unirse a él.

apiVersion: v1
kind: PersistentVolume
metadata:
  name: foo-pv
spec:
  storageClassName: ""
  claimRef:
    name: foo-pvc
    namespace: foo
  ...

Esto es útil si deseas consumir PersistentVolumes que tienen su claimPolicy establecido en Retain, incluyendo casos donde estás reutilizando un PV existente.

Expandiendo Persistent Volume Claims

FEATURE STATE: Kubernetes v1.24 [stable]

El soporte para expandir PersistentVolumeClaims (PVCs) está habilitado por defecto. Puedes expandir los siguientes tipos de volúmenes:

  • azureFile (obsoleto)
  • csi
  • flexVolume (obsoleto)
  • rbd (obsoleto)
  • portworxVolume (obsoleto)

Solo puedes expandir un PVC si el campo allowVolumeExpansion de su clase de almacenamiento está establecido en verdadero.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: example-vol-default
provisioner: vendor-name.example/magicstorage
parameters:
  resturl: "http://192.168.10.100:8080"
  restuser: ""
  secretNamespace: ""
  secretName: ""
allowVolumeExpansion: true

Para solicitar un volumen más grande para un PVC, edita el objeto PVC y especifica un tamaño mayor. Esto desencadena la expansión del volumen que respalda el PersistentVolume subyacente. Nunca se crea un nuevo PersistentVolume para satisfacer la solicitud. En cambio, se redimensiona un volumen existente.

Expansión de volúmenes CSI

FEATURE STATE: Kubernetes v1.24 [stable]

El soporte para la expansión de volúmenes CSI está habilitado por defecto, pero también requiere que un controlador CSI específico soporte la expansión de volumen. Consulta la documentación del controlador CSI específico para obtener más información.

Redimensionando un volumen que contiene un sistema de archivos

Solo puedes redimensionar volúmenes que contienen un sistema de archivos si el sistema de archivos es XFS, Ext3 o Ext4.

Cuando un volumen contiene un sistema de archivos, el sistema de archivos solo se redimensiona cuando un nuevo Pod está utilizando el PersistentVolumeClaim en modo ReadWrite. La expansión del sistema de archivos se realiza cuando un Pod se está iniciando o cuando un Pod está en ejecución y el sistema de archivos subyacente soporta la expansión en línea.

Los FlexVolumes (obsoletos desde Kubernetes v1.23) permiten redimensionar si el controlador está configurado con la capacidad RequiresFSResize en true. El FlexVolume se puede redimensionar al reiniciar el Pod.

Redimensionando un PersistentVolumeClaim en uso

FEATURE STATE: Kubernetes v1.24 [stable]

En este caso, no necesitas eliminar y recrear un Pod o despliegue que esté utilizando un PVC existente. Cualquier PVC en uso se vuelve automáticamente disponible para su Pod tan pronto como su sistema de archivos haya sido expandido. Esta característica no tiene efecto en PVCs que no están en uso por un Pod o despliegue. Debes crear un Pod que utilice el PVC antes de que la expansión pueda completarse.

Similar a otros tipos de volúmenes - los volúmenes FlexVolume también pueden ser expandidos cuando están en uso por un Pod.

Recuperación de fallos al expandir volúmenes

Si un usuario especifica un nuevo tamaño que es demasiado grande para ser satisfecho por el sistema de almacenamiento subyacente, la expansión del PVC será reintentada continuamente hasta que el usuario o el administrador del clúster tomen alguna acción. Esto puede ser indeseable y por lo tanto Kubernetes proporciona los siguientes métodos de recuperación de tales fallos.

Si la expansión del almacenamiento subyacente falla, el administrador del clúster puede recuperar manualmente el estado del Persistent Volume Claim (PVC) y cancelar las solicitudes de redimensionamiento. De lo contrario, las solicitudes de redimensionamiento son reintentadas continuamente por el controlador sin intervención del administrador.

  1. Marca el PersistentVolume (PV) que está vinculado al PersistentVolumeClaim (PVC) con una política de reclamación Retain.
  2. Elimina el PVC. Dado que el PV tiene una política de reclamación Retain, no perderemos ningún dato al recrear el PVC.
  3. Elimina la entrada claimRef de las especificaciones del PV, para que un nuevo PVC pueda vincularse a él. Esto debería hacer que el PV esté Available (Disponible).
  4. Vuelve a crear el PVC con un tamaño menor que el PV y establece el campo volumeName del PVC con el nombre del PV. Esto debería vincular el nuevo PVC al PV existente.
  5. No olvides restaurar la política de reclamación del PV.

FEATURE STATE: Kubernetes v1.23 [alpha]

Si el interruptor de funcionalidad de RecoverVolumeExpansionFailure está habilitado en tu clúster, y la expansión ha fallado para un PVC, puedes intentar nuevamente la expansión con un tamaño menor al valor previamente solicitado. Para solicitar un nuevo intento de expansión con un tamaño propuesto menor, edita .spec.resources para ese PVC y elige un valor que sea menor al valor que intentaste previamente. Esto es útil si la expansión a un valor más alto no tuvo éxito debido a una restricción de capacidad. Si eso ha ocurrido, o sospechas que podría haber ocurrido, puedes intentar nuevamente la expansión especificando un tamaño que esté dentro de los límites de capacidad del proveedor de almacenamiento subyacente. Puedes monitorear el estado de la operación de redimensionamiento observando .status.allocatedResourceStatuses y los eventos en el PVC.

Ten en cuenta que, aunque puedes especificar una cantidad menor de almacenamiento que la solicitada anteriormente, el nuevo valor aún debe ser mayor que .status.capacity. Kubernetes no admite reducir un PVC a menos de su tamaño actual.

Tipos de Persistent Volumes

Los tipos de PersistentVolume se implementan como plugins. Actualmente, Kubernetes admite los siguientes plugins:

  • csi - Interfaz de Almacenamiento de Contenedores (CSI)
  • fc - Almacenamiento de Canal de Fibra (FC)
  • hostPath - Volumen HostPath (solo para pruebas en un único nodo; NO FUNCIONARÁ en un clúster multinodo; considera usar el volumen local en su lugar)
  • iscsi - iSCSI (SCSI sobre IP) almacenamiento
  • local - dispositivos de almacenamiento locales montados en los nodos.
  • nfs - Almacenamiento del Sistema de Archivos de Red (NFS)

Los siguientes tipos de PersistentVolume están obsoletos. Esto significa que el soporte aún está disponible, pero se eliminará en una futura versión de Kubernetes.

  • azureFile - Azure File (obsoleto en v1.21)
  • flexVolume - FlexVolume (obsoleto en v1.23)
  • portworxVolume - Volumen Portworx (obsoleto en v1.25)
  • vsphereVolume - Volumen VMDK de vSphere (obsoleto en v1.19)
  • cephfs - Volumen CephFS (obsoleto en v1.28)
  • rbd - Dispositivo de Bloque Rados (RBD) (obsoleto en v1.28)

Versiones anteriores de Kubernetes también soportaban los siguientes tipos de PersistentVolume integrados:

  • awsElasticBlockStore - AWS Elastic Block Store (EBS) (no disponible en v1.27)
  • azureDisk - Disco Azure (no disponible en v1.27)
  • cinder - Cinder (almacenamiento en bloque de OpenStack) (no disponible en v1.26)
  • photonPersistentDisk - Disco persistente del controlador Photon. (no disponible a partir de v1.15)
  • scaleIO - Volumen ScaleIO. (no disponible a partir de v1.21)
  • flocker - Almacenamiento Flocker. (no disponible a partir de v1.25)
  • quobyte - Volumen Quobyte. (no disponible a partir de v1.25)
  • storageos - Volumen StorageOS. (no disponible a partir de v1.25)

Volúmenes Persistentes

Cada PV contiene una especificación y un estado, que corresponden a la especificación y el estado del volumen. El nombre de un objeto PersistentVolume debe ser un válido nombre de subdominio DNS.

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0003
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: slow
  mountOptions:
    - hard
    - nfsvers=4.1
  nfs:
    path: /tmp
    server: 172.17.0.2

Capacidad

Por lo general, un PV tendrá una capacidad de almacenamiento específica. Esto se establece utilizando el atributo capacity del PV. Lea el término del glosario Cantidad para comprender las unidades esperadas por capacity.

Actualmente, el tamaño de almacenamiento es el único recurso que se puede establecer o solicitar. En el futuro, los atributos adicionales pueden incluir IOPS, throughput, etc.

Modo de Volumen

FEATURE STATE: Kubernetes v1.18 [stable]

Kubernetes admite dos volumeModes (modos de volumen) para PersistentVolumes: Filesystem y Block (Sistema de archivos y Bloque).

volumeMode es un parámetro opcional de la API. Filesystem es el modo predeterminado que se utiliza cuando se omite el parámetro volumeMode.

Un volumen con volumeMode: Filesystem se monta en Pods en un directorio. Si el volumen está respaldado por un dispositivo de bloque y el dispositivo está vacío, Kubernetes crea un sistema de archivos en el dispositivo antes de montarlo por primera vez.

Puedes establecer el valor de volumeMode en Block para utilizar un volumen como un dispositivo de bloque sin formato. Este tipo de volumen se presenta en un Pod como un dispositivo de bloque, sin ningún sistema de archivos en él. Este modo es útil para proporcionar al Pod la forma más rápida posible de acceder a un volumen, sin ninguna capa de sistema de archivos entre el Pod y el volumen.

Por otro lado, la aplicación que se ejecuta en el Pod debe saber cómo manejar un dispositivo de bloque sin formato.

Consulta Soporte para Volúmenes en Bloque sin Procesar para ver un ejemplo de cómo usar un volumen con volumeMode: Block en un Pod.

Modos de Acceso

Un PersistentVolume puede montarse en un host de cualquier manera compatible con el proveedor de recursos. Como se muestra en la tabla a continuación, los proveedores tendrán diferentes capacidades y los modos de acceso de cada PV se establecerán en los modos específicos admitidos por ese volumen en particular. Por ejemplo, NFS puede admitir múltiples clientes de lectura/escritura, pero un PV de NFS específico podría exportarse en el servidor como de solo lectura. Cada PV tiene su propio conjunto de modos de acceso que describen las capacidades específicas de ese PV en particular.

Los modos de acceso son:

ReadWriteOnce
el volumen puede montarse como lectura-escritura por un solo nodo. El modo de acceso ReadWriteOnce aún puede permitir que varios Pods accedan al volumen cuando los Pods se ejecutan en el mismo nodo. Para el acceso de un solo Pod, consulta ReadWriteOncePod.
ReadOnlyMany
el volumen puede montarse como solo lectura por muchos nodos.
ReadWriteMany
el volumen puede montarse como lectura-escritura por muchos nodos.
ReadWriteOncePod
FEATURE STATE: Kubernetes v1.29 [stable]
el volumen puede montarse como lectura-escritura por un solo Pod. Utiliza el modo de acceso ReadWriteOncePod si deseas garantizar que solo un Pod en todo el clúster pueda leer ese PVC o escribir en él.

En la línea de comandos (CLI), los modos de acceso se abrevian de la siguiente manera:

  • RWO - ReadWriteOnce (Lectura/Escritura para Uno)
  • ROX - ReadOnlyMany (Solo Lectura para Muchos)
  • RWX - ReadWriteMany (Lectura/Escritura para Muchos)
  • RWOP - ReadWriteOncePod (Lectura/Escritura para Uno por Pod)

¡Importante! Un volumen solo puede montarse utilizando un modo de acceso a la vez, incluso si admite varios modos.

Volume Plugin ReadWriteOnce ReadOnlyMany ReadWriteMany ReadWriteOncePod
AzureFile -
CephFS -
CSI depends on the driver depends on the driver depends on the driver depends on the driver
FC - -
FlexVolume depends on the driver -
HostPath - - -
iSCSI - -
NFS -
RBD - -
VsphereVolume - - (works when Pods are collocated) -
PortworxVolume - - -

Clase

Un PV puede tener una clase, que se especifica configurando el atributo storageClassName con el nombre de una StorageClass. Un PV de una clase particular solo puede vincularse a PVC que soliciten esa clase. Un PV sin storageClassName no tiene clase y solo puede vincularse a PVC que no soliciten una clase en particular.

En el pasado, en lugar del atributo storageClassName, se utilizaba la anotación volume.beta.kubernetes.io/storage-class. Esta anotación todavía funciona; sin embargo, quedará completamente en desuso en una versión futura de Kubernetes.

Política de Reclamación

Las políticas de reclamación actuales son:

  • Retain (Retener) -- reclamación manual
  • Recycle (Reciclar) -- limpieza básica (rm -rf /elvolumen/*)
  • Delete (Eliminar) -- eliminar el volumen

Para Kubernetes 1.32, solo los tipos de volumen nfs y hostPath admiten el reciclaje.

Opciones de Montaje

Un administrador de Kubernetes puede especificar opciones de montaje adicionales cuando se monta un Persistent Volume en un nodo.

Los siguientes tipos de volumen admiten opciones de montaje:

  • azureFile
  • cephfs (desaconsejado en v1.28)
  • cinder (desaconsejado en v1.18)
  • iscsi
  • nfs
  • rbd (desaconsejado en v1.28)
  • vsphereVolume

Las opciones de montaje no se validan. Si una opción de montaje es inválida, el montaje falla.

En el pasado, en lugar del atributo mountOptions, se utilizaba la anotación volume.beta.kubernetes.io/mount-options. Esta anotación todavía funciona; sin embargo, quedará completamente en desuso en una versión futura de Kubernetes. Se recomienda utilizar el atributo mountOptions para especificar las opciones de montaje en lugar de la anotación.

Afinidad de Nodos

Un PV puede especificar la afinidad de nodos para definir restricciones que limiten desde qué nodos se puede acceder a este volumen. Los Pods que utilizan un PV solo se programarán en nodos seleccionados por la afinidad de nodos. Para especificar la afinidad de nodos, configura nodeAffinity en .spec de un PV. La referencia de API de PersistentVolume tiene más detalles sobre este campo.

Fase

Un PersistentVolume estará en una de las siguientes fases:

Available (Disponible)
un recurso gratuito que aún no está vinculado a una solicitud
Bound (Vinculado)
el volumen está vinculado a una solicitud
Released (Liberado)
la solicitud ha sido eliminada, pero el recurso de almacenamiento asociado aún no ha sido reclamado por el clúster
Failed (Fallido)
el volumen ha fallado en su proceso de reclamación (automatizada)

Puedes ver el nombre de la PVC vinculada al PV utilizando kubectl describe persistentvolume <nombre>.

Marca de tiempo de transición de fase

FEATURE STATE: Kubernetes v1.29 [beta]

El campo .status de un PersistentVolume puede incluir un campo lastPhaseTransitionTime en versión alfa. Este campo registra la marca de tiempo de la última transición de fase del volumen. Para volúmenes recién creados, la fase se establece en Pending (Pendiente) y lastPhaseTransitionTime se establece en el tiempo actual.

PersistentVolumeClaims

Cada PVC contiene un spec (especificación) y un status (estado), que es la especificación y el estado de la solicitud. El nombre de un objeto PersistentVolumeClaim debe ser un nombre de subdominio DNS válido.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 8Gi
  storageClassName: slow
  selector:
    matchLabels:
      release: "stable"
    matchExpressions:
      - { key: environment, operator: In, values: [dev] }

Modos de Acceso

Las solicitudes utilizan las mismas convenciones que los volúmenes al solicitar almacenamiento con modos de acceso específicos.

Modos de Volumen

Las solicitudes utilizan la misma convención que los volúmenes para indicar el consumo del volumen como un sistema de archivos o un dispositivo de bloque.

Recursos

Las solicitudes, al igual que los Pods, pueden solicitar cantidades específicas de un recurso. En este caso, la solicitud es para almacenamiento. El mismo modelo de recurso se aplica tanto a los volúmenes como a las solicitudes.

Selector

Las solicitudes pueden especificar un selector de etiqueta para filtrar aún más el conjunto de volúmenes. Solo los volúmenes cuyas etiquetas coincidan con el selector pueden vincularse a la solicitud. El selector puede constar de dos campos:

  • matchLabels (coincidencia de etiquetas) - el volumen debe tener una etiqueta con este valor.
  • matchExpressions (expresiones de coincidencia) - una lista de requisitos especificados por clave, lista de valores y operador que relaciona la clave y los valores. Los operadores válidos incluyen In, NotIn, Exists y DoesNotExist.

Todos los requisitos, tanto de matchLabels como de matchExpressions, se combinan mediante un operador AND: todos deben cumplirse para que haya coincidencia.

Clase

Una solicitud puede solicitar una clase en particular especificando el nombre de una StorageClass utilizando el atributo storageClassName. Solo los PV de la clase solicitada, es decir, aquellos con el mismo storageClassName que el PVC, pueden vincularse al PVC.

Las PVC no necesariamente tienen que solicitar una clase. Una PVC con su storageClassName configurado igual a "" siempre se interpreta como una solicitud de un PV sin clase, por lo que solo puede vincularse a PV sin clase (sin anotación o con "" configurado). Una PVC sin storageClassName no es exactamente lo mismo y se trata de manera diferente por parte del clúster, dependiendo de si el plugin de admisión DefaultStorageClass está activado.

  • Si el plugin de admisión está activado, el administrador puede especificar una StorageClass predeterminada. Todas las PVC que no tengan storageClassName solo podrán vincularse a los PV de esa clase predeterminada. La especificación de una StorageClass predeterminada se realiza configurando la anotación storageclass.kubernetes.io/is-default-class igual a true en un objeto StorageClass. Si el administrador no especifica una predeterminada, el clúster responderá a la creación de PVC como si el plugin de admisión estuviera desactivado. Si se especifican más de una StorageClass predeterminada, la más nueva se utilizará cuando se provisione dinámicamente la PVC.
  • Si el plugin de admisión está desactivado, no existe una noción de una StorageClass predeterminada. Todas las PVC que tengan storageClassName configurado como "" solo podrán vincularse a los PV que también tengan storageClassName configurado como "". Sin embargo, las PVC sin storageClassName pueden actualizarse más adelante una vez que esté disponible la StorageClass predeterminada. Si la PVC se actualiza, ya no se vinculará a los PV que tengan storageClassName configurado como "".

Consulta asignación retroactiva de StorageClass predeterminada para obtener más detalles.

Según el método de instalación, el administrador puede implementar una StorageClass predeterminada en un clúster de Kubernetes mediante el administrador de complementos durante la instalación.

Cuando una PVC especifica un selector además de solicitar una StorageClass, los requisitos se combinan mediante una operación AND: solo se puede vincular un PV de la clase solicitada y con las etiquetas solicitadas a la PVC.

En el pasado, en lugar del atributo storageClassName, se utilizaba la anotación volume.beta.kubernetes.io/storage-class. Esta anotación todavía funciona; sin embargo, no será compatible en futuras versiones de Kubernetes. Se recomienda utilizar el atributo storageClassName en su lugar.

Asignación retroactiva de StorageClass predeterminada

FEATURE STATE: Kubernetes v1.28 [stable]

Puedes crear una PersistentVolumeClaim sin especificar un storageClassName para la nueva PVC, incluso cuando no exista una StorageClass predeterminada en tu clúster. En este caso, la nueva PVC se crea tal como la definiste, y el storageClassName de esa PVC permanece sin configurar hasta que esté disponible la predeterminada.

Cuando está disponible una StorageClass predeterminada, el plano de control identifica cualquier PVC existente sin storageClassName. Para las PVC que tienen un valor vacío para storageClassName o que no tienen esta clave, el plano de control actualiza esas PVC para establecer storageClassName de acuerdo con la nueva StorageClass predeterminada. Si tienes una PVC existente donde storageClassName es "", y configuras una StorageClass predeterminada, entonces esta PVC no se actualizará.

Para seguir vinculando a PV con storageClassName configurado como "" (mientras existe una StorageClass predeterminada), debes establecer el storageClassName de la PVC asociada en "".

Este comportamiento ayuda a los administradores a cambiar la StorageClass predeterminada eliminando primero la antigua y luego creando o configurando otra. Este breve período en el que no hay una predeterminada hace que las PVC creadas en ese momento sin storageClassName no tengan ninguna predeterminada, pero debido a la asignación retroactiva de la StorageClass predeterminada, esta forma de cambiar las predeterminadas es segura.

Solicitudes como Volúmenes

Los Pods acceden al almacenamiento utilizando la solicitud como un volumen. Las solicitudes deben existir en el mismo Namespace que el Pod que utiliza la solicitud. El clúster encuentra la solicitud en el Namespace del Pod y la utiliza para obtener el PersistentVolume que respalda la solicitud. Luego, el volumen se monta en el host y dentro del Pod.

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: myfrontend
      image: nginx
      volumeMounts:
        - mountPath: "/var/www/html"
          name: mypd
  volumes:
    - name: mypd
      persistentVolumeClaim:
        claimName: myclaim

Una Nota sobre Espacios de Nombres

Las vinculaciones de PersistentVolumes son exclusivas y, dado que las PersistentVolumeClaims son objetos con espacio de nombres, montar solicitudes con modos "Many" (ROX, RWX) solo es posible dentro de un mismo espacio de nombres (namespace).

Los PersistentVolumes de tipo hostPath

Los PersistentVolumes de tipo hostPath utilizan un archivo o directorio en el nodo para emular almacenamiento conectado a la red. Consulta un ejemplo de un volumen de tipo hostPath.

Soporte para Volúmenes en Bloque sin Procesar

FEATURE STATE: Kubernetes v1.18 [stable]

Los siguientes complementos de volumen admiten volúmenes en bloque sin procesar, incluida la provisión dinámica cuando corresponda:

  • CSI
  • FC (Fibre Channel)
  • iSCSI
  • Volumen local
  • OpenStack Cinder
  • RBD (desaconsejado)
  • RBD (Ceph Block Device; desaconsejado)
  • VsphereVolume

PersistentVolume que Utiliza un Volumen en Bloque sin Procesar

apiVersion: v1
kind: PersistentVolume
metadata:
  name: block-pv
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  volumeMode: Block
  persistentVolumeReclaimPolicy: Retain
  fc:
    targetWWNs: ["50060e801049cfd1"]
    lun: 0
    readOnly: false

PersistentVolumeClaim Solicitando un Volumen en Bloque sin Procesar

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: block-pvc
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Block
  resources:
    requests:
      storage: 10Gi

Especificación de Pod Agregando la Ruta del Dispositivo en Bloque sin Procesar en el Contenedor

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-block-volume
spec:
  containers:
    - name: fc-container
      image: fedora:26
      command: ["/bin/sh", "-c"]
      args: ["tail -f /dev/null"]
      volumeDevices:
        - name: data
          devicePath: /dev/xvda
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: block-pvc

Vinculación de Volúmenes en Bloque

Si un usuario solicita un volumen en bloque sin procesar indicándolo mediante el campo volumeMode en la especificación de PersistentVolumeClaim, las reglas de vinculación difieren ligeramente de versiones anteriores que no consideraban este modo como parte de la especificación. A continuación, se muestra una tabla de las posibles combinaciones que el usuario y el administrador pueden especificar para solicitar un dispositivo en bloque sin procesar. La tabla indica si el volumen se vinculará o no dadas las combinaciones: Matriz de vinculación de volumen para volúmenes provisionados estáticamente:

PV volumeMode PVC volumeMode Resultado
unspecified unspecified BIND
unspecified Block NO BIND
unspecified Filesystem BIND
Block unspecified NO BIND
Block Block BIND
Block Filesystem NO BIND
Filesystem Filesystem BIND
Filesystem Block NO BIND
Filesystem unspecified BIND

Soporte para Instantáneas de Volúmenes y Restauración de Volúmenes desde Instantáneas

FEATURE STATE: Kubernetes v1.20 [stable]

Las instantáneas de volúmenes solo admiten los plugins de volumen CSI fuera del árbol. Para obtener más detalles, consulta Instantáneas de Volúmenes. Los plugins de volumen dentro del árbol están obsoletos. Puedes obtener información sobre los plugins de volumen obsoletos en el FAQ de Plugins de Volumen.

Crear una PersistentVolumeClaim desde una Instantánea de Volumen

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: restore-pvc
spec:
  storageClassName: csi-hostpath-sc
  dataSource:
    name: new-snapshot-test
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

Clonación de Volúmenes

Clonación de Volúmenes solo está disponible para plugins de volumen CSI.

Crear una PersistentVolumeClaim desde una PVC existente

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: cloned-pvc
spec:
  storageClassName: my-csi-plugin
  dataSource:
    name: existing-src-pvc-name
    kind: PersistentVolumeClaim
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

Pobladores de Volúmenes y Fuentes de Datos

FEATURE STATE: Kubernetes v1.24 [beta]

Kubernetes admite pobladores de volúmenes personalizados. Para utilizar pobladores de volúmenes personalizados, debes habilitar la característica AnyVolumeDataSource feature gate para el kube-apiserver y kube-controller-manager.

Los pobladores de volúmenes aprovechan un campo de especificación de PVC llamado dataSourceRef. A diferencia del campo dataSource, que solo puede contener una referencia a otra PersistentVolumeClaim o a un VolumeSnapshot, el campo dataSourceRef puede contener una referencia a cualquier objeto en el mismo Namespace, excepto los objetos principales que no sean PVC. Para los clústeres que tienen habilitada la característica, se prefiere el uso de dataSourceRef en lugar de dataSource.

Fuentes de Datos entre Espacios de Nombres

FEATURE STATE: Kubernetes v1.26 [alpha]

Kubernetes admite fuentes de datos de volúmenes entre espacios de nombres. Para utilizar fuentes de datos de volúmenes entre espacios de nombres, debes habilitar las características AnyVolumeDataSource y CrossNamespaceVolumeDataSource Interruptores de funcionalidades (feature gates) para el kube-apiserver y kube-controller-manager. Además, debes habilitar la característica CrossNamespaceVolumeDataSource para el csi-provisioner.

Al habilitar la característica CrossNamespaceVolumeDataSource, puedes especificar un Namespace en el campo dataSourceRef.

Las referencias a fuentes de datos

El campo dataSourceRef se comporta de manera casi idéntica al campo dataSource. Si se especifica uno mientras que el otro no, el servidor de la API asignará el mismo valor a ambos campos. Ninguno de los campos puede cambiarse después de su creación, y cualquier intento de especificar valores diferentes para los dos campos resultará en un error de validación. Por lo tanto, los dos campos siempre tendrán el mismo contenido.

Hay dos diferencias importantes entre el campo dataSourceRef y el campo dataSource que los usuarios deben tener en cuenta:

  • El campo dataSource ignora los valores no válidos (como si el campo estuviera en blanco), mientras que el campo dataSourceRef nunca ignora los valores y generará un error si se utiliza un valor no válido. Los valores no válidos son cualquier objeto central (objetos sin apiGroup) excepto PVCs.

  • El campo dataSourceRef puede contener diferentes tipos de objetos, mientras que el campo dataSource solo permite PVCs y VolumeSnapshots.

Cuando se habilita la característica CrossNamespaceVolumeDataSource, existen diferencias adicionales:

  • El campo dataSource solo permite objetos locales, mientras que el campo dataSourceRef permite objetos en cualquier Namespaces.
  • Cuando se especifica un Namespace, dataSource y dataSourceRef no están sincronizados.

Los usuarios siempre deben utilizar dataSourceRef en clústeres que tengan habilitada la puerta de enlace de características y recurrir a dataSource en clústeres que no la tengan habilitada. No es necesario mirar ambos campos bajo ninguna circunstancia. Los valores duplicados con semántica ligeramente diferente existen solo por compatibilidad con versiones anteriores. En particular, una mezcla de controladores más antiguos y más nuevos pueden interoperar porque los campos son iguales.

Uso de pobladores de volúmenes

Los pobladores de volúmenes son controladores que pueden crear volúmenes no vacíos, donde el contenido del volumen es determinado por un Recurso Personalizado. Los usuarios crean un volumen poblado haciendo referencia a un Recurso Personalizado utilizando el campo dataSourceRef:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: populated-pvc
spec:
  dataSourceRef:
    name: example-name
    kind: ExampleDataSource
    apiGroup: example.storage.k8s.io
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

Dado que los pobladores de volúmenes son componentes externos, los intentos de crear una PVC que los utilice pueden fallar si no se han instalado todos los componentes correctos. Los controladores externos deben generar eventos en la PVC para proporcionar retroalimentación sobre el estado de la creación, incluyendo advertencias si la PVC no puede ser creada debido a la falta de algún componente necesario. Esto ayuda a los usuarios a comprender por qué la creación de la PVC ha fallado y qué componentes faltan para que funcione correctamente.

Puedes instalar el controlador volume data source validator en tu clúster. Este controlador genera eventos de advertencia en una PVC en caso de que no haya ningún poblador registrado para manejar ese tipo de fuente de datos. Cuando se instala un poblador adecuado para una PVC, es responsabilidad de ese controlador de poblador informar sobre eventos relacionados con la creación del volumen y problemas durante el proceso. Esto proporciona información útil para los usuarios y administradores del clúster sobre el estado y los problemas relacionados con las PVC que utilizan pobladores de volúmenes.

Uso de una fuente de datos de volumen entre Namespaces

FEATURE STATE: Kubernetes v1.26 [alpha]

Crea un ReferenceGrant para permitir que el propietario del Namespace acepte la referencia. Define un volumen poblado especificando una fuente de datos de volumen entre Namespaces utilizando el campo dataSourceRef. Debes tener un ReferenceGrant válido en el Namespace de origen previamente:

apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-ns1-pvc
  namespace: default
spec:
  from:
    - group: ""
      kind: PersistentVolumeClaim
      namespace: ns1
  to:
    - group: snapshot.storage.k8s.io
      kind: VolumeSnapshot
      name: new-snapshot-demo
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: foo-pvc
  namespace: ns1
spec:
  storageClassName: example
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  dataSourceRef:
    apiGroup: snapshot.storage.k8s.io
    kind: VolumeSnapshot
    name: new-snapshot-demo
    namespace: default
  volumeMode: Filesystem

Escribiendo configuraciones portátiles

Si estás escribiendo plantillas de configuración o ejemplos que se ejecutarán en una amplia gama de clústeres y necesitas almacenamiento persistente, se recomienda que utilices el siguiente patrón:

  • Incluye objetos PersistentVolumeClaim en tu conjunto de configuración (junto con Despliegues, ConfigMaps, etc.).
  • No incluyas objetos PersistentVolume en la configuración, ya que el usuario que instala la configuración puede no tener permisos para crear PersistentVolumes.
  • Ofrece al usuario la opción de proporcionar un nombre de clase de almacenamiento al instanciar la plantilla.
    • Si el usuario proporciona un nombre de clase de almacenamiento, coloca ese valor en el campo persistentVolumeClaim.storageClassName. Esto hará que la PVC coincida con la clase de almacenamiento correcta si el administrador del clúster ha habilitado las StorageClasses.
    • Si el usuario no proporciona un nombre de clase de almacenamiento, deja el campo persistentVolumeClaim.storageClassName como nulo. Esto hará que se provisione automáticamente un PV para el usuario con la StorageClass predeterminada en el clúster. Muchos entornos de clúster tienen una StorageClass predeterminada instalada, o los administradores pueden crear su propia StorageClass predeterminada.
  • En tus herramientas, observa las PVC que no se están enlazando después de algún tiempo y comunica esto al usuario, ya que esto puede indicar que el clúster no tiene soporte de almacenamiento dinámico (en cuyo caso el usuario debe crear un PV correspondiente) o que el clúster no tiene un sistema de almacenamiento (en cuyo caso el usuario no puede implementar configuraciones que requieran PVCs).

Siguientes pasos

Referencias de API

Puedes encontrar información detallada sobre las APIs PersistentVolume y PersistentVolumeClaim en la documentación oficial de Kubernetes. Estos enlaces te llevarán a las páginas de referencia que describen las especificaciones y los campos de estas API, junto con ejemplos y ejercicios de uso.

6.4 - Volúmenes proyectados

Este documento describe los volúmenes proyectados en Kubernetes. Necesita estar familiarizado con volúmenes.

Introducción

Un volumen proyectado asigna varias fuentes de volúmenes existentes al mismo directorio.

Actualmente se pueden proyectar los siguientes tipos de fuentes de volumen:

Se requiere que todas las fuentes estén en el mismo espacio de nombres que el Pod. Para más detalles, vea el documento de diseño all-in-one volume.

Configuración de ejemplo con un secreto, una downwardAPI y una configMap

apiVersion: v1
kind: Pod
metadata:
  name: volume-test
spec:
  containers:
  - name: container-test
    image: busybox:1.28
    volumeMounts:
    - name: all-in-one
      mountPath: "/projected-volume"
      readOnly: true
  volumes:
  - name: all-in-one
    projected:
      sources:
      - secret:
          name: mysecret
          items:
            - key: username
              path: my-group/my-username
      - downwardAPI:
          items:
            - path: "labels"
              fieldRef:
                fieldPath: metadata.labels
            - path: "cpu_limit"
              resourceFieldRef:
                containerName: container-test
                resource: limits.cpu
      - configMap:
          name: myconfigmap
          items:
            - key: config
              path: my-group/my-config

Configuración de ejemplo: secretos con un modo de permiso no predeterminado establecido

apiVersion: v1
kind: Pod
metadata:
  name: volume-test
spec:
  containers:
  - name: container-test
    image: busybox:1.28
    volumeMounts:
    - name: all-in-one
      mountPath: "/projected-volume"
      readOnly: true
  volumes:
  - name: all-in-one
    projected:
      sources:
      - secret:
          name: mysecret
          items:
            - key: username
              path: my-group/my-username
      - secret:
          name: mysecret2
          items:
            - key: password
              path: my-group/my-password
              mode: 511

Cada fuente de volumen proyectada aparece en la especificación bajo sources. Los parámetros son casi los mismos con dos excepciones:

  • Para los secretos, el campo secretName se ha cambiado a name para que sea coherente con el nombre de ConfigMap.
  • El defaultMode solo se puede especificar en el nivel proyectado y no para cada fuente de volumen. Sin embargo, como se ilustra arriba, puede configurar explícitamente el mode para cada proyección individual.

Volúmenes proyectados de serviceAccountToken

Puede inyectar el token para la service account actual en un Pod en una ruta especificada. Por ejemplo:

apiVersion: v1
kind: Pod
metadata:
  name: sa-token-test
spec:
  containers:
  - name: container-test
    image: busybox:1.28
    volumeMounts:
    - name: token-vol
      mountPath: "/service-account"
      readOnly: true
  serviceAccountName: default
  volumes:
  - name: token-vol
    projected:
      sources:
      - serviceAccountToken:
          audience: api
          expirationSeconds: 3600
          path: token

El Pod de ejemplo tiene un volumen proyectado que contiene el token de cuenta de servicio inyectado. Los contenedores en este Pod pueden usar ese token para acceder al servidor API de Kubernetes, autenticándose con la identidad de the pod's ServiceAccount.

El campo audience contiene la audiencia prevista del token. Un destinatario del token debe identificarse con un identificador especificado en la audiencia del token y, de lo contrario, debe rechazar el token. Este campo es opcional y de forma predeterminada es el identificador del servidor API.

The expirationSeconds es la duración esperada de validez del token de la cuenta de servicio. El valor predeterminado es 1 hora y debe durar al menos 10 minutos (600 segundos). Un administrador también puede limitar su valor máximo especificando la opción --service-account-max-token-expiration para el servidor API. El campo path especifica una ruta relativa al punto de montaje del volumen proyectado.

Interacciones SecurityContext

La propuesta para el manejo de permisos de archivos en la mejora del volumen de cuentas de servicio proyectadas introdujo los archivos proyectados que tienen los permisos de propietario correctos establecidos.

Linux

En los pods de Linux que tienen un volumen proyectado y RunAsUser configurado en el Pod SecurityContext, los archivos proyectados tienen la conjunto de propiedad correcto, incluida la propiedad del usuario del contenedor.

Cuando todos los contenedores en un pod tienen el mismo runAsUser configurado en su PodSecurityContext o el contenedor SecurityContext, entonces el kubelet garantiza que el contenido del volumen serviceAccountToken sea propiedad de ese usuario y que el archivo token tenga su modo de permiso establecido en 0600.

Windows

En los pods de Windows que tienen un volumen proyectado y RunAsUsername configurado en el pod SecurityContext, la propiedad no se aplica debido a la forma en que se administran las cuentas de usuario en Windows. Windows almacena y administra cuentas de grupos y usuarios locales en un archivo de base de datos llamado Administrador de cuentas de seguridad (SAM). Cada contenedor mantiene su propia instancia de la base de datos SAM, de la cual el host no tiene visibilidad mientras el contenedor se está ejecutando. Los contenedores de Windows están diseñados para ejecutar la parte del modo de usuario del sistema operativo de forma aislada del host, de ahí el mantenimiento de una base de datos SAM virtual. Como resultado, el kubelet que se ejecuta en el host no tiene la capacidad de configurar dinámicamente la propiedad de los archivos del host para cuentas de contenedores virtualizados. Se recomienda que, si los archivos de la máquina host se van a compartir con el contenedor, se coloquen en su propio montaje de volumen fuera de C:\.

De forma predeterminada, los archivos proyectados tendrán la siguiente propiedad, como se muestra en un archivo de volumen proyectado de ejemplo:

PS C:\> Get-Acl C:\var\run\secrets\kubernetes.io\serviceaccount\..2021_08_31_22_22_18.318230061\ca.crt | Format-List

Path   : Microsoft.PowerShell.Core\FileSystem::C:\var\run\secrets\kubernetes.io\serviceaccount\..2021_08_31_22_22_18.318230061\ca.crt
Owner  : BUILTIN\Administrators
Group  : NT AUTHORITY\SYSTEM
Access : NT AUTHORITY\SYSTEM Allow  FullControl
         BUILTIN\Administrators Allow  FullControl
         BUILTIN\Users Allow  ReadAndExecute, Synchronize
Audit  :
Sddl   : O:BAG:SYD:AI(A;ID;FA;;;SY)(A;ID;FA;;;BA)(A;ID;0x1200a9;;;BU)

Esto implica que todos los usuarios administradores como ContainerAdministrator tendrán acceso de lectura, escritura y ejecución, mientras que los usuarios que no sean administradores tendrán acceso de lectura y ejecución.

6.5 - Clonación de volumen CSI

Este documento describe el concepto para clonar volúmenes CSI existentes en Kubernetes. Se sugiere estar familiarizado con Volúmenes.

Introducción

La función de clonación de volumen CSI agrega soporte para especificar PVCs existentes en el campo dataSource para indicar que un usuario desea clonar un Volume.

Un Clon se define como un duplicado de un volumen de Kubernetes existente que se puede consumir como lo sería cualquier volumen estándar. La única diferencia es que al aprovisionar, en lugar de crear un "nuevo" Volumen vacío, el dispositivo de backend crea un duplicado exacto del Volumen especificado.

La implementación de la clonación, desde la perspectiva de la API de Kubernetes, agrega la capacidad de especificar un PVC existente como dataSource durante la creación de un nuevo PVC. El PVC de origen debe estar vinculado y disponible (no en uso).

Los usuarios deben tener en cuenta lo siguiente cuando utilicen esta función:

  • El soporte de clonación (VolumePVCDataSource) sólo está disponible para controladores CSI.
  • El soporte de clonación sólo está disponible para aprovisionadores dinámicos.
  • Los controladores CSI pueden haber implementado o no la funcionalidad de clonación de volúmenes.
  • Sólo puede clonar un PVC cuando existe en el mismo Namespace que el PVC de destino (el origen y el destino deben estar en el mismo Namespace).
  • La clonación sólo se admite dentro de la misma Clase de Almacenamiento.
    • El volumen de destino debe ser de la misma clase de almacenamiento que el origen
    • Se puede utilizar la clase de almacenamiento predeterminada y se puede omitir storageClassName en la especificación
  • La clonación sólo se puede realizar entre dos volúmenes que usan la misma configuración de VolumeMode (si solicita un volumen en modo de bloqueo, la fuente DEBE también ser en modo de bloqueo)

Aprovisionamiento

Los clones se aprovisionan como cualquier otro PVC con la excepción de agregar un origen de datos que hace referencia a un PVC existente en el mismo Namespace.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: clone-of-pvc-1
  namespace: myns
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: cloning
  resources:
    requests:
      storage: 5Gi
  dataSource:
    kind: PersistentVolumeClaim
    name: pvc-1

El resultado es un nuevo PVC con el nombre clone-of-pvc-1 que tiene exactamente el mismo contenido que la fuente especificada pvc-1.

Uso

Una vez disponible el nuevo PVC, el PVC clonado se consume igual que el resto de PVC. También se espera en este punto que el PVC recién creado sea un objeto independiente. Se puede consumir, clonar, tomar snapshots, o eliminar de forma independiente y sin tener en cuenta sus datos originales. Esto también implica que la fuente no está vinculada de ninguna manera al clon recién creado, también puede modificarse o eliminarse sin afectar al clon recién creado.

6.6 - Volume Snapshot Classes

Este documento describe el concepto de VolumeSnapshotClass en Kubernetes. Se sugiere estar familiarizado con Volume Snapshots y Storage Classes.

Introducción

Al igual que StorageClass proporciona a los administradores una forma de describir las “clases” de almacenamiento que ofrecen al aprovisionar un volumen, VolumeSnapshotClass proporciona una forma de describir las “clases” de almacenamiento al aprovisionar un Snapshot de volumen.

El Recurso VolumeSnapshotClass

Cada VolumeSnapshotClass contiene los campos driver, deletionPolicy, y parameters, que se utilizan cuando un VolumeSnapshot que pertenece a la clase, necesita aprovisionarse dinámicamente.

El nombre de un objeto VolumeSnapshotClass es significativo y es la forma en que los usuarios pueden solicitar una clase en particular. Los administradores establecen el nombre y parámetros de una clase cuando crean por primera vez objetos VolumeSnapshotClass; una vez creados los objetos no pueden ser actualizados.

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
  name: csi-hostpath-snapclass
driver: hostpath.csi.k8s.io
deletionPolicy: Delete
parameters:

Los administradores pueden especificar un VolumeSnapshotClass predeterminado para VolumeSnapshots que no solicitan ninguna clase en particular. Para definir la clase predeterminada agregue la anotación: snapshot.storage.kubernetes.io/is-default-class: "true".

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
  name: csi-hostpath-snapclass
  annotations:
    snapshot.storage.kubernetes.io/is-default-class: "true"
driver: hostpath.csi.k8s.io
deletionPolicy: Delete
parameters:

Driver

Las clases de Snapshot de volumen tienen un controlador que determina que complemento de volumen CSI se utiliza para aprovisionar VolumeSnapshots. Este campo debe especificarse.

DeletionPolicy

Las clases de Snapshot de volumen tienen un deletionPolicy. Permite configurar lo que sucede con un VolumeSnapshotContent cuando se va a eliminar el objeto VolumeSnapshot al que está vinculado. La deletionPolicy de una clase de Snapshot de volumen puede Retain o Delete. Este campo debe ser especificado.

Si la deletionPolicy es Delete, el Snapshot de almacenamiento subyacente se eliminará junto con el objeto VolumeSnapshotContent. Si deletionPolicy es Retain, tanto el Snapshot subyacente como VolumeSnapshotContent permanecerán.

Parameters

Las clases de Snapshot de volumen tienen parámetros que describen los Snapshots de volumen que pertenecen a la clase de Snapshot de volumen. Se pueden aceptar diferentes parámetros dependiendo del driver.

6.7 - Volúmenes efímeros

Este documento describe volúmenes efímeros en Kubernetes. Se sugiere tener conocimiento previo sobre volúmenes, en particular PersistentVolumeClaim y PersistentVolume.

Algunas aplicaciones requieren almacenamiento adicional, pero no les preocupa si esos datos se almacenan de manera persistente entre reinicios. Por ejemplo, los servicios de caché a menudo tienen limitaciones de tamaño de memoria y pueden trasladar datos poco utilizados a un almacenamiento más lento que la memoria, con un impacto mínimo en el rendimiento general.

Otras aplicaciones esperan que algunos datos de entrada de solo lectura estén presentes en archivos, como datos de configuración o claves secretas.

Los volúmenes efímeros están diseñados para estos casos de uso. Debido a que los volúmenes siguen el ciclo de vida del Pod y se crean y eliminan junto con el Pod, los Pods pueden detenerse y reiniciarse sin estar limitados a la disponibilidad de algún volumen persistente.

Los volúmenes efímeros se especifican en línea en la especificación del Pod, lo que simplifica la implementación y gestión de aplicaciones.

Tipos de volúmenes efímeros

Kubernetes admite varios tipos diferentes de volúmenes efímeros para diversos propósitos:

emptyDir, configMap, downwardAPI, secret se proporcionan como almacenamiento efímero local. Ellos son administrados por kubelet en cada nodo.

Los volúmenes efímeros CSI deben ser proporcionados por controladores de almacenamiento CSI de terceros.

Los volúmenes efímeros genéricos pueden ser proporcionados por controladores de almacenamiento CSI de terceros, pero también por cualquier otro controlador de almacenamiento que admita la provisión dinámica. Algunos controladores CSI están escritos específicamente para volúmenes efímeros CSI y no admiten la provisión dinámica; por lo tanto, no se pueden utilizar para volúmenes efímeros genéricos.

La ventaja de utilizar controladores de terceros es que pueden ofrecer funcionalidades que Kubernetes en sí mismo no admite, como el almacenamiento con características de rendimiento diferentes al disco gestionado por kubelet o la inyección de datos diversos.

Volúmenes efímeros de CSI

FEATURE STATE: Kubernetes v1.25 [stable]

Conceptualmente, los volúmenes efímeros CSI son similares a los tipos de volumen configMap, downwardAPI y secret: el almacenamiento se gestiona localmente en cada nodo y se crea junto con otros recursos locales después de que un Pod ha sido programado en un nodo. Kubernetes ya no tiene ningún concepto de reprogramación de Pods en esta etapa. La creación de volúmenes debe ser poco propensa a fallos, de lo contrario, el inicio del Pod queda atascado. En particular, la programación de Pods con conciencia de la capacidad de almacenamiento no está admitida para estos volúmenes. Actualmente, tampoco están cubiertos por los límites de uso de recursos de almacenamiento de un Pod, porque eso es algo que kubelet solo puede aplicar para el almacenamiento que administra él mismo.

Aquí tienes un ejemplo de manifiesto para un Pod que utiliza almacenamiento efímero CSI:

kind: Pod
apiVersion: v1
metadata:
  name: my-csi-app
spec:
  containers:
    - name: my-frontend
      image: busybox:1.28
      volumeMounts:
        - mountPath: "/data"
          name: my-csi-inline-vol
      command: ["sleep", "1000000"]
  volumes:
    - name: my-csi-inline-vol
      csi:
        driver: inline.storage.kubernetes.io
        volumeAttributes:
          foo: bar

Los volumeAttributes determinan qué volumen es preparado por el controlador. Estos atributos son específicos de cada controlador y no están estandarizados. Consulta la documentación de cada controlador CSI para obtener instrucciones adicionales.

Restricciones del conductor CSI

Los volúmenes efímeros CSI permiten a los usuarios proporcionar volumeAttributes directamente al controlador CSI como parte de la especificación del Pod. Un controlador CSI que permite volumeAttributes que normalmente están restringidos a administradores NO es adecuado para su uso en un volumen efímero en línea. Por ejemplo, los parámetros que normalmente se definen en la clase de almacenamiento no deben estar expuestos a los usuarios a través del uso de volúmenes efímeros en línea.

Los administradores del clúster que necesiten restringir los controladores CSI que se pueden utilizar como volúmenes en línea dentro de una especificación de Pod pueden hacerlo mediante:

  • Eliminar Ephemeral de volumeLifecycleModes en la especificación de CSIDriver, lo que evita que los controladores CSI admitan volúmenes efímeros en línea.

  • Usando un webhook de admisión para restringir el uso de este controlador.

Volúmenes efímeros genéricos

FEATURE STATE: Kubernetes v1.23 [stable]

Los volúmenes efímeros genéricos son similares a los volúmenes emptyDir en el sentido de que proporcionan un directorio por Pod para datos temporales que generalmente está vacío después de la provisión. Pero también pueden tener características adicionales:

  • El almacenamiento puede ser local o conectado a la red.
  • Los volúmenes pueden tener un tamaño fijo que los Pods no pueden exceder.
  • Los volúmenes pueden tener algunos datos iniciales, dependiendo del controlador y los parámetros.
  • Se admiten operaciones típicas en los volúmenes, siempre que el controlador las soporte, incluyendo instantáneas, clonación, cambiar el tamaño, y seguimiento de la capacidad de almacenamiento.

Ejemplo:

kind: Pod
apiVersion: v1
metadata:
  name: my-app
spec:
  containers:
    - name: my-frontend
      image: busybox:1.28
      volumeMounts:
        - mountPath: "/scratch"
          name: scratch-volume
      command: ["sleep", "1000000"]
  volumes:
    - name: scratch-volume
      ephemeral:
        volumeClaimTemplate:
          metadata:
            labels:
              type: my-frontend-volume
          spec:
            accessModes: ["ReadWriteOnce"]
            storageClassName: "scratch-storage-class"
            resources:
              requests:
                storage: 1Gi

Ciclo de vida y reclamo de volumen persistente

La idea clave de diseño es que los parámetros para una solicitud de volumen se permiten dentro de una fuente de volumen del Pod. Se admiten etiquetas, anotaciones y todo el conjunto de campos para una PersistentVolumeClaim. Cuando se crea un Pod de este tipo, el controlador de volúmenes efímeros crea entonces un objeto PersistentVolumeClaim real en el mismo espacio de nombres que el Pod y asegura que la PersistentVolumeClaim se elimine cuando se elimina el Pod.

Eso desencadena la vinculación y/o aprovisionamiento de volúmenes, ya sea de inmediato si el StorageClass utiliza la vinculación inmediata de volúmenes o cuando el Pod está programado provisionalmente en un nodo (modo de vinculación de volumen WaitForFirstConsumer). Este último se recomienda para volúmenes efímeros genéricos, ya que permite al planificador elegir libremente un nodo adecuado para el Pod. Con la vinculación inmediata, el planificador está obligado a seleccionar un nodo que tenga acceso al volumen una vez que esté disponible.

En términos de propiedad de recursos, un Pod que tiene almacenamiento efímero genérico es el propietario de la PersistentVolumeClaim(s) que proporciona ese almacenamiento efímero. Cuando se elimina el Pod, el recolector de basura de Kubernetes elimina la PVC, lo que suele desencadenar la eliminación del volumen, ya que la política de recuperación predeterminada de las clases de almacenamiento es eliminar los volúmenes. Puedes crear almacenamiento local cuasi-efímero utilizando una StorageClass con una política de recuperación de retain: el almacenamiento sobrevive al Pod y, en este caso, debes asegurarte de que la limpieza del volumen se realice por separado.

Mientras estas PVC existen, pueden usarse como cualquier otra PVC. En particular, pueden ser referenciadas como fuente de datos en la clonación o creación de instantáneas de volúmenes. El objeto PVC también contiene el estado actual del volumen.

Nomenclatura de PersistentVolumeClaim.

La nomenclatura de las PVC creadas automáticamente es determinista: el nombre es una combinación del nombre del Pod y el nombre del volumen, con un guion medio (-) en el medio. En el ejemplo anterior, el nombre de la PVC será my-app-scratch-volume. Esta nomenclatura determinista facilita la interacción con la PVC, ya que no es necesario buscarla una vez que se conocen el nombre del Pod y el nombre del volumen.

La nomenclatura determinista también introduce un posible conflicto entre diferentes Pods (un Pod "pod-a" con el volumen "scratch" y otro Pod con nombre "pod" y volumen "a-scratch" terminan teniendo el mismo nombre de PVC "pod-a-scratch") y entre Pods y PVCs creadas manualmente.

Estos conflictos se detectan: una PVC solo se utiliza para un volumen efímero si se creó para el Pod. Esta comprobación se basa en la relación de propiedad. Una PVC existente no se sobrescribe ni se modifica. Pero esto no resuelve el conflicto, ya que sin la PVC adecuada, el Pod no puede iniciarse.

Seguridad

El uso de volúmenes efímeros genéricos permite a los usuarios crear PVC de forma indirecta si pueden crear Pods, incluso si no tienen permiso para crear PVC directamente. Los administradores del clúster deben ser conscientes de esto. Si esto no encaja en su modelo de seguridad, deberían utilizar un webhook de admisión que rechace objetos como Pods que tienen un volumen efímero genérico.

La cuota normal del espacio de nombres para PVC sigue aplicándose, por lo que incluso si a los usuarios se les permite utilizar este nuevo mecanismo, no pueden utilizarlo para eludir otras políticas.

Siguientes pasos

Volúmenes efímeros gestionados por kubelet

Ver almacenamiento efímero local.

Volúmenes efímeros de CSI

Volúmenes efímeros genéricos

6.8 - Aprovisionamiento Dinámico de volumen

El aprovisionamiento dinámico de volúmenes permite crear volúmenes de almacenamiento bajo demanda. Sin el aprovisionamiento dinámico, los administradores de clústeres tienen que realizar llamadas manualmente a su proveedor de almacenamiento o nube para crear nuevos volúmenes de almacenamiento y luego crear objetos de PersistentVolume para representarlos en Kubernetes. La función de aprovisionamiento dinámico elimina la necesidad de que los administradores del clúster aprovisionen previamente el almacenamiento. En cambio, el aprovisionamiento ocurre automáticamente cuando los usuarios lo solicitan.

Antecedentes

La implementación del aprovisionamiento dinámico de volúmenes se basa en el objeto API StorageClass del grupo API storage.k8s.io. Un administrador de clúster puede definir tantos objetos StorageClass como sea necesario, cada uno especificando un volume plugin (aka provisioner) que aprovisiona un volumen y el conjunto de parámetros para pasar a ese aprovisionador. Un administrador de clúster puede definir y exponer varios tipos de almacenamiento (del mismo o de diferentes sistemas de almacenamiento) dentro de un clúster, cada uno con un conjunto personalizado de parámetros. Este diseño también garantiza que los usuarios finales no tengan que preocuparse por la complejidad y los matices de cómo se aprovisiona el almacenamiento, pero que aún tengan la capacidad de seleccionar entre múltiples opciones de almacenamiento.

Puede encontrar más información sobre las clases de almacenamiento aqui.

Habilitación del aprovisionamiento dinámico

Para habilitar el aprovisionamiento dinámico, un administrador de clúster debe crear previamente uno o más objetos StorageClass para los usuarios. Los objetos StorageClass definen qué aprovisionador se debe usar y qué parámetros se deben pasar a ese aprovisionador cuando se invoca el aprovisionamiento dinámico. El nombre de un objeto StorageClass debe ser un nombre de subdominio de DNS válido.

El siguiente manifiesto crea una clase de almacenamiento llamada "slow" que aprovisiona discos persistentes estándar similares a discos.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: slow
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-standard

El siguiente manifiesto crea una clase de almacenamiento llamada "fast" que aprovisiona discos persistentes similares a SSD.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-ssd

Usar Aprovisionamiento Dinámico

Los usuarios solicitan almacenamiento aprovisionado dinámicamente al incluir una clase de almacenamiento en su PersistentVolumeClaim. Antes de Kubernetes v1.6, esto se hacía a través del la anotación volume.beta.kubernetes.io/storage-class. Sin embargo, esta anotación está obsoleta desde v1.9. Los usuarios ahora pueden y deben usar el campo storageClassName del objeto PersistentVolumeClaim. El valor de este campo debe coincidir con el nombre de un StorageClass configurada por el administrador (ver documentación).

Para seleccionar la clase de almacenamiento llamada "fast", por ejemplo, un usuario crearía el siguiente PersistentVolumeClaim:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: claim1
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: fast
  resources:
    requests:
      storage: 30Gi

Esta afirmación da como resultado que se aprovisione automáticamente un disco persistente similar a SSD. Cuando se elimina la petición, se destruye el volumen.

Comportamiento Predeterminado

El aprovisionamiento dinámico se puede habilitar en un clúster de modo que todas las peticiones se aprovisionen dinámicamente si no se especifica una clase de almacenamiento. Un administrador de clúster puede habilitar este comportamiento al:

Un administrador puede marcar un StorageClass específico como predeterminada agregando la anotación storageclass.kubernetes.io/is-default-class. Cuando existe un StorageClass predeterminado en un clúster y un usuario crea un PersistentVolumeClaim con storageClassName sin especificar, el controlador de admisión DefaultStorageClass agrega automáticamente el campo storageClassName que apunta a la clase de almacenamiento predeterminada.

Tenga en cuenta que puede haber como máximo una clase de almacenamiento default, o un PersistentVolumeClaim sin storageClassName especificado explícitamente.

Conocimiento de la Topología

En los clústeres Multi-Zone, los Pods se pueden distribuir en zonas de una región. Los backends de almacenamiento de zona única deben aprovisionarse en las zonas donde se programan los Pods. Esto se puede lograr configurando el Volume Binding Mode.

6.9 - StorageClass (Clases de Almacenamiento)

Este documento describe el concepto de una StorageClass (Clases de Almacenamiento) en Kubernetes. Necesita estar familiarizado con volumes y persistent volumes.

Introducción

Una StorageClass proporciona una forma para que los administradores describan las "clases" de almacenamiento que ofrecen. Diferentes clases pueden corresponder a niveles de calidad de servicio, o a políticas de copia de seguridad, o a políticas arbitrarias determinadas por los administradores del clúster de Kubernetes. Kubernetes en sí no tiene opiniones sobre lo que representan las clases. Este concepto a veces se denomina "profiles" en otros sistemas de almacenamiento.

El recurso StorageClass

Cada StorageClass contiene los campos provisioner, parameters y reclaimPolicy, que se utilizan cuando un PersistentVolume que pertenece a la clase debe aprovisionarse dinámicamente.

El nombre de un objeto StorageClass es significativo y es la forma en que los usuarios pueden solicitar una clase en particular. Los administradores establecen el nombre y otros parámetros de una clase al crear objetos StorageClass por primera vez, y los objetos no pueden actualizarse una vez creados.

Los administradores pueden especificar una StorageClass predeterminada solo para los PVC que no solicite cualquier clase en particular a la que vincularse: vea la sección PersistentVolumeClaim para detalles.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp2
reclaimPolicy: Retain
allowVolumeExpansion: true
mountOptions:
  - debug
volumeBindingMode: Immediate

Proveedor

Cada StorageClass tiene un aprovisionador que determina qué complemento de volumen se usa para el aprovisionamiento de PV. Este campo debe ser especificado.

Complemento de volumen Aprovisionador interno Ejemplo de configuración
AWSElasticBlockStore AWS EBS
AzureFile Azure File
AzureDisk Azure Disk
CephFS - -
Cinder OpenStack Cinder
FC - -
FlexVolume - -
GCEPersistentDisk GCE PD
iSCSI - -
NFS - NFS
RBD Ceph RBD
VsphereVolume vSphere
PortworxVolume Portworx Volume
Local - Local

No está restringido especificar los aprovisionadores "internos" enumerados aquí (cuyos nombres tienen el prefijo "kubernetes.io" y se envían junto con Kubernetes). También puede ejecutar y especificar aprovisionadores externos, que son programas independientes que siguen una especificación definida por Kubernetes. Los autores de proveedores externos tienen total discreción sobre dónde vive su código, cómo se envía el aprovisionador, cómo debe ser ejecutada, qué complemento de volumen usa (incluido Flex), etc. El repositorio kubernetes-sigs/sig-storage-lib-external-provisioner alberga una biblioteca para escribir aprovisionadores externos que implementa la mayor parte de la especificación. Algunos proveedores externos se enumeran en el repositorio kubernetes-sigs/sig-storage-lib-external-provisioner.

Por ejemplo, NFS no proporciona un aprovisionador interno, pero se puede usar un aprovisionador externo. También hay casos en los que los proveedores de almacenamiento de terceros proporcionan su propio aprovisionador externo.

Política de reclamación

Los PersistentVolumes creados dinámicamente por StorageClass tendrán la política de recuperación especificada en el campo reclaimPolicy de la clase, que puede ser Delete o Retain. Si no se especifica reclaimPolicy cuando se crea un objeto StorageClass, el valor predeterminado será Delete.

Los PersistentVolumes que se crean manualmente y se administran a través de StorageClass tendrán la política de recuperación que se les asignó en el momento de la creación.

Permitir expansión de volumen

FEATURE STATE: Kubernetes v1.11 [beta]

PersistentVolumes se puede configurar para que sea ampliable. Esta función, cuando se establece en true, permite a los usuarios cambiar el tamaño del volumen editando el objeto de PVC correspondiente.

Los siguientes tipos de volúmenes admiten la expansión de volumen, cuando el StorageClass subyacente tiene el campo allowVolumeExpansion establecido en verdadero.

Table of Volume types and the version of Kubernetes they require
Tipo de volumen Versión requerida de Kubernetes
gcePersistentDisk 1.11
awsElasticBlockStore 1.11
Cinder 1.11
rbd 1.11
Azure File 1.11
Azure Disk 1.11
Portworx 1.11
FlexVolume 1.13
CSI 1.14 (alpha), 1.16 (beta)

Opciones de montaje

Los PersistentVolumes creados dinámicamente por StorageClass tendrán las opciones de montaje especificadas en el campo mountOptions de la clase.

Si el complemento de volumen no admite opciones de montaje pero se especifican opciones de montaje, el aprovisionamiento fallará. Las opciones de montura no se validan ni en la clase ni en el PV. Si una opción de montaje no es válida, el montaje PV falla.

Modo de enlace de volumen

El campo volumeBindingMode controla cuándo debe ocurrir enlace de volumen y aprovisionamiento dinámico. Cuando no está configurado, el modo "Inmediato" se usa de manera predeterminada.

El modo Inmediato indica que el enlace de volumen y la dinámica el aprovisionamiento ocurre una vez que se crea PersistentVolumeClaim. Para los backends de almacenamiento que están restringidos por topología y no son accesibles globalmente desde todos los nodos del clúster, los volúmenes persistentes se vincularán o aprovisionarán sin conocimiento de los requisitos de programación del pod. Esto puede resultar en Pods no programables.

Un administrador de clústeres puede abordar este problema especificando el modo WaitForFirstConsumer que retrasará el enlace y el aprovisionamiento de un PersistentVolume hasta que se cree un Pod que use PersistentVolumeClaim. PersistentVolumes se seleccionarán o aprovisionarán de acuerdo con la topología especificada por las restricciones de programación del pod. Estos incluyen, pero no se limitan a requerimientos de recursos, node selectors, pod affinity and anti-affinity, y taints and tolerations.

Los siguientes complementos admiten WaitForFirstConsumer con aprovisionamiento dinámico:

Los siguientes complementos admiten WaitForFirstConsumer con enlace PersistentVolume creado previamente:

FEATURE STATE: Kubernetes v1.17 [stable]
CSI volumes también son compatibles con el aprovisionamiento dinámico y los PV creados previamente, pero deberá consultar la documentación de un controlador CSI específico para ver sus claves de topología y ejemplos compatibles.

apiVersion: v1
kind: Pod
metadata:
  name: task-pv-pod
spec:
  nodeSelector:
    kubernetes.io/hostname: kube-01
  volumes:
    - name: task-pv-storage
      persistentVolumeClaim:
        claimName: task-pv-claim
  containers:
    - name: task-pv-container
      image: nginx
      ports:
        - containerPort: 80
          name: "http-server"
      volumeMounts:
        - mountPath: "/usr/share/nginx/html"
          name: task-pv-storage

Topologías permitidas

Cuando un operador de clúster especifica el modo de enlace de volumen WaitForFirstConsumer, ya no es necesario restringir el aprovisionamiento a topologías específicas en la mayoría de las situaciones. Sin embargo, si todavía es necesario, se puede especificar allowedTopologies.

Este ejemplo demuestra cómo restringir la topología de los volúmenes aprovisionados a determinadas zonas y debe usarse como reemplazo de los parámetros zone y zones para el complementos compatibles.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-standard
volumeBindingMode: WaitForFirstConsumer
allowedTopologies:
  - matchLabelExpressions:
      - key: failure-domain.beta.kubernetes.io/zone
        values:
          - us-central-1a
          - us-central-1b

Parámetros

Las clases de almacenamiento tienen parámetros que describen los volúmenes que pertenecen a la clase de almacenamiento. Se pueden aceptar diferentes parámetros dependiendo del provisioner. Por ejemplo, el valor io1, para el parámetro type y el parámetro iopsPerGB son específicos de EBS. Cuando se omite un parámetro, se utiliza algún valor predeterminado.

Puede haber como máximo 512 parámetros definidos para StorageClass. La longitud total del objeto de parámetros, incluidas sus claves y valores, no puede superar los 256 KiB.

AWS EBS

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: slow
provisioner: kubernetes.io/aws-ebs
parameters:
  type: io1
  iopsPerGB: "10"
  fsType: ext4
  • type: io1, gp2, sc1, st1. Ver AWS docs para detalles. Por defecto: gp2.

  • zone (Obsoleto): AWS zona. Si no se especifica zone ni zones, los volúmenes generalmente se distribuyen por turnos en todas las zonas activas donde el clúster de Kubernetes tiene un nodo. Los parámetros zone y zones no deben usarse al mismo tiempo.

  • zones (Obsoleto): una lista separada por comas de las zonas de AWS. Si no se especifica zone ni zones, los volúmenes generalmente se distribuyen por turnos en todas las zonas activas donde el clúster de Kubernetes tiene un nodo. Los parámetros zone y zones no deben usarse al mismo tiempo.

  • iopsPerGB: solo para volúmenes io1. Operaciones de E/S por segundo por GiB. El complemento de volumen de AWS multiplica esto por el tamaño del volumen solicitado para calcular las IOPS del volumen y lo limita a 20 000 IOPS (máximo admitido por AWS, consulte Documentos de AWS). Aquí se espera una cadena, es decir, "10", no 10.

  • fsType: fsType que es compatible con kubernetes. Predeterminado: "ext4".

  • encrypted: indica si el volumen de EBS debe cifrarse o no. Los valores válidos son "true" o "false". Aquí se espera una cadena, es decir, "true", no true.

  • kmsKeyId: opcional. El nombre de recurso de Amazon completo de la clave que se utilizará al cifrar el volumen. Si no se proporciona ninguno pero encrypted es verdadero, AWS genera una clave. Consulte los documentos de AWS para obtener un valor de ARN válido.

GCE PD

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: slow
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-standard
  fstype: ext4
  replication-type: none
  • type: pd-standard o pd-ssd. Por defecto: pd-standard

  • zone (Obsoleto): zona GCE. Si no se especifica zone ni zones, los volúmenes generalmente se distribuyen por turnos en todas las zonas activas donde el clúster de Kubernetes tiene un nodo. Los parámetros zone y zones no deben usarse al mismo tiempo.

  • zones (Obsoleto): Una lista separada por comas de zona(s) GCE. Si no se especifica zone ni zones, los volúmenes generalmente se distribuyen por turnos en todas las zonas activas donde el clúster de Kubernetes tiene un nodo. Los parámetros zone y zones no deben usarse al mismo tiempo.

  • fstype: ext4 o xfs. Por defecto: ext4. El tipo de sistema de archivos definido debe ser compatible con el sistema operativo host.

  • replication-type: none or regional-pd. Por defecto: none.

Si replication-type se establece en none, se aprovisionará un PD regular (zonal).

Si replication-type se establece enregional-pd, a Regional Persistent Disk será aprovisionado. Es muy recomendable tener volumeBindingMode: WaitForFirstConsumer establecido, en cuyo caso cuando crea un Pod que consume un PersistentVolumeClaim que usa esta clase de almacenamiento, un disco persistente regional se aprovisiona con dos zonas. Una zona es la misma que la zona en la que está programado el Pod. La otra zona se selecciona aleatoriamente de las zonas disponibles para el clúster. Las zonas de disco se pueden restringir aún más usando allowedTopologies.

NFS

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: example-nfs
provisioner: example.com/external-nfs
parameters:
  server: nfs-server.example.com
  path: /share
  readOnly: "false"
  • server: Servidor es el nombre de host o la dirección IP del servidor NFS.
  • path: Ruta que exporta el servidor NFS.
  • readOnly: Una bandera que indica si el almacenamiento se montará como solo lectura (falso por defecto)

Kubernetes no incluye un proveedor de NFS interno. Debe usar un aprovisionador externo para crear una StorageClass para NFS. Aquí hay unos ejemplos:

OpenStack Cinder

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gold
provisioner: kubernetes.io/cinder
parameters:
  availability: nova
  • availability: Zona de disponibilidad. Si no se especifica, los volúmenes generalmente se distribuyen por turnos en todas las zonas activas donde el clúster de Kubernetes tiene un nodo.

vSphere

Hay dos tipos de aprovisionadores para las clases de almacenamiento de vSphere:

Los proveedores In-tree estan obsoletos. Para obtener más información sobre el aprovisionador de CSI, consulte Kubernetes vSphere CSI Driver y vSphereVolume CSI migration.

Aprovisionador de CSI

El aprovisionador vSphere CSI StorageClass funciona con clústeres de Tanzu Kubernetes. Para ver un ejemplo, consulte el vSphere CSI repository.

Aprovisionador de vCP

Los siguientes ejemplos utilizan el aprovisionador StorageClass de VMware Cloud Provider (vCP).

  1. Cree una StorageClass con un formato de disco especificado por el usuario.

    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: fast
    provisioner: kubernetes.io/vsphere-volume
    parameters:
      diskformat: zeroedthick
    

    diskformat: thin, zeroedthick y eagerzeroedthick. Por defecto: "thin".

  2. Cree una StorageClass con un formato de disco en un almacén de datos especificado por el usuario.

    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: fast
    provisioner: kubernetes.io/vsphere-volume
    parameters:
      diskformat: zeroedthick
      datastore: VSANDatastore
    

    datastore: el usuario también puede especificar el almacén de datos en StorageClass. El volumen se creará en el almacén de datos especificado en StorageClass, que en este caso es VSANDatastore. Este campo es opcional. Si no se especifica el almacén de datos, el volumen se creará en el almacén de datos especificado en el archivo de configuración de vSphere utilizado para inicializar vSphere Cloud Provider.

  3. Gestión de políticas de almacenamiento dentro de Kubernetes

    • Uso de la política de vCenter SPBM existente

      Una de las características más importantes de vSphere for Storage Management es la administración basada en políticas. La gestión basada en políticas de almacenamiento (SPBM) es un marco de políticas de almacenamiento que proporciona un único plano de control unificado en una amplia gama de servicios de datos y soluciones de almacenamiento. SPBM permite a los administradores de vSphere superar los desafíos iniciales de aprovisionamiento de almacenamiento, como la planificación de la capacidad, los niveles de servicio diferenciados y la gestión del margen de capacidad.

      Las políticas de SPBM se pueden especificar en StorageClass mediante el parámetro storagePolicyName.

    • Soporte de políticas Virtual SAN dentro de Kubernetes

      Los administradores de Vsphere Infrastructure (VI) tendrán la capacidad de especificar capacidades de almacenamiento Virtual SAN personalizadas durante el aprovisionamiento dinámico de volúmenes. Ahora puede definir los requisitos de almacenamiento, como el rendimiento y la disponibilidad, en forma de capacidades de almacenamiento durante el aprovisionamiento dinámico de volúmenes. Los requisitos de capacidad de almacenamiento se convierten en una política de Virtual SAN que luego se transfiere a la capa de Virtual SAN cuando se crea un volumen persistente (disco virtual). El disco virtual se distribuye en el almacén de datos de Virtual SAN para cumplir con los requisitos.

      Puedes ver la Administración basada en políticas de almacenamiento para el aprovisionamiento dinámico de volúmenes para obtener más detalles sobre cómo utilizar las políticas de almacenamiento para la gestión de volúmenes persistentes.

Hay pocos ejemplos de vSphere que prueba para la administración persistente de volúmenes dentro de Kubernetes para vSphere.

Ceph RBD

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast
provisioner: kubernetes.io/rbd
parameters:
  monitors: 10.16.153.105:6789
  adminId: kube
  adminSecretName: ceph-secret
  adminSecretNamespace: kube-system
  pool: kube
  userId: kube
  userSecretName: ceph-secret-user
  userSecretNamespace: default
  fsType: ext4
  imageFormat: "2"
  imageFeatures: "layering"
  • monitors: Monitores Ceph, delimitados por comas. Este parámetro es obligatorio.

  • adminId: ID de cliente de Ceph que es capaz de crear imágenes en el grupo. El valor predeterminado es "admin".

  • adminSecretName: Nombre secreto para adminId. Este parámetro es obligatorio. El secreto proporcionado debe tener el tipo "kubernetes.io/rbd".

  • adminSecretNamespace: El espacio de nombres para adminSecretName. El valor predeterminado es "predeterminado".

  • pool: Grupo Ceph RBD. El valor predeterminado es "rbd".

  • userId: ID de cliente de Ceph que se utiliza para asignar la imagen RBD. El valor predeterminado es el mismo que adminId.

  • userSecretName: El nombre de Ceph Secret para userId para mapear la imagen RBD. Él debe existir en el mismo espacio de nombres que los PVC. Este parámetro es obligatorio. El secreto proporcionado debe tener el tipo "kubernetes.io/rbd", por ejemplo creado de esta manera:

    kubectl create secret generic ceph-secret --type="kubernetes.io/rbd" \
      --from-literal=key='QVFEQ1pMdFhPUnQrSmhBQUFYaERWNHJsZ3BsMmNjcDR6RFZST0E9PQ==' \
      --namespace=kube-system
    
  • userSecretNamespace: El espacio de nombres para userSecretName.

  • fsType: fsType que es compatible con Kubernetes. Por defecto: "ext4".

  • imageFormat: Ceph RBD formato de imagen, "1" o "2". El valor predeterminado es "2".

  • imageFeatures: Este parámetro es opcional y solo debe usarse si establece imageFormat a "2". Las características admitidas actualmente son layering solamente. El valor predeterminado es "" y no hay funciones activadas.

Azure Disk

Clase de almacenamiento Azure Unmanaged Disk

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: slow
provisioner: kubernetes.io/azure-disk
parameters:
  skuName: Standard_LRS
  location: eastus
  storageAccount: azure_storage_account_name
  • skuName: Nivel de SKU de la cuenta de almacenamiento de Azure. El valor predeterminado está vacío.
  • location: Ubicación de la cuenta de almacenamiento de Azure. El valor predeterminado está vacío.
  • storageAccount: Nombre de la cuenta de almacenamiento de Azure. Si se proporciona una cuenta de almacenamiento, debe residir en el mismo grupo de recursos que el clúster y se ignora la location. Si no se proporciona una cuenta de almacenamiento, se creará una nueva cuenta de almacenamiento en el mismo grupo de recursos que el clúster.

Clase de almacenamiento Azure Disk (empezando desde v1.7.2)

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: slow
provisioner: kubernetes.io/azure-disk
parameters:
  storageaccounttype: Standard_LRS
  kind: managed
  • storageaccounttype: Nivel de SKU de la cuenta de almacenamiento de Azure. El valor predeterminado está vacío.
  • kind: Los valores posibles shared, dedicated, y managed (por defecto). Cuando kind es shared, todos los discos no administrados se crean en algunas cuentas de almacenamiento compartido en el mismo grupo de recursos que el clúster. Cuando kind es dedicated, se creará una nueva cuenta de almacenamiento dedicada para el nuevo disco no administrado en el mismo grupo de recursos que el clúster. Cuando kind es managed, todos los discos administrados se crean en el mismo grupo de recursos que el clúster.
  • resourceGroup: Especifique el grupo de recursos en el que se creará el disco de Azure. Debe ser un nombre de grupo de recursos existente. Si no se especifica, el disco se colocará en el mismo grupo de recursos que el clúster de Kubernetes actual.
  • Premium VM puede conectar discos Standard_LRS y Premium_LRS, mientras que Standard VM solo puede conectar discos Standard_LRS.
  • La VM administrada solo puede adjuntar discos administrados y la VM no administrada solo puede adjuntar discos no administrados.

Azure File

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: azurefile
provisioner: kubernetes.io/azure-file
parameters:
  skuName: Standard_LRS
  location: eastus
  storageAccount: azure_storage_account_name
  • skuName: Nivel de SKU de la cuenta de almacenamiento de Azure. El valor predeterminado está vacío.
  • location: Ubicación de la cuenta de almacenamiento de Azure. El valor predeterminado está vacío.
  • storageAccount: Nombre de la cuenta de almacenamiento de Azure. El valor predeterminado está vacío. Si un almacenamiento no se proporciona la cuenta, se buscan todas las cuentas de almacenamiento asociadas con el grupo de recursos para encontrar una que coincida con skuName y location. Si se proporciona una cuenta de almacenamiento, debe residir en el mismo grupo de recursos que el clúster y se ignoran skuName y location.
  • secretNamespace: el espacio de nombres del secreto que contiene el nombre y la clave de la cuenta de Azure Storage. El valor predeterminado es el mismo que el Pod.
  • secretName: el nombre del secreto que contiene el nombre y la clave de la cuenta de Azure Storage. El valor predeterminado es azure-storage-account-<accountName>-secret
  • readOnly: una bandera que indica si el almacenamiento se montará como de solo lectura. El valor predeterminado es falso, lo que significa un montaje de lectura/escritura. Esta configuración también afectará la configuración ReadOnly en VolumeMounts.

Durante el aprovisionamiento de almacenamiento, se crea un secreto denominado secretName para las credenciales de montaje. Si el clúster ha habilitado ambos RBAC y Controller Roles, agregue el permiso de create de recurso secret para clusterrole system:controller:persistent-volume-binder.

En un contexto de tenencia múltiple, se recomienda enfáticamente establecer el valor para secretNamespace explícitamente; de lo contrario, las credenciales de la cuenta de almacenamiento pueden ser leído por otros usuarios.

Volumen Portworx

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: portworx-io-priority-high
provisioner: kubernetes.io/portworx-volume
parameters:
  repl: "1"
  snap_interval: "70"
  priority_io: "high"
  • fs: sistema de archivos a distribuir: none/xfs/ext4 (predeterminado: ext4).

  • block_size: tamaño de bloque en Kbytes (predeterminado: 32).

  • repl: número de réplicas síncronas que se proporcionarán en forma de factor de replicación 1..3 (predeterminado: 1) Aquí se espera una cadena, es decir "1" y no 1.

  • priority_io: determina si el volumen se creará a partir de un almacenamiento de mayor rendimiento o de menor prioridad high/medium/low (predeterminado: low).

  • snap_interval: reloj/intervalo de tiempo en minutos para determinar cuándo activar las instantáneas. Las instantáneas son incrementales según la diferencia con la instantánea anterior, 0 desactiva las instantáneas (predeterminado: 0). Aquí se espera una cadena, es decir "70" y no 70.

  • aggregation_level: especifica el número de fragmentos en los que se distribuiría el volumen, 0 indica un volumen no agregado (predeterminado: 0). Aquí se espera una cadena, es decir, "0" y no 0

  • ephemeral: especifica si el volumen debe limpiarse después de desmontarlo o si debe ser persistente. El caso de uso emptyDir puede establecer este valor en verdadero y el caso de uso de persistent volumes, como para bases de datos como Cassandra, debe establecerse en falso, true/false (predeterminado false). Aquí se espera una cadena, es decir, "true" y no true.

Local

FEATURE STATE: Kubernetes v1.14 [stable]
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer

Actualmente, los volúmenes locales no admiten el aprovisionamiento dinámico; sin embargo, aún se debe crear una StorageClass para retrasar el enlace del volumen hasta la programación del Pod. Esto se especifica mediante el modo de enlace de volumen WaitForFirstConsumer.

Retrasar el enlace de volumen permite que el programador considere todos los datos de un Pod. restricciones de programación al elegir un PersistentVolume apropiado para un PersistentVolumeClaim.

6.10 - Capacidad de Almacenamiento

La capacidad de almacenamiento es limitada y puede variar según el nodo en el que un Pod se ejecuta: es posible que no todos los nodos puedan acceder al almacenamiento conectado a la red o que, para empezar, el almacenamiento sea local en un nodo.

FEATURE STATE: Kubernetes v1.21 [beta]

Esta página describe cómo Kubernetes realiza un seguimiento de la capacidad de almacenamiento y cómo el planificador usa esa información para programar Pods en nodos que tienen acceso a suficiente capacidad de almacenamiento para los volúmenes restantes que faltan. Sin el seguimiento de la capacidad de almacenamiento, el planificador puede elegir un nodo que no tenga suficiente capacidad para aprovisionar un volumen y se necesitarán varios reintentos de planificación.

El seguimiento de la capacidad de almacenamiento es compatible con los controladores de la Interfaz de Almacenamiento de Contenedores (CSI) y necesita estar habilitado al instalar un controlador CSI.

API

Hay dos extensiones de API para esta función:

  • Los objetos CSIStorageCapacity: son producidos por un controlador CSI en el Namespace donde está instalado el controlador. Cada objeto contiene información de capacidad para una clase de almacenamiento y define qué nodos tienen acceso a ese almacenamiento.
  • El campo CSIDriverSpec.StorageCapacity: cuando se establece en true, el Planificador de Kubernetes considerará la capacidad de almacenamiento para los volúmenes que usan el controlador CSI.

Planificación

El planificador de Kubernetes utiliza la información sobre la capacidad de almacenamiento si:

  • la Feature gate de CSIStorageCapacity es true,
  • un Pod usa un volumen que aún no se ha creado,
  • ese volumen usa un StorageClass que hace referencia a un controlador CSI y usa el [modo de enlace de volumen] (/docs/concepts/storage/storage-classes/#volume-binding-mode)WaitForFirstConsumer, y
  • el objeto CSIDriver para el controlador tiene StorageCapacity establecido en true.

En ese caso, el planificador sólo considera los nodos para el Pod que tienen suficiente almacenamiento disponible. Esta verificación es muy simplista y solo compara el tamaño del volumen con la capacidad indicada en los objetos CSIStorageCapacity con una topología que incluye el nodo.

Para los volúmenes con el modo de enlace de volumen Immediate, el controlador de almacenamiento decide dónde crear el volumen, independientemente de los pods que usarán el volumen. Luego, el planificador programa los pods en los nodos donde el volumen está disponible después de que se haya creado.

Para los volúmenes efímeros de CSI, la planificación siempre ocurre sin considerar la capacidad de almacenamiento. Esto se basa en la suposición de que este tipo de volumen solo lo utilizan controladores CSI especiales que son locales a un nodo y no necesitan allí recursos importantes.

Replanificación

Cuando se selecciona un nodo para un Pod con volúmenes WaitForFirstConsumer, esa decisión sigue siendo tentativa. El siguiente paso es que se le pide al controlador de almacenamiento CSI que cree el volumen con una pista de que el volumen está disponible en el nodo seleccionado.

Debido a que Kubernetes pudo haber elegido un nodo basándose en información de capacidad desactualizada, es posible que el volumen no se pueda crear realmente. Luego, la selección de nodo se restablece y el planificador de Kubernetes intenta nuevamente encontrar un nodo para el Pod.

Limitaciones

El seguimiento de la capacidad de almacenamiento aumenta las posibilidades de que la planificación funcione en el primer intento, pero no puede garantizarlo porque el planificador tiene que decidir basándose en información potencialmente desactualizada. Por lo general, el mismo mecanismo de reintento que para la planificación sin información de capacidad de almacenamiento es manejado por los errores de planificación.

Una situación en la que la planificación puede fallar de forma permanente es cuando un pod usa varios volúmenes: es posible que un volumen ya se haya creado en un segmento de topología que luego no tenga suficiente capacidad para otro volumen. La intervención manual es necesaria para recuperarse de esto, por ejemplo, aumentando la capacidad o eliminando el volumen que ya se creó. Trabajo adicional para manejar esto automáticamente.

Habilitación del seguimiento de la capacidad de almacenamiento

El seguimiento de la capacidad de almacenamiento es una función beta y está habilitada de forma predeterminada en un clúster de Kubernetes desde Kubernetes 1.21. Además de tener la función habilitada en el clúster, un controlador CSI también tiene que admitirlo. Consulte la documentación del controlador para obtener más detalles.

Siguientes pasos

6.11 - Almacenamiento en Windows

Esta página proporciona una descripción general del almacenamiento específico para el sistema operativo Windows.

Almacenamiento persistente

Windows tiene un controlador de sistema de archivos en capas para montar las capas del contenedor y crear un sistema de archivos de copia basado en NTFS. Todas las rutas de archivos en el contenedor se resuelven únicamente dentro del contexto de ese contenedor.

  • Con Docker, los montajes de volumen solo pueden apuntar a un directorio en el contenedor y no a un archivo individual. Esta limitación no se aplica a containerd.

  • Los montajes de volumen no pueden proyectar archivos o directorios de vuelta al sistema de archivos del host.

  • No se admiten sistemas de archivos de solo lectura debido a que siempre se requiere acceso de escritura para el registro de Windows y la base de datos SAM. Sin embargo, se admiten volúmenes de solo lectura.

  • Las máscaras y permisos de usuario en los volúmenes no están disponibles. Debido a que la base de datos SAM no se comparte entre el host y el contenedor, no hay un mapeo entre ellos. Todos los permisos se resuelven dentro del contexto del contenedor.

Como resultado, las siguientes funcionalidades de almacenamiento no son compatibles en nodos de Windows:

  • Montajes de subruta de volumen: solo es posible montar el volumen completo en un contenedor de Windows
  • Montaje de subruta de volumen para secretos
  • Proyección de montaje en el host
  • Sistema de archivos raíz de solo lectura (los volúmenes mapeados todavía admiten readOnly)
  • Mapeo de dispositivos de bloque
  • Memoria como medio de almacenamiento (por ejemplo, emptyDir.medium configurado como Memory)
  • Características del sistema de archivos como uid/gid; permisos de sistema de archivos de Linux por usuario
  • Configuración de permisos de secretos con DefaultMode (debido a la dependencia de UID/GID)
  • Soporte de almacenamiento/volumen basado en NFS
  • Ampliación del volumen montado (resizefs)

Los volúmenes de Kubernetes habilitan la implementación de aplicaciones complejas, con requisitos de persistencia de datos y uso compartido de volúmenes de Pod, en Kubernetes. La gestión de volúmenes persistentes asociados a un backend o protocolo de almacenamiento específico incluye acciones como la provisión/desprovisión/redimensión de volúmenes, la conexión/desconexión de un volumen de/para un nodo de Kubernetes, y el montaje/desmontaje de un volumen de/para contenedores individuales en un Pod que necesita persistir datos.

Los componentes de gestión de volúmenes se envían como plugin de volumen de Kubernetes. Las siguiente variedad de clases de plugins de volumen de Kubernetes son compatibles en Windows:

Plugins de volumen incorporados

Los siguientes plugins incorporados admiten almacenamiento persistente en nodos de Windows:

6.12 - Límites de Volumen específicos del Nodo

Esta página describe la cantidad máxima de Volúmenes que se pueden adjuntar a un Nodo para varios proveedores de nube.

Los proveedores de la nube como Google, Amazon y Microsoft suelen tener un límite en la cantidad de Volúmenes que se pueden adjuntar a un Nodo. Es importante que Kubernetes respete esos límites. De lo contrario, los Pods planificados en un Nodo podrían quedarse atascados esperando que los Volúmenes se conecten.

Límites predeterminados de Kubernetes

El Planificador de Kubernetes tiene límites predeterminados en la cantidad de Volúmenes que se pueden adjuntar a un Nodo:

Servicio de almacenamiento en la nubeVolúmenes máximos por Nodo
Amazon Elastic Block Store (EBS)39
Google Persistent Disk16
Microsoft Azure Disk Storage16

Límites personalizados

Puede cambiar estos límites configurando el valor de la variable de entorno KUBE_MAX_PD_VOLS y luego iniciando el Planificador. Los controladores CSI pueden tener un procedimiento diferente, consulte su documentación sobre cómo personalizar sus límites.

Tenga cuidado si establece un límite superior al límite predeterminado. Consulte la documentación del proveedor de la nube para asegurarse de que los Nodos realmente puedan admitir el límite que establezca.

El límite se aplica a todo el clúster, por lo que afecta a todos los Nodos.

Límites de Volumen dinámico

FEATURE STATE: Kubernetes v1.17 [stable]

Los límites de Volumen dinámico son compatibles con los siguientes tipos de Volumen.

  • Amazon EBS
  • Google Persistent Disk
  • Azure Disk
  • CSI

Para los Volúmenes administrados por in-tree plugins de Volumen, Kubernetes determina automáticamente el tipo de Nodo y aplica la cantidad máxima adecuada de Volúmenes para el Nodo. Por ejemplo:

  • En Google Compute Engine, se pueden adjuntar hasta 127 Volúmenes a un Nodo, según el tipo de Nodo.

  • Para los discos de Amazon EBS en los tipos de instancias M5,C5,R5,T3 y Z1D, Kubernetes permite que solo se adjunten 25 Volúmenes a un Nodo. Para otros tipos de instancias en Amazon Elastic Compute Cloud (EC2), Kubernetes permite adjuntar 39 Volúmenes a un Nodo.

  • En Azure, se pueden conectar hasta 64 discos a un Nodo, según el tipo de Nodo. Para obtener más detalles, consulte Sizes for virtual machines in Azure.

  • Si un controlador de almacenamiento CSI anuncia una cantidad máxima de Volúmenes para un Nodo (usando NodeGetInfo), el kube-scheduler respeta ese límite. Consulte las especificaciones de CSI para obtener más información.

  • Para los Volúmenes administrados por in-tree plugins que han sido migrados a un controlador CSI, la cantidad máxima de Volúmenes será la que informe el controlador CSI.

6.13 - Supervisión del Estado del Volumen

FEATURE STATE: Kubernetes v1.21 [alpha]

La supervisión del estado del volumen de CSI permite que los controladores de CSI detecten condiciones de volumen anómalas de los sistemas de almacenamiento subyacentes y las notifiquen como eventos en PVCs o Pods.

Supervisión del Estado del Volumen

El monitoreo del estado del volumen de Kubernetes es parte de cómo Kubernetes implementa la Interfaz de Almacenamiento de Contenedores (CSI). La función de supervisión del estado del volumen se implementa en dos componentes: un controlador de supervisión del estado externo y Kubelet.

Si un controlador CSI admite la función supervisión del estado del volumen desde el lado del controlador, se informará un evento en el PersistentVolumeClaim (PVC) relacionado cuando se detecte una condición de volumen anormal en un volumen CSI.

El controlador de estado externo también observa los eventos de falla del nodo. Se puede habilitar la supervisión de fallas de nodos configurando el indicador enable-node-watcher en verdadero. Cuando el monitor de estado externo detecta un evento de falla de nodo, el controlador reporta que se informará un evento en el PVC para indicar que los Pods que usan este PVC están en un nodo fallido.

Si un controlador CSI es compatible con la función monitoreo del estado del volumen desde el lado del nodo, se informará un evento en cada Pod que use el PVC cuando se detecte una condición de volumen anormal en un volumen CSI.

Siguientes pasos

Ver la documentación del controlador CSI para averiguar qué controladores CSI han implementado esta característica.

7 - Configuración

7.1 - Prácticas Recomendadas de Configuración

Este documento destaca y consolida las prácticas recomendadas de configuración que se presentan a lo largo de la guía del usuario, la documentación de Introducción y los ejemplos.

Este es un documento vivo. Si se te ocurre algo que no está en esta lista pero que puede ser útil a otros, no dudes en crear un issue o enviar un PR.

Consejos Generales de Configuración

  • Al definir configuraciones, especifica la última versión estable de la API.

  • Los archivos de configuración deben almacenarse en el control de versiones antes de enviarse al clúster. Este le permite revertir rápidamente un cambio de configuración si es necesario. También ayuda a la recreación y restauración del clúster.

  • Escribe tus archivos de configuración usando YAML en lugar de JSON. Aunque estos formatos se pueden utilizarse indistintamente en casi todos los escenarios, YAML tiende a ser más amigable con el usuario.

  • Agrupa los objetos relacionados en un solo archivo siempre que tenga sentido. Un archivo suele ser más fácil de administrar que varios. Ver el archivo guestbook-all-in-one.yaml como un ejemplo de esta sintaxis.

  • Ten en cuenta también que se pueden llamar muchos comandos kubectl en un directorio. Por ejemplo, puedes llamar kubectl apply en un directorio de archivos de configuración.

  • No especifiques valores predeterminados innecesariamente: una configuración simple y mínima hará que los errores sean menos probables.

  • Coloca descripciones de objetos en anotaciones, para permitir una mejor introspección.

"Naked" Pods vs ReplicaSets, Deployments y Jobs

  • No usar "Naked" Pods (es decir, Pods no vinculados a un ReplicaSet o a un Deployment) si puedes evitarlo. Los Naked Pods no se reprogramará en caso de falla de un nodo.

    Un Deployment, que crea un ReplicaSet para garantizar que se alcance la cantidad deseada de Pods está siempre disponible y especifica una estrategia para reemplazar los Pods (como RollingUpdate), es casi siempre preferible a crear Pods directamente, excepto por algunos explícitos restartPolicy: Never escenarios. Un Job también puede ser apropiado.

Servicios

  • Crea un Service antes de tus cargas de trabajo de backend correspondientes (Deployments o ReplicaSets) y antes de cualquier carga de trabajo que necesite acceder a él. Cuando Kubernetes inicia un contenedor, proporciona variables de entorno que apuntan a todos los Services que se estaban ejecutando cuando se inició el contenedor. Por ejemplo, si existe un Service llamado foo, todos los contenedores obtendrán las siguientes variables en su entorno inicial:

    FOO_SERVICE_HOST=<el host en el que se ejecuta el Service>
    FOO_SERVICE_PORT=<el puerto en el que se ejecuta el Service>
    

    * Esto implica un requisito de ordenamiento - cualquier Service al que un Pod quiera acceder debe ser creado antes del Pod en sí mismo, de lo contrario, las variables de entorno no se completarán. El DNS no tiene esta restricción.

  • Un cluster add-on opcional (aunque muy recomendable) es un servidor DNS. El servidor DNS observa la API de Kubernetes en busca de nuevos Servicios y crea un conjunto de registros DNS para cada uno. Si el DNS se ha habilitado en todo el clúster, todos los Pods deben ser capaces de hacer la resolución de nombres de Services automáticamente.

  • No especifiques un hostPort para un Pod a menos que sea absolutamente necesario. Cuando vinculas un Pod a un hostPort, limita la cantidad de lugares en los que se puede agendar el Pod, porque cada combinación <hostIP, hostPort, protocol> debe ser única. Si no especificas el hostIP y protocol explícitamente, Kubernetes usará 0.0.0.0 como el hostIP predeterminado y TCP como el protocol por defecto.

    Si solo necesitas acceder al puerto con fines de depuración, puedes utilizar el apiserver proxy o kubectl port-forward.

    Si necesitas exponer explícitamente el puerto de un Pod en el nodo, considera usar un NodePort Service antes de recurrir a hostPort.

  • Evita usar hostNetwork, por las mismas razones que hostPort.

  • Usa headless Services (que tiene un ClusterIP de None) para el descubrimiento de servicios cuando no necesites balanceo de carga kube-proxy.

Usando Labels

  • Define y usa labels que identifiquen atributos semánticos de tu aplicación o Deployment, como { app.kubernetes.io/name: MyApp, tier: frontend, phase: test, deployment: v3 }. Puedes utilizar estas labels para seleccionar los Pods apropiados para otros recursos; por ejemplo, un Service que selecciona todo los Pods tier: frontend, o todos los componentes phase: test de app.kubernetes.io/name: MyApp. Revisa el libro de visitas para ver ejemplos de este enfoque.

    Un Service puede hacer que abarque múltiples Deployments omitiendo las labels específicas de la versión de su selector. Cuando necesites actualizar un servicio en ejecución sin downtime, usa un Deployment.

    Un estado deseado de un objeto se describe mediante una implementación, y si los cambios a esa especificación son aplicados, el controlador de implementación cambia el estado actual al estado deseado en un ritmo controlado.

  • Use las labels comunes de Kubernetes para casos de uso común. Estas labels estandarizadas enriquecen los metadatos de una manera que permite que las herramientas, incluyendo kubectl y el dashboard, trabajen de forma interoperable.

  • Puedes manipular las labels para la depuración. Debido a que los controladores de Kubernetes (como ReplicaSet) y los Services coinciden con los Pods usando labels de selector, se detendrá la eliminación de las labels relevantes de un Pod que sea considerado por un controlador o que un Service sirva tráfico. si quitas las labels de un Pod existente, su controlador creará un nuevo Pod para ocupar su lugar. Esto es un forma útil de depurar un Pod previamente "vivo" en un entorno de "cuarentena". Para eliminar interactivamente o agregar labels, usa kubectl label.

Usando kubectl

7.2 - ConfigMaps

Un configmap es un objeto de la API utilizado para almacenar datos no confidenciales en el formato clave-valor. Los Pods pueden utilizar los ConfigMaps como variables de entorno, argumentos de la linea de comandos o como ficheros de configuración en un Volumen.

Un ConfigMap te permite desacoplar la configuración de un entorno específico de una imagen de contenedor, así las aplicaciones son fácilmente portables.

Motivo

Utiliza un ConfigMap para crear una configuración separada del código de la aplicación.

Por ejemplo, imagina que estás desarrollando una aplicación que puedes correr en tu propio equipo (para desarrollo) y en el cloud (para mantener tráfico real). Escribes el código para configurar una variable llamada DATABASE_HOST. En tu equipo configuras la variable con el valor localhost. En el cloud, la configuras con referencia a un kubernetes Service que expone el componente de la base de datos en tu cluster.

Esto permite tener una imagen corriendo en un cloud y tener el mismo código localmente para checkearlo si es necesario.

Objeto ConfigMap

Un ConfigMap es un objeto de la API que permite almacenar la configuración de otros objetos utilizados. Aunque muchos objetos de kubernetes que tienen un spec, un ConfigMap tiene una sección data para almacenar items, identificados por una clave, y sus valores.

El nombre del ConfigMap debe ser un nombre de subdominio DNS válido.

ConfigMaps y Pods

Puedes escribir un Pod spec y referenciarlo a un ConfigMap y configurar el contenedor(es) de ese Pod en base a los datos del ConfigMap. El Pod y el ConfigMap deben estar en el mismo Namespace.

Este es un ejemplo de ConfigMap que tiene algunas claves con un valor simple, y otras claves donde el valor tiene un formato de un fragmento de configuración.

apiVersion: v1
kind: ConfigMap
metadata:
  name: game-demo
data:
  # property-like keys; each key maps to a simple value
  player_initial_lives: "3"
  ui_properties_file_name: "user-interface.properties"
  #
  # file-like keys
  game.properties: |
    enemy.types=aliens,monsters
    player.maximum-lives=5    
  user-interface.properties: |
    color.good=purple
    color.bad=yellow
    allow.textmode=true    

Hay cuatro maneras diferentes de usar un ConfigMap para configurar un contenedor dentro de un Pod:

  1. Argumento en la linea de comandos como entrypoint de un contenedor
  2. Variable de entorno de un contenedor
  3. Como fichero en un volumen de solo lectura, para que lo lea la aplicación
  4. Escribir el código para ejecutar dentro de un Pod que utiliza la API para leer el ConfigMap

Estos diferentes mecanismos permiten utilizar diferentes métodos para modelar los datos que se van a usar. Para los primeros tres mecanismos, el kubelet utiliza la información del ConfigMap cuando lanza un contenedor (o varios) en un Pod.

Para el cuarto método, tienes que escribir el código para leer el ConfigMap y sus datos. Sin embargo, como estás utilizando la API de kubernetes directamente, la aplicación puede suscribirse para obtener actualizaciones cuando el ConfigMap cambie, y reaccionar cuando esto ocurra. Accediendo directamente a la API de kubernetes, esta técnica también permite acceder al ConfigMap en diferentes namespaces.

En el siguiente ejemplo el Pod utiliza los valores de game-demo para configurar el contenedor:

apiVersion: v1
kind: Pod
metadata:
  name: configmap-demo-pod
spec:
  containers:
    - name: demo
      image: game.example/demo-game
      env:
        # Define the environment variable
        - name: PLAYER_INITIAL_LIVES # Notice that the case is different here
                                     # from the key name in the ConfigMap.
          valueFrom:
            configMapKeyRef:
              name: game-demo           # The ConfigMap this value comes from.
              key: player_initial_lives # The key to fetch.
        - name: UI_PROPERTIES_FILE_NAME
          valueFrom:
            configMapKeyRef:
              name: game-demo
              key: ui_properties_file_name
      volumeMounts:
      - name: config
        mountPath: "/config"
        readOnly: true
  volumes:
    # You set volumes at the Pod level, then mount them into containers inside that Pod
    - name: config
      configMap:
        # Provide the name of the ConfigMap you want to mount.
        name: game-demo
        # An array of keys from the ConfigMap to create as files
        items:
        - key: "game.properties"
          path: "game.properties"
        - key: "user-interface.properties"
          path: "user-interface.properties"

Un ConfigMap no diferencia entre las propiedades de una linea individual y un fichero con múltiples lineas y valores. Lo importante es como los Pods y otros objetos consumen estos valores.

Para este ejemplo, definimos un Volumen y lo montamos dentro del contenedor demo como /config creando dos ficheros, /config/game.properties y /config/user-interface.properties, aunque haya cuatro claves en el ConfigMap. Esto es debido a que enla definición del Pod se especifica el array items en la sección volumes. Si quieres omitir el array items entero, cada clave del ConfigMap se convierte en un fichero con el mismo nombre que la clave, y tienes 4 ficheros.

Usando ConfigMaps

Los ConfigMaps pueden montarse como volúmenes. También pueden ser utilizados por otras partes del sistema, sin ser expuestos directamente al Pod. Por ejemplo, los ConfigMaps pueden contener información para que otros elementos del sistema utilicen para su configuración.

Usando ConfigMaps como ficheros en un Pod

Para usar un ConfigMap en un volumen en un Pod:

  1. Crear un ConfigMap o usar uno que exista. Múltiples Pods pueden utilizar el mismo ConfigMap.
  2. Modifica la configuración del Pod para añadir el volumen en .spec.volumes[]. Pon cualquier nombre al Volumen, y tienes un campo .spec.volumes[].configMap.name configurado con referencia al objeto ConfigMap.
  3. Añade un .spec.containers[].volumeMounts[] a cada contenedor que necesite el ConfigMap. Especifica .spec.containers[].volumeMounts[].readOnly = true y .spec.containers[].volumeMounts[].mountPath en un directorio sin uso donde quieras que aparezca el ConfigMap.
  4. Modifica la imagen o el comando utilizado para que el programa busque los ficheros en el directorio. Cada clave del ConfigMap data se convierte en un un fichero en el mountPath.

En este ejemplo, el Pod monta un ConfigMap como un volumen:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    configMap:
      name: myconfigmap

Cada ConfigMap que quieras utilizar debe estar referenciado en .spec.volumes.

Si hay múltiples contenedores en el Pod, cada contenedor tiene su propio bloque volumeMounts, pero solo un .spec.volumes es necesario por cada ConfigMap.

ConfigMaps montados son actualizados automáticamente

Cuando un ConfigMap está siendo utilizado en un volumen y es actualizado, las claves son actualizadas también. El kubelet comprueba si el ConfigMap montado está actualizado cada periodo de sincronización. Sin embargo, el kubelet utiliza su caché local para obtener el valor actual del ConfigMap. El tipo de caché es configurable usando el campo ConfigMapAndSecretChangeDetectionStrategy en el KubeletConfiguration struct. Un ConfigMap puede ser propagado por vista (default), ttl-based, o simplemente redirigiendo todas las consultas directamente a la API. Como resultado, el retraso total desde el momento que el ConfigMap es actualizado hasta el momento que las nuevas claves son proyectadas en el Pod puede ser tan largo como la sincronización del Pod

  • el retraso de propagación de la caché, donde la propagación de la caché depende del tipo de caché elegido (es igual al retraso de propagación, ttl de la caché, o cero correspondientemente).
FEATURE STATE: Kubernetes v1.18 [alpha]

La característica alpha de kubernetes Immutable Secrets and ConfigMaps provee una opción para configurar Secrets individuales y ConfigMaps como inmutables. Para los Clústeres que usan ConfigMaps como extensión (al menos decenas o cientos de un único ConfigMap montado en Pods), previene cambios en sus datos con las siguientes ventajas:

  • protección de actualizaciones accidentales (o no deseadas) que pueden causar caídas de aplicaciones
  • mejora el rendimiento del Clúster significativamente reduciendo la carga del kube-apiserver, cerrando las vistas para el ConfigMap marcado como inmutable.

Para usar esta característica, habilita el ImmutableEmphemeralVolumes feature gate y configura el campo del Secret o ConfigMap immutable como true. Por ejemplo:

apiVersion: v1
kind: ConfigMap
metadata:
  ...
data:
  ...
immutable: true

Siguientes pasos

7.3 - Sobrecarga de Pod

FEATURE STATE: Kubernetes v1.16 [alpha]

Cuando se está ejecutando un Pod en un nodo, el Pod por sí mismo utiliza una cantidad de recursos del sistema. Estos recursos son adicionales a los recursos necesarios para hacer funcionar el/los contenedor(es) dentro del Pod. La Sobrecarga de Pod es una característica para contabilizar los recursos consumidos por la infraestructura de Pods que están por encima de los valores de Requests y Limits del/los contenedor(es).

Sobrecarga de Pod

En Kubernetes, la sobrecarga de Pod se configura en el tiempo de admisión con respecto a la sobrecarga asociada con el RuntimeClass del Pod.

Cuando se habilita la opción de sobrecarga de Pod, se considera tanto la propia sobrecarga como la suma de solicitudes de recursos del contenedor al programar el Pod. Del mismo modo, Kubelet incluirá la sobrecarga de Pod cuando se dimensione el cgroup del Pod, y cuando se realice la clasificación de la expulsión de Pods.

Configuración

Debe asegurarse de que el Feature Gate PodOverhead esté activado (su valor está desactivado de manera predeterminada) en todo el clúster. Esto significa:

7.4 - Administrando los recursos de los contenedores

Cuando especificas un Pod, opcionalmente puedes especificar los recursos que necesita un Contenedor. Los recursos que normalmente se definen son CPU y memoria (RAM); pero hay otros.

Cuando especificas el recurso request para Contenedores en un Pod, el Scheduler de Kubernetes usa esta información para decidir en qué nodo colocar el Pod. Cuando especificas el recurso limit para un Contenedor, Kubelet impone estos límites, así que el contenedor no puede utilizar más recursos que el límite que le definimos. Kubelet también reserva al menos la cantidad especificada en request para el contenedor.

Peticiones y límites

Si el nodo donde está corriendo un pod tiene suficientes recursos disponibles, es posible (y válido) que el contenedor utilice más recursos de los especificados en request. Sin embargo, un contenedor no está autorizado a utilizar más de lo especificado en limit.

Por ejemplo, si configuras una petición de memory de 256 MiB para un contenedor, y ese contenedor está en un Pod colocado en un nodo con 8GiB de memoria y no hay otros Pod, entonces el contenedor puede intentar usar más RAM.

Si configuras un límite de memory de 4GiB para el contenedor, kubelet) (y motor de ejecución del contenedor) impone el límite. El Runtime evita que el contenedor use más recursos de los configurados en el límite. Por ejemplo: cuando un proceso en el contenedor intenta consumir más cantidad de memoria de la permitida, el Kernel del sistema termina el proceso que intentó la utilización de la memoria, con un error de out of memory (OOM).

Los límites se pueden implementar de forma reactiva (el sistema interviene cuando ve la violación) o por imposición (el sistema previene al contenedor de exceder el límite). Diferentes Runtimes pueden tener distintas implementaciones a las mismas restricciones.

Tipos de recursos

CPU y memoria son cada uno un tipo de recurso. Un tipo de recurso tiene una unidad base. CPU representa procesos de computación y es especificada en unidades de Kubernetes CPUs. Memoria es especificada en unidades de bytes. Si estás usando Kubernetes v1.14 o posterior, puedes especificar recursos huge page. Huge pages son una característica de Linux específica donde el kernel del nodo asigna bloques de memoria que son más grandes que el tamaño de paginación por defecto.

Por ejemplo, en un sistema donde el tamaño de paginación por defecto es de 4KiB, podrías especificar un límite, hugepages-2Mi: 80Mi. Si el contenedor intenta asignar más de 40 2MiB huge pages (un total de 80 MiB), la asignación fallará.

CPU y memoria son colectivamente conocidos como recursos de computación, o simplemente como recursos. Los recursos de computación son cantidades medibles que pueden ser solicitadas, asignadas y consumidas. Son distintas a los Recursos API. Los recursos API , como Pods y Services son objetos que pueden ser leídos y modificados a través de la API de Kubernetes.

Peticiones y límites de recursos de Pods y Contenedores

Cada contenedor de un Pod puede especificar uno o más de los siguientes:

  • spec.containers[].resources.limits.cpu
  • spec.containers[].resources.limits.memory
  • spec.containers[].resources.limits.hugepages-<size>
  • spec.containers[].resources.requests.cpu
  • spec.containers[].resources.requests.memory
  • spec.containers[].resources.requests.hugepages-<size>

Aunque las peticiones y límites pueden ser especificadas solo en contenedores individuales, es conveniente hablar sobre los recursos de peticiones y límites del Pod. Un limite/petición de recursos de un Pod para un tipo de recurso particular es la suma de peticiones/límites de cada tipo para cada contenedor del Pod.

Unidades de recursos en Kubernetes

Significado de CPU

Límites y peticiones para recursos de CPU son medidos en unidades de cpu. Una cpu, en Kubernetes, es equivalente a 1 vCPU/Core para proveedores de cloud y 1 hyperthread en procesadores bare-metal Intel.

Las peticiones fraccionadas están permitidas. Un contenedor con spec.containers[].resources.requests.cpu de 0.5 tiene garantizada la mitad, tanto CPU como otro que requiere 1 CPU. La expresión 0.1 es equivalente a la expresión 100m, que puede ser leída como "cien millicpus". Algunas personas dicen "cienmilicores", y se entiende que quiere decir lo mismo. Una solicitud con un punto decimal, como 0.1, es convertido a 100m por la API, y no se permite una precisión mayor que 1m. Por esta razón, la forma 100m es la preferente. CPU es siempre solicitada como una cantidad absoluta, nunca como una cantidad relativa; 0.1 es la misma cantidad de cpu que un core-simple, dual-core, o máquina de 48-core.

Significado de memoria

Los límites y peticiones de memoria son medidos en bytes. Puedes expresar la memoria como un número entero o como un número decimal usando alguno de estos sufijos: E, P, T, G, M, k, m (millis). También puedes usar los equivalentes en potencia de dos: Ei, Pi, Ti, Gi, Mi, Ki. Por ejemplo, los siguientes valores representan lo mismo:

128974848, 129e6, 129M, 128974848000m, 123Mi

Aquí un ejemplo. El siguiente Pod tiene dos contenedores. Cada contenedor tiene una petición de 0.25 cpu y 64MiB (226 bytes) de memoria. Cada contenedor tiene un límite de 0.5 cpu y 128MiB de memoria. Puedes decirle al Pod que solicite 0.5 cpu y 128MiB de memoria y un límite de 1 cpu y 256MiB de memoria.

apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: app
    image: images.my-company.example/app:v4
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
  - name: log-aggregator
    image: images.my-company.example/log-aggregator:v6
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

Cómo son programados los Pods con solicitudes de recursos

Cuando creas un Pod, el planificador de Kubernetes determina el nodo para correr dicho Pod. Cada nodo tiene una capacidad máxima para cada tipo de recurso: la cantidad de CPU y memoria que dispone para los Pods. El planificador de Kubernetes se asegura de que, para cada tipo de recurso, la suma de los recursos solicitados de los contenedores programados sea menor a la capacidad del nodo. Cabe mencionar que aunque la memoria actual o CPU en uso de los nodos sea muy baja, el planificador todavía rechaza programar un Pod en un nodo si la comprobación de capacidad falla. Esto protege contra escasez de recursos en un nodo cuando el uso de recursos posterior crece, por ejemplo, durante un pico diario de solicitud de recursos.

Cómo corren los Pods con límites de recursos

Cuando el kubelet inicia un contenedor de un Pod, este pasa los límites de CPU y memoria al runtime del contenedor.

Cuando usas Docker:

  • El spec.containers[].resources.requests.cpu es convertido a su valor interno, el cuál es fraccional, y multiplicado por 1024. El mayor valor de este número o 2 es usado por el valor de --cpu-shares en el comando docker run.

  • El spec.containers[].resources.limits.cpu se convierte a su valor en milicore y multiplicado por 100. El resultado es el tiempo total de CPU que un contenedor puede usar cada 100ms. Un contenedor no puede usar más tiempo de CPU que del solicitado durante este intervalo.

  • El spec.containers[].resources.limits.memory se convierte a entero, y se usa como valor de --memory del comando docker run.

Si el contenedor excede su límite de memoria, este quizá se detenga. Si es reiniciable, el kubelet lo reiniciará, así como cualquier otro error.

Si un Contenedor excede su petición de memoria, es probable que ese Pod sea desalojado en cualquier momento que el nodo se quede sin memoria.

Un Contenedor puede o no tener permitido exceder el límite de CPU por algunos períodos de tiempo. Sin embargo, esto no lo destruirá por uso excesivo de CPU.

Para conocer cuando un Contenedor no puede ser programado o será destruido debido a límite de recursos, revisa la sección de Troubleshooting.

Monitorización del uso de recursos de computación y memoria.

El uso de recursos de un Pod es reportado como parte del estado del Pod.

Si herramientas opcionales para monitorización están disponibles en tu cluster, entonces el uso de recursos del Pod puede extraerse directamente de Métricas API o desde tus herramientas de monitorización.

Almacenamiento local efímero

FEATURE STATE: Kubernetes v1.10 [beta]

Los nodos tienen almacenamiento local efímero, respaldado por dispositivos de escritura agregados o, a veces, por RAM. "Efímero" significa que no se garantiza la durabilidad a largo plazo. . Los Pods usan el almacenamiento local efímero para añadir espacio, caché, y para logs. Kubelet puede proveer espacio añadido a los Pods usando almacenamiento local efímero para montar emptyDir volumes en los contenedores.

Kubelet también usa este tipo de almacenamiento para guardar logs de contenedores a nivel de nodo, imágenes de contenedores, y la capa de escritura de los contenedores.

Como característica beta, Kubernetes te deja probar, reservar y limitar la cantidad de almacenamiento local efímero que un Pod puede consumir.

Configuraciones para almacenamiento local efímero

Kubernetes soporta 2 maneras de configurar el almacenamiento local efímero en un nodo:

En esta configuración, colocas todos los tipos de datos (emptyDir volúmenes, capa de escritura, imágenes de contenedores, logs) en un solo sistema de ficheros. La manera más efectiva de configurar Kubelet es dedicando este sistema de archivos para los datos de Kubernetes (kubelet).

Kubelet también escribe logs de contenedores a nivel de nodo y trata estos de manera similar al almacenamiento efímero.

Kubelet escribe logs en ficheros dentro del directorio de logs (por defecto /var/log ); y tiene un directorio base para otros datos almacenados localmente (/var/lib/kubelet por defecto).

Por lo general, /var/lib/kubelet y /var/log están en el sistema de archivos de root, y Kubelet es diseñado con ese objetivo en mente.

Tu nodo puede tener tantos otros sistema de archivos, no usados por Kubernetes, como quieras.

Tienes un sistema de archivos en el nodo que estás usando para datos efímeros que provienen de los Pods corriendo: logs, y volúmenes emptyDir. Puedes usar este sistema de archivos para otros datos (por ejemplo: logs del sistema no relacionados con Kubernetes); estos pueden ser incluso del sistema de archivos root.

Kubelet también escribe logs de contenedores a nivel de nodo en el primer sistema de archivos, y trata estos de manera similar al almacenamiento efímero.

También usas un sistema de archivos distinto, respaldado por un dispositivo de almacenamiento lógico diferente. En esta configuración, el directorio donde le dices a Kubelet que coloque las capas de imágenes de los contenedores y capas de escritura es este segundo sistema de archivos.

El primer sistema de archivos no guarda ninguna capa de imágenes o de escritura.

Tu nodo puede tener tantos sistemas de archivos, no usados por Kubernetes, como quieras.

Kubelet puede medir la cantidad de almacenamiento local que se está usando. Esto es posible por:

  • el LocalStorageCapacityIsolation feature gate está habilitado (esta caracterísitca está habilitada por defecto), y
  • has configurado el nodo usando una de las configuraciones soportadas para almacenamiento local efímero..

Si tienes una configuración diferente, entonces Kubelet no aplica límites de recursos para almacenamiento local efímero.

Configurando solicitudes y límites para almacenamiento local efímero

Puedes usar ephemeral-storage para manejar almacenamiento local efímero. Cada contenedor de un Pod puede especificar uno o más de los siguientes:

  • spec.containers[].resources.limits.ephemeral-storage
  • spec.containers[].resources.requests.ephemeral-storage

Los límites y solicitudes para almacenamiento-efímero son medidos en bytes. Puedes expresar el almacenamiento como un numero entero o flotante usando los siguientes sufijos: E, P, T, G, M, K. También puedes usar las siguientes equivalencias: Ei, Pi, Ti, Gi, Mi, Ki. Por ejemplo, los siguientes representan el mismo valor:

128974848, 129e6, 129M, 123Mi

En el siguiente ejemplo, el Pod tiene dos contenedores. Cada contenedor tiene una petición de 2GiB de almacenamiento local efímero. Cada contenedor tiene un límite de 4GiB de almacenamiento local efímero. Sin embargo, el Pod tiene una petición de 4GiB de almacenamiento efímero , y un límite de 8GiB de almacenamiento local efímero.

apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: app
    image: images.my-company.example/app:v4
    resources:
      requests:
        ephemeral-storage: "2Gi"
      limits:
        ephemeral-storage: "4Gi"
    volumeMounts:
      - name: ephemeral
        mountPath: "/tmp"
  - name: log-aggregator
    image: images.my-company.example/log-aggregator:v6
    resources:
      requests:
        ephemeral-storage: "2Gi"
      limits:
        ephemeral-storage: "4Gi"
    volumeMounts:
      - name: ephemeral
        mountPath: "/tmp"
  volumes:
    - name: ephemeral
      emptyDir: {}

Como son programados los Pods con solicitudes de almacenamiento efímero

Cuando creas un Pod, el planificador de Kubernetes selecciona un nodo para el Pod donde sera creado. Cada nodo tiene una cantidad máxima de almacenamiento local efímero que puede proveer a los Pods. Para más información, mira Node Allocatable.

El planificador se asegura de que el total de los recursos solicitados para los contenedores sea menor que la capacidad del nodo.

Manejo del consumo de almacenamiento efímero

Si Kubelet está manejando el almacenamiento efímero local como un recurso, entonces Kubelet mide el uso de almacenamiento en:

  • volúmenes emptyDir, excepto tmpfs volúmenesemptyDir
  • directorios que guardan logs de nivel de nodo
  • capas de escritura de contenedores

Si un Pod está usando más almacenamiento efímero que el permitido, Kubelet establece una señal de desalojo que desencadena el desalojo del Pod.

Para aislamiento a nivel de contenedor, si una capa de escritura del contenedor y logs excede el límite de uso del almacenamiento, Kubelet marca el Pod para desalojo.

Para aislamiento a nivel de Pod, Kubelet calcula un límite de almacenamiento general para el Pod sumando los límites de los contenedores de ese Pod. En este caso, si la suma del uso de almacenamiento local efímero para todos los contenedores y los volúmenes emptyDir de los Pods excede el límite de almacenamiento general del Pod, Kubelet marca el Pod para desalojo.

Kubelet soporta diferentes maneras de medir el uso de almacenamiento del Pod:

Kubelet realiza frecuentemente, verificaciones programadas que revisan cada volumen emptyDir, directorio de logs del contenedor, y capa de escritura del contenedor.

El escáner mide cuanto espacio está en uso.

FEATURE STATE: Kubernetes v1.15 [alpha]

Las cuotas de proyecto están definidas a nivel de sistema operativo para el manejo de uso de almacenamiento en uso de sistema de archivos. Con Kubernetes, puedes habilitar las cuotas de proyecto para el uso de la monitorización del almacenamiento. Asegúrate que el respaldo del Sistema de archivos de los volúmenes emptyDir , en el nodo, provee soporte de cuotas de proyecto. Por ejemplo, XFS y ext4fs ofrecen cuotas de proyecto.

Kubernetes usa IDs de proyecto empezando por 1048576. Los IDs en uso son registrados en /etc/projects y /etc/projid. Si los IDs de proyecto en este rango son usados para otros propósitos en el sistema, esos IDs de proyecto deben ser registrados en /etc/projects y /etc/projid para que Kubernetes no los use.

Las cuotas son más rápidas y más precisas que el escáner de directorios. Cuando un directorio es asignado a un proyecto, todos los ficheros creados bajo un directorio son creados en ese proyecto, y el kernel simplemente tiene que mantener rastreados cuántos bloques están en uso por ficheros en ese proyecto. Si un fichero es creado y borrado, pero tiene un fichero abierto, continúa consumiendo espacio. El seguimiento de cuotas registra ese espacio con precisión mientras que los escaneos de directorios pasan por alto el almacenamiento utilizado por los archivos eliminados

Si quieres usar cuotas de proyecto, debes:

  • Habilitar el LocalStorageCapacityIsolationFSQuotaMonitoring=true feature gate en la configuración del kubelet.

  • Asegúrese de que el sistema de archivos raíz (o el sistema de archivos en tiempo de ejecución opcional) tiene las cuotas de proyectos habilitadas. Todos los sistemas de archivos XFS admiten cuotas de proyectos. Para los sistemas de archivos ext4, debe habilitar la función de seguimiento de cuotas del proyecto mientras el sistema de archivos no está montado.

    # For ext4, with /dev/block-device not mounted
    sudo tune2fs -O project -Q prjquota /dev/block-device
    
  • Asegúrese de que el sistema de archivos raíz (o el sistema de archivos de tiempo de ejecución opcional) esté montado con cuotas de proyecto habilitadas. Tanto para XFS como para ext4fs, la opción de montaje se llama prjquota.

Recursos extendidos

Los recursos extendidos son nombres de recursos calificados fuera del dominio kubernetes.io. Permiten que los operadores de clústers publiciten y los usuarios consuman los recursos no integrados de Kubernetes.

Hay dos pasos necesarios para utilizar los recursos extendidos. Primero, el operador del clúster debe anunciar un Recurso Extendido. En segundo lugar, los usuarios deben solicitar el Recurso Extendido en los Pods.

Manejando recursos extendidos

Recursos extendido a nivel de nodo

Los recursos extendidos a nivel de nodo están vinculados a los nodos

Device plugin managed resources

Mira Plugins de Dispositivos para percibir como los plugins de dispositivos manejan los recursos en cada nodo.

Otros recursos

Para anunciar un nuevo recurso extendido a nivel de nodo, el operador del clúster puede enviar una solicitud HTTP PATCH al servidor API para especificar la cantidad disponible en el status.capacity para un nodo en el clúster. Después de esta operación, el status.capacity del nodo incluirá un nuevo recurso. El campo status.allocatable se actualiza automáticamente con el nuevo recurso de forma asíncrona por el kubelet. Tenga en cuenta que debido a que el planificador utiliza el valor de status.allocatable del nodo cuando evalúa la aptitud del Pod, puede haber un breve retraso entre parchear la capacidad del nodo con un nuevo recurso y el primer Pod que solicita el recurso en ese nodo.

Ejemplo:

Aquí hay un ejemplo que muestra cómo usar curl para formar una solicitud HTTP que anuncia cinco recursos "example.com/foo" en el nodo k8s-node-1 cuyo nodo master es k8s-master.

curl --header "Content-Type: application/json-patch+json" \
--request PATCH \
--data '[{"op": "add", "path": "/status/capacity/example.com~1foo", "value": "5"}]' \
http://k8s-master:8080/api/v1/nodes/k8s-node-1/status

Recursos extendidos a nivel de Clúster

Los recursos extendidos a nivel de clúster no están vinculados a los nodos. Suelen estar gestionados por extensores del scheduler, que manejan el consumo de recursos y la cuota de recursos.

Puedes especificar los recursos extendidos que son mantenidos por los extensores del scheduler en configuración de políticas del scheduler.

Ejemplo:

La siguiente configuración para una política del scheduler indica que el recurso extendido a nivel de clúster "example.com/foo" es mantenido por el extensor del scheduler.

  • El scheduler envía un Pod al extensor del scheduler solo si la solicitud del Pod "example.com/foo".
  • El campo ignoredByScheduler especifica que el schduler no compruba el recurso "example.com/foo" en su predicado PodFitsResources.
{
  "kind": "Policy",
  "apiVersion": "v1",
  "extenders": [
    {
      "urlPrefix":"<extender-endpoint>",
      "bindVerb": "bind",
      "managedResources": [
        {
          "name": "example.com/foo",
          "ignoredByScheduler": true
        }
      ]
    }
  ]
}

Consumiendo recursos extendidos

Los usuarios pueden consumir recursos extendidos en las especificaciones del Pod, como la CPU y la memoria. El planificador se encarga de la contabilidad de recursos para que no más de la cantidad disponible sea asignada simultáneamente a los Pods.

El servidor de API restringe las cantidades de recursos extendidos a números enteros. Ejemplos de cantidades validas son 3, 3000m y 3Ki. Ejemplos de cantidades no válidas son 0.5 y 1500m.

Para consumir un recurso extendido en un Pod, incluye un nombre de recurso como clave en spec.containers[].resources.limits en las especificaciones del contenedor.

Un pod se programa solo si se satisfacen todas las solicitudes de recursos, incluidas CPU, memoria y cualquier recurso extendido. El Pod permanece en estado PENDING siempre que no se pueda satisfacer la solicitud de recursos.

Ejemplo:

El siguiente Pod solicita 2CPUs y 1 "example.com/foo" (un recurso extendido).

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
  - name: my-container
    image: myimage
    resources:
      requests:
        cpu: 2
        example.com/foo: 1
      limits:
        example.com/foo: 1

Solución de problemas

Mis Pods están en estado pendiente con un mensaje de failedScheduling

Si el planificador no puede encontrar ningún nodo donde pueda colocar un Pod, el Pod permanece no programado hasta que se pueda encontrar un lugar. Se produce un evento cada vez que el planificador no encuentra un lugar para el Pod, como este:

kubectl describe pod frontend | grep -A 3 Events
Events:
  FirstSeen LastSeen   Count  From          Subobject   PathReason      Message
  36s   5s     6      {scheduler }              FailedScheduling  Failed for reason PodExceedsFreeCPU and possibly others

En el ejemplo anterior, el Pod llamado "frontend" no se puede programar debido a recursos de CPU insuficientes en el nodo. Mensajes de error similares también pueden sugerir fallo debido a memoria insuficiente (PodExceedsFreeMemory). En general, si un Pod está pendiente con un mensaje de este tipo, hay varias cosas para probar:

  • Añadir más nodos al clúster.
  • Terminar Pods innecesarios para hacer hueco a los Pods en estado pendiente.
  • Compruebe que el Pod no sea más grande que todos los nodos. Por ejemplo, si todos los los nodos tienen una capacidad de cpu: 1, entonces un Pod con una solicitud de cpu: 1.1 nunca se programará.

Puedes comprobar las capacidades del nodo y cantidad utilizada con el comando kubectl describe nodes. Por ejemplo:

kubectl describe nodes e2e-test-node-pool-4lw4
Name:            e2e-test-node-pool-4lw4
[ ... lines removed for clarity ...]
Capacity:
 cpu:                               2
 memory:                            7679792Ki
 pods:                              110
Allocatable:
 cpu:                               1800m
 memory:                            7474992Ki
 pods:                              110
[ ... lines removed for clarity ...]
Non-terminated Pods:        (5 in total)
  Namespace    Name                                  CPU Requests  CPU Limits  Memory Requests  Memory Limits
  ---------    ----                                  ------------  ----------  ---------------  -------------
  kube-system  fluentd-gcp-v1.38-28bv1               100m (5%)     0 (0%)      200Mi (2%)       200Mi (2%)
  kube-system  kube-dns-3297075139-61lj3             260m (13%)    0 (0%)      100Mi (1%)       170Mi (2%)
  kube-system  kube-proxy-e2e-test-...               100m (5%)     0 (0%)      0 (0%)           0 (0%)
  kube-system  monitoring-influxdb-grafana-v4-z1m12  200m (10%)    200m (10%)  600Mi (8%)       600Mi (8%)
  kube-system  node-problem-detector-v0.1-fj7m3      20m (1%)      200m (10%)  20Mi (0%)        100Mi (1%)
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  CPU Requests    CPU Limits    Memory Requests    Memory Limits
  ------------    ----------    ---------------    -------------
  680m (34%)      400m (20%)    920Mi (11%)        1070Mi (13%)

EN la salida anterior, puedes ver si una solicitud de Pod mayor que 1120m CPUs o 6.23Gi de memoria, no cabrán en el nodo.

Echando un vistazo a la sección Pods, puedes ver qué Pods están ocupando espacio en el nodo.

La cantidad de recursos disponibles para los pods es menor que la capacidad del nodo, porque los demonios del sistema utilizan una parte de los recursos disponibles. El campo allocatable NodeStatus indica la cantidad de recursos que están disponibles para los Pods. Para más información, mira Node Allocatable Resources.

La característica resource quota se puede configurar para limitar la cantidad total de recursos que se pueden consumir. Si se usa en conjunto con espacios de nombres, puede evitar que un equipo acapare todos los recursos.

Mi contenedor está terminado

Es posible que su contenedor se cancele porque carece de recursos. Para verificar si un contenedor está siendo eliminado porque está alcanzando un límite de recursos, ejecute kubectl describe pod en el Pod de interés:

kubectl describe pod simmemleak-hra99
Name:                           simmemleak-hra99
Namespace:                      default
Image(s):                       saadali/simmemleak
Node:                           kubernetes-node-tf0f/10.240.216.66
Labels:                         name=simmemleak
Status:                         Running
Reason:
Message:
IP:                             10.244.2.75
Replication Controllers:        simmemleak (1/1 replicas created)
Containers:
  simmemleak:
    Image:  saadali/simmemleak
    Limits:
      cpu:                      100m
      memory:                   50Mi
    State:                      Running
      Started:                  Tue, 07 Jul 2015 12:54:41 -0700
    Last Termination State:     Terminated
      Exit Code:                1
      Started:                  Fri, 07 Jul 2015 12:54:30 -0700
      Finished:                 Fri, 07 Jul 2015 12:54:33 -0700
    Ready:                      False
    Restart Count:              5
Conditions:
  Type      Status
  Ready     False
Events:
  FirstSeen                         LastSeen                         Count  From                              SubobjectPath                       Reason      Message
  Tue, 07 Jul 2015 12:53:51 -0700   Tue, 07 Jul 2015 12:53:51 -0700  1      {scheduler }                                                          scheduled   Successfully assigned simmemleak-hra99 to kubernetes-node-tf0f
  Tue, 07 Jul 2015 12:53:51 -0700   Tue, 07 Jul 2015 12:53:51 -0700  1      {kubelet kubernetes-node-tf0f}    implicitly required container POD   pulled      Pod container image "registry.k8s.io/pause:0.8.0" already present on machine
  Tue, 07 Jul 2015 12:53:51 -0700   Tue, 07 Jul 2015 12:53:51 -0700  1      {kubelet kubernetes-node-tf0f}    implicitly required container POD   created     Created with docker id 6a41280f516d
  Tue, 07 Jul 2015 12:53:51 -0700   Tue, 07 Jul 2015 12:53:51 -0700  1      {kubelet kubernetes-node-tf0f}    implicitly required container POD   started     Started with docker id 6a41280f516d
  Tue, 07 Jul 2015 12:53:51 -0700   Tue, 07 Jul 2015 12:53:51 -0700  1      {kubelet kubernetes-node-tf0f}    spec.containers{simmemleak}         created     Created with docker id 87348f12526a

En el ejemplo anterior, Restart Count: 5 indica que el contenedor simmemleak del Pod se reinició cinco veces.

Puedes ejecutar kubectl get pod con la opción -o go-template=... para extraer el estado previos de los Contenedores terminados:

kubectl get pod -o go-template='{{range.status.containerStatuses}}{{"Container Name: "}}{{.name}}{{"\r\nLastState: "}}{{.lastState}}{{end}}'  simmemleak-hra99
Container Name: simmemleak
LastState: map[terminated:map[exitCode:137 reason:OOM Killed startedAt:2015-07-07T20:58:43Z finishedAt:2015-07-07T20:58:43Z containerID:docker://0e4095bba1feccdfe7ef9fb6ebffe972b4b14285d5acdec6f0d3ae8a22fad8b2]]

Puedes ver que el Contenedor fué terminado a causa de reason:OOM Killed, donde OOM indica una falta de memoria.

Siguientes pasos

7.5 - Secrets

Los objetos de tipo Secret en Kubernetes te permiten almacenar y administrar información confidencial, como contraseñas, tokens OAuth y llaves ssh. Poniendo esta información en un Secret es más seguro y más flexible que ponerlo en la definición de un Pod o en un container image. Ver Secrets design document para más información.

Introducción a Secrets

Un Secret es un objeto que contiene una pequeña cantidad de datos confidenciales como contraseñas, un token, o una llave. Tal información podría ser puesta en la especificación de un Pod o en una imagen; poniendolo en un objeto de tipo Secret permite mayor control sobre como se usa, y reduce el riesgo de exposicición accidental.

Los usuarios pueden crear Secrets, y el sistema también puede crearlos.

Para usar un Secret, un Pod debe hacer referencia a este. Un Secret puede ser usado con un Pod de dos formas: como archivos en un volume montado en uno o más de sus contenedores, o utilizados por el kubelet al extraer imágenes del pod.

Secrets incorporados

Las Cuentas de Servicio Crean y Adjuntan Secrets con las Credenciales de la API

Kubernetes crea automaticamente Secrets que contienen credenciales para acceder a la API y modifica automáticamente sus pods para usar este tipo de Secret.

La creación y el uso automático de las credenciales de la API, pueden desabilitarse o anularse si se desea. Sin embargo, si todo lo que necesita hacer es acceder de forma segura al apiserver, este es el flujo de trabajo recomendado.

Ver la documentación de Service Account para más información sobre cómo funcionan las Cuentas de Servicio.

Creando tu propio Secret

Creando un Secret Usando kubectl create Secret

Pongamos como ejemplo el caso de una grupo de pods que necesitan acceder a una base de datos. El nombre y contraseña que los pods deberían usar están en los archivos: ./username.txt y ./password.txt en tu máquina local.

# Crear archivos necesarios para el resto del ejemplo.
echo -n 'admin' > ./username.txt
echo -n '1f2d1e2e67df' > ./password.txt

El comando kubectl create secret empaqueta esos archivos en un Secret y crea el objeto en el Apiserver.

kubectl create secret generic db-user-pass --from-file=./username.txt --from-file=./password.txt
Secret "db-user-pass" created

Puedes comprobar que el Secret se haya creado, así:

kubectl get secrets
NAME                  TYPE                                  DATA      AGE
db-user-pass          Opaque                                2         51s
kubectl describe secrets/db-user-pass
Name:            db-user-pass
Namespace:       default
Labels:          <none>
Annotations:     <none>

Type:            Opaque

Data
====
password.txt:    12 bytes
username.txt:    5 bytes

Ver Decodificando un Secret para ver el contenido de un Secret.

Creando un Secret Manualmente

Puedes crear también un Secret primero en un archivo, en formato json o en yaml, y luego crear ese objeto. El Secret contiene dos mapas: data y stringData. El campo de data es usado para almacenar datos arbitrarios, codificado usando base64. El campo stringData se proporciona para su conveniencia, y le permite proporcionar datos secretos como cadenas no codificadas.

Por ejemplo, para almacenar dos cadenas en un Secret usando el campo data, conviértalos a base64 de la siguiente manera:

echo -n 'admin' | base64
YWRtaW4=
echo -n '1f2d1e2e67df' | base64
MWYyZDFlMmU2N2Rm

Escribe un secret que se vea así:

apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  username: YWRtaW4=
  password: MWYyZDFlMmU2N2Rm

Ahora escribe un Secret usando kubectl apply:

kubectl apply -f ./secret.yaml
secret "mysecret" created

Para ciertos escenarios, es posible que desee utilizar el campo de stringData field en su lugar. Este campo le permite poner una cadena codificada que no sea base64 directamente en el Secret, y la cadena será codificada para ti cuando el Secret es creado o actualizado.

Un ejemplo práctico de esto podría ser donde está implementando una aplicación que usa un Secret para almacenar un archivo de configuración, y desea completar partes de ese archivo de configuración durante su proceso de implementación.

Si su aplicación usa el siguiente archivo de configuración:

apiUrl: "https://my.api.com/api/v1"
username: "user"
password: "password"

Podrías almacenarlo en un Secret usando lo siguiente:

apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
stringData:
  config.yaml: |-
    apiUrl: "https://my.api.com/api/v1"
    username: {{username}}
    password: {{password}}    

Su herramienta de despliegue podría entonces reemplazar el {{username}} y {{password}} variables de plantilla antes de ejecutar kubectl apply.

stringData es un campo de conveniencia de solo lectura. Nunca se muestran cuando se recuperan Secrets. Por ejemplo, si ejecuta el siguiente comando:

kubectl get secret mysecret -o yaml

La salida será similar a:

apiVersion: v1
kind: Secret
metadata:
  creationTimestamp: 2018-11-15T20:40:59Z
  name: mysecret
  namespace: default
  resourceVersion: "7225"
  selfLink: /api/v1/namespaces/default/secrets/mysecret
  uid: c280ad2e-e916-11e8-98f2-025000000001
type: Opaque
data:
  config.yaml: YXBpVXJsOiAiaHR0cHM6Ly9teS5hcGkuY29tL2FwaS92MSIKdXNlcm5hbWU6IHt7dXNlcm5hbWV9fQpwYXNzd29yZDoge3twYXNzd29yZH19

Si se especifica un campo tanto de data y stringData, el valor de StringData es usado. Por ejemplo, la siguiente definición de Secret:

apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  username: YWRtaW4=
stringData:
  username: administrator

Los resultado en el siguiente Secret:

apiVersion: v1
kind: Secret
metadata:
  creationTimestamp: 2018-11-15T20:46:46Z
  name: mysecret
  namespace: default
  resourceVersion: "7579"
  selfLink: /api/v1/namespaces/default/secrets/mysecret
  uid: 91460ecb-e917-11e8-98f2-025000000001
type: Opaque
data:
  username: YWRtaW5pc3RyYXRvcg==

Donde YWRtaW5pc3RyYXRvcg== decodifica a administrator.

Las llaves de data y stringData deben consistir en caracteres alfanuméricos, '-', '_' or '.'.

Nota de codificación: Los valores serializados JSON y YAML de los datos secretos estan codificadas como cadenas base64. Las nuevas lineas no son válidas dentro de esa cadena y debe ser omitido. Al usar base64 en Darwin/macOS, los usuarios deben evitar el uso de la opción -b para dividir líneas largas. Por lo contratio los usuarios de Linux deben añadir la opción -w 0 a los comandos base64 o al pipeline base64 | tr -d '\n' si la opción -w no esta disponible.

Creando un Secret a partir de Generador

Kubectl soporta managing objects using Kustomize desde 1.14. Con esta nueva característica, puedes tambien crear un Secret a partir de un generador y luego aplicarlo para crear el objeto en el Apiserver. Los generadores deben ser especificados en un kustomization.yaml dentro de un directorio.

Por ejemplo, para generar un Secret a partir de los archivos ./username.txt y ./password.txt

# Crear un fichero llamado kustomization.yaml con SecretGenerator
cat <<EOF >./kustomization.yaml
secretGenerator:
- name: db-user-pass
  files:
  - username.txt
  - password.txt
EOF

Aplica el directorio kustomization para crear el objeto Secret.

$ kubectl apply -k .
secret/db-user-pass-96mffmfh4k created

Puedes verificar que el secret fue creado de la siguiente manera:

$ kubectl get secrets
NAME                             TYPE                                  DATA      AGE
db-user-pass-96mffmfh4k          Opaque                                2         51s

$ kubectl describe secrets/db-user-pass-96mffmfh4k
Name:            db-user-pass
Namespace:       default
Labels:          <none>
Annotations:     <none>

Type:            Opaque

Data
====
password.txt:    12 bytes
username.txt:    5 bytes

Por ejemplo, para generar un Secret a partir de literales username=admin y password=secret, puedes especificar el generador del Secret en kustomization.yaml como:

# Crea un fichero kustomization.yaml con SecretGenerator
cat <<EOF >./kustomization.yaml
secretGenerator:
- name: db-user-pass
  literals:
  - username=admin
  - password=secret
EOF

Aplica el directorio kustomization para crear el objeto Secret.

kubectl apply -k .
secret/db-user-pass-dddghtt9b5 created

Decodificando un Secret

Los Secrets se pueden recuperar a través del comando kubectl get secret . Por ejemplo, para recuperar el Secret creado en la sección anterior:

kubectl get secret mysecret -o yaml
apiVersion: v1
kind: Secret
metadata:
  creationTimestamp: 2016-01-22T18:41:56Z
  name: mysecret
  namespace: default
  resourceVersion: "164619"
  selfLink: /api/v1/namespaces/default/secrets/mysecret
  uid: cfee02d6-c137-11e5-8d73-42010af00002
type: Opaque
data:
  username: YWRtaW4=
  password: MWYyZDFlMmU2N2Rm

Decodifica el campo de contraseña:

echo 'MWYyZDFlMmU2N2Rm' | base64 --decode
1f2d1e2e67df

Usando Secrets

Los Secrets se pueden montar como volúmenes de datos o ser expuestos como variables de entorno para ser usados por un contenedor en un pod. También pueden ser utilizados por otras partes del sistema, sin estar directamente expuesto en el pod. Por ejemplo, pueden tener credenciales que otras partes del sistema usan para interactuar con sistemas externos en su nombre.

Usando Secrets como Archivos de un Pod

Para consumir un Secret en un volumen en un Pod:

  1. Crear un Secret o usar uno existente. Múltiples pods pueden referenciar el mismo Secret.
  2. Modifique la definición del Pod para agregar un volumen debajo de .spec.volumes[]. Asigne un nombre al volumen y tenga un campo .spec.volumes[].secret.secretName igual al nombre del objeto del Secret.
  3. Agrega un .spec.containers[].volumeMounts[] a cada contenedor que necesite un Secret. Especifica .spec.containers[].volumeMounts[].readOnly = true y .spec.containers[].volumeMounts[].mountPath a un nombre de directorio no utilizado donde desea que aparezca los Secrets.
  4. Modifique la imagen y/o linea de comando para que el programa busque archivos en ese directorio. Cada llave en el data map del los Secrets se convierte en el nombre del archivo bajo mountPath.

Este es un ejemplo de un pod que monta un Secret en un volumen:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: mysecret

Cada Secret que desea usar debe mencionarse en .spec.volumes.

Si hay múltiples contenedores en un Pod, entonces cada contenedor necesita su propio bloque volumeMounts , pero solo un .spec.volumes se necesita por Secret.

Puede empaquetar muchos archivos en un Secret, o usar muchos Secrets, lo que sea conveniente.

Proyección de llaves Secret a rutas específicas

También podemos controlar las rutas dentro del volumen donde se proyectan las llaves Secrets. Puede usar el campo .spec.volumes[].secret.items para cambiar la ruta de destino de cada clave:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      items:
      - key: username
        path: my-group/my-username

Lo que sucederá:

  • El Secret username se almacena bajo el archivo /etc/foo/my-group/my-username en lugar de /etc/foo/username.
  • El Secret password no se proyecta

Si se utiliza .spec.volumes[].secret.items , solo se proyectan las llaves específicadas en los items. Para consumir todas las llaves del Secret, Todas deben ser enumeradas en el campo items. Todas las llaves enumeradas deben existir en el Secret correspondiente. De lo contrario, el volumen no se crea.

Permisos de archivos Secrets

Tambien puede especificar el modo de permiso de los archivos de bits que tendrá una parte de un Secret. Si no especifica ninguno, 0644 es usado por defecto. Puede especificar un modo predeterminado para todo el volumen del Secret y sobreescribir por llave si es necesario.

Por ejemplo, puede especificar un modo predeterminado como este:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      defaultMode: 256

Entonces, el Secret será montado en /etc/foo y todos los archivos creados por el montaje del volumen del Secret tendrán permiso 0400.

Tenga en cuenta que la especificación JSON no soporta la notación octal, entonces use el valor 256 para permisos 0400. Si usa yaml en lugar de json para el pod, puede usar notación octal para especificar permisos de una manera más natural.

También puede usar el mapeo, como en el ejemplo anterior, y especificar diferentes permisos para diferentes archivos como:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      items:
      - key: username
        path: my-group/my-username
        mode: 511

En este caso, el archivo resultante en /etc/foo/my-group/my-username tendrá un valor de permiso 0777. Debido a las limitaciones de JSON, debe especificar el modo en notación decimal.

Tenga en cuenta que este valor de permiso puede mostrarse en notación decimal si lo lee después.

Consumir Valores Secrets de Volúmenes

Dentro del contenedor que monta un volumen del Secret, las llaves del Secret aparece como archivos y los valores del Secret son decodificados en base-64 y almacenados dentro de estos archivos. Este es el resultado de comandos ejecutados dentro del contenedor del ejemplo anterior:

ls /etc/foo/
username
password
cat /etc/foo/username
admin
cat /etc/foo/password
1f2d1e2e67df

El programa en un contenedor es responsable de leer los Secrets de los archivos.

Los Secrets Montados se actualizan automáticamente

Cuando se actualiza un Secret que ya se está consumiendo en un volumen, las claves proyectadas también se actualizan eventualmente. Kubelet está verificando si el Secret montado esta actualizado en cada sincronización periódica. Sin embargo, está usando su caché local para obtener el valor actual del Secret. El tipo de caché es configurable usando el (campo ConfigMapAndSecretChangeDetectionStrategy en KubeletConfiguration struct). Puede ser propagado por el reloj (default), ttl-based, o simplemente redirigiendo todas las solicitudes a kube-apiserver directamente. Como resultado, el retraso total desde el momento en que se actualiza el Secret hasta el momento en que se proyectan las nuevas claves en el Pod puede ser tan largo como el periodo de sincronización de kubelet + retraso de propagación de caché, donde el retraso de propagación de caché depende del tipo de caché elegido. (Es igual a mirar el retraso de propagación, ttl of cache, o cero correspondientemente).

Usando Secrets como Variables de Entorno

Para usar un Secret en una variable de entorno en un pod:

  1. Crea un Secret o usa uno existente. Múltiples pods pueden hacer referencia a un mismo Secret.
  2. Modifique la definición de su Pod en cada contenedor que desee consumir el valor de una llave Secret para agregar una variable de entorno para cada llave Secret que deseas consumir. La variable de entorno que consume la llave Secret debe completar el nombre y la llave del Secret en env[].valueFrom.secretKeyRef.
  3. Modifique su imagen y/o linea de comandos para que el programa busque valores en las variables de entorno especificadas.

Esto es un ejemplo de un pod que usa Secrets de variables de entorno:

apiVersion: v1
kind: Pod
metadata:
  name: secret-env-pod
spec:
  containers:
  - name: mycontainer
    image: redis
    env:
      - name: SECRET_USERNAME
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: username
      - name: SECRET_PASSWORD
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: password
  restartPolicy: Never

Consumiendo Valores Secrets a partir de Variables de Entorno

Dentro de un contenedor que consume un Secret en una variable de entorno, las claves Secrets aparecen como variables de entorno normal que contienen valores decodificados de base-64 de los datos del Secret. Este es el resultado de comandos ejecutados dentro del contenedor del ejemplo anterior.

echo $SECRET_USERNAME
admin
echo $SECRET_PASSWORD
1f2d1e2e67df

Usando imagePullSecrets

Una imagePullSecret es una forma de pasar a kubelet un Secret que contiene las credenciales para un registro de imagenes de Docker (u otro) para que pueda obtener una imagen privada en nombre de su pod.

Especificar manualmente una imagePullSecret

El uso de imagePullSecrets se desccriben en la documentación de las imágenes images documentation

Organización de imagePullSecrets para que se Adjunte Automáticamente

Puede crear manualmente un imagePullSecret, y hacer referencia a él desde un serviceAccount. Cualquier pod creado con ese serviceAccount o ese valor predeterminado para usar serviceAccount, obtendrá su campo imagePullSecret establecido en el service account. Ver Agregar ImagePullSecrets a una cuenta de servicio para una explicación detallada de ese proceso.

Montaje Automatico de Secrets Creados Manualmente

Secrets creados Manualmente, (por ejemplo uno que contiene un token para acceder a una cuenta github) se puede conectar automáticamente a los pods según su cuenta de servicio. Vea Inyección de infromación en pods usando un a PodPreset para una explicación detallada de este proceso.

Detalles

Restricciones

Las fuentes del volumen del Secret se validan para garantizar que la referencia del objeto especificado apunte a un objeto de tipo Secret. Por lo tanto, se debe crear un Secret antes de que cualquier pod dependa de él.

Los objetos API Secret residen en namespace. Solo pueden ser referenciados por pods en el mismo namespace.

Los Secrets individuales estan limitados a 1MiB de tamaño. Esto es para desalentar la creación de Secrets muy grandes que agotarían la memoria del apiserver y de kubelet. Sin embargo la creación de muchos Secrets más pequeños también podría agotar la memoria. Límites más completos en el uso de memoria debido a Secret es una característica planificada.

Kubelet solo admite el uso de Secret para Pods que obtiene del API server. Esto incluye cualquier pods creado usando kubectl, o indirectamente a través de un contralador de replicación. No incluye pods creados a través de los kubelets --manifest-url flag, its --config flag, o su REST API (estas no son formas comunes de crear pods.)

Los Secrets deben crearse antes de que se consuman en pod como variables de entono a menos que estén marcados como optional. Referencias a Secrets que no existen evitarán que el pod inicie. Las referencias a través de secretKeyRef a claves que no existen en un Secret con nombre evitarán que el pod se inicie.

Los Secrets que se utilizan para poblar variables de entorno a través de envFrom que tienen claves que se consideran nombres de variables de entorno no validos, tendran esas claves omitidas. El Pod se permitira reiniciar. Habrá un evento cuyo motivo es InvalidVariableNames y el mensaje contendrá la lista de claves no validas que se omitieron. El ejemplo muestra un pod que se refiere al default/mysecret que contiene 2 claves no validas, 1 badkey y 2 alsobad.

kubectl get events
LASTSEEN   FIRSTSEEN   COUNT     NAME            KIND      SUBOBJECT                         TYPE      REASON
0s         0s          1         dapi-test-pod   Pod                                         Warning   InvalidEnvironmentVariableNames   kubelet, 127.0.0.1      Keys [1badkey, 2alsobad] from the EnvFrom secret default/mysecret were skipped since they are considered invalid environment variable names.

Interacción del Secret y Pod de por vida

Cuando se crea un Pod a través de la API, no se verifica que exista un recreto referenciado. Una vez que se programa el Pod, kubelet intentará obtener el valor del Secret. Si el Secret no se puede recuperar será por que no existe o por una falla temporal de conexión al servidor API, kubelet volverá a intentarlo periodicamente. Enviará un evento sobre el pod explicando las razones por la que aún no se inició. Una vez que el Secret es encontrado, kubelet creará y montará el volumen que lo contiene. Ninguno de los contenedorees del pod se iniciará hasta que se monten todos los volúmes del pod.

Casos de uso

Caso de Uso: Pod con llaves ssh

Cree un fichero kustomization.yaml con SecretGenerator conteniendo algunas llaves ssh:

kubectl create secret generic ssh-key-secret --from-file=ssh-privatekey=/path/to/.ssh/id_rsa --from-file=ssh-publickey=/path/to/.ssh/id_rsa.pub
secret "ssh-key-secret" created

Ahora podemos crear un pod que haga referencia al Secret con la llave ssh key y lo consuma en un volumen:

apiVersion: v1
kind: Pod
metadata:
  name: secret-test-pod
  labels:
    name: secret-test
spec:
  volumes:
  - name: secret-volume
    secret:
      secretName: ssh-key-secret
  containers:
  - name: ssh-test-container
    image: mySshImage
    volumeMounts:
    - name: secret-volume
      readOnly: true
      mountPath: "/etc/secret-volume"

Cuando se ejecuta el comando del contenedor, las partes de la llave estarán disponible en:

/etc/secret-volume/ssh-publickey
/etc/secret-volume/ssh-privatekey

El contenedor es libre de usar los datos del Secret para establecer conexión ssh.

Caso de uso: Pods con credenciales prod / test

Este ejemplo ilustra un pod que consume un Secret que contiene credenciales de prod y otro pod que consume un Secret con credenciales de entorno de prueba.

Crear un fichero kustomization.yaml con SecretGenerator

kubectl create secret generic prod-db-secret --from-literal=username=produser --from-literal=password=Y4nys7f11
secret "prod-db-secret" created
kubectl create secret generic test-db-secret --from-literal=username=testuser --from-literal=password=iluvtests
secret "test-db-secret" created

Ahora haz los pods:

cat <<EOF > pod.yaml
apiVersion: v1
kind: List
items:
- kind: Pod
  apiVersion: v1
  metadata:
    name: prod-db-client-pod
    labels:
      name: prod-db-client
  spec:
    volumes:
    - name: secret-volume
      secret:
        secretName: prod-db-secret
    containers:
    - name: db-client-container
      image: myClientImage
      volumeMounts:
      - name: secret-volume
        readOnly: true
        mountPath: "/etc/secret-volume"
- kind: Pod
  apiVersion: v1
  metadata:
    name: test-db-client-pod
    labels:
      name: test-db-client
  spec:
    volumes:
    - name: secret-volume
      secret:
        secretName: test-db-secret
    containers:
    - name: db-client-container
      image: myClientImage
      volumeMounts:
      - name: secret-volume
        readOnly: true
        mountPath: "/etc/secret-volume"
EOF

Añade los pods a el mismo fichero kustomization.yaml

cat <<EOF >> kustomization.yaml
resources:
- pod.yaml
EOF

Aplique todos estos objetos en el Apiserver por

kubectl apply --k .

Ambos contenedores tendrán los siguientes archivos presentes en sus sistemas de archivos con valores para el entorno de cada contenedor:

/etc/secret-volume/username
/etc/secret-volume/password

observe cómo las especificaciones para los dos pods difieren solo en un campo; esto facilita la creación de pods con diferentes capacidades de una plantilla de configuración de pod común.

Deberías simplificar aún más la especificación del pod base utilizando dos Cuentas de Servicio: uno llamado, prod-user con el prod-db-secret, y otro llamado, test-user con el test-db-secret. Luego, la especificación del pod se puede acortar a, por ejemplo:

apiVersion: v1
kind: Pod
metadata:
  name: prod-db-client-pod
  labels:
    name: prod-db-client
spec:
  serviceAccount: prod-db-client
  containers:
  - name: db-client-container
    image: myClientImage

Caso de uso: Dotfiles en el volume del Secret

Para hacer que los datos esten 'ocultos' (es decir, en un file dónde el nombre comienza con un caracter de punto), simplemente haga que esa clave comience con un punto. Por ejemplo, cuando el siguiente Secret es montado en un volumen:

apiVersion: v1
kind: Secret
metadata:
  name: dotfile-secret
data:
  .secret-file: dmFsdWUtMg0KDQo=
---
apiVersion: v1
kind: Pod
metadata:
  name: secret-dotfiles-pod
spec:
  volumes:
  - name: secret-volume
    secret:
      secretName: dotfile-secret
  containers:
  - name: dotfile-test-container
    image: registry.k8s.io/busybox
    command:
    - ls
    - "-l"
    - "/etc/secret-volume"
    volumeMounts:
    - name: secret-volume
      readOnly: true
      mountPath: "/etc/secret-volume"

El secret-volume contendrá un solo archivo, llamado .secret-file, y el dotfile-test-container tendrá este fichero presente en el path /etc/secret-volume/.secret-file.

Caso de uso: Secret visible para un contenedor en un pod

Considere un programa que necesita manejar solicitudes HTTP, hacer una lógica empresarial compleja y luego firmar algunos mensajes con un HMAC. Debido a que tiene una lógica de aplicación compleja, puede haber una vulnerabilidad de lectura remota de archivos inadvertida en el servidor, lo que podría exponer la clave privada a un atacante.

Esto podría dividirse en dos procesos en dos contenedores: un contenedor de frontend que maneja la interacción del usuario y la lógica empresarial. pero que no puede ver la clave privada; y un contenedor de firmante que puede ver la clave privada, y responde a solicitudes de firma simples del frontend (ejemplo, a través de redes de localhost).

Con este enfoque particionado, un atacante ahora tiene que engañar a un servidor de aplicaciones para que haga algo bastante arbitrario, lo que puede ser más difícil que hacer que lea un archivo.

Mejores prácticas

Clientes que usan la API de Secrets

Al implementar aplicaciones que interactuan con los API Secrets, el acceso debe limitarse utilizando authorization policies como RBAC.

Los Secrets a menudo contienen valores que abarcan un espectro de importancia, muchos de los cuales pueden causar escalamientos dentro de Kubernetes (ejememplo, tokens de cuentas de servicio) y a sistemas externos. Incluso si una aplicación individual puede razonar sobre el poder de los Secrets con los que espera interactuar, otras aplicaciones dentro dle mismo namespace pueden invalidar esos supuestos.

Por esas razones las solicitudes de watch y list dentro de un espacio de nombres son extremadamente poderosos y deben evitarse, dado que listar Secrets permiten a los clientes inspecionar los valores de todos los Secrets que estan en el namespace. La capacidad para watch and list todos los Secrets en un cluster deben reservarse solo para los componentes de nivel de sistema más privilegiados.

Las aplicaciones que necesitan acceder a la API de Secrets deben realizar solicitudes de get de los Secrets que necesitan. Esto permite a los administradores restringir el acceso a todos los Secrets mientras white-listing access to individual instances que necesita la aplicación.

Para un mejor rendimiento sobre un bucle get, los clientes pueden diseñar recursos que hacen referencia a un Secret y luego un Secret watch el recurso, al volver a solicitar el Secret cuando cambie la referencia. Además,, un "bulk watch" API para que los clientes puedan watch recursos individuales, y probablemente estará disponible en futuras versiones de Kubernetes.

Propiedades de seguridad

Protecciones

Debido a que los objetos Secret se pueden crear independientemente de los Pods que los usan, hay menos riesgo de que el Secret expuesto durante el flujo de trabajo de la creación, visualización, y edición de pods. El sistema también puede tomar precausiones con los objetosSecret, tal como eviar escribirlos en el disco siempre que sea posible.

Un Secret solo se envía a un nodo si un pod en ese nodo lo requiere. Kubelet almacena el Secret en un tmpfs para que el Secret no se escriba en el almacenamiento de disco. Una vez que se elimina el pod que depende del Secret, kubelet eliminará su copia local de los datos de Secrets.

Puede haber Secrets para varios Pods en el mismo nodo. Sin embargo, solo los Secrets que solicita un Pod son potencialmente visibles dentro de sus contenedores. Por lo tanto, un Pod no tiene acceso a los Secrets de otro Pod.

Puede haber varios contenedores en un Pod. Sin embargo, cada contenedor en un pod tiene que solicitar el volumen del Secret en su volumeMounts para que sea visible dentro del contenedor. Esto se puede usar para construir particiones de seguridad útiles en el Pod level](#use-case-secret-visible-to-one-container-in-a-pod).

En la mayoría de las distribuciones Kubernetes-project-maintained, la comunicación entre usuario a el apiserver, y del apiserver a kubelets, ista protegido por SSL/TLS. Los Secrets estan protegidos cuando se transmiten por estos canales.

FEATURE STATE: Kubernetes v1.13 [beta]

Puedes habilitar encryption at rest para datos secretos, para que los Secrets no se almacenen en claro en etcd.

Riesgos

  • En el servidor API, los datos de los Secrets se almacenan en etcd; por lo tanto:
    • Los adminsitradores deben habilitar el cifrado en reposo para los datos del cluster (requiere v1.13 o posterior)
    • Los administradores deben limitar el acceso a etcd a los usuarios administradores
    • Los administradores pueden querer borrar/destruir discos usados por etcd cuando ya no estén en uso
    • Si ejecuta etcd en un clúster, los administradores deben asegurarse de usar SSL/TSL para la comunicación entre pares etcd.
  • Si configura el Secret a través de un archivo de (JSON o YAML) que tiene los datos del Secret codificados como base64, compartir este archivo o registrarlo en un repositorio de origen significa que el Secret está comprometido. La codificación Base64 no es un método de cifrado y se considera igual que un texto plano.
  • Las aplicaciones aún necesitan proteger el valor del Secret después de leerlo del volumen, como no registrarlo accidentalmente o transmitirlo a una parte no confiable.
  • Un usuario que puede crear un pod que usa un Secret también puede ver el valor del Secret. Incluso si una política del apiserver no permite que ese usuario lea el objeto Secret, el usuario puede ejecutar el pod que expone el Secret.
  • Actualmente, cualquier persona con root en cualquier nodo puede leer cualquier secret del apiserver, haciéndose pasar por el kubelet. Es una característica planificada enviar Secrets a los nodos que realmente lo requieran, para restringir el impacto de una explosión de root en un single node.

Siguientes pasos

7.6 - Organizar el acceso a los clústeres utilizando archivos kubeconfig

Utilice los archivos kubeconfig para organizar la información acerca de los clústeres, los usuarios, los Namespaces y los mecanismos de autenticación. La herramienta de línea de comandos kubectl utiliza los archivos kubeconfig para hallar la información que necesita para escoger un clúster y comunicarse con el servidor API de un clúster.

Por defecto, kubectl busca un archivo llamado config en el directorio $HOME/.kube. Puedes especificar otros archivos kubeconfig mediante la configuración de la variable de entorno KUBECONFIG o mediante la configuracion del flag --kubeconfig.

Para obtener instrucciones paso a paso acerca de cómo crear y especificar los archivos kubeconfig, consulte el recurso Configurar El Acceso A Múltiples Clústeres.

Compatibilidad con múltiples clústeres, usuarios y mecanismos de autenticación

Suponga que tiene diversos clústeres y que sus usuarios y componentes se autentican de diversas maneras. Por ejemplo:

  • Un kubelet en ejecución se podría autenticar usando certificados.
  • Un usuario se podría autenticar utilizando tokens.
  • Los administradores podrían tener un conjunto de certificados que sean suministrados a los usuarios individualmente.

Con los archivos kubeconfig puedes organizar tus clústeres, usuarios y Namespaces. También puedes definir diferentes contextos para realizar de forma rápida y fácil cambios entre clústeres y Namespaces.

Contexto

Un elemento context en un archivo kubeconfig se utiliza para agrupar los parámetros de acceso bajo un nombre apropiado. Cada contexto tiene tres parámetros: clúster, Namespace y usuario. Por defecto, la herramienta de línea de comandos kubectl utiliza los parámetros del contexto actual para comunicarse con el clúster.

Para seleccionar el contexto actual:

kubectl config use-context

Variable de entorno KUBECONFIG

La variable de entorno KUBECONFIG contiene una lista de archivos kubeconfig. En el caso de Linux y Mac, la lista está delimitada por dos puntos. Si se trata de Windows, la lista está delimitada por punto y coma. La variable de entorno KUBECONFIG no es indispensable. Si la variable de entorno KUBECONFIG no existe, kubectl utiliza el archivo kubeconfig por defecto $HOME/.kube/config.

Si la variable de entorno KUBECONFIG existe, kubectl utiliza una configuración eficiente que es el resultado de la fusión de los archivos listados en la variable de entorno KUBECONFIG.

Fusionando archivos kubeconfig

Para poder ver su configuración, escriba el siguiente comando:

kubectl config view

Como se ha descrito anteriormente, la respuesta de este comando podría resultar a partir de un solo archivo kubeconfig, o podría ser el resultado de la fusión de varios archivos kubeconfig.

A continuación se muestran las reglas que usa kubectl cuando fusiona archivos kubeconfig:

  1. Si el flag --kubeconfig está activado, usa solamente el archivo especificado. Sin fusionar. Sólo se permite una instancia con este flag.

    En caso contrario, si la variable de entorno KUBECONFIG está activada, sera usada como un listado de los archivos a ser fusionados. Fusionar los archivos listados en la variable de entorno KUBECONFIG de acuerdo con estas reglas:

    • Ignorar nombres de archivo vacíos.
    • Producir errores para archivos con contenido que no pueden ser deserializados.
    • El primer archivo que establezca un valor particular o una clave se impone.
    • Nunca cambie el valor o la clave. Ejemplo: Conserva el contexto del primer archivo para configurar el contexto actual. Ejemplo: Si dos archivos especifican un red-user, utilice sólo los valores del primer archivo. Incluso desechar el segundo archivo aunque tenga registros que no tengan conflictos.

    Para obtener un ejemplo de configuración de la variable de entorno KUBECONFIG, consulte la sección Configuración de la variable de entorno KUBECONFIG.

    En caso contrario, utilice el archivo kubeconfig predeterminado $HOME/.kube/config, sin fusionar.

  2. Determinar el contexto a utilizar con base en el primer acierto en esta secuencia:

    1. Si es que existe, utilice el flag ---contexto de la línea de comandos.
    2. Utilice el contexto actual procedente de los archivos kubeconfig fusionados.

    En este punto se permite un contexto vacío.

  3. Determinar el clúster y el usuario. En este caso, puede o no haber un contexto. Determine el clúster y el usuario con base en el primer acierto que se ejecute dos veces en esta secuencia: una para el usuario y otra para el clúster:

    1. Si es que existen, utilice el flag --user o --cluster de la línea de comandos.
    2. Si el contexto no está vacío, tome el usuario o clúster del contexto.

    En este caso el usuario y el clúster pueden estar vacíos.

  4. Determinar la información del clúster a utilizar. En este caso, puede o no haber información del clúster. Se construye cada pieza de la información del clúster con base en esta secuencia, el primer acierto se impone:

    1. Si es que existen, use el flag --server, --certificate-authority, --insecure-skip-tls-verify en la línea de comandos.
    2. Si existen atributos de información de clúster procedentes de los archivos kubeconfig fusionados, utilícelos.
    3. Falla si no existe la ubicación del servidor.
  5. Determinar la información del usuario a utilizar. Cree información de usuario utilizando las mismas reglas que la información de clúster, con la excepción de permitir sólo un mecanismo de autenticación por usuario:

    1. Si es que existen, utilice el flag --client-certificate, --client-key, --username, --password, --token de la línea de comandos.
    2. Utilice los campos user de los archivos kubeconfig fusionados.
    3. Falla si hay dos mecanismos de autenticación contradictorios.
  6. Si todavía falta información, utilice los valores predeterminados y solicite información de autenticación.

Referencias de archivos

Las referencias, así también como, las rutas de un archivo kubeconfig son relativas a la ubicación del archivo kubeconfig. Las referencias de un archivo en la línea de comandos son relativas al directorio actual de trabajo. Dentro de $HOME/.kube/config, las rutas relativas se almacenan de manera relativa a la ubicación del archivo kubeconfig , al igual que las rutas absolutas se almacenan absolutamente.

Siguientes pasos

8 - Seguridad

8.1 - Vista General de Seguridad Cloud Native

Esta descripción general define un modelo para la seguridad de Kubernetes en el contexto de Seguridad en Cloud Native.

Las 4C de Seguridad en Cloud Native

Puede pensar en seguridad por capas. Las 4C de la seguridad en Cloud Native son la nube (Cloud), Clústeres, Contenedores y Código.

Las 4C de Seguridad en Cloud Native

Cada capa del modelo de seguridad Cloud Native es basada en la siguiente capa más externa. La capa de código se beneficia de una base sólida (nube, clúster, contenedor) de capas seguras. No podemos garantizar la seguridad aplicando solo seguridad a nivel del código, y usar estándares de seguridad deficientes en las otras capas.

Nube (Cloud)

En muchos sentidos, la nube (o los servidores o el centro de datos corporativo) es la base de computador confiable de un clúster de Kubernetes. Si la capa de la nube es vulnerable (o configurado de alguna manera vulnerable), por consecuencia no hay garantía de que los componentes construidos encima de la base sean seguros. Cada proveedor de la nube tiene recomendaciones de seguridad para ejecutar las cargas de trabajo de forma segura en sus entornos.

Seguridad del proveedor de la nube

Si está ejecutando un clúster de Kubernetes en su propio hardware o en un proveedor de nube diferente, consulte la documentación para conocer las mejores prácticas de seguridad. A continuación, algunos enlaces a la documentación de seguridad de los proveedores de nube más populares:

Cloud provider security
Proveedor IaaS Link
Alibaba Cloud https://www.alibabacloud.com/trust-center
Amazon Web Services https://aws.amazon.com/security/
Google Cloud Platform https://cloud.google.com/security/
Huawei Cloud https://www.huaweicloud.com/intl/es-us/securecenter/overallsafety
IBM Cloud https://www.ibm.com/cloud/security
Microsoft Azure https://docs.microsoft.com/en-us/azure/security/azure-security
Oracle Cloud Infrastructure https://www.oracle.com/security/
VMWare VSphere https://www.vmware.com/security/hardening-guides.html

Seguridad de la Infraestructura

Sugerencias para proteger su infraestructura en un clúster de Kubernetes:

Infrastructure security
Área de Interés para la Infraestructura de Kubernetes Recomendación
Acceso de red al Plano de Control Todo acceso público al plano de control del Kubernetes en Internet no está permitido y es controlado por listas de control de acceso a la red estrictas a un conjunto de direcciones IP necesarias para administrar el clúster.
Acceso a la red de los Nodos Los nodos deben ser configurados para solo aceptar conexiones (por medio de listas de control de acceso a la red) desde el plano de control en los puertos especificados y aceptar conexiones para servicios en Kubernetes del tipo NodePort y LoadBalancer. Si es posible, estos nodos no deben exponerse públicamente en Internet.
Acceso a la API de Kubernetes del proveedor de la nube Cada proveedor de la nube debe dar un conjunto de permisos al plano de control y nodos del Kubernetes. Es mejor otorgar al clúster el permiso de acceso al proveedor de nube siguiendo el principio de mínimo privilegio para los recursos que necesite administrar. La documentación del Kops ofrece información sobre las políticas y roles de IAM.
Acceso a etcd El acceso a etcd (banco de datos de Kubernetes) debe ser limitado apenas al plano de control. Dependiendo de su configuración, debería intentar usar etcd sobre TLS. Puede encontrar mas información en la documentación de etcd.
Encriptación etcd Siempre que sea posible, es una buena práctica encriptar todas las unidades de almacenamiento. Etcd mantiene el estado de todo el clúster (incluidos los Secretos), por lo que su disco debe estar encriptado.

Clúster

Existen dos áreas de preocupación para proteger Kubernetes:

  • Protección de las configuraciones de los componentes del clúster.
  • Protección de las aplicaciones que se ejecutan en el clúster.

Componentes del Clúster

Si desea proteger su clúster de accesos accidentales o maliciosos y adoptar buenas prácticas de seguridad, a continuación sigue estos consejos sobre como proteger el clúster.

Componentes del clúster (su aplicación)

Dependiendo de la superficie de ataque de su aplicación, es posible que desee concentrarse en temas de seguridad específicos. Por ejemplo: si está ejecutando un servicio (Servicio A) que es crítico en una cadena de otros recursos y otra carga de trabajo separada (Servicio B) que es vulnerable a un ataque de sobrecarga de recursos, el riesgo de comprometer el Servicio A es alto si no limita las funciones del Servicio B. La siguiente tabla enumera áreas de atención de seguridad y recomendaciones para proteger las cargas de trabajo que se ejecutan en Kubernetes:

Áreas para la seguridad de la carga del trabajo Recomendación
Autorización RBAC (acceso a la API Kubernetes) https://kubernetes.io/docs/reference/access-authn-authz/rbac/
Autenticación https://kubernetes.io/docs/concepts/security/controlling-access/
Administrar secretos en la aplicación (encriptar el etcd - dato en reposo) https://kubernetes.io/docs/concepts/configuration/secret/
https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/
Políticas de seguridad de Pod https://kubernetes.io/docs/concepts/policy/pod-security-policy/
Calidad de servicio (y gestión de recursos del clúster) https://kubernetes.io/docs/tasks/configure-pod-container/quality-service-pod/
Políticas de Red https://kubernetes.io/docs/concepts/services-networking/network-policies/
TLS para Kubernetes Ingress https://kubernetes.io/docs/concepts/services-networking/ingress/#tls

Contenedor

La seguridad de los contenedores está fuera del alcance de la guía. Aquí hay recomendaciones generales y enlaces para explorar este tema:

Área de Interés para Contenedores Recomendación
Escáneres de vulnerabilidad de contenedores y seguridad de dependencia del sistema operativo Como parte del paso de la creación de la imagen, se debe utilizar un escáner de contenedores para detectar vulnerabilidades.
Firma de Imágenes y Aplicación Firma de imágenes de contenedores para mantener un sistema confiable para el contenido de sus contenedores.
Prohibir Usuarios Privilegiados Al crear contenedores, consulte la documentación para crear usuarios dentro de los contenedores con el menor privilegio necesario para cumplir con el propósito del contenedor en el sistema operativo.
Utilice el contenedor de tiempo de ejecución con el aislamiento más fuerte Seleccione clases del contenedor runtime con el proveedor de aislamiento más fuerte.

Código

El código de la aplicación es una de las principales superficies de ataque sobre las que tenemos más control. Aunque la protección del código de la aplicación está fuera del tema de seguridad de Kubernetes, aquí algunas recomendaciones para proteger el código de su aplicación:

Seguridad del código

Code security
Áreas de Atención para el Código Recomendación
Acceso solo a través de TLS Si su código necesita comunicarse a través de TCP, ejecute un handshake TLS con el cliente anticipadamente. Con la excepción de algunos casos, encripte todo lo que está en tránsito. Yendo un paso más allá, es una buena idea cifrar el tráfico de red entre los servicios. Esto se puede hacer a través del proceso de autenticación mutua o mTLS, que realiza una verificación bilateral de la comunicación a través de los certificados en los servicios.
Limitación de rangos de puertos de comunicación Esta recomendación puede ser un poco evidente, pero siempre que sea posible, solo debe exponer los puertos de su servicio que son absolutamente esenciales para la comunicación o la recopilación de métricas.
Seguridad en dependencia de terceros Es una buena práctica comprobar periódicamente las bibliotecas de terceros de su aplicación en busca de vulnerabilidades de seguridad. Cada lenguaje de programación tiene una herramienta para realizar esta verificación de forma automática.
Análisis de código estático La mayoría de los lenguajes proporcionan una forma de analizar el código en busca de prácticas de codificación potencialmente inseguras. Siempre que sea posible, debe automatizar los escaneos utilizando herramientas que puedan escanear las bases del código en busca de errores de seguridad comunes. Algunas de las herramientas se pueden encontrar en OWASP Source Code Analysis Tools.
Ataques de sondeo dinámico Existen algunas herramientas automatizadas que puede ejecutar en su servicio para explorar algunos de los ataques más conocidos. Esto incluye la inyección de SQL, CSRF y XSS. Una de las herramientas de análisis dinámico más populares es la OWASP Zed Attack proxy.

Siguientes pasos

Obtenga más información sobre los temas de seguridad de Kubernetes:

8.2 - Políticas de Seguridad del Pod

En vez de usar PodSecurityPolicy, puedes aplicar restricciones similares en Pods usando cualquiera o los dos:

Para obtener una guía de migración, consulte Migrar de PodSecurityPolicy al Controlador de Admisión de Seguridad de Pod Integrado. Para obtener más información sobre la eliminación de esta API, consulte Obsoleto de PodSecurityPolicy: Pasado, Presente y Futuro.

Si no está ejecutando Kubernetes v1.32, consulte la documentación para su versión de Kubernetes.

8.3 - Controlando el Acceso a la API de Kubernetes

Esta página proporciona información sobre cómo controlar el acceso a la API de Kubernetes.

Los usuarios acceden a la API de Kubernetes usando kubectl, bibliotecas de cliente, o haciendo peticiones REST. Usuarios y Kubernetes service accounts pueden ser autorizados para acceder a la API. Cuando una petición llega a la API, pasa por varias etapas, están ilustradas en el siguiente diagrama:

Diagrama de pasos para una petición a la API de Kubernetes

Seguridad en la capa de transporte

En un cluster típico de Kubernetes, la API sirve peticiones en el puerto 443, protegida por TLS. El API Server presenta un certificado. Este certificado puede ser firmando usando un certificado de autoridad privada (CA) o basado en una llave pública relacionada generalmente a un CA reconocido.

Si el cluster usa un certificado de autoridad privado, se necesita copiar este certificado CA configurado dentro de su ~/.kube/config en el cliente, entonces se podrá confiar en la conexión y estar seguro que no será comprometida.

El cliente puede presentar un certificado TLS de cliente en esta etapa.

Autenticación

Una vez que se estableció la conexión TLS, las peticiones HTTP avanzan a la etapa de autenticación. Esto se muestra en el paso 1 del diagrama. El script de creación del cluster o el administrador del cluster puede configurar el API Server para ejecutar uno o mas módulos de autenticación. Los Autenticadores están descritos con más detalle en Authentication.

La entrada al paso de autenticación es la petición HTTP completa, aun así, esta tipicamente examina las cabeceras y/o el certificado del cliente.

Los modulos de autenticación incluyen certificado de cliente, contraseña, tokens planos, tokens de inicio y JSON Web Tokens (usados para los service accounts).

Múltiples módulos de autenticación puede ser especificados, en este caso cada uno es probado secuencialmente, hasta que uno de ellos tiene éxito.

Si la petición no puede ser autenticada, la misma es rechazada con un código HTTP 401. Si la autenticación tiene éxito, el usuario es validado con el username específico, y el nombre de usuario esta disponible para los pasos siguientes. Algunos autenticadores también proporcionan membresías de grupo al usuario, mientras que otros no lo hacen.

Aunque Kubernetes utiliza los nombres de usuario para tomar decisiones durante el control de acceso y para registrar las peticiones de entrada, no tiene un objeto User ni tampoco almacena información sobre los usuarios en la API.

Autorización

Después de autenticar la petición como proveniente de un usuario específico, la petición debe ser autorizada. Esto se muestra en el paso 2 del diagrama.

Una petición debe incluir el nombre de usuario solicitante, la acción solicitada y el objeto afectado por la acción. La petición es autorizada si hay una política existente que declare que el usuario tiene permisos para la realizar la acción.

Por ejemplo, si el usuario Bob tiene la siguiente política, entonces puede leer pods solamente en el namespace projectCaribou:

{
    "apiVersion": "abac.authorization.kubernetes.io/v1beta1",
    "kind": "Policy",
    "spec": {
        "user": "bob",
        "namespace": "projectCaribou",
        "resource": "pods",
        "readonly": true
    }
}

Si Bob hace la siguiente petición, será autorizada dado que tiene permitido leer los objetos en el namespace projectCaribou :

{
  "apiVersion": "authorization.k8s.io/v1beta1",
  "kind": "SubjectAccessReview",
  "spec": {
    "resourceAttributes": {
      "namespace": "projectCaribou",
      "verb": "get",
      "group": "unicorn.example.org",
      "resource": "pods"
    }
  }
}

En cambio, si Bob en su petición intenta escribir (create o update) en los objetos del namespace projectCaribou, la petición será denegada. Del mismo modo, si Bob hace una petición para leer (get) objetos en otro namespace como projectFish, la autorización también será denegada.

Las autorizaciones en Kubernetes requieren que se usen atributos REST comunes para interactuar con el existente sistema de control de toda la organización o del proveedor cloud. Es importante usar formatos REST porque esos sistemas de control pueden interactuar con otras APIs además de la API de Kubernetes.

Kubernetes soporta múltiples módulos de autorización, como el modo ABAC, el modo RBAC y el modo Webhook. Cuando un administrador crea un cluster, se realiza la configuración de los módulos de autorización que deben ser usados con la API del server. Si más de uno módulo de autorización es configurado, Kubernetes verificada cada uno y si alguno de ellos autoriza la petición entonces la misma se ejecuta. Si todos los modules deniegan la petición, entonces la misma es denegada (Con un error HTTP con código 403).

Para leer más acerca de las autorizaciones en Kubernetes, incluyendo detalles sobre cómo crear politicas usando los módulos de autorización soportados, vea Authorization.

Control de Admisión

Los módulos de Control de Admisión son módulos de software que solo pueden modificar o rechazar peticiones. Adicionalmente a los atributos disponibles en los módulos de Autorización, los de Control de Admisión pueden acceder al contenido del objeto que esta siendo creado o modificado.

Los Controles de Admisión actúan en las peticiones que crean, modifican, borran o se conectan (proxy) a un objeto. Cuando múltiples módulos de control de admisión son configurados, son llamados en orden.

Esto se muestra en el paso 3 del diagrama.

A diferencia de los módulos de Autorización y Autenticación, si uno de los módulos de control de admisión rechaza la petición, entonces es inmediatamente rechazada.

Adicionalmente a rechazar objetos, los controles de admisión también permiten establecer valores predeterminados complejos.

Los módulos de Control de Admisión disponibles están descritos en Admission Controllers.

Cuando una petición pasa todos los controles de admisión, esta es validada usando la rutinas de validación para el objeto API correspondiente y luego es escrita en el objeto.

Puertos e IPs del API server

La discusión previa aplica a peticiones enviadas a un puerto seguro del servidor API (el caso típico). El servidor API puede en realidad servir en 2 puertos:

Por defecto, la API de Kubernetes entrega HTTP en 2 puertos:

  1. puerto localhost:

    • debe usarse para testeo e iniciar el sistema y para otros componentes del nodo maestro (scheduler, controller-manager) para hablar con la API
    • no se usa TLS
    • el puerto predeterminado es el 8080
    • la IP por defecto es localhost, la puede cambiar con el flag --insecure-bind-address.
    • la petición no pasa por los mecanismos de autenticación ni autorización
    • peticiones controladas por los modulos de control de admisión.
    • protegidas por necesidad para tener acceso al host
  2. “Puerto seguro”:

    • usar siempre que sea posible
    • usa TLS. Se configura el certificado con el flag --tls-cert-file y la clave con --tls-private-key-file.
    • el puerto predeterminado es 6443, se cambia con el flag --secure-port.
    • la IP por defecto es la primer interface que no es la localhost. se cambia con el flag --bind-address.
    • peticiones controladas por los módulos de autenticación y autorización.
    • peticiones controladas por los módulos de control de admisión.

Siguientes pasos

En los siguientes enlaces, encontrará mucha más documentación sobre autenticación, autorización y el control de acceso a la API:

9 - Políticas

Políticas configurables que se aplican a grupos de recursos.

La sección de Políticas describe las diferentes políticas configurables que se aplican a grupos de recursos:

9.1 - Rangos de límites (Limit Ranges)

Aplica límites de recursos a un Namespace para restringir y garantizar la asignación y consumo de recursos informáticos.

Contexto

Por defecto, los contenedores se ejecutan sin restricciones sobre los recursos informáticos disponibles en un clúster de Kubernetes. Si el Nodo dispone de los recursos informáticos, un Pod o sus Contenedores tienen permitido consumir por encima de la cuota solicitada si no superan el límite establecido en su especificación. Existe la preocupación de que un Pod o Contenedor pueda monopolizar todos los recursos disponibles.

Utilidad

Aplicando restricciones de asignación de recursos, los administradores de clústeres se aseguran del cumplimiento del consumo de recursos por espacio de nombre (Namespace).

Un LimitRange es la política que permite:

  • Imponer restricciones de requisitos de recursos a Pods o Contenedores por Namespace.
  • Imponer las limitaciones de recursos mínimas/máximas para Pods o Contenedores dentro de un Namespace.
  • Especificar requisitos y límites de recursos predeterminados para Pods o Contenedores de un Namespace.
  • Imponer una relación de proporción entre los requisitos y el límite de un recurso.
  • Imponer el cumplimiento de las demandas de almacenamiento mínimo/máximo para Solicitudes de Volúmenes Persistentes.

Habilitar el LimitRange

La compatibilidad con LimitRange está habilitada por defecto en Kubernetes desde la versión 1.10.

Para que un LimitRange se active en un Namespace en particular, el LimitRange debe definirse con el Namespace, o aplicarse a éste.

El nombre de recurso de un objeto LimitRange debe ser un nombre de subdominio DNS válido.

Aplicando LimitRanges

  • El administrador crea un LimitRange en un Namespace.
  • Los usuarios crean recursos como Pods, Contenedores o Solicitudes de Volúmenes Persistentes en el Namespace.
  • El controlador de admisión LimitRanger aplicará valores predeterminados y límites, para todos los Pods o Contenedores que no establezcan requisitos de recursos informáticos. Y realizará un seguimiento del uso para garantizar que no excedan el mínimo, el máximo, y la proporción de ningún LimitRange definido en el Namespace.
  • Si al crear o actualizar un recurso del ejemplo (Pods, Contenedores, Solicitudes de Volúmenes Persistentes) se viola una restricción al LimitRange, la solicitud al servidor API fallará con un código de estado HTTP "403 FORBIDDEN" y un mensaje que explica la restricción que se ha violado.
  • En caso de que en se active un LimitRange para recursos de cómputos como cpu y memory, los usuarios deberán especificar los requisitos y/o límites de recursos a dichos valores. De lo contrario, el sistema puede rechazar la creación del Pod.
  • Las validaciones de LimitRange ocurren solo en la etapa de Admisión de Pod, no en Pods que ya se han iniciado (Running Pods).

Algunos ejemplos de políticas que se pueden crear utilizando rangos de límites son:

  • En un clúster de 2 nodos con una capacidad de 8 GiB de RAM y 16 núcleos, podría restringirse los Pods en un Namespace a requerir 100m de CPU con un límite máximo de 500m para CPU y requerir 200Mi de memoria con un límite máximo de 600Mi de memoria.
  • Definir el valor por defecto de límite y requisitos de CPU a 150m y el valor por defecto de requisito de memoria a 300Mi Contenedores que se iniciaron sin requisitos de CPU y memoria en sus especificaciones.

En el caso de que los límites totales del Namespace sean menores que la suma de los límites de los Pods, puede haber contienda por los recursos. En este caso, los contenedores o pods no seran creados.

Ni la contención ni los cambios en un LimitRange afectarán a los recursos ya creados.

Siguientes pasos

Consulte el documento de diseño del LimitRanger para más información.

Los siguientes ejemplos utilizan límites y están pendientes de su traducción:

10 - Administración del Clúster

10.1 - Instalación de Complementos (AddOns)

Los complementos amplían las funcionalidades de Kubernetes.

En esta página se listan algunos de los complementos disponibles con sus respectivos enlaces de instrucciones para su instalación y uso. La lista no pretende ser exhaustiva.

Redes y Política de Redes

  • ACI (Cisco ACI) proporciona redes de contenedores integradas y seguridad de red.
  • Antrea proporciona servicios de red y seguridad para Kubernetes, aprovechando Open vSwitch como plano de datos de red, opera en la capa 3/4. Antrea es un proyecto de la CNCF de nivel Sandbox.
  • Calico es un proveedor de redes y políticas de red. Calico admite un conjunto flexible de opciones de red, para poder elegir la opción más eficiente para su situación, incluidas las redes superpuestas y no superpuestas, con o sin BGP (Border Gateway Protocol). Calico utiliza el mismo motor para aplicar las políticas de red para hosts, Pods, y (si se usa Istio y Envoy) aplicaciones en la capa de la malla de servicios.
  • Canal Es la unión de Flannel y Calico, proporciona redes y políticas de redes.
  • Cilium es una solución de red, observabilidad y seguridad con un plano de datos basado en eBPF. Cilium proporciona una red sencilla y plana en capa 3 con la capacidad de abarcar varios clústeres en un modo de enrutamiento nativo o de superposición/encapsulación, y puede aplicar políticas de red en L3-L7 utilizando un modelo de seguridad basado en identidad que está desacoplado del direccionamiento de red. Cilium puede actuar como sustituto de kube-proxy, también ofrece características adicionales de observabilidad y seguridad de manera opcional. Cilium es un proyecto de la CNCF de nivel Incubación.
  • CNI-Genie permite a Kubernetes conectarse sin problemas a una selección de complementos de CNI, como Calico, Canal, Flannel o Weave. CNI-Genie es un proyecto de la CNCF de nivel Sandbox.
  • Contiv proporciona redes configurables (L3 nativo mediante BGP, con superposición mediante vxlan, L2 clásica y Cisco-SDN/ACI) para diversos casos de uso y un vasto marco de políticas.
    El proyecto Contiv es de código abierto. El instalador ofrece opciones de instalación basadas en kubeadm y no basadas en kubeadm.
  • Contrail, basada en Tungsten Fabric, es una plataforma de gestión de políticas y virtualización de redes multicloud de código abierto. Contrail y Tungsten Fabric se integran con sistemas de orquestación como Kubernetes, OpenShift, OpenStack y Mesos, y proporcionan modos de aislamiento para máquinas virtuales, contenedores/Pods y cargas de trabajo para bare metal.
  • Flannel es un proveedor de red superpuesta que se puede usar con Kubernetes.
  • Knitter es un complemento que soportar múltiples interfaces de red en un Pod de Kubernetes.
  • Multus es un multicomplemento para soporte de múltiple redes en Kubernetes, que admite todos los complementos de CNI (ej. Calico, Cilium, Contiv, Flannel), además de SRIOV, DPDK, OVS-DPDK y cargas de trabajo basadas en VPP en Kubernetes.
  • OVN-Kubernetes es un proveedor de red para Kubernetes basado en OVN (Open Virtual Network), es una implementación de red virtual que surgió del proyecto Open vSwitch (OVS). OVN-Kubernetes proporciona una implementación de red basada en la superposición para Kubernetes, incluyendo una implementación basada en OVS de balanceo de carga y política de red.
  • Nodus es un complemento de controlador CNI basado en OVN para proveer Service function chaining(SFC) con base nativa para la nube.
  • NSX-T Container Plug-in (NCP) proporciona integración entre VMware NSX-T y orquestadores de contenedores como Kubernetes, así como integración entre NSX-T y plataformas CaaS/PaaS basadas en contenedores como Pivotal Container Service (PKS) y OpenShift.
  • Nuage es una plataforma SDN que proporciona redes basadas en políticas entre Kubernetes Pods y entornos no Kubernetes con visibilidad y supervisión de la seguridad.
  • Romana es una solución de red de capa 3 para las redes de Pods que también son compatibles con la API de NetworkPolicy.
  • Weave Net proporciona redes y políticas de red, funciona en ambos lados de una partición de red y no requiere una base de datos externa.

Detección de Servicios

  • CoreDNS es un servidor de DNS flexible y extensible que
    puede instalarse como DNS dentro del clúster para los Pods.

Visualización y Control

  • Dashboard es un panel de control con una interfaz web para Kubernetes.

Infraestructura

Complementos Antiguos

Hay otros complementos documentados como obsoletos en el directorio cluster/addons.

Los que mejor mantenimiento tienen deben estar vinculados aquí. !PRs son bienvenidos!

11 - Extendiendo Kubernetes

11.1 - Extendiendo la API de Kubernetes

11.2 - Extensiones de computación, almacenamiento y redes