En el artículo anterior, ofrecí una introducción detallada al concepto de Pods, que son los más importantes en el proyecto Kubernetes. En el artículo de hoy, compartiré más detalles sobre los objetos Pod.
Pods vs. Máquinas Virtuales
A estas alturas, debería tener muy claro que es el Pod, no el contenedor, la unidad de orquestación más pequeña en el proyecto Kubernetes. Este diseño se refleja en los objetos de la API, donde el contenedor se convierte en un campo más dentro de los atributos del Pod. Naturalmente, surge la pregunta: ¿qué atributos pertenecen al objeto Pod, y cuáles al Contenedor?
El Pod desempeña el papel de una “máquina virtual” en los entornos de despliegue tradicionales. Este diseño tiene como objetivo facilitar la transición desde entornos tradicionales (entornos de máquinas virtuales) a Kubernetes (entornos de contenedores).
Si consideramos el Pod como la “máquina” en un entorno tradicional y el contenedor como el “programa de usuario” que se ejecuta en esta “máquina”, entonces muchos aspectos del diseño del objeto Pod se vuelven mucho más fáciles de entender.
Por ejemplo, los atributos relacionados con la programación (scheduling), la red, el almacenamiento y la seguridad están esencialmente a nivel del Pod. La característica común de estos atributos es que describen la “máquina” en su conjunto, no los “programas” que se ejecutan en su interior.
Por ejemplo, configurar la tarjeta de red de la “máquina” (es decir, la definición de red del Pod), configurar el disco de la “máquina” (es decir, la definición de almacenamiento del Pod) y configurar el cortafuegos de la “máquina” (es decir, la definición de seguridad del Pod). Sin mencionar en qué servidor se está ejecutando esta “máquina” (es decir, la programación del Pod).
Kubernetes YAML
En Kubernetes, definimos los recursos Pod mediante archivos declarativos.
NodeSelector: Este es un campo que permite a los usuarios vincular un Pod a un Nodo, como se muestra a continuación:
apiVersion: v1
kind: Pod
...
spec:
nodeSelector:
disktype: ssd
Esta configuración significa que este Pod solo puede ejecutarse en un nodo con la etiqueta “disktype: ssd”; de lo contrario, fallará la programación.
NodeName: Una vez que se asigna este campo de un Pod, el proyecto Kubernetes considerará que el Pod ha sido programado, y el resultado de la programación es el nombre del nodo asignado. Por lo tanto, este campo generalmente lo establece el programador, pero los usuarios también pueden configurarlo para “engañar” al programador, aunque esta práctica solo se usa normalmente durante pruebas o depuración.
HostAliases: Define el contenido del archivo hosts del Pod (por ejemplo, /etc/hosts), como se muestra a continuación:
apiVersion: v1
kind: Pod
...
spec:
hostAliases:
- ip: "10.1.2.3"
hostnames:
- "foo.remote"
- "bar.remote"
...
Cabe señalar que en el proyecto Kubernetes, si desea establecer el contenido del archivo hosts, debe hacerlo a través de este método. De lo contrario, si modifica directamente el archivo hosts, kubelet sobrescribirá automáticamente el contenido modificado después de que el Pod se elimine y se vuelva a crear.
Además de las configuraciones relacionadas con la “máquina” anteriores, también puede notar que cualquier atributo relacionado con el Espacio de Nombres (Namespace) de Linux del contenedor también está a nivel del Pod. Esto también es fácil de entender: el diseño del Pod es permitir que los contenedores en su interior compartan tantos Namespaces de Linux como sea posible, conservando solo las capacidades de aislamiento y restricción necesarias. De esta manera, el efecto simulado por el Pod es muy similar a la relación entre los programas en una máquina virtual.
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
containers:
- name: lifecycle-demo-container
image: nginx
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]
preStop:
exec:
command: ["/usr/sbin/nginx","-s","quit"]
Este es un archivo YAML de Pod de la documentación oficial de Kubernetes. En realidad es muy simple, solo define un contenedor con la imagen nginx. Sin embargo, en la sección de contenedores de este archivo YAML, verá que el contenedor tiene configurados los parámetros postStart y preStop.
¿Qué significa esto?
Comencemos con postStart, que se refiere a una operación que se ejecuta inmediatamente después de que el contenedor se inicia. Debe quedar claro que la operación definida por postStart se ejecuta después del ENTRYPOINT del contenedor Docker, pero no garantiza estrictamente el orden. Es decir, cuando postStart comienza, es posible que ENTRYPOINT aún no haya terminado.
Por supuesto, si la ejecución de postStart se agota o encuentra un error, Kubernetes informará un mensaje de error indicando que el contenedor no pudo iniciarse en los Eventos de ese Pod, lo que provocará que el Pod también esté en estado de fallo.
De manera similar, el momento de preStop ocurre antes de que el contenedor sea eliminado (por ejemplo, cuando recibe una señal SIGKILL). Es importante aclarar que la ejecución de la operación preStop es sincrónica. Por lo tanto, bloquea el proceso de eliminación del contenedor actual hasta que se complete la operación definida, lo que es diferente de postStart.
Entonces, en este ejemplo, después de que el contenedor se inicia correctamente, escribe un “mensaje de saludo” en /usr/share/message (es decir, la operación definida por postStart). Y antes de eliminar este contenedor, primero llamamos al comando de salida de nginx (es decir, la operación definida por preStop), logrando así un “apagado elegante” del contenedor.
Después de familiarizarse con los campos principales del Pod y su sección de Contenedor, compartiré el ciclo de vida de un objeto Pod en Kubernetes.
Los cambios en el ciclo de vida del Pod se reflejan principalmente en la parte Status del objeto API Pod, que es su tercer campo importante además de Metadata y Spec. Entre ellos, pod.status.phase representa el estado actual del Pod, con las siguientes condiciones posibles:
-
Pending. Este estado significa que el archivo YAML del Pod ha sido enviado a Kubernetes, el objeto API ha sido creado y guardado en Etcd. Sin embargo, algunos contenedores en este Pod no pueden crearse sin problemas por alguna razón. Por ejemplo, fallo de programación.
-
Running. En este estado, el Pod ha sido programado exitosamente y está vinculado a un nodo específico. Todos los contenedores que contiene se han creado exitosamente, y al menos uno se está ejecutando actualmente.
-
Succeeded. Este estado significa que todos los contenedores en el Pod se han ejecutado hasta completarse y han salido. Esta situación es más común cuando se ejecutan tareas de una sola vez.
-
Failed. En este estado, al menos un contenedor en el Pod ha salido en un estado anormal (un código de retorno distinto de cero). La aparición de este estado significa que necesita averiguar cómo depurar la aplicación en el contenedor, por ejemplo, revisando los Eventos y registros del Pod.
-
Unknown. Este es un estado anormal, que indica que el estado del Pod no puede ser informado continuamente por kubelet al kube-apiserver, lo que probablemente se debe a un problema de comunicación entre el maestro y el Kubelet.
Además, el campo Status del objeto Pod se puede desglosar en un conjunto de Conditions. Estos valores de estado detallados incluyen: PodScheduled, Ready, Initialized y Unschedulable. Se utilizan principalmente para describir las razones específicas del Status actual.
Por ejemplo, si el Status actual del Pod es Pending, y la Condition correspondiente es Unschedulable, significa que hay un problema con su programación.
Entre ellos, el sub-estado Ready merece especial atención: significa que el Pod no solo se ha iniciado normalmente (en estado Running), sino que también está listo para proporcionar servicios externamente. Hay una diferencia entre estos dos (Running y Ready), por lo que quizás quiera reflexionar sobre ello.
Estos mensajes de estado del Pod son un criterio importante para juzgar la condición de ejecución de la aplicación, especialmente cuando el Pod entra en un estado no “Running”, debe poder reaccionar rápidamente, comenzar a rastrear y localizar basándose en la situación anormal representada, en lugar de consultar frenéticamente la documentación.
Resumen
En el artículo de hoy, he explicado en detalle el objeto API Pod, he presentado los métodos de uso principales de los Pods, y he analizado las similitudes y diferencias entre Pods y Contenedores en términos de campos. Espero que estas explicaciones le ayuden a comprender y recordar mejor los campos principales en el YAML del Pod y sus significados precisos.
De hecho, el objeto API Pod es el concepto más central en todo el sistema Kubernetes y también se utiliza en los controladores que explicaré más adelante.
