Pods

Que es un Pod?

Son la unidad mas pequenia que podemos tener (un runtime), los pods internamente estan compuestos por contenedores Docker o cualquier otro runtime que elijamos.

Ciclo de vidas de una app contenizada

ciclo-de-vida-de-una-app-contenizada

Cuando ya tenemos nuestra app en una imagen contenerizada, tenemos que hacer el deploy, para esto existen dos maneras, una es la manera imperativa donde por linea de comandos desplegamos la app en un cluster.

Por otro lado tenemos de declarativa que es atravez de un manitest yaml_ donde tendremos los pasos para ejecutar la app en un cluster. Este metodo nos sirve ademas para tener un control automatico del estado deseado, ya que los diferentes controladores de kubernetes estaran pendientes de que tengamos online todos los detellaes y características que hayamos puesto en el manifest, por ejemplo si declaramos que queremos tener siempre 5 replicas , y se detecta que tenemos 3, se crearan nuevamente 2 instancias.

Introduccion a los Pods

En Docker el objeto minimo es el contenedor, en kubernetes eso mismo se lo denomina Pod .

Un pod no es nada mas que un contenedor que da una serie de caracteristicas y funcionalidades a los contenedores de Docker.

Podemos tener varios contenedores dentro de un Pod pero lo mejor es tener uno solo por Pod.

Que características le otorga un Pod a un cotenedor?

  • Direcciones.
  • Puertos.
  • Hostnames.
  • Sockets.
  • Memoria.
  • Volumenes.
  • Etc.
  • Ip.

Este Pod se despliega dentro del nodo de un cluster.(worker)

Los Pod son stateless no tienen estado , por lo tanto no deberiamos guardar informacion en ellos, tal como lo son los contenedores docker.

pod-and-app

Pods con varios contenedores.

El Tema aca es pensar en microservicios, por ejemplo cada aplicacion deberia vivir y no estar fuertemente enlazada dentro de un pod con otra applicacion.

Por ejemplo si tenemos una imagen del front y una del la DB , podes pensar que lo optimo seria tener un pod con ambos container. Pero si necesitases hacer un backup de la DB tendrias que detenerla , lo que probocaria que el front funcionace mal.

Creando el primer pod de manera imperativa.

El orden indica kubectl run nombre_del_pod --image=imagen-para-que-se-base

kubectl run nginx1 --image=nginx
pod/nginx1 created
| NAME   | READY | STATUS  | RESTARTS | AGE |
|--------|-------|---------|----------|-----|
| nginx1 | 1/1   | Running | 0        | 68s |

Podemos usar -o wide

| NAME   | READY | STATUS  | RESTARTS | AGE  | IP          | NODE           | NOMINATED NODE | READINESS GATES |
|--------|-------|---------|----------|------|-------------|----------------|----------------|-----------------|
| nginx1 | 1/1   | Running | 0        | 22m  | 10.244.1.4  | micluster-m02  |  none          | none          |

Ver las propiedades de un Pod con DESCRIBE

Si hacemos kubectl get pods podremos ver los NAME de los pods.

Si luego hacemos kubectl describe pod/nginx1 veremos muchos mas detalles:

Name:             nginx1
Namespace:        default
Priority:         0
Service Account:  default
Node:             micluster-m02/192.168.49.3
Start Time:       Sat, 28 Sep 2024 14:50:52 -0300
Labels:           run=nginx1
Annotations:      none
Status:           Running
IP:               10.244.1.4
IPs:
  IP:  10.244.1.4
Containers:
  nginx1:
    Container ID:   docker://ae1de1ff7b424e8d4e05995418ec304a68b29575734eb586e4a70f3505d12326
    Image:          nginx
    Image ID:       docker-pullable://nginx@sha256:b5d3f3e104699f0768e5ca8626914c16e52647943c65274d8a9e63072bd015bb
    Port:           none
    Host Port:      none
    State:          Running
      Started:      Sat, 28 Sep 2024 14:51:09 -0300
    Ready:          True
    Restart Count:  0
    Environment:    none
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-29qbj (ro)
Conditions:
  Type                        Status
  PodReadyToStartContainers   True 
  Initialized                 True 
  Ready                       True 
  ContainersReady             True 
  PodScheduled                True 
Volumes:
  kube-api-access-29qbj:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       nil
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              none
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  45m   default-scheduler  Successfully assigned default/nginx1 to micluster-m02
  Normal  Pulling    45m   kubelet            Pulling image "nginx"
  Normal  Pulled     44m   kubelet            Successfully pulled image "nginx" in 17.021s (17.021s including waiting). Image size: 187706909 bytes.
  Normal  Created    44m   kubelet            Created container nginx1
  Normal  Started    44m   kubelet            Started container nginx1

Es importante anteponer pod/ ya que sino, dira que no reconoce el recurso.

Ejecutar comandos contra un POD. Comando EXEC

kubectl exec nombre_del_pod comando 
kubectl exec nginx1 -- ls 

Para el modo interactivo seria :

kubectl exec nginx1 -it -- sh

Ver los logs de un Pod

kubectl logs nombre_del_pod
kubectl logs nginx1

Si queremos ir viendo los logs en vivo hacemos

kubectl logs -f nginx1

Si queremos ver las ultimas lineas :

kubectl logs -f nginx1 --tail=30

Probar pod con kubectl proxy - (no me funciono)

Hasta aca no podemos acceder al pod de una manera directa desde fuera. Por ejemplo si nuestro pod tiene un apache con una web, no podriamos verlo.

Pero podemos usar una herramienta: kubectl proxy

Primero cremos un server apache en un pod.

kubectl run apache --image=httpd --port=80
kubectl get pods -o wide

Hacemos kubectl proxy veremos :

kubectl proxy 
Starting to serve on 127.0.0.1:8001

Esto nos permite acceder a los recursos mediante el navegador web:

Algunas url son:

  1. http://127.0.0.1:8001
  2. http://127.0.0.1:8001/version
  3. http://127.0.0.1:8001/healthz

Lo que haremos para saber si apache esta funcionando, es usar la api que tenemos disponible.

Pero vayamos de a poco.Naveguemos:

http://127.0.0.1:8001/api/v1/ veremos una lista de los recursos.

Ahora agregamos namespace a la url

http://127.0.0.1:8001/api/v1/namespaces

Entre los namespaces veremos default elejimos ese y lo ponemos en la url.

http://127.0.0.1:8001/api/v1/namespaces/default

Si decimos que dentro de default queremos ver los pods ponemos:

http://127.0.0.1:8001/api/v1/namespaces/default/pods

Alli en la lista encontraremos apache y lo agregamos al url:

[http://127.0.0.1:8001/api/v1/namespaces/default/pods/apache] (http://127.0.0.1:8001/api/v1/namespaces/default/pods/apache)

Por ultimo si agregamos proxy al final , veremos la pagina web del apache:

http://127.0.0.1:8001/api/v1/namespaces/default/pods/apache/proxy

Es probable que el ejemplo anterior no te funcione. Cuando sepa lo acomodo.

Mientras tanto podemos hacer otro ejercicio que si funciona.

Otra manera de acceder al recuso del pod como servicio.

# Si ya tenes un cluster podes omitirlo
minikube start -p newCluster --nodes 2 --driver=docker

# Creamos un pod apache httpd 
kubectl run httpd --image=httpd --port=80

# Vemos si se creo el pod 
kubectl get pods
httpd   1/1     Running   0          22s

# Exponemos el servicio 
kubectl expose pod httpd --type=NodePort --port=80 --target-port=80 --name=httpd-node-port
service/httpd-node-port exposed

# Vemos los services expuestos 

kubectl get services
httpd-node-port   NodePort    10.103.237.31   none        80:30995/TCP   69s

# Obtenemos la ip del nodo

kubectl get nodes -o wide

Veras algo como:

NAMESTATUSROLESAGEVERSIONINTERNAL-IPEXTERNAL-IP
newClusterReadycontrol-plane,master15mv1.31.0192.168.49.2<none>
newCluster-m02Readyworker15mv1.31.0192.168.49.3<none>

Elegimos una de las ips, en teoria cualquiera de las dos funcionaria, pero esto no siempre es asi, asi que elegi una y proba, sino es la otra, Anda al navegador y ponemos la IP:PUERTO donde el puerto es el que nos dio httpd-node-port NodePort 10.103.237.31 none 80:30995/TCP 69s quedaria http://192.168.49.3:30995. Deberias poder ver la web de bienvenida de httpd.

LoadBalancer vs NodePort

La principal diferencia entre un servicio NodePort y un servicio LoadBalancer en Kubernetes es cómo exponen las aplicaciones y cómo gestionan el acceso a estas aplicaciones desde fuera del clúster.

1. NodePort

  • Función: Un servicio de tipo NodePort expone un puerto específico en cada nodo del clúster, permitiendo el acceso externo a tu aplicación desde cualquier nodo, a través de ese puerto.
  • Cómo funciona:
    • Kubernetes asigna un puerto dentro del rango 30000–32767 (o uno personalizado que definas) en cada nodo del clúster.
    • Puedes acceder a la aplicación utilizando la IP de cualquier nodo del clúster y el puerto asignado.
    • Ejemplo:
      • Si tienes un pod que corre en el puerto 80 y defines un servicio NodePort, Kubernetes asignará un puerto como 30226.
      • Podrás acceder a la aplicación en cualquier nodo en http://<Node-IP>:30226.
  • Ventajas:
    • Funciona en cualquier clúster de Kubernetes, ya sea en la nube o local.
    • No depende de un balanceador de carga externo.
  • Limitaciones:
    • El balanceo de carga no es tan eficiente como con un servicio LoadBalancer.
    • Depende de que el cliente conozca la IP de al menos uno de los nodos.
    • No es ideal para entornos de producción a gran escala, ya que no ofrece un balanceo de carga integrado.

2. LoadBalancer

  • Función: Un servicio de tipo LoadBalancer expone tu aplicación utilizando un balanceador de carga externo. Es ideal para entornos de producción en la nube, donde se puede usar un balanceador de carga para distribuir el tráfico entre los nodos y los pods de manera más eficiente.
  • Cómo funciona:
    • Kubernetes solicita al proveedor de la nube (AWS, GCP, Azure) que cree un balanceador de carga externo para tu servicio.
    • Este balanceador de carga dirige el tráfico a los nodos del clúster en el puerto apropiado.
    • Asigna una External IP al balanceador, lo que permite el acceso desde fuera del clúster.
    • Ejemplo:
      • Si defines un servicio LoadBalancer, Kubernetes le pide a tu proveedor de nube que cree un balanceador de carga que reciba tráfico en http://<External-IP>:80 y lo distribuya a los nodos del clúster.
  • Ventajas:
    • Proporciona balanceo de carga real entre los nodos y es ideal para entornos de producción.
    • Simplifica el acceso externo porque proporciona una dirección IP pública directamente accesible.
  • Limitaciones:
    • Solo está disponible en proveedores de nube que soportan la creación automática de balanceadores de carga (AWS, GCP, Azure).
    • No es una opción en clústeres locales sin herramientas adicionales (como MetalLB).

Resumen de Diferencias

CaracterísticaNodePortLoadBalancer
AccesoA través de la IP de los nodos y un puerto específico.A través de una IP pública asignada por el balanceador de carga.
Tipo de ExposiciónExposición limitada, solo a través de los nodos.Exposición pública a través de un balanceador de carga.
Uso en la nubeFunciona, pero no optimizado para grandes cargas.Ideal para entornos de nube y producción.
Uso en localFunciona en cualquier entorno.Requiere MetalLB u otra herramienta en entornos locales.
Balanceo de cargaNo tiene balanceo de carga externo.Balanceo de carga real entre los nodos y pods.

¿Cuál usar?

  • NodePort: Es útil para pruebas locales o cuando solo necesitas exponer tu aplicación rápidamente. No es ideal para entornos de producción debido a la falta de un balanceo de carga real.
  • LoadBalancer: Ideal para entornos de producción en la nube, ya que proporciona una IP pública y un balanceo de carga eficaz.

Port-Forwarding

Otra manera de acceder a un recurso web que esta dentro de un pod es:

kubectl port-forward httpd 9999:80

Vamos a localhost:9999 y deberiamos poder ver la web de apache.

Esto es util en entornos de prueba no produccion.

Crear un POD mediante un MANIFEST

Lo que vamos a hacer es lo siguiente para simular un flujo real:

  1. Creamos la imagen
  2. Subimos la imagen a docker hub
  3. Usamos la imagen del docker hub en el archivo .yml

Primero creamos la imagen de Docker dockerfile:

##Descargamos UBUNTU
FROM ubuntu

##Actualizamos el sistema
RUN apt-get update

##En algunas versiones de Linux es necesario configurar una variable para el TIMEZONE
ENV TZ=Europe/Madrid

##Luego creamos un fichero llamado /etc/timezone para configurar 
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

##Instalamos NGINX
RUN apt-get install -y nginx

##Creamos un fichero index.html en el directorio por defecto de nginx
RUN echo 'Ejemplo de POD con KUBERNETES y YAML' > /var/www/html/index.html

##Arrancamos NGINX a través de ENTRYPOINT para que no pueda ser modificado en la creación del contenedor
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]

##Exponemos el Puerto 80
EXPOSE 80

Luego creamos el MANIFEST , nginx.yml:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    zone: prod
    version: v1
spec:
  containers:
   - name: nginx   
     image: nahueljj/nginx:v1

Es importante que pongamos nuestra cuenta personal de dockerhub en image: nahueljj/nginx:v1 en mi caso nahueljj.

Ahora hacemos docker build -t nahueljj/nginx:v1 .

Una vez generada :

  1. Docker login -u mi-user
  2. Docker push nahueljj/nginx:v1
  3. Probamos la imagen subida docker run -d -p 80:80 --name nginx nahueljj/nginx:v1
  4. Vamos al navegador http://localhost:80 -> Vemos la web. 5, Docker stop nginx

Veamos en detalle el nginx.yml

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    zone: prod
    version: v1
spec:
  containers:
   - name: nginx   
     image: nahueljj/nginx:v1
  • kind : Le dice a kubernetes que tipo de componente que quiero crear.
  • metadata:
    • name: nombre del pod.
    • labels: permite poner etiquetas.
  • spec: Estas características desebles se guardan en etcd.
    • containers: Lista de contenedores que quiero correr en un pod.
      • name: nombre del container.
      • image: que imagen voy a usar para levantar el contenedor.

Una vez tengamos nuetro fichero yml hacemos:

kubectl create -f nginx.yml

El comando create nos permite generar un recurso basado en un yml.

Ahora podemos ver si se creo correctamente con:

kubectl get pods

Podemos validar que esta funcionando con kubectl describe pod nginx o con kubectl logs pod nginx.

Pero tambiem podemos ver la web exponiendolo como servicio:

#exponemos el servicio
kubectl expose pod nginx --name=nginx-service --port=80 --type=LoadBalancer

#vemos el puerto del servicio
kubectl get svc nginx-service

Por ejemplo podemos ver el contenido del servicio en http://192.168.58.3:32457/

Sino podes hacer minikube ip para ver la ip del master plane

Obtener la configuracion de un pod en JSON o YAML

Podemos obtener la config de un POD con:

kubectl get pod nginx -o yaml > config.yaml
kubectl get pod nginx -o yaml > config.json

Comando APPLY

Anteriormente creamos un POD de manera declartiva con un yml, el comando fue kubectl create -f nginx.yml, pero una vez que hayamos corrido ese comando no podremos hacer modificaciones y que estas se reflejen en tiempo de ejecucion.

Para lograr cambiar a un nuevo estado deseado tenemos que usar APPLY de la siguiente manera:

kubectl apply -f nginx.yml

Lo que hace es , si no existe un pod lo crea , y si ya existe lo modifica.

Borrar PODS

# Eliminamos un POD o Varios 
# Normmalmente hace un delete controlado siguiendo 
# un orden y espera a los subprocesos a que se cierren.
kubectl delete pod pod1,pod2 ... 

# Eliminamos un pod con delay en segundos.
kubectl delete pod apache --grace-period=5

# Elimina un pod automaticamente sin esperar a ningun otro proceso 
delete pod apache --now

# Elimina todos los pods sin peguntar nada.
kubectl delete pods --all

# Eliminamos todo lo existente, pods ,servicios, etc.
kubectl delte all --all

PODS Multicontenedores

Vamos a hacer el siguiente ejercicio:

Tendremos un pod con un servidor nginx para mostrar una web y otro servidor a su lado que lo monitorice, que le haga un ping cada 5 segundos. Todo en el mismo POD.

manitest.yaml

apiVersion: v1
kind: Pod 
metadata:
  name: multi
spec:
  containers:
    - name: web
      image: nginx
      ports:
        - containerPort: 80
    - name: frontal
      image: alpine
      command: ["watch","-n5","ping","localhost"]

Cada pod tiene una ip unica que se comparte entre los containers que viven dentro de el, es por eso que podemos usar localhost en el comando.

Ejecutamos kubectl apply -f manifest.yaml

Vemos el pod generado:

kubectl get pods
multi   2/2     ContainerCreating   0          13s

Esta vez veremos 2/2 antes si habia un solo container veiamos 1/1.

Verificamos que todo funcine con kubectl describe pod multi.

Podemos intenta ver los logs con:

kubectl logs multi

Pero esto nos devolvera solo el log del primer container, ya que lo toma por orden del yaml.

Si queremos ver el log del que esta tirando ping, usamos su nombre :

kubectl logs multi -c frontal

Mantenemos escuchado con -f

kubectl logs -f multi -c frontal

Como debe reiniciarse un pod?

Cuando creamos un pod con containers podemos declarar restartPolicy en los siguientes valores : always, onFailure o Never

  1. Always: Esta es la opción por defecto si no se especifica una restartPolicy. Kubernetes siempre reiniciará el contenedor si este termina, independientemente de si fue exitoso o fallido. Es ideal para servicios que deben ejecutarse continuamente.

  2. OnFailure:

    Kubernetes reiniciará el contenedor solo si este termina con un código de error distinto a 0. Útil para procesos que deben volver a intentarse en caso de fallos, como trabajos de tipo “batch” que deben completarse con éxito.

  3. Never:

    Kubernetes no reiniciará el contenedor sin importar si finaliza correctamente o con errores. Se utiliza en situaciones donde el contenedor debe ejecutarse una sola vez y no debe reiniciarse, como en tareas que se completan y no requieren repetición.

apiVersion: v1
kind: Pod
metadata:
  name: ejemplo-pod
spec:
  containers:
    - name: ejemplo-container
      image: nginx
  restartPolicy: OnFailure

Consideraciones sobre escalado?

Cuando escalamos apps sin etado no tendremos mayor problema, el tema es cuando queremos replicar bases de datos, por ejemplo, si estamos usando postgres, o mariadb podriamos pensar en tener 3 replicas y que esas tres apunten a un mismo volumen. Lo cual es incorrecto. , compartir el mismo volumen de datos entre ambas instancias no es recomendable ni soportado ya que puede llevar a corrupción de datos y otros problemas graves. En lugar de eso, se deben utilizar estrategias de replicación y sincronización adecuadas.

Hay alternativas de MYSQL como MYSQL CLUSTER o MYSQL GALERA cada una tiene su propia estrategia para manajar la replica de las DB.