Capítulo 4. Programación y utillaje

Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com

La parte de programación de la CKA se centra en los efectos de definir los límites de los recursos cuando los evalúa el programador de Kubernetes. El comportamiento por defecto en tiempo de ejecución del planificador también puede modificarse definiendo reglas de afinidad de nodos, así como manchas y tolerancias. De estos conceptos, sólo se espera que comprendas los matices de los límites de recursos y su efecto en el planificador en diferentes escenarios. Por último, este dominio del plan de estudios menciona conocimientos de alto nivel sobre herramientas de gestión de manifiestos y plantillas.

A alto nivel, este capítulo abarca los siguientes conceptos:

  • Límites de recursos para Pods

  • Gestión de manifiestos imperativa y declarativa

  • Herramientas comunes de plantillas como Kustomize, yq, y Helm

Comprender cómo afectan los límites de recursos ala programación de Pods

Un clúster Kubernetes puede constar de varios nodos. Dependiendo de una serie de reglas (por ejemplo, selectores de nodo, afinidad de nodo, manchas y tolerancias), el programador de Kubernetes decide qué nodo elegir para ejecutar la carga de trabajo. El examen CKA no te pide que comprendas los conceptos de programación mencionados anteriormente, pero sería útil tener una idea aproximada de cómo funcionan a alto nivel.

Una métrica que entra en juego para la programación de la carga de trabajo es la solicitud de recursos definida por los contenedores de un Pod. Los recursos más comunes que se pueden especificar son la CPU y la memoria. El programador se asegura de que la capacidad de recursos del nodo pueda satisfacer las necesidades de recursos del Pod. Más concretamente, el programadordetermina la suma de solicitudes de recursos por tipo en todos los contenedores definidos en el Pod y las compara con los recursos disponibles del nodo. La Figura 4-1 ilustra el proceso de programación basado en las solicitudes de recursos.

ckas 0401
Figura 4-1. Programación de pods basada en solicitudes de recursos

Definir solicitudes de recursos de contenedores

Cada contenedor de un Pod puede definir sus propias peticiones de recursos. La Tabla 4-1 describe las opciones disponibles, incluyendo un valor de ejemplo.

Tabla 4-1. Opciones para solicitar recursos
Atributo YAML Descripción Valor de ejemplo

spec.containers[].resources.requests.cpu

Tipo de recurso CPU

500m (quinientos milicpu)

spec.containers[].resources.requests.memory

Tipo de recurso de memoria

64Mi (2^26 bytes)

spec.containers[].resources.requests.hugepages-<size>

Tipo de recurso de página enorme

hugepages-2Mi: 60Mi

spec.containers[].resources.requests.ephemeral-storage

Tipo de recurso de almacenamiento efímero

4Gi

Kubernetes utiliza unidades de recursos para tipos de recursos que se desvían de las unidades de recursos estándar como megabytes y gigabytes. Explicar todos los entresijos de esas unidades va más allá del alcance de este libro, pero puedes leer los detalles en la documentación.

Para que el uso de esas peticiones de recursos sea transparente, echaremos un vistazo a un ejemplo de definición. El manifiesto Pod YAML que se muestra en el Ejemplo 4-1 define dos contenedores, cada uno con sus propias peticiones de recursos. Cualquier nodo al que se le permita ejecutar el Pod debe ser capaz de soportar una capacidad mínima de memoria de 320Mi y 1250m de CPU, la suma de los recursos de ambos contenedores.

Ejemplo 4-1. Configurar solicitudes de recursos del contenedor
apiVersion: v1
kind: Pod
metadata:
  name: rate-limiter
spec:
  containers:
  - name: business-app
    image: bmuschko/nodejs-business-app:1.0.0
    ports:
    - containerPort: 8080
    resources:
      requests:
        memory: "256Mi"
        cpu: "1"
  - name: ambassador
    image: bmuschko/nodejs-ambassador:1.0.0
    ports:
    - containerPort: 8081
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"

En este escenario, se trata de un clúster Minikube Kubernetes formado por tres nodos, un nodo del plano de control y dos nodos trabajadores. El siguiente comando lista todos los nodos:

$ kubectl get nodes
NAME           STATUS   ROLES                  AGE   VERSION
minikube       Ready    control-plane,master   12d   v1.21.2
minikube-m02   Ready    <none>                 42m   v1.21.2
minikube-m03   Ready    <none>                 41m   v1.21.2

En el siguiente paso, crearemos el Pod a partir del manifiesto YAML. El programador coloca el Pod en el nodo llamado minikube-m03:

$ kubectl create -f rate-limiter-pod.yaml
pod/rate-limiter created
$ kubectl get pod rate-limiter -o yaml | grep nodeName:
  nodeName: minikube-m03

Tras una inspección más detallada del nodo, puedes inspeccionar su capacidad máxima, cuánta de esta capacidad es asignable y las peticiones de memoria de los Pods programados en el nodo. El siguiente comando enumera la información y condensa la salida a los fragmentos relevantes:

$ kubectl describe node minikube-m03
...
Capacity:
  cpu:                2
  ephemeral-storage:  17784752Ki
  hugepages-2Mi:      0
  memory:             2186612Ki
  pods:               110
Allocatable:
  cpu:                2
  ephemeral-storage:  17784752Ki
  hugepages-2Mi:      0
  memory:             2186612Ki
  pods:               110
...
Non-terminated Pods:          (3 in total)
  Namespace                   Name                CPU Requests  CPU Limits  \
  Memory Requests  Memory Limits  AGE
  ---------                   ----                ------------  ----------  \
  ---------------  -------------  ---
  default                     rate-limiter        1250m (62%)   0 (0%)      \
  320Mi (14%)      0 (0%)         9m
...

Ciertamente, es posible que un Pod no pueda programarse debido a la insuficiencia de recursos disponibles en los nodos. En esos casos, el registro de eventos del Pod indicará esta situación con los motivos PodExceedsFreeCPU o PodExceedsFreeMemory. Para más información sobre cómo solucionar y resolver esta situación, consulta la sección correspondiente de la documentación.

Definir los límites de recursos de los contenedores

Otra métrica que puedes establecer para un contenedor son sus límites de recursos. Los límites de recursos garantizan que el contenedor no pueda consumir más recursos de los asignados. Por ejemplo, podrías expresar que la aplicación que se ejecuta en el contenedor debe limitarse a 1000m de CPU y 512Mi de memoria.

Dependiendo del tiempo de ejecución del contenedor utilizado por el clúster, superar cualquiera de los límites de recursos permitidos provoca la finalización del proceso de aplicación que se ejecuta en el contenedor o hace que el sistema impida por completo la asignación de recursos más allá de los límites. Para una discusión en profundidad sobre cómo son tratados los límites de recursos porel tiempo de ejecucióndel contenedor Docker, consulta la documentación.

La Tabla 4-2 describe las opciones disponibles, incluyendo un valor de ejemplo.

Tabla 4-2. Opciones para los límites de recursos
Atributo YAML Descripción Valor de ejemplo

spec.containers[].resources.limits.cpu

Tipo de recurso CPU

500m (500 milicpu)

spec.containers[].resources.limits.memory

Tipo de recurso de memoria

64Mi (2^26 bytes)

spec.containers[].resources.limits.hugepages-<size>

Tipo de recurso de página enorme

hugepages-2Mi: 60Mi

spec.containers[].resources.limits.ephemeral-storage

Tipo de recurso de almacenamiento efímero

4Gi

El Ejemplo 4-2 muestra la definición de límites en acción. Aquí, el contenedor llamado business-app no puede utilizar más de 512Mi de memoria y 2000m de CPU. El contenedor llamado ambassador define un límite de 128Mi de memoria y 500m de CPU.

Ejemplo 4-2. Establecer límites de recursos del contenedor
apiVersion: v1
kind: Pod
metadata:
  name: rate-limiter
spec:
  containers:
  - name: business-app
    image: bmuschko/nodejs-business-app:1.0.0
    ports:
    - containerPort: 8080
    resources:
      limits:
        memory: "512Mi"
        cpu: "2"
  - name: ambassador
    image: bmuschko/nodejs-ambassador:1.0.0
    ports:
    - containerPort: 8081
    resources:
      limits:
        memory: "128Mi"
        cpu: "500m"

Supongamos que el Pod se programó en el nodo minikube-m03. La descripción de los detalles del nodo revela que los límites de CPU y memoria surtieron efecto. Pero aún hay más. Kubernetes asigna automáticamente la misma cantidad de recursos para las peticiones si sólo defines los límites:

$ kubectl describe node minikube-m03
...
Non-terminated Pods:          (3 in total)
  Namespace                   Name                CPU Requests  CPU Limits  \
   Memory Requests  Memory Limits  AGE
  ---------                   ----                ------------  ----------  \
  ---------------  -------------  ---
  default                     rate-limiter        1250m (62%)   1250m (62%) \
  320Mi (14%)      320Mi (14%)    11s
...

Definir solicitudes y límites de recursos del contenedor

Es una práctica recomendada que especifiques las solicitudes de recursos y los límites para cada contenedor. Determinar esas expectativas de recursos no siempre es fácil, especialmente para las aplicaciones que aún no se han ejercitado en un entorno de producción. Probar la carga de la aplicación al principio del ciclo de desarrollo puede ayudar a analizar las necesidades de recursos. Se pueden hacer más ajustes monitorizando el consumo de recursos de la aplicación después de desplegarla en el clúster. El Ejemplo 4-3 combina las solicitudes y los límites de recursos en un único manifiesto YAML.

Ejemplo 4-3. Establecer solicitudes y límites de recursos del contenedor
apiVersion: v1
kind: Pod
metadata:
  name: rate-limiter
spec:
  containers:
  - name: business-app
    image: bmuschko/nodejs-business-app:1.0.0
    ports:
    - containerPort: 8080
    resources:
      requests:
        memory: "256Mi"
        cpu: "1"
      limits:
        memory: "512Mi"
        cpu: "2"
  - name: ambassador
    image: bmuschko/nodejs-ambassador:1.0.0
    ports:
    - containerPort: 8081
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

Como resultado, puedes ver las diferentes configuraciones para las solicitudes y límites de recursos:

$ kubectl describe node minikube-m03
...
Non-terminated Pods:          (3 in total)
  Namespace                   Name                CPU Requests  CPU Limits   \
   Memory Requests  Memory Limits  AGE
  ---------                   ----                ------------  ----------   \
  ---------------  -------------  ---
  default                     rate-limiter        1250m (62%)   2500m (125%) \
  320Mi (14%)      640Mi (29%)    3s
...

Gestionar objetos

Los objetos de Kubernetes pueden crearse, modificarse y eliminarse utilizando comandos imperativos de kubectl o ejecutando un comando de kubectl contra un archivo de configuración que declara el estado deseado de un objeto, lo que se denomina manifiesto. El lenguaje principal de definición de un manifiesto es YAML, aunque puedes optar por JSON, que es el formato menos adoptado entre la comunidad de Kubernetes. Se recomienda que los equipos de desarrollo envíen y empujen esos archivos de configuración a repositorios de control de versiones, ya que ayudará con el seguimiento y la auditoría de los cambios a lo largo del tiempo.

Modelar una aplicación en Kubernetes suele requerir un conjunto de objetos de apoyo, cada uno de los cuales puede tener su propio manifiesto. Por ejemplo, puedes querer crear una Implementación que ejecute la aplicación en cinco Pods, un ConfigMap para inyectar datos de configuración como variables de entorno, y un Servicio para exponer el acceso a la red.

Esta sección se centra principalmente en el soporte declarativo de gestión de objetos con ayuda de manifiestos. Para profundizar en el soporte imperativo, consulta las partes pertinentes de la documentación. Además, tocaremos herramientas como Kustomize y Helm para darte una idea de sus ventajas, capacidades y flujos de trabajo.

Gestión declarativa de objetos mediante archivos de configuración

La gestión declarativa de objetos requiere uno o varios archivos de configuración en formato YAML o JSON que describan el estado deseado de un objeto. Con este enfoque creas, actualizas y eliminas objetos.

Crear objetos

Para crear nuevos objetos, ejecuta el comando apply señalando un archivo, un directorio de archivos o un archivo referenciado por una URL HTTP(S) utilizando la opción -f. Si uno o varios de los objetos ya existen, el comando sincronizará los cambios realizados en la configuración con el objeto vivo.

Para demostrar la funcionalidad, supondremos los siguientes directorios y archivos de configuración. Los siguientes comandos crean objetos a partir de un único archivo, de todos los archivos de un directorio y de todos los archivos de un directorio de forma recursiva:

.
├── app-stack
│   ├── mysql-pod.yaml
│   ├── mysql-service.yaml
│   ├── web-app-pod.yaml
│   └── web-app-service.yaml
├── nginx-deployment.yaml
└── web-app
    ├── config
    │   ├── db-configmap.yaml
    │   └── db-secret.yaml
    └── web-app-pod.yaml

Crear un objeto a partir de un único archivo:

$ kubectl apply -f nginx-deployment.yaml
deployment.apps/nginx-deployment created

Crear objetos a partir de varios archivos dentro de un directorio:

$ kubectl apply -f app-stack/
pod/mysql-db created
service/mysql-service created
pod/web-app created
service/web-app-service created

Crear objetos a partir de un árbol de directorios recursivo que contenga archivos:

$ kubectl apply -f web-app/ -R
configmap/db-config configured
secret/db-creds created
pod/web-app created

Crear objetos a partir de un archivo referenciado por una URL HTTP(S):

$ kubectl apply -f https://raw.githubusercontent.com/bmuschko/cka-study-guide/ \
  master/ch04/object-management/nginx-deployment.yaml
deployment.apps/nginx-deployment created

El comando apply realiza un seguimiento de los cambios añadiendo o modificando la anotación con la clave kubectl.kubernetes.io/last-applied-configuration. Puedes encontrar un ejemplo de anotación en la salida del comando get pod aquí:

$ kubectl get pod web-app -o yaml
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{}, \
      "labels":{"app":"web-app"},"name":"web-app","namespace":"default"}, \
      "spec":{"containers":[{"envFrom":[{"configMapRef":{"name":"db-config"}}, \
      {"secretRef":{"name":"db-creds"}}],"image":"bmuschko/web-app:1.0.1", \
      "name":"web-app","ports":[{"containerPort":3000,"protocol":"TCP"}]}], \
      "restartPolicy":"Always"}}
...

Actualizar objetos

La actualización de un objeto existente se realiza con el mismo comando apply. Lo único que tienes que hacer es modificar el archivo de configuración y luego ejecutar el comando contra él. El ejemplo 4-4 modifica la configuración existente de una Implementación en el archivo nginx-deployment.yaml. Añadimos una nueva etiqueta con la clave team y cambiamos el número de réplicas de 3 a 5.

Ejemplo 4-4. Archivo de configuración modificado para una Implementación
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
    team: red
spec:
  replicas: 5
...

El siguiente comando aplica el archivo de configuración modificado. Como resultado, el número de Pods controlados por el ReplicaSet subyacente es 5. La anotación de la Implementación kubectl.kubernetes.io/last-applied-configuration refleja el último cambio en la configuración:

$ kubectl apply -f nginx-deployment.yaml
deployment.apps/nginx-deployment configured
$ kubectl get deployments,pods
NAME                               READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx-deployment   5/5     5            5           10m

NAME                                    READY   STATUS    RESTARTS   AGE
pod/nginx-deployment-66b6c48dd5-79j6t   1/1     Running   0          35s
pod/nginx-deployment-66b6c48dd5-bkkgb   1/1     Running   0          10m
pod/nginx-deployment-66b6c48dd5-d26c8   1/1     Running   0          10m
pod/nginx-deployment-66b6c48dd5-fcqrs   1/1     Running   0          10m
pod/nginx-deployment-66b6c48dd5-whfnn   1/1     Running   0          35s
$ kubectl get deployment nginx-deployment -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{}, \
      "labels":{"app":"nginx","team":"red"},"name":"nginx-deployment", \
      "namespace":"default"},"spec":{"replicas":5,"selector":{"matchLabels": \
      {"app":"nginx"}},"template":{"metadata":{"labels":{"app":"nginx"}}, \
      "spec":{"containers":[{"image":"nginx:1.14.2","name":"nginx", \
      "ports":[{"containerPort":80}]}]}}}}
...

Borrar objetos

Aunque existe una forma de borrar objetos mediante el comando apply proporcionando las opciones --prune -l <labels>, se recomienda borrar un objeto mediante el comando delete y dirigirlo al archivo de configuración. El siguiente comando elimina una Implementación y los objetos que controla (ReplicaSet y Pods):

$ kubectl delete -f nginx-deployment.yaml
deployment.apps "nginx-deployment" deleted
$ kubectl get deployments,replicasets,pods
No resources found in default namespace.

Gestión Declarativa de Objetos con Kustomize

Kustomize es una herramienta introducida con Kubernetes 1.14 que pretende hacer más cómoda la gestión de manifiestos. Admite tres casos de uso diferentes:

  • Generar manifiestos a partir de otras fuentes. Por ejemplo, crear un ConfigMap y rellenar sus pares clave-valor a partir de un archivo de propiedades.

  • Añadir una configuración común a varios manifiestos. Por ejemplo, añadir un espacio de nombres y un conjunto de etiquetas para una Implementación y un Servicio.

  • Componer y personalizar una colección de manifiestos. Por ejemplo, establecer límites de recursos para múltiples Implementaciones.

El archivo central necesario para que Kustomize funcione es el archivo de kustomización. El nombre normalizado del fichero es kustomization.yaml y no puede cambiarse. Un fichero de personalización define las reglas de procesamiento con las que trabaja la Personalización.

Kustomize está totalmente integrado con kubectl y puede ejecutarse en dos modos: mostrando la salida del procesamiento en la consola o creando los objetos. Ambos modos pueden operar sobre un directorio, un tarball, un archivo Git o una URL, siempre que contengan el archivo de kustomización y los archivos de recursos referenciados:

Renderización de la salida producida

El primer modo utiliza el subcomando kustomize para renderizar el resultado producido en la consola, pero no crea los objetos. Este comando funciona de forma similar a la opción de ejecución en seco que puedes conocer del comando run:

$ kubectl kustomize <target>
Crear los objetos

El segundo modo utiliza el comando apply junto con la opción de línea de comandos -k para aplicar los recursos procesados por Kustomize, como se explica en la sección anterior:

$ kubectl apply -k <target>

Las siguientes secciones demuestran cada uno de los casos de uso mediante un único ejemplo. Para una cobertura completa de todos los escenarios posibles, consulta la documentación o el repositorio GitHub de Personalizar.

Componer manifiestos

Una de las principales funcionalidades de Personalizar es crear un manifiesto compuesto a partir de otros manifiestos. Combinar varios manifiestos en uno solo puede no parecer tan útil por sí mismo, pero muchas de las otras funciones que se describen más adelante se basarán en esta capacidad. Supongamos que quieres componer un manifiesto a partir de un archivo de recursos de Implementación y un archivo de recursos de Servicio. Todo lo que tienes que hacer es colocar los archivos de recursos en la misma carpeta que el archivo de personalización:

.
├── kustomization.yaml
├── web-app-deployment.yaml
└── web-app-service.yaml

El archivo de personalización enumera los recursos en la sección resources, como se muestra en el Ejemplo 4-5.

Ejemplo 4-5. Un archivo de personalización que combina dos manifiestos
resources:
- web-app-deployment.yaml
- web-app-service.yaml

Como resultado, el subcomando kustomize muestra el manifiesto combinado que contiene todos los recursos separados por tres guiones (---) para denotar las distintas definiciones de objetos:

$ kubectl kustomize ./
apiVersion: v1
kind: Service
metadata:
  labels:
    app: web-app-service
  name: web-app-service
spec:
  ports:
  - name: web-app-port
    port: 3000
    protocol: TCP
    targetPort: 3000
  selector:
    app: web-app
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: web-app-deployment
  name: web-app-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      containers:
      - env:
        - name: DB_HOST
          value: mysql-service
        - name: DB_USER
          value: root
        - name: DB_PASSWORD
          value: password
        image: bmuschko/web-app:1.0.1
        name: web-app
        ports:
        - containerPort: 3000

Generar manifiestos a partir de otras fuentes

Anteriormente en este capítulo, aprendimos que los ConfigMap y los Secretos se pueden crear apuntándolos a un archivo que contenga los datos de configuración reales para él. Kustomize puede ayudar en el proceso mapeando la relación entre el manifiesto YAML de esos objetos de configuración y sus datos. Además, querremos inyectar el ConfigMap y el Secret creados en un Pod como variables de entorno. En esta sección, aprenderás cómo conseguirlo con la ayuda de Kustomize.

La siguiente estructura de archivos y directorios contiene el archivo de manifiesto del Pod y los archivos de datos de configuración que necesitamos para el ConfigMap y Secret. El archivo kustomization obligatorio vive en el nivel raíz del árbol de directorios:

.
├── config
│   ├── db-config.properties
│   └── db-secret.properties
├── kustomization.yaml
└── web-app-pod.yaml

En kustomization.yaml, puedes definir que el objeto ConfigMap y Secret se generen con el nombre dado. El nombre del ConfigMap será db-config, y el nombre del Secreto será db-creds. Ambos atributos del generador, configMapGenerator y secretGenerator, hacen referencia a un archivo de entrada utilizado para introducir los datos de configuración. Cualquier recurso adicional puede especificarse con el atributo resources. El ejemplo 4-6 muestra el contenido del archivo de personalización.

Ejemplo 4-6. Un archivo de personalización utilizando un ConfigMap y un generador de secretos
configMapGenerator:
- name: db-config
  files:
  - config/db-config.properties
secretGenerator:
- name: db-creds
  files:
  - config/db-secret.properties
resources:
- web-app-pod.yaml

Kustomize genera ConfigMaps y Secretos añadiendo un sufijo al nombre. Puedes ver este comportamiento al crear los objetos utilizando el comando apply. Se puede hacer referencia al ConfigMap y al Secreto por su nombre en el manifiesto del Pod:

$ kubectl apply -k ./
configmap/db-config-t4c79h4mtt unchanged
secret/db-creds-4t9dmgtf9h unchanged
pod/web-app created

Esta estrategia de nomenclatura puede configurarse con el atributo generatorOptions en el archivo de personalización. Consulta la documentación para obtener másinformación.

Probemos también el subcomando kustomize. En lugar de crear los objetos, el comando muestra la salida procesada en la consola:

$ kubectl kustomize ./
apiVersion: v1
data:
  db-config.properties: |-
    DB_HOST: mysql-service
    DB_USER: root
kind: ConfigMap
metadata:
  name: db-config-t4c79h4mtt
---
apiVersion: v1
data:
  db-secret.properties: REJfUEFTU1dPUkQ6IGNHRnpjM2R2Y21RPQ==
kind: Secret
metadata:
  name: db-creds-4t9dmgtf9h
type: Opaque
---
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: web-app
  name: web-app
spec:
  containers:
  - envFrom:
    - configMapRef:
        name: db-config-t4c79h4mtt
    - secretRef:
        name: db-creds-4t9dmgtf9h
    image: bmuschko/web-app:1.0.1
    name: web-app
    ports:
    - containerPort: 3000
      protocol: TCP
  restartPolicy: Always

Añadir una configuración común a varios manifiestos

Los desarrolladores de aplicaciones suelen trabajar en un conjunto de pilas de aplicaciones compuesto por múltiples manifiestos. Por ejemplo, una pila de aplicaciones podría constar de un microservicio frontend, un microservicio backend y una base de datos. Es práctica habitual utilizar la misma configuración transversal para cada uno de los manifiestos. Kustomize ofrece una serie de campos compatibles (por ejemplo, espacio de nombres, etiquetas o anotaciones). Consulta la documentación para conocer todos los campos admitidos.

Para el siguiente ejemplo, supondremos que una Implementación y un Servicio viven en el mismo espacio de nombres y utilizan un conjunto común de etiquetas. El espacio de nombres se llama persistence y la etiqueta es el par clave-valor team: helix. El Ejemplo 4-7 ilustra cómo establecer esos campos comunes en el archivo de personalización.

Ejemplo 4-7. Un fichero de personalización que utiliza un campo común
namespace: persistence
commonLabels:
  team: helix
resources:
- web-app-deployment.yaml
- web-app-service.yaml

Para crear los objetos referenciados en el archivo de personalización, ejecuta el comando apply. Asegúrate de crear previamente el espacio de nombres persistence:

$ kubectl create namespace persistence
namespace/persistence created
$ kubectl apply -k ./
service/web-app-service created
deployment.apps/web-app-deployment created

La representación YAML de los archivos procesados tiene el siguiente aspecto:

$ kubectl kustomize ./
apiVersion: v1
kind: Service
metadata:
  labels:
    app: web-app-service
    team: helix
  name: web-app-service
  namespace: persistence
spec:
  ports:
  - name: web-app-port
    port: 3000
    protocol: TCP
    targetPort: 3000
  selector:
    app: web-app
    team: helix
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: web-app-deployment
    team: helix
  name: web-app-deployment
  namespace: persistence
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
      team: helix
  template:
    metadata:
      labels:
        app: web-app
        team: helix
    spec:
      containers:
      - env:
        - name: DB_HOST
          value: mysql-service
        - name: DB_USER
          value: root
        - name: DB_PASSWORD
          value: password
        image: bmuschko/web-app:1.0.1
        name: web-app
        ports:
        - containerPort: 3000

Personalizar una colección de manifiestos

Kustomize puede fusionar el contenido de un manifiesto YAML con un fragmento de código de otro manifiesto YAML. Los casos de uso típicos incluyen añadir la configuración del contexto de seguridad a la definición de un Pod o establecer límites de recursos para una Implementación. El archivo de personalización permite especificar distintas estrategias de parcheo comopatchesStrategicMerge y patchesJson6902. Para profundizar en las diferencias entre las estrategias de parcheado, consulta la documentación.

El ejemplo 4-8 muestra el contenido de un archivo de personalización que parchea una definición de Implementación en el archivo nginx-deployment.yaml con el contenido del archivo security-context.yaml.

Ejemplo 4-8. Un archivo de personalización que define un parche
resources:
- nginx-deployment.yaml
patchesStrategicMerge:
- security-context.yaml

El archivo de parche que se muestra en el Ejemplo 4-9 define un contexto de seguridad a nivel de contenedor para la plantilla Pod de la Implementación. En tiempo de ejecución, la estrategia de parche intenta encontrar el contenedor llamado nginx y mejora la configuración adicional.

Ejemplo 4-9. El manifiesto YAML del parche
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  template:
    spec:
      containers:
      - name: nginx
        securityContext:
          runAsUser: 1000
          runAsGroup: 3000
          fsGroup: 2000

El resultado es una definición de Implementación parcheada, como se muestra en la salida del subcomandokustomize que se muestra a continuación. El mecanismo del parche puede aplicarse a otros archivos que requieran una definición de contexto de seguridad uniforme:

$ kubectl kustomize ./
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.14.2
        name: nginx
        ports:
        - containerPort: 80
        securityContext:
          fsGroup: 2000
          runAsGroup: 3000
          runAsUser: 1000

Herramientas comunes de plantillas

Como se ha demostrado en la sección anterior, Kustomize ofrece la funcionalidad de plantillas. El ecosistema Kubernetes ofrece otras soluciones al problema que trataremos aquí. Hablaremos del procesador YAML yq y del motor de plantillas Helm.

Utilizar el procesador YAML yq

La herramienta yq se utiliza para leer, modificar y mejorar el contenido de un archivo YAML. Esta sección demostrará los tres casos de uso. Para ver una lista detallada de ejemplos de uso, consulta el repositorio de GitHub. Durante el examen CKA, es posible que se te pida que apliques esas técnicas, aunque no se espera que comprendas todos los entresijos de las herramientas en cuestión. La versión de yq utilizada para describir la funcionalidad que se describe a continuación es la 4.2.1.

Valores de lectura

La lectura de valores de un archivo YAML existente requiere el uso de una expresión de ruta YAML. Una expresión de ruta te permite navegar en profundidad por la estructura YAML y extraer el valor de un atributo que estés buscando. El Ejemplo 4-10 muestra el manifiesto YAML de un Pod que define dos variables de entorno.

Ejemplo 4-10. El manifiesto YAML de un Pod
apiVersion: v1
kind: Pod
metadata:
  name: spring-boot-app
spec:
  containers:
  - image: bmuschko/spring-boot-app:1.5.3
    name: spring-boot-app
    env:
    - name: SPRING_PROFILES_ACTIVE
      value: prod
    - name: VERSION
      value: '1.5.3'

Para leer un valor, utiliza el comando eval o la forma abreviada e, proporciona la expresión de ruta YAML y apunta al archivo fuente. Los dos comandos siguientes leen el nombre del Pod y el valor de la segunda variable de entorno definida por un único contenedor. Ten en cuenta que la expresión de la ruta debe comenzar con un carácter obligatorio de punto (.) para denotar el nodo raíz de la estructura YAML:

$ yq e .metadata.name pod.yaml
spring-boot-app
$ yq e .spec.containers[0].env[1].value pod.yaml
1.5.3

Modificar valores

Modificar un valor existente es tan fácil como utilizar el mismo comando y añadir la bandera -i. La asignación del nuevo valor a un atributo se realiza asignándolo a la expresión de ruta. El siguiente comando cambia la segunda variable de entorno del archivo Pod YAML al valor 1.6.0:

$ yq e -i .spec.containers[0].env[1].value = "1.6.0" pod.yaml
$ cat pod.yaml
...
    env:
    - name: SPRING_PROFILES_ACTIVE
      value: prod
    - name: VERSION
      value: 1.6.0

Fusionar archivos YAML

Al igual que Kustomize, yq puede fusionar varios archivos YAML. Definitivamente, Personalizar es más potente y cómodo de usar; sin embargo, yq puede resultar útil para proyectos más pequeños. Supongamos que quieres fusionar la definición de contenedor sidecar mostrada en el Ejemplo 4-11 en el archivo YAML de Pod.

Ejemplo 4-11. El manifiesto YAML de una definición de contenedor
spec:
  containers:
  - image: envoyproxy/envoy:v1.19.1
    name: proxy-container
    ports:
    - containerPort: 80

El comando para conseguirlo es eval-all. No entraremos en detalles dada la multitud de opciones de configuración de este comando. Para profundizar, consulta el manual de usuario de yq sobre la operación "Multiplicar (Fusionar)". El siguiente comando añade el contenedor sidecar a la matriz de contenedores existente en el manifiesto Pod:

$ yq eval-all 'select(fileIndex == 0) *+ select(fileIndex == 1)' pod.yaml \
  sidecar.yaml
apiVersion: v1
kind: Pod
metadata:
  name: spring-boot-app
spec:
  containers:
  - image: bmuschko/spring-boot-app:1.5.3
    name: spring-boot-app
    env:
    - name: SPRING_PROFILES_ACTIVE
      value: prod
    - name: VERSION
      value: '1.5.3'
  - image: envoyproxy/envoy:v1.19.1
    name: proxy-container
    ports:
    - containerPort: 80

Usar Helm

Helm es un motor de plantillas y gestor de paquetes para un conjunto de manifiestos de Kubernetes. En tiempo de ejecución, sustituye los marcadores de posición de los archivos de plantilla YAML por valores reales definidos por el usuario final. El artefacto producido por el ejecutable Helm es un denominado archivo gráfico que agrupa los manifiestos que componen los recursos API de una aplicación. Este archivo gráfico puede cargarse en un gestor de paquetes para utilizarlo durante el proceso de implementación. El ecosistema Helm ofrece una amplia gama de gráficos reutilizables para casos de uso comunes en un repositorio central de gráficos (por ejemplo, para ejecutar Grafana o PostgreSQL).

Debido a la gran cantidad de funciones de que dispone Helm, sólo trataremos las más básicas. El examen CKA no espera que seas un experto en Helm, sino que estés familiarizado con sus ventajas y conceptos. Para obtener información más detallada sobre Helm, consulta la documentación de usuario. La versión de Helm utilizada para describir la funcionalidad aquí es la 3.7.0.

Estructura estándar del gráfico

Un gráfico debe seguir una estructura de directorios predefinida. Puedes elegir cualquier nombre para el directorio raíz. Dentro del directorio, deben existir dos archivos: Chart.yaml y values.yaml. El archivo Chart.yaml describe la metainformación del gráfico (por ejemplo, nombre y versión). El archivo values.yaml contiene los pares clave-valor utilizados en tiempo de ejecución para sustituir los marcadores de posición en los manifiestos YAML. Cualquier archivo de plantilla destinado a ser empaquetado en el archivo del gráfico debe colocarse en el directorio templates. Los archivos ubicados en el directorio template no tienen que seguir ninguna convención de nombres.

La siguiente estructura de directorios muestra un gráfico de ejemplo. El directorio templates contiene un archivo para un Pod y un Servicio:

$ tree
.
├── Chart.yaml
├── templates
│   ├── web-app-pod-template.yaml
│   └── web-app-service-template.yaml
└── values.yaml

El archivo gráfico

El archivo Chart.yaml describe el gráfico a alto nivel. Los atributos obligatorios incluyen la versión API del gráfico, el nombre y la versión. Además, se pueden proporcionar atributos opcionales. Para obtener una lista completa de atributos, consulta la documentación correspondiente. El Ejemplo 4-12 muestra lo mínimo de un archivo de gráfico.

Ejemplo 4-12. Un archivo básico de gráficos Helm
apiVersion: 1.0.0
name: web-app
version: 2.5.4

El archivo de valores

El archivo values.yaml define los pares clave-valor que se utilizarán para sustituir los marcadores de posición en los archivos de plantilla YAML. El Ejemplo 4-13 especifica cuatro pares clave-valor. Ten en cuenta que el archivo puede estar vacío si no quieres sustituir valores en tiempo de ejecución.

Ejemplo 4-13. Un archivo de valores Helm
db_host: mysql-service
db_user: root
db_password: password
service_port: 3000

Los archivos de plantilla

Los archivos de plantilla deben estar en el directorio templates. Un archivo de plantilla es un manifiesto YAML normal que puede (opcionalmente) definir marcadores de posición con la ayuda de llaves dobles ({{ }}). Para hacer referencia a un valor del archivo values.yaml, utiliza la expresión {{ .Values.<key> }}. Por ejemplo, para sustituir el valor de la clave db_host en tiempo de ejecución, utiliza la expresión {{ .Values.db_host }}. El ejemplo 4-14 define un Pod como plantilla y tres marcadores de posición que hacen referencia a valores de values.yaml.

Ejemplo 4-14. El manifiesto de plantilla YAML de un Pod
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: web-app
  name: web-app
spec:
  containers:
  - image: bmuschko/web-app:1.0.1
    name: web-app
    env:
    - name: DB_HOST
      value: {{ .Values.db_host }}
    - name: DB_USER
      value: {{ .Values.db_user }}
    - name: DB_PASSWORD
      value: {{ .Values.db_password }}
    ports:
    - containerPort: 3000
      protocol: TCP
  restartPolicy: Always

Ejecutar comandos de Helm

El ejecutable Helm viene con una amplia gama de comandos. Vamos a demostrar algunos de ellos. El comando template representa localmente las plantillas de gráficos y muestra los resultados en la consola. Puedes ver la operación en acción en la siguiente salida. Todos los marcadores de posición se han sustituido por sus valores reales obtenidos del archivo values.yaml:

$ helm template .
---
# Source: Web Application/templates/web-app-service-template.yaml
...
---
# Source: Web Application/templates/web-app-pod-template.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: web-app
  name: web-app
spec:
  containers:
  - image: bmuschko/web-app:1.0.1
    name: web-app
    env:
    - name: DB_HOST
      value: mysql-service
    - name: DB_USER
      value: root
    - name: DB_PASSWORD
      value: password
    ports:
    - containerPort: 3000
      protocol: TCP
  restartPolicy: Always

Cuando estés satisfecho con el resultado, deberás agrupar los archivos de plantilla en un archivo de gráficos. El archivo de gráficos es un archivo TAR comprimido con la terminación .tgz. El comando package evalúa la información de metadatos de Chart.yaml para obtener el nombre del archivo de gráficos:

$ helm package .
Successfully packaged chart and saved it to: /Users/bmuschko/dev/projects/ \
cka-study-guide/ch04/templating-tools/helm/web-app-2.5.4.tgz

Para obtener una lista completa de comandos y flujos de trabajo típicos, consulta la página de documentación de Helm.

Resumen

Los límites de recursos son uno de los muchos factores que el algoritmo kube-scheduler tiene en cuenta a la hora de tomar decisiones sobre en qué nodo se puede programar un Pod. Un contenedor puede especificar solicitudes y límites de recursos. El programador elige un nodo en función de su capacidad de hardware disponible.

La gestión declarativa de manifiestos es la forma preferida de crear, modificar y eliminar objetos en proyectos nativos en la nube del mundo real. El manifiesto YAML subyacente está pensado para ser comprobado en el control de versiones y rastrea automáticamente los cambios realizados en un objeto, incluyendo su marca de tiempo para un hash de confirmación correspondiente. Los comandos kubectl apply y delete pueden realizar esas operaciones para uno o varios manifiestos YAML.

Surgieron herramientas adicionales para una gestión más cómoda de los manifiestos. Kustomize está totalmente integrado en la cadena de herramientas de kubectl. Ayuda a generar, componer y personalizar manifiestos. Las herramientas con capacidad de creación de plantillas, como yq y Helm, pueden facilitar aún más diversos flujos de trabajo para gestionar pilas de aplicaciones representadas por un conjunto de manifiestos.

Aspectos esenciales del examen

Comprender los efectos de los límites de los recursos en la programación

Un contenedor definido por un Pod puede especificar solicitudes y límites de recursos. Trabaja con escenarios en los que definas esos límites de forma individual y conjunta para Pods de uno o varios contenedores. Tras la creación del Pod, deberías ser capaz de ver los efectos sobre la programación del objeto en un nodo. Además, practica cómo identificar la capacidad de recursos disponible de un nodo.

Gestionar objetos utilizando el enfoque imperativo y declarativo

Los manifiestos YAML son esenciales para expresar el estado deseado de un objeto. Tendrás que entender cómo crear, actualizar y eliminar objetos utilizando el comando kubectl apply. El comando puede apuntar a un único archivo de manifiesto o a un directorio que contenga varios archivos de manifiesto.

Tener una comprensión de alto nivel de las herramientas comunes de plantillas

Kustomize, yg y Helm son herramientas consolidadas para gestionar manifiestos YAML. Su funcionalidad de plantillas soporta escenarios complejos como la composición y fusión de múltiples manifiestos. Para el examen, echa un vistazo práctico a las herramientas, su funcionalidad y los problemas que resuelven.

Ejercicios de muestra

Las soluciones a estos ejercicios están disponibles en el Apéndice.

  1. Escribe un manifiesto para un nuevo Pod llamado ingress-controller con un único contenedor que utilice la imagen bitnami/nginx-ingress-controller:1.0.0. Para el contenedor, establece la petición de recursos en 256Mi de memoria y 1 CPU. Establece los límites de recursos en 1024Mi de memoria y 2,5 CPU.

  2. Utilizando el manifiesto, programa el Pod en un cluster con tres nodos. Una vez creado, identifica el nodo que ejecuta el Pod. Escribe el nombre del nodo en el archivo node.txt.

  3. Crea el directorio manifests. Dentro del directorio, crea dos archivos: pod.yaml y configmap.yaml. El archivo pod.yaml debe definir un Pod llamado nginx con la imagen nginx:1.21.1. El archivo configmap.yaml define un ConfigMap llamado logs-config con el par clave-valor dir=/etc/logs/traffic.log. Crea ambos objetos con un único comando declarativo.

  4. Modifica el manifiesto ConfigMap cambiando el valor de la clave dir por /etc/logs/traffic-log.txt. Aplica los cambios. Elimina ambos objetos con un único comando declarativo.

  5. Utiliza Personalizar para establecer un espacio de nombres común t012 para el archivo de recursos pod.yaml. El archivo pod.yaml define el Pod llamado nginx con la imagen nginx:1.21.1 sin un espacio de nombres. Ejecuta el comando Kustomize que muestra el manifiesto transformado en la consola.

Get Guía de estudio del Administrador Certificado de Kubernetes (CKA) now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.