Simplificando Kubernetes

 

Día 2

 

Contenido del Día 2

 

Ínicio de la clase Día 2

 

¿Qué vamos a ver hoy?

Durante la clase de hoy, veremos todos los detalles importantes sobre la unidad más pequeña de Kubernetes, el Pod. Vamos a abordar desde la creación de un Pod simple, pasando por Pods con múltiples contenedores, con volúmenes e incluso con limitación en el consumo de recursos, como CPU o memoria. Y por supuesto, aprenderemos cómo ver todos los detalles de un Pod en funcionamiento y jugaremos bastante con nuestros archivos YAML.

¿Qué es un Pod?

Lo primero, el Pod es la unidad más pequeña dentro de un clúster de Kubernetes.

Cuando hablamos de Pod, debemos pensar en él como una caja que contiene uno o más contenedores. Estos contenedores comparten los mismos recursos del Pod, como por ejemplo, la dirección IP, el namespace, el volumen, etc.

Entonces, cuando hablamos de Pod, nos referimos a uno o más contenedores que comparten los mismos recursos, punto.  

Creando un Pod

Básicamente, existen dos formas de crear un Pod: la primera es mediante un comando en la terminal y la segunda es a través de un archivo YAML.

Comencemos creando un Pod mediante un comando en la terminal.

kubectl run giropops --image=nginx --port=80

El comando anterior creará un Pod llamado giropops, con una imagen de nginx y el puerto 80 expuesto.

Visualización de detalles sobre los Pods

Para ver el Pod creado, podemos utilizar el siguiente comando:

kubectl get pods

El comando anterior listará todos los Pods que están en ejecución en el clúster, en el espacio de nombres (namespace) predeterminado.

Sí, en Kubernetes tenemos espacios de nombres (namespaces), pero eso es tema para otro día. Por ahora, nos centraremos en los Pods y solo debemos saber que por defecto, Kubernetes creará todos los objetos dentro del espacio de nombres predeterminado si no especificamos otro.

Para ver los Pods en ejecución en todos los espacios de nombres, podemos usar el siguiente comando:

kubectl get pods --all-namespaces

O también, podemos utilizar el siguiente comando:

kubectl get pods -A

Ahora bien, si deseas ver todos los Pods de un espacio de nombres específico, puedes utilizar el siguiente comando:

kubectl get pods -n <namespace>

Por ejemplo:

kubectl get pods -n kube-system

El comando anterior listará todos los Pods en ejecución en el espacio de nombres kube-system, que es el espacio de nombres donde Kubernetes creará todos los objetos relacionados con el clúster, como los Pods de CoreDNS, Kube-Proxy, Kube-Controller-Manager, Kube-Scheduler, etc.

Si deseas obtener aún más detalles sobre el Pod, puedes solicitar a Kubernetes que muestre los detalles del Pod en formato YAML mediante el siguiente comando:

kubectl get pods <nombre-del-pod> -o yaml

Por ejemplo:

kubectl get pods giropops -o yaml

El comando anterior mostrará todos los detalles del Pod en formato YAML, prácticamente igual a lo que verías en el archivo YAML que creamos para crear el Pod, pero con algunos detalles adicionales, como el UID del Pod, el nombre del nodo donde se está ejecutando el Pod, etc. Después de todo, este Pod ya está en ejecución, por lo que Kubernetes ya tiene más detalles sobre él.

Otra salida interesante es el formato JSON, que puedes visualizar utilizando el siguiente comando:

kubectl get pods <nombre-del-pod> -o json

Por ejemplo:

kubectl get pods giropops -o json

En otras palabras, al usar el parámetro -o, puedes elegir el formato de salida que deseas ver, como yaml, json, wide, etc.

La salida 'wide' es interesante, ya que proporciona más detalles sobre el Pod, como la dirección IP del Pod y el nodo en el que se está ejecutando.

kubectl get pods <nombre-del-pod> -o wide

Por ejemplo:

kubectl get pods giropops -o wide

Ahora bien, si deseas ver los detalles del Pod en formato YAML pero sin necesidad de utilizar el comando get, puedes emplear el comando:

kubectl describe pods <nombre-del-pod>

Por ejemplo:

kubectl describe pods giropops

Mediante el comando describe, podrás visualizar todos los detalles del Pod, incluidos los detalles del contenedor que se encuentra dentro del Pod.

Eliminando un Pod

Ahora, vamos a eliminar el Pod que creamos utilizando el siguiente comando:

kubectl delete pods giropops

¿Fácil, verdad? Ahora, vamos a crear un Pod a través de un archivo YAML.

 

Creando un Pod mediante un archivo YAML

Vamos a crear un archivo YAML llamado pod.yaml con el siguiente contenido:

apiVersion: v1 # versión de la API de Kubernetes
kind: Pod # tipo de objeto que estamos creando
metadata: # metadatos del Pod 
  name: giropops # nombre del Pod que estamos creando
labels: # etiquetas del Pod
  run: giropops # etiqueta run con el valor giropops
spec: # especificación del Pod
  containers: # contenedores que están dentro del Pod
  - name: giropops # nombre del contenedor
    image: nginx # imagen del contenedor
    ports: # puertos que están siendo expuestos por el contenedor
    - containerPort: 80 # puerto 80 expuesto por el contenedor

Ahora, vamos a crear el Pod utilizando el archivo YAML que acabamos de crear.

kubectl apply -f pod.yaml

El comando anterior creará el Pod utilizando el archivo YAML que creamos.

Para ver el Pod creado, podemos utilizar el siguiente comando:

kubectl get pods

Dado que utilizamos el comando apply, creo que vale la pena explicar lo que hace.

El comando apply realiza lo que su nombre indica: aplica el archivo YAML al clúster, es decir, crea el objeto que se describe en el archivo YAML en el clúster. Si el objeto ya existe, actualizará el objeto con la información que se encuentra en el archivo YAML.

Otro comando que puedes utilizar para crear un objeto en el clúster es el comando create, que también crea el objeto que se describe en el archivo YAML en el clúster; sin embargo, si el objeto ya existe, devolverá un error. Por esta razón, el comando apply es más comúnmente utilizado, ya que actualiza el objeto en caso de que ya exista. :)

Ahora, vamos a ver los detalles del Pod que acabamos de crear.

kubectl describe pods giropops

Visualizando los registros (logs) del Pod

Otro comando muy útil para ver lo que está sucediendo con el Pod, específicamente para ver lo que el contenedor está registrando, es el siguiente:

kubectl logs giropops

Donde "giropops" es el nombre del Pod que creamos.

Si deseas ver los registros del contenedor en tiempo real, puedes utilizar el siguiente comando:

kubectl logs -f giropops

¡Simple, verdad? Ahora, vamos a eliminar el Pod que creamos, utilizando el comando:

kubectl delete pods giropops

 

Creando un Pod con múltiples contenedores

Vamos a crear un archivo YAML llamado pod-multi-container.yaml con el siguiente contenido:

apiVersion: v1 # versión de la API de Kubernetes
kind: Pod # tipo de objeto que estamos creando
metadata: # metadatos del Pod 
  name: giropops # nombre del Pod que estamos creando
labels: # etiquetas del Pod
  run: giropops # etiqueta run con el valor giropops
spec: # especificación del Pod
  containers: # contenedores que están dentro del Pod
  - name: girus # nombre del contenedor
    image: nginx # imagen del contenedor
    ports: # puertos que están siendo expuestos por el contenedor
    - containerPort: 80 # puerto 80 expuesto por el contenedor
  - name: strigus # nombre del contenedor
    image: alpine # imagen del contenedor
    args:
    - sleep
    - "1800"

Con el manifiesto anterior, estamos creando un Pod con dos contenedores: uno llamado "girus" con la imagen de nginx y otro llamado "strigus" con la imagen de alpine. Es importante recordar que el contenedor de Alpine se crea con el comando sleep 1800 para que el contenedor no se detenga, a diferencia del contenedor de Nginx que tiene un proceso principal en ejecución en primer plano, lo que evita que el contenedor se detenga.

El Alpine es una distribución Linux muy ligera que no tiene un proceso principal en primer plano en funcionamiento. Por lo tanto, necesitamos ejecutar el comando sleep 1800 para mantener en funcionamiento el contenedor, añadiendo así un proceso principal en primer plano.

Ahora, crearemos el Pod utilizando el archivo YAML que acabamos de crear.

kubectl apply -f pod-multi-container.yaml

Para ver el Pod creado, podemos usar el siguiente comando:

kubectl get pods

Ahora, vamos a ver los detalles del Pod que acabamos de crear.

kubectl describe pods giropops

Los comandos attach y exec

Vamos a conocer dos nuevos comandos, attach y exec.

El comando attach se utiliza para conectarse a un contenedor que se está ejecutando dentro de un Pod. Por ejemplo, nos conectaremos al contenedor de Alpine que se está ejecutando dentro del Pod que creamos.

kubectl attach giropops -c strigus

Usar attach es similar a conectarse directamente a una consola de una máquina virtual; no estamos creando ningún proceso dentro del contenedor, solo nos estamos conectando a él.

Por esta razón, si intentamos utilizar attach para conectarnos al contenedor donde se está ejecutando Nginx, nos conectaremos al contenedor y quedaremos atrapados en el proceso de Nginx que se está ejecutando en primer plano, y no podremos ejecutar ningún otro comando.

kubectl attach giropops -c girus

Para salir del contenedor, simplemente presiona las teclas Ctrl + C.

¿Entendido? Solo utilizaremos attach para conectarnos a un contenedor que se está ejecutando dentro de un Pod, y no para ejecutar comandos dentro del contenedor.

Ahora bien, si deseas ejecutar comandos dentro del contenedor, puedes usar el comando exec.

El comando exec se utiliza para ejecutar comandos dentro de un contenedor que se está ejecutando dentro de un Pod. Por ejemplo, vamos a ejecutar el comando ls dentro del contenedor de Alpine que se está ejecutando dentro del Pod que creamos.

kubectl exec giropops -c strigus -- ls

También podemos utilizar exec para conectarnos a un contenedor que se está ejecutando dentro de un Pod; sin embargo, para hacerlo, debemos pasar el parámetro -it al comando exec.

kubectl exec giropops -c strigus -it -- sh

El parámetro -it se utiliza para que el comando exec cree un proceso dentro del contenedor con interactividad y un terminal, haciendo que el comando exec se comporte de manera similar al comando attach, pero con la diferencia de que el comando exec crea un proceso dentro del contenedor, en este caso, el proceso sh. Es por esta razón que el comando exec se utiliza con mayor frecuencia, ya que crea un proceso dentro del contenedor, a diferencia del comando attach, que no crea ningún proceso dentro del contenedor.

En este caso, incluso podemos conectarnos al contenedor de Nginx, ya que se conectará al contenedor creando un proceso que es nuestro intérprete de comandos sh, lo que nos permitirá ejecutar cualquier comando dentro del contenedor, ya que tenemos un shell para interactuar con él.

kubectl exec giropops -c girus -it -- sh

Para salir del contenedor, simplemente presiona la tecla Ctrl + D.

 

Creando un contenedor con límites de memoria y CPU

Vamos a crear un archivo YAML llamado pod-limitado.yaml con el siguiente contenido:

apiVersion: v1 # versión de la API de Kubernetes
kind: Pod # tipo de objeto que estamos creando
metadata: # metadatos del Pod
  name: giropops # nombre del Pod que estamos creando
labels: # etiquetas del Pod
  run: giropops # etiqueta run con el valor giropops
spec: # especificación del Pod 
  containers: # contenedores que están dentro del Pod 
  - name: girus # nombre del contenedor 
    image: nginx # imagen del contenedor
    ports: # puertos que están siendo expuestos por el contenedor
    - containerPort: 80 # puerto 80 expuesto por el contenedor
    resources: # recursos que están siendo utilizados por el contenedor
      limits: # límites máximos de recursos que el contenedor puede utilizar
        memory: "128Mi" # límite de memoria que está siendo utilizado por el contenedor, en este caso 128 megabytes como máximo 
        cpu: "0.5" # límite máximo de CPU que el contenedor puede utilizar, en este caso 50% de una CPU como máximo
      requests: # recursos garantizados al contenedor
        memory: "64Mi" # memoria garantizada al contenedor, en este caso 64 megabytes
        cpu: "0.3" # CPU garantizada al contenedor, en este caso 30% de una CPU

Observe que estamos introduciendo algunos nuevos campos, como resources, limits y requests.

El campo resources se utiliza para definir los recursos que utiliza el contenedor, y dentro de él encontramos los campos limits y requests.

El campo limits se utiliza para definir los límites máximos de recursos que puede utilizar el contenedor, y el campo requests se utiliza para definir los recursos garantizados al contenedor.

¡Super simple!

Los valores que pasamos para los campos limits y requests fueron:

  • memory: la cantidad de memoria que el contenedor puede utilizar, por ejemplo, 128Mi o 1Gi. El valor Mi significa mebibytes y el valor Gi significa gibibytes. El valor M significa megabytes y el valor G significa gigabytes. El valor Mi se utiliza para definir el límite de memoria en mebibytes, ya que Kubernetes utiliza el sistema de unidades binarias, y no el sistema de unidades decimales. El valor M se utiliza para definir el límite de memoria en megabytes, ya que Docker utiliza el sistema de unidades decimales, y no el sistema de unidades binarias. Por lo tanto, si estás utilizando Docker, puedes usar el valor M para definir el límite de memoria, pero si estás utilizando Kubernetes, debes usar el valor Mi para definir el límite de memoria.

Ahora vamos a crear el Pod con los límites de memoria y CPU.

kubectl create -f pod-limitado.yaml

Ahora verifiquemos si el Pod ha sido creado.

kubectl get pods

Comprobemos los detalles del Pod.

kubectl describe pod giropops

Observe que el Pod se creó exitosamente y los límites de memoria y CPU se establecieron según el archivo YAML.

A continuación, se muestra la parte de la salida del comando describe que muestra los límites de memoria y CPU.

Containers:
  girus:
    Container ID:   docker://e7b0c7b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0
    Image:          nginx
    Image ID:       docker-pullable://nginx@sha256:0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b
    Port:           80/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Wed, 01 Jan 2023 00:00:00 +0000
    Ready:          True
    Restart Count:  0
    Limits:
      cpu:     500m
      memory:  128Mi
    Requests:
      cpu:        300m
      memory:     64Mi
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-0b0b0 (ro)

Observe en la salida anterior que se muestra el campo CPU con el valor 500m. Esto significa que el contenedor puede utilizar como máximo el 50% de una CPU, ya que una CPU equivale a 1000 milicpus, y el 50% de 1000 milicpus son 500 milicpus.

Por lo tanto, si deseas establecer un límite de CPU al 50% de una CPU, puedes definir el valor 500m, o puedes establecer el valor 0.5, que es lo mismo que establecer el valor 500m.

Para probar los límites de memoria y CPU, puedes ejecutar el comando stress dentro del contenedor. Este es un comando que hace que el contenedor consuma recursos de CPU y memoria. Recuerda instalar el comando stress, ya que no está instalado por defecto.

Para facilitar las pruebas, crearemos un Pod con Ubuntu y limitaremos la memoria, además, instalaremos el comando stress dentro del contenedor.

Llama al archivo pod-ubuntu-limitado.yaml.

apiVersion: v1
kind: Pod
metadata:
  name: giropops
spec:
  containers:
  - name: girus
    image: ubuntu
    args:
    - sleep
    - infinity
    resources:
      limits:
        memory: "128Mi"
        cpu: "0.5"
      requests:
        memory: "64Mi"
        cpu: "0.3"

Observa que usamos el parámetro infinity, el cual hace que el contenedor espere indefinidamente y, por lo tanto, permanezca en ejecución.

Ahora crearemos el Pod.

kubectl create -f pod-ubuntu-limitado.yaml

Una vez creado, el contenedor de Ubuntu con los límites de memoria y CPU se ejecutará. Luego, podrás conectarte a él y probar el comando stress para ver cómo afecta el consumo de recursos en el contenedor.

Ahora verificaremos si el Pod ha sido creado.

kubectl get pods

Luego, accederemos al interior del contenedor.

kubectl exec -it giropops -- bash

A continuación, instalaremos el comando stress.

apt update
apt install -y stress

Finalmente, ejecutaremos el comando stress para consumir memoria.

stress --vm 1 --vm-bytes 100M

Hasta aquí todo está bien, ya que definimos el límite de memoria en 128Mi, y el comando stress está consumiendo 100M, así que está todo correcto.

Ahora vamos a aumentar el consumo de memoria a 200M.

stress --vm 1 --vm-bytes 200M

Observa que el comando stress no puede consumir 200M, ya que el límite de memoria es de 128Mi, que es menor que 200M. Esto generará un error y el comando stress se detendrá.

¡Hemos alcanzado nuestro objetivo, hemos alcanzado el límite de nuestro contenedor! :D

¿Quieres jugar un poco más con el comando stress? Echa un vistazo a su --help.

stress --help

Este comando ofrece varias opciones para experimentar con el consumo de memoria y CPU. Puedes explorar estas opciones para entender mejor cómo el comando stress puede simular diferentes cargas en tu contenedor y probar cómo responde ante distintas condiciones. ¡Diviértete probando y descubriendo cómo puedes ajustar el rendimiento del contenedor con el uso de stress!

 

Agregando un volumen EmptyDir al Pod

Una cosa, no es el momento adecuado para entrar en detalles más profundos sobre volúmenes. Tendremos todo un día para hablar sobre volúmenes, así que no te preocupes por eso por ahora.

Hoy nos centraremos en asegurarnos de estar muy cómodos con los Pods, desde su creación, administración, ejecución de comandos, etc.

Así que vamos a crear un Pod con un volumen EmptyDir.

Pero primero, ¿qué es un volumen EmptyDir?

Un volumen del tipo EmptyDir es un volumen que se crea cuando se crea el Pod y se destruye cuando el Pod se elimina, es decir, es un volumen temporal.

En el día a día, es posible que no uses mucho este tipo de volumen, pero es importante que sepas que existe. Uno de los casos de uso más comunes es cuando necesitas compartir datos entre los contenedores de un Pod. Imagina que tienes dos contenedores en un Pod y uno de ellos tiene un directorio con datos, y deseas que el otro contenedor tenga acceso a esos datos. En este caso, puedes crear un volumen del tipo EmptyDir y compartirlo entre los dos contenedores.

Llama al archivo pod-emptydir.yaml.

apiVersion: v1
kind: Pod
metadata:
  name: giropops
spec:
  containers:
  - name: girus
    image: ubuntu
    args:
    - sleep
    - infinity
    volumeMounts:
    - name: primeiro-emptydir
      mountPath: /giropops
  volumes:
  - name: primeiro-emptydir
    emptyDir:
      sizeLimit: 256Mi

Ahora crearemos el Pod.

kubectl create -f pod-emptydir.yaml

Ahora verificaremos si el Pod ha sido creado.

kubectl get pods

Puedes observar la salida del comando kubectl describe pod giropops para ver el volumen que se ha creado.

kubectl describe pod giropops

Luego, accederemos al interior del contenedor.

kubectl exec -it giropops -- bash

A continuación, crearemos un archivo dentro del directorio /giropops.

touch /giropops/FUNCIONAAAAAA

Listo, hemos creado nuestro archivo dentro del directorio /giropops, que es un directorio dentro del volumen de tipo EmptyDir. :-)

Recordando una vez más que aún veremos mucho, muchísimo más sobre volúmenes, así que no te preocupes por eso por ahora.

results matching ""

    No results matching ""