Git Product home page Git Product logo

kubernetes's Introduction

Kubernetes

Índice

1. Kubernetes

Kubernetes (abreviado, K8s), es un orquestador de contenedores. Se encarga de ejecutarlos cuando se cumplan las condiciones adecuadas y de vigilar que tengan los recursos que necesiten. Corre en un cluster de nodos, en el que uno o varios de ellos (los masters), ejecutan el _control plane, que controla el cluster y tiene los siguientes componentes:

  • etcd, que guarda el estado completo del cluster: los parámetros de configuración, las especificaciones y el estado de los trabajos.

  • kube-controller-manager, encargado de ejecutar los distintos bucles de control o controllers necesarios para alcanzar el estado deseado del cluster, como los siguientes:

    • Node controller, que vigila los nodos y responde ante sus cambios.

    • Job controller, que vigila si es necesario lanzar trabajos y crea pods para hacerlo.

    • Endpoints controller, que asocia los servicios con los pods.

    • Service accounts y Token controlers, que crean las cuentas y los tokens de acceso para las API de nuevos espacios de nombres.

  • kube-scheduler, que decide en qué nodo ejecutar un nuevo pod ajustándose lo más posible a sus necesidades.

  • kube-apiserver, que proporciona una API para trabajar con K8s, y cuyo principal cliente es la orden kubectl.

Los clusters que corren en un proveedor en la nube también tienen un componente llamado cloud-controller-manager, que se encarga de ejecutar los controladores específicos para gestionar los recursos del proveedor.

Todos los nodos son capaces de ejecutar trabajos, aunque suele evitarse hacer esto en los nodos que corren el control plane. Para ejecutar trabajos, los nodos tienen:

  • Un proceso llamado kubelet que se encarga de comunicarse con el control plane y ejecutar lo que les pidan.

  • El proceso kube-proxy, que se encarga de gestionar las reglas de red de los nodos para permitir la comunicación con los pods tanto dentro del cluster como fuera. Implementan el concepto de servicio. Utiliza las reglas de filtrado del sistema operativo si están disponibles, o hace de proxy a nivel de aplicación si no.

  • Un runtime para ejecutar los contenedores, como Docker, containerd, CRI-O, rktlet…​

2. Instalación de un cluster de pruebas

Podemos instalar un cluster de K8s en un equipo con Linux y Docker o Podman (para contenedores rootless), utilizando herramientas como kind o minikube. Kind funciona mejor con Podman, pero solo crea clusters con un nodo. Para mis pruebas, utilizaré minikube con Docker, con un nodo master y dos adicionales, todo ello corriendo en una distribución Debian Sid..

2.1. Instalación de minikube sobre docker

Instalamos Docker desde los repositorios de Debian:

$ sudo apt install docker.io
...

$ docker version
Client:
 Version:           20.10.14+dfsg1
 API version:       1.41
 Go version:        go1.18.1
 Git commit:        a224086
 Built:             Sun May  1 19:59:40 2022
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server:
 Engine:
  Version:          20.10.14+dfsg1
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.18.1
  Git commit:       87a90dc
  Built:            Sun May  1 19:59:40 2022
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.6~ds1
  GitCommit:        1.6.6~ds1-1
 runc:
  Version:          1.1.1+ds1
  GitCommit:        1.1.1+ds1-1+b1
 docker-init:
  Version:          0.19.0
  GitCommit:

Descargamos e instalamos el paquete de Debian de minikube, que solo tiene el ejecutable.

$ cd /tmp
$ curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube_latest_amd64.deb
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 25.3M  100 25.3M    0     0  9556k      0  0:00:02  0:00:02 --:--:-- 9556k

$ dpkg -I minikube_latest_amd64.deb
 new Debian package, version 2.0.
 size 26558662 bytes: control archive=409 bytes.
     406 bytes,    12 lines      control
 Package: minikube
 Version: 1.26.0-0
 Section: base
 Priority: optional
 Architecture: amd64
 Recommends: virtualbox
 Maintainer: Thomas Strömberg <[email protected]>
 Description: Minikube
  minikube is a tool that makes it easy to run Kubernetes locally.
  minikube runs a single-node Kubernetes cluster inside a VM on your
  laptop for users looking to try out Kubernetes or develop with it
  day-to-day.

$ sudo dpkg -i minikube_latest_amd64.deb
(Reading database ... 214618 files and directories currently installed.)
Preparing to unpack minikube_latest_amd64.deb ...
Unpacking minikube (1.26.0-0) over (1.25.2-0) ...
Setting up minikube (1.26.0-0) ...

$ dpkg -L minikube
/.
/usr
/usr/bin
/usr/bin/minikube

Lanzamos minikube para que levante tres nodos sobre Docker:

$ minikube start --kubernetes-version=latest --driver=docker --nodes=3
😄  minikube v1.26.0 on Debian bookworm/sid
✨  Using the docker driver based on user configuration
📌  Using Docker driver with root privileges
👍  Starting control plane node minikube in cluster minikube
🚜  Pulling base image ...
💾  Downloading Kubernetes v1.24.1 preload ...
    > preloaded-images-k8s-v18-v1...: 405.83 MiB / 405.83 MiB  100.00% 5.38 MiB
    > gcr.io/k8s-minikube/kicbase: 386.00 MiB / 386.00 MiB  100.00% 3.95 MiB p/
    > gcr.io/k8s-minikube/kicbase: 0 B [_________________________] ?% ? p/s 53s
🔥  Creating docker container (CPUs=2, Memory=2200MB) ...
🐳  Preparing Kubernetes v1.24.1 on Docker 20.10.17 ...
    ▪ Generating certificates and keys ...
    ▪ Booting up control plane ...
    ▪ Configuring RBAC rules ...
🔗  Configuring CNI (Container Networking Interface) ...
🔎  Verifying Kubernetes components...
    ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🌟  Enabled addons: storage-provisioner, default-storageclass

👍  Starting worker node minikube-m02 in cluster minikube
🚜  Pulling base image ...
🔥  Creating docker container (CPUs=2, Memory=2200MB) ...
🌐  Found network options:
    ▪ NO_PROXY=192.168.49.2
🐳  Preparing Kubernetes v1.24.1 on Docker 20.10.17 ...
    ▪ env NO_PROXY=192.168.49.2
🔎  Verifying Kubernetes components...

👍  Starting worker node minikube-m03 in cluster minikube
🚜  Pulling base image ...
🔥  Creating docker container (CPUs=2, Memory=2200MB) ...
🌐  Found network options:
    ▪ NO_PROXY=192.168.49.2,192.168.49.3
🐳  Preparing Kubernetes v1.24.1 on Docker 20.10.17 ...
    ▪ env NO_PROXY=192.168.49.2
    ▪ env NO_PROXY=192.168.49.2,192.168.49.3
🔎  Verifying Kubernetes components...
🏄  Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default

minikube crea una configuración para kubectl en ~/kube/config para permitirle conectarse al cluster recién creado.

2.2. Instalación de metrics server

Algunas funciones de K8s, como la obtención de métricas de los pods con kubectl top o el autoescalado horizontal, necesitan que esté instalado el paquete Kubernetes Metrics Server, que se puede desplegar sobre minikube siguiendo un remiendo documentado aquí, que hace falta porque minikube usa certificados digitales autofirmados para los kubelet de los nodos:

$ curl -sL https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml | sed -e '/cert-dir/p' -e '0,/cert-dir/s/cert-dir.*/kubelet-insecure-tls/'| kubectl apply -f -
serviceaccount/metrics-server created
clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader created
clusterrole.rbac.authorization.k8s.io/system:metrics-server created
rolebinding.rbac.authorization.k8s.io/metrics-server-auth-reader created
clusterrolebinding.rbac.authorization.k8s.io/metrics-server:system:auth-delegator created
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-server created
service/metrics-server created
deployment.apps/metrics-server created
apiservice.apiregistration.k8s.io/v1beta1.metrics.k8s.io created

2.3. Instalación de kubectl

Aunque recomienda definir el alias kubectl='minikube kubectl --' para utilizar su propio cliente de kubectl, para garantizar que usamos la misma versión del cliente y del servidor, pero con él no funciona el autocompletado. En Debian, podemos instalar kubectl con un snap, aunque la versión es distinta que la de minikube:

$ sudo snap install kubectl --classic
2022-06-10T18:41:03+02:00 INFO Waiting for automatic snapd restart...
kubectl 1.24.0 from Canonical✓ installed

$ kubectl version --output=yaml
clientVersion:
  buildDate: "2022-07-14T02:31:37Z"
  compiler: gc
  gitCommit: aef86a93758dc3cb2c658dd9657ab4ad4afc21cb
  gitTreeState: clean
  gitVersion: v1.24.3
  goVersion: go1.18.3
  major: "1"
  minor: "24"
  platform: linux/amd64
kustomizeVersion: v4.5.4
serverVersion:
  buildDate: "2022-05-24T12:18:48Z"
  compiler: gc
  gitCommit: 3ddd0f45aa91e2f30c70734b175631bec5b5825a
  gitTreeState: clean
  gitVersion: v1.24.1
  goVersion: go1.18.2
  major: "1"
  minor: "24"
  platform: linux/amd64

2.4. Servicios de Kubernetes expuestos por minikube

Al instalar el cluster de minikube sobre Docker, se lanza un contenedor por cada nodo:

$ docker ps
CONTAINER ID   IMAGE                                 COMMAND                  CREATED      STATUS      PORTS                                                                                                                                  NAMES
365c9ccc54af   gcr.io/k8s-minikube/kicbase:v0.0.32   "/usr/local/bin/entr…"   2 days ago   Up 2 days   127.0.0.1:49177->22/tcp, 127.0.0.1:49176->2376/tcp, 127.0.0.1:49175->5000/tcp, 127.0.0.1:49174->8443/tcp, 127.0.0.1:49173->32443/tcp   minikube-m03
bf74d36b2b9f   gcr.io/k8s-minikube/kicbase:v0.0.32   "/usr/local/bin/entr…"   2 days ago   Up 2 days   127.0.0.1:49172->22/tcp, 127.0.0.1:49171->2376/tcp, 127.0.0.1:49170->5000/tcp, 127.0.0.1:49169->8443/tcp, 127.0.0.1:49168->32443/tcp   minikube-m02
0b6f58cb11c3   gcr.io/k8s-minikube/kicbase:v0.0.32   "/usr/local/bin/entr…"   2 days ago   Up 2 days   127.0.0.1:49167->22/tcp, 127.0.0.1:49166->2376/tcp, 127.0.0.1:49165->5000/tcp, 127.0.0.1:49164->8443/tcp, 127.0.0.1:49163->32443/tcp   minikube

Cada uno de los nodos tiene expuestos varios puertos mediante reglas de iptables, tanto de filtrado como de NAT:

$ sudo iptables -nvL
Chain INPUT (policy ACCEPT 2267K packets, 15G bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain FORWARD (policy DROP 41 packets, 3444 bytes)
 pkts bytes target     prot opt in     out     source               destination
 352K  369M DOCKER-USER  all  --  *      *       0.0.0.0/0            0.0.0.0/0
 352K  369M DOCKER-ISOLATION-STAGE-1  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    0     0 ACCEPT     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 DOCKER     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0
    0     0 ACCEPT     all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0
    0     0 ACCEPT     all  --  docker0 docker0  0.0.0.0/0            0.0.0.0/0
 341K  368M ACCEPT     all  --  *      br-c37e90db80de  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
   98  6864 DOCKER     all  --  *      br-c37e90db80de  0.0.0.0/0            0.0.0.0/0
11692  700K ACCEPT     all  --  br-c37e90db80de !br-c37e90db80de  0.0.0.0/0            0.0.0.0/0
   52  3120 ACCEPT     all  --  br-c37e90db80de br-c37e90db80de  0.0.0.0/0            0.0.0.0/0

Chain OUTPUT (policy ACCEPT 849K packets, 113M bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain DOCKER (2 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 ACCEPT     tcp  --  !br-c37e90db80de br-c37e90db80de  0.0.0.0/0            192.168.49.2         tcp dpt:32443
    1    60 ACCEPT     tcp  --  !br-c37e90db80de br-c37e90db80de  0.0.0.0/0            192.168.49.2         tcp dpt:8443
    1    60 ACCEPT     tcp  --  !br-c37e90db80de br-c37e90db80de  0.0.0.0/0            192.168.49.2         tcp dpt:5000
    0     0 ACCEPT     tcp  --  !br-c37e90db80de br-c37e90db80de  0.0.0.0/0            192.168.49.2         tcp dpt:2376
    1    60 ACCEPT     tcp  --  !br-c37e90db80de br-c37e90db80de  0.0.0.0/0            192.168.49.2         tcp dpt:22
    0     0 ACCEPT     tcp  --  !br-c37e90db80de br-c37e90db80de  0.0.0.0/0            192.168.49.3         tcp dpt:32443
    0     0 ACCEPT     tcp  --  !br-c37e90db80de br-c37e90db80de  0.0.0.0/0            192.168.49.3         tcp dpt:8443
    0     0 ACCEPT     tcp  --  !br-c37e90db80de br-c37e90db80de  0.0.0.0/0            192.168.49.3         tcp dpt:5000
    0     0 ACCEPT     tcp  --  !br-c37e90db80de br-c37e90db80de  0.0.0.0/0            192.168.49.3         tcp dpt:2376
    1    60 ACCEPT     tcp  --  !br-c37e90db80de br-c37e90db80de  0.0.0.0/0            192.168.49.3         tcp dpt:22
    0     0 ACCEPT     tcp  --  !br-c37e90db80de br-c37e90db80de  0.0.0.0/0            192.168.49.4         tcp dpt:32443
    0     0 ACCEPT     tcp  --  !br-c37e90db80de br-c37e90db80de  0.0.0.0/0            192.168.49.4         tcp dpt:8443
    0     0 ACCEPT     tcp  --  !br-c37e90db80de br-c37e90db80de  0.0.0.0/0            192.168.49.4         tcp dpt:5000
    0     0 ACCEPT     tcp  --  !br-c37e90db80de br-c37e90db80de  0.0.0.0/0            192.168.49.4         tcp dpt:2376
    1    60 ACCEPT     tcp  --  !br-c37e90db80de br-c37e90db80de  0.0.0.0/0            192.168.49.4         tcp dpt:22

Chain DOCKER-ISOLATION-STAGE-1 (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DOCKER-ISOLATION-STAGE-2  all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0
11692  700K DOCKER-ISOLATION-STAGE-2  all  --  br-c37e90db80de !br-c37e90db80de  0.0.0.0/0            0.0.0.0/0
 352K  369M RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0

Chain DOCKER-ISOLATION-STAGE-2 (2 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DROP       all  --  *      docker0  0.0.0.0/0            0.0.0.0/0
    0     0 DROP       all  --  *      br-c37e90db80de  0.0.0.0/0            0.0.0.0/0
11692  700K RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0

Chain DOCKER-USER (1 references)
 pkts bytes target     prot opt in     out     source               destination
 352K  369M RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0
$ sudo iptables -L -nv -t nat
Chain PREROUTING (policy ACCEPT 20097 packets, 6260K bytes)
 pkts bytes target     prot opt in     out     source               destination
 1364  118K DOCKER     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT 2926 packets, 519K bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 17981 packets, 2940K bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DOCKER     all  --  *      *       0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT 18042 packets, 2943K bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0
  395 24397 MASQUERADE  all  --  *      !br-c37e90db80de  192.168.49.0/24      0.0.0.0/0
    0     0 MASQUERADE  tcp  --  *      *       192.168.49.2         192.168.49.2         tcp dpt:32443
    0     0 MASQUERADE  tcp  --  *      *       192.168.49.2         192.168.49.2         tcp dpt:8443
    0     0 MASQUERADE  tcp  --  *      *       192.168.49.2         192.168.49.2         tcp dpt:5000
    0     0 MASQUERADE  tcp  --  *      *       192.168.49.2         192.168.49.2         tcp dpt:2376
    0     0 MASQUERADE  tcp  --  *      *       192.168.49.2         192.168.49.2         tcp dpt:22
    0     0 MASQUERADE  tcp  --  *      *       192.168.49.3         192.168.49.3         tcp dpt:32443
    0     0 MASQUERADE  tcp  --  *      *       192.168.49.3         192.168.49.3         tcp dpt:8443
    0     0 MASQUERADE  tcp  --  *      *       192.168.49.3         192.168.49.3         tcp dpt:5000
    0     0 MASQUERADE  tcp  --  *      *       192.168.49.3         192.168.49.3         tcp dpt:2376
    0     0 MASQUERADE  tcp  --  *      *       192.168.49.3         192.168.49.3         tcp dpt:22
    0     0 MASQUERADE  tcp  --  *      *       192.168.49.4         192.168.49.4         tcp dpt:32443
    0     0 MASQUERADE  tcp  --  *      *       192.168.49.4         192.168.49.4         tcp dpt:8443
    0     0 MASQUERADE  tcp  --  *      *       192.168.49.4         192.168.49.4         tcp dpt:5000
    0     0 MASQUERADE  tcp  --  *      *       192.168.49.4         192.168.49.4         tcp dpt:2376
    0     0 MASQUERADE  tcp  --  *      *       192.168.49.4         192.168.49.4         tcp dpt:22

Chain DOCKER (2 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0
    0     0 RETURN     all  --  br-c37e90db80de *       0.0.0.0/0            0.0.0.0/0
    0     0 DNAT       tcp  --  !br-c37e90db80de *       0.0.0.0/0            127.0.0.1            tcp dpt:49163 to:192.168.49.2:32443
    0     0 DNAT       tcp  --  !br-c37e90db80de *       0.0.0.0/0            127.0.0.1            tcp dpt:49164 to:192.168.49.2:8443
    0     0 DNAT       tcp  --  !br-c37e90db80de *       0.0.0.0/0            127.0.0.1            tcp dpt:49165 to:192.168.49.2:5000
    0     0 DNAT       tcp  --  !br-c37e90db80de *       0.0.0.0/0            127.0.0.1            tcp dpt:49166 to:192.168.49.2:2376
    0     0 DNAT       tcp  --  !br-c37e90db80de *       0.0.0.0/0            127.0.0.1            tcp dpt:49167 to:192.168.49.2:22
    0     0 DNAT       tcp  --  !br-c37e90db80de *       0.0.0.0/0            127.0.0.1            tcp dpt:49168 to:192.168.49.3:32443
    0     0 DNAT       tcp  --  !br-c37e90db80de *       0.0.0.0/0            127.0.0.1            tcp dpt:49169 to:192.168.49.3:8443
    0     0 DNAT       tcp  --  !br-c37e90db80de *       0.0.0.0/0            127.0.0.1            tcp dpt:49170 to:192.168.49.3:5000
    0     0 DNAT       tcp  --  !br-c37e90db80de *       0.0.0.0/0            127.0.0.1            tcp dpt:49171 to:192.168.49.3:2376
    0     0 DNAT       tcp  --  !br-c37e90db80de *       0.0.0.0/0            127.0.0.1            tcp dpt:49172 to:192.168.49.3:22
    0     0 DNAT       tcp  --  !br-c37e90db80de *       0.0.0.0/0            127.0.0.1            tcp dpt:49173 to:192.168.49.4:32443
    0     0 DNAT       tcp  --  !br-c37e90db80de *       0.0.0.0/0            127.0.0.1            tcp dpt:49174 to:192.168.49.4:8443
    0     0 DNAT       tcp  --  !br-c37e90db80de *       0.0.0.0/0            127.0.0.1            tcp dpt:49175 to:192.168.49.4:5000
    0     0 DNAT       tcp  --  !br-c37e90db80de *       0.0.0.0/0            127.0.0.1            tcp dpt:49176 to:192.168.49.4:2376
    0     0 DNAT       tcp  --  !br-c37e90db80de *       0.0.0.0/0            127.0.0.1            tcp dpt:49177 to:192.168.49.4:22

Los puertos que aparecen en las salidas anteriores corresponden a los distintos servicios de Kubernetes que están aceptando peticiones por red, como kubelet, en el 10250, o el API server, en el 8433. La siguiente salida muestra los servicios que están escuchando en los puertos TCP del master del cluster de minikube:

$ docker exec -it minikube ss -utanp | grep LIST
tcp   LISTEN    0      4096               127.0.0.1:10248               0.0.0.0:*                     users:(("kubelet",pid=1843,fd=20))
tcp   LISTEN    0      4096            192.168.49.2:2379                0.0.0.0:*                     users:(("etcd",pid=1607,fd=9))
tcp   LISTEN    0      4096               127.0.0.1:2379                0.0.0.0:*                     users:(("etcd",pid=1607,fd=8))
tcp   LISTEN    0      4096            192.168.49.2:2380                0.0.0.0:*                     users:(("etcd",pid=1607,fd=7))
tcp   LISTEN    0      4096               127.0.0.1:2381                0.0.0.0:*                     users:(("etcd",pid=1607,fd=13))
tcp   LISTEN    0      4096               127.0.0.1:10257               0.0.0.0:*                     users:(("kube-controller",pid=1675,fd=7))
tcp   LISTEN    0      4096               127.0.0.1:10259               0.0.0.0:*                     users:(("kube-scheduler",pid=1606,fd=7))
tcp   LISTEN    0      4096              127.0.0.11:35443               0.0.0.0:*
tcp   LISTEN    0      128                  0.0.0.0:22                  0.0.0.0:*                     users:(("sshd",pid=120,fd=3))
tcp   LISTEN    0      4096            192.168.49.2:10010               0.0.0.0:*                     users:(("containerd",pid=114,fd=11))
tcp   LISTEN    0      4096                       *:2376                      *:*                     users:(("dockerd",pid=635,fd=9))
tcp   LISTEN    0      4096                       *:10249                     *:*                     users:(("kube-proxy",pid=2229,fd=21))
tcp   LISTEN    0      4096                       *:10250                     *:*                     users:(("kubelet",pid=1843,fd=25))
tcp   LISTEN    0      4096                       *:10256                     *:*                     users:(("kube-proxy",pid=2229,fd=20))
tcp   LISTEN    0      4096                       *:40885                     *:*                     users:(("cri-dockerd",pid=546,fd=10))
tcp   LISTEN    0      128                     [::]:22                     [::]:*                     users:(("sshd",pid=120,fd=4))
tcp   LISTEN    0      4096                       *:8443                      *:*                     users:(("kube-apiserver",pid=1603,fd=7))

El API del cluster de minikube se puede alcanzar desde otra máquina que esté en la misma red que el host donde se despliegue con solo configurar una ruta hacia la red de los nodos. En el siguiente ejemplo, el cluster está instalado en la dirección IP 192.168.1.55, y queremos alcanzar la API desde la 192.168.1.5 de la misma red:

$ ip -c -br -4 a
lo               UNKNOWN        127.0.0.1/8
eth0             UP             192.168.1.5/24

$ ip route
default via 192.168.1.1 dev eth0 proto static metric 100
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.5 metric 100

$ sudo ip route add 192.168.49.0/24 via 192.168.1.55

$ ip route
default via 192.168.1.1 dev eth0 proto static metric 100
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.5 metric 100
192.168.49.0/24 via 192.168.1.55 dev eth0

$ nc -v 192.168.49.2 8443
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Connected to 192.168.49.2:8443.
^C

3. API de Kubernetes

kube-apiserver implementa un servicio API REST que utilizan los usuarios, partes del cluster y los componentes externos para interactuar con K8s. La API permite consultar y manipular el estado de los API objects de K8s, como pods, namespaces, ConfigMaps, eventos…​ Todas las entradas tienen el formato <punto_de_entrada_a_API>/<group>/<version>/<resource>

Se puede ver qué APIs soporta un cluster con kubectl api-versions, y qué recursos podemos manipular con kubectl api-resources.

La API de K8s requiere que los objetos se pasen en formato JSON. kubectl se encarga de convertir los objetos especificados como YAML a JSON.

Para poder manipular un objeto en K8s, necesitamos:

  • apiVersion, la versión de la API que utiliza el objeto.

  • kind, la clase del objeto.

  • metadata.name, el nombre único del objeto en su namespace.

  • metadata.namespace, el namespace donde está definido el objeto (por defecto, el actual o current).

  • metadata.uid, el identificador único generado para el objeto.

En YAML, esto tendría el siguiente aspecto:

apiVersion: v1
kind: Pod
metadata:
    name: mypod
    namespace: default
    uid: '145c2436-e0bb-11ec-b44c-e7f1d45f0a43'

Los objetos de K8s pueden examinarse con kubectl get.

Las versiones de API apiVersion tienen tres niveles de soporte:

  • Alpha, para todos los nombres que contienen alpha, como v1alpha2. No hay ningún tipo de garantía sobre estas API: pueden cambiar o desaparecer en cualquier momento.

  • Beta, para todos los nombres que contienen beta, como v2beta1. Son API probadas, aunque puede que se introduzcan pequeños cambios en versiones posteriores beta o estables, que obliguen a recrear los objetos afectados. Hay garantías de que no desaparecerán. No se recomienda que se usen estas API en producción, salvo que tengamos varios clusters que se puedan actualizar de forma independiente.

  • Estable, que se refieren a todos los nombres que no contienen alpha ni beta.

3.1. Control de acceso a la API

Warning
TODO.

Por defecto, la API de K8s está accesible en dos direcciones, una insegura y otra segura. La dirección insegura está pensada para hacer diagnóstico, y se encuentra en la dirección localhost:8080 de los nodos que tienen el control plane. Utiliza HTTP en claro y no requiere autenticación ni autorización, aunque sí que aplican los módulos de control de entrada (admission control). La dirección segura es la que usamos habitualmente con kubectl.

3.2. Creación de plantillas YAML

Cada recurso de K8s se puede definir en YAML o en JSON. Aunque kubectl no tiene forma directa de crear las plantillas con todas las opciones de un recurso, se puede sacar suficiente información con kubectl explain <recurso>, y generar una base bastante parecida a YAML, y demasiado extensa, con la opción --recursive:

$ kubectl explain pod
KIND:     Pod
VERSION:  v1

DESCRIPTION:
     Pod is a collection of containers that can run on a host. This resource is
     created by clients and scheduled onto hosts.

FIELDS:
   apiVersion   <string>
     APIVersion defines the versioned schema of this representation of an
     object. Servers should convert recognized schemas to the latest internal
     value, and may reject unrecognized values. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources

   kind <string>
     Kind is a string value representing the REST resource this object
     represents. Servers may infer this from the endpoint the client submits
     requests to. Cannot be updated. In CamelCase. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

   metadata     <Object>
     Standard object's metadata. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata

   spec <Object>
     Specification of the desired behavior of the pod. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
...

$ kubectl explain pod.spec
KIND:     Pod
VERSION:  v1

RESOURCE: spec <Object>

DESCRIPTION:
     Specification of the desired behavior of the pod. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

     PodSpec is a description of a pod.

FIELDS:
   activeDeadlineSeconds        <integer>
     Optional duration in seconds the pod may be active on the node relative to
     StartTime before the system will actively try to mark it failed and kill
     associated containers. Value must be a positive integer.

   affinity     <Object>
     If specified, the pod's scheduling constraints

   automountServiceAccountToken <boolean>
     AutomountServiceAccountToken indicates whether a service account token
     should be automatically mounted.

   containers   <[]Object> -required-
     List of containers belonging to the pod. Containers cannot currently be
     added or removed. There must be at least one container in a Pod. Cannot be
     updated.
...

$ kubectl explain pod --recursive
KIND:     Pod
VERSION:  v1

DESCRIPTION:
     Pod is a collection of containers that can run on a host. This resource is
     created by clients and scheduled onto hosts.

FIELDS:
   apiVersion   <string>
   kind <string>
   metadata     <Object>
      annotations       <map[string]string>
      clusterName       <string>
      creationTimestamp <string>
      deletionGracePeriodSeconds        <integer>
      deletionTimestamp <string>
      finalizers        <[]string>
      generateName      <string>
      generation        <integer>
      labels    <map[string]string>
      managedFields     <[]Object>
         apiVersion     <string>
         fieldsType     <string>
         fieldsV1       <map[string]>
         manager        <string>
         operation      <string>
         subresource    <string>
         time   <string>
      name      <string>
      namespace <string>
      ownerReferences   <[]Object>
         apiVersion     <string>
         blockOwnerDeletion     <boolean>
         controller     <boolean>
         kind   <string>
         name   <string>
         uid    <string>
      resourceVersion   <string>
      selfLink  <string>
      uid       <string>
   spec <Object>
      activeDeadlineSeconds     <integer>
      affinity  <Object>
         nodeAffinity   <Object>
            preferredDuringSchedulingIgnoredDuringExecution     <[]Object>
               preference       <Object>
                  matchExpressions      <[]Object>
                     key        <string>
                     operator   <string>
                     values     <[]string>
                  matchFields   <[]Object>
                     key        <string>
                     operator   <string>
                     values     <[]string>
               weight   <integer>
...

Para los recursos que se pueden crear con kubectl create, también se puede hacer una prueba de la creación de un objeto con la opción --dry-run=client:

$ kubectl create deployment trospido --image=nginx --dry-run=client -o=yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: trospido
  name: trospido
spec:
  replicas: 1
  selector:
    matchLabels:
      app: trospido
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: trospido
    spec:
      containers:
      - image: nginx
        name: nginx
        resources: {}
status: {}

4. Etiquetas

Todos los objetos de K8s pueden tener etiquetas asociadas (labels), que se utilizan para agruparlos de forma lógica, pudiéndose utilizar en los seleccionadores (selectors). Podemos crear o modificar Las etiquetas de los objetos en cualquier momento.

Las etiquetas y los seleccionadores pueden usarse para cosas como decidir en qué nodos del cluster deben ejecutarse determinados servicios o el tipo de almacenamiento a utilizar.

Las etiquetas se asignan como parte de los metadatos de un objeto:

metadata:
  labels:
    key1: value1
    key2: value2

Las claves tienen la forma [prefijo/]nombre, con un prefijo opcional que tiene la forma de un dominio DNS, y un nombre obligatorio que empieza y termina por un carácter alfanumérico y que puede incluir entre medias eso mismo más -, _ y .. Se entiende que las claves sin prefijo son privadas para los usuarios. Todas las etiquetas que utilizan los componentes propios de K8s tienen prefijo. Los prefijos kubernetes.io y k8s.io están reservados para ellos.

K8s recomienda utilizar algunas etiquetas para agrupar objetos, todas con el prefijo app.kubernetes.io.

Note
Es importante que las organizaciones definan un conjunto estándar de etiquetas para facilitar la gestión de los objetos de sus clusters, y que se utilicen en las plantillas de los distintos objetos.

4.1. Seleccionadores

Son filtros que permiten elegir objetos de K8s basándose en valores de sus etiquetas. Los hay de dos tipos, los basados en la igualdad y los que permiten buscar en conjuntos de valores.

Seleccionador basado en la igualdad
selector:
  matchLabels:
    key1: value

Los seleccionadores basados en la igualdad admiten tres operadores, = e ==, que son equivalentes y requieren que las etiquetas sean iguales a un valor, y !=, para requerir que sean distintas a un valor o que el objeto no tenga esa etiqueta. Pueden tener uno o varios requisitos separados por comas, que actúan como un AND lógico (deben cumplirse todos los requisitos):

$ get pods --selector environment=pro,tier!=frontend
Warning
Parece que no hay forma de conseguir el efecto de != en YAML con los seleccionadores basados en igualdad. Se puede conseguir algo similar con los seleccionadores basados en conjuntos y el operador NotIn, pero no todos los objetos de K8s soportan este tipo de seleccionadores.
Warning
No hay operador OR para ninguno de los dos tipos de seleccionadores.
Seleccionador basado en conjuntos [source,yaml]
selector:
  matchExpressions:
  - key: key1
    operator: In
    values:
    - value1
    - value2

Este tipo de seleccionadores admite los operadores In, NotIn, Exists, DoesNotExist, Gt y Lt.

5. Pods

Un pod (en el sentido de "manada"), es la unidad mínima de proceso de Kubernetes. Consiste en un grupo de contenedores que comparten ciertos recursos, como los volúmenes (aunque cada uno tenga su propio mount namespace), el namespace de red y el de IPC (comunicación entre procesos Posix y System V). El contenido de un pod se lanza en un único nodo, y se gestiona como un todo. Se puede pensar en ellos como en hosts virtuales para ejecutar procesos fuertemente acoplados.

Al compartir el namespace de red, todos los procesos de un pod pueden comunicarse mediante la dirección IP del localhost (127.0.0.1). Como comparten los números de puertos, es necesario que los contenedores de un pod utilicen puertos distintos para prestar sus servicios.

Para comprobar qué namespaces comparten dos procesos que forman parte del mismo pod en el cluster de minikube creado antes, lanzamos el siguiente pod:

pod-2containers.yaml
---
apiVersion: v1
kind: Pod
metadata:
  name: pod-2containers
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
    - containerPort: 80
  - name: loop
    image: nginx
    command:  ['sh', '-c', 'while true; do date; sleep 10s; done']
$ kubectl apply -f pod-2containers.yaml
pod/pod-2containers created

$ kubectl get pods
NAME               READY   STATUS    RESTARTS   AGE
pod-2containers   2/2     Running   0          5s

$  ps -ef | grep -iE 'nginx|sleep'
root     1014754 1014734  0 18:15 ?        00:00:00 nginx: master process nginx -g daemon off;
systemd+ 1014792 1014754  0 18:15 ?        00:00:00 nginx: worker process
systemd+ 1014793 1014754  0 18:15 ?        00:00:00 nginx: worker process
systemd+ 1014794 1014754  0 18:15 ?        00:00:00 nginx: worker process
systemd+ 1014795 1014754  0 18:15 ?        00:00:00 nginx: worker process
root     1014841 1014820  0 18:15 ?        00:00:00 sh -c while true; do date; sleep 10s; done
root     1049523 1014841  0 18:52 ?        00:00:00 sleep 10s

$ pstree -pslT 1014754
systemd(1)───containerd-shim(12954)───systemd(12978)───containerd-shim(1014734)───nginx(1014754)─┬─nginx(1014792)
                                                                                                 ├─nginx(1014793)
                                                                                                 ├─nginx(1014794)
                                                                                                 └─nginx(1014795)

$ pstree -plT 12978
systemd(12978)─┬─containerd(13170)
               ├─containerd-shim(15374)───pause(15394)
               ├─containerd-shim(15416)───pause(15437)
               ├─containerd-shim(15465)───kube-proxy(15514)
               ├─containerd-shim(15494)───kindnetd(15522)
               ├─containerd-shim(1014591)───pause(1014611)
               ├─containerd-shim(1014734)───nginx(1014754)─┬─nginx(1014792)
               │                                           ├─nginx(1014793)
               │                                           ├─nginx(1014794)
               │                                           └─nginx(1014795)
               ├─containerd-shim(1014820)───sh(1014841)───sleep(1088462)
               ├─dbus-daemon(13166)
               ├─dockerd(13196)
               ├─kubelet(14943)
               ├─sshd(13183)
               └─systemd-journal(13145)

# diff -y (readlink /proc/1014754/ns/* | psub) (readlink /proc/1014841/ns/* | psub)
cgroup:[4026534600]                                           | cgroup:[4026534604]
ipc:[4026534462]                                                ipc:[4026534462]
mnt:[4026534597]                                              | mnt:[4026534601]
net:[4026534464]                                                net:[4026534464]
pid:[4026534599]                                              | pid:[4026534603]
pid:[4026534599]                                              | pid:[4026534603]
time:[4026531834]                                               time:[4026531834]
time:[4026531834]                                               time:[4026531834]
user:[4026531837]                                               user:[4026531837]
uts:[4026534598]                                              | uts:[4026534602]

# systemd-cgls -l
...
  │     └─kubepods-besteffort-podd9d2bfea_9b77_43db_9741_e5f9ad6a70ec.slice (#99978)
  │       → trusted.invocation_id: 26cce36417ae4549bf775fb45a9c2bf8
  │       ├─docker-6b5120debb47b88bef33a471edf1ce451f679587033b44f4fe83ac4e2be5e190.scope … (#100173)
  │       │ → trusted.delegate: 1
  │       │ → trusted.invocation_id: 76b501c6ec94475f81fa407e21cfe218
  │       │ ├─1014841 sh -c while true; do date; sleep 10s; done
  │       │ └─1068429 sleep 10s
  │       ├─docker-f34da3d995e1fc06f1d71e22b960e7b4d16fb1cefe71c40c1184229c1f62b0b2.scope … (#100043)
  │       │ → trusted.delegate: 1
  │       │ → trusted.invocation_id: 0bfd50d366084aa4828f6ac260afc6aa
  │       │ └─1014611 /pause
  │       └─docker-e4d2f81c40fc09a437ba15a3fd4f3da859744d91dd4819283c04e3a8ded0843e.scope … (#100108)
  │         → trusted.delegate: 1
  │         → trusted.invocation_id: b1a2b05b1a19412298ae8aa02d06919a
  │         ├─1014754 nginx: master process nginx -g daemon off;
  │         ├─1014792 nginx: worker process
  │         ├─1014793 nginx: worker process
  │         ├─1014794 nginx: worker process
  │         └─1014795 nginx: worker process
...

Como puede verse en las salidas anteriores, y al menos en el caso de un cluster de minikube sobre Docker, los contenedores de un mismo pod comparten los namespaces de red, IPC, time y user (el que aísla los UID, GID y las capacidades de los procesos). Dentro de la jerarquía de cgroups, comparten un ancestro común (el kubepods-besteffort-pod…​), lo que permite gestionar los recursos globales asignados a ellos.

Los contenedores de un pod ven como hostname el campo name configurado en el pod.

$ kubectl exec pod/pod-2containers -- hostname
Defaulted container "nginx" out of: nginx, loop
pod-2containers

Se puede hacer que los contenedores de un pod compartan el namespace de procesos incluyendo en su definición la propiedad shareProcessNaespace: true`. Esto permite ver los procesos desde otros contenedores del pod, lo que es útil para usar contenedores efímeros (ephemeral containers) para diagnosticar problemas en contenedores distroless, por ejemplo.

5.1. Contenedores de aplicación

La carga de trabajo de los pods se hace dentro de los contenedores de aplicación (app containers), que se definen dentro del array containers. Con la orden kubectl explain pod, se puede ver todos los campos que admiten, pero los más importantes son la imagen del contenedor (image), la orden que debe ejecutarse y sus argumentos (command y args), las variables de entorno (env o envFrom), los scripts a ejecutar en ciertos momentos del ciclo de vida de los pods, las pruebas de estado, y los puertos de servicio (ports).

También es importante especificar los recursos que necesitan para funcionar, (memoria y CPU). Podemos indicar los recursos mínimos (resources.request), que K8s utilizará para asignar el nodo que ejecutará el pod, y también los máximos que el contenedor no podrá sobrepasar (resources.limits). Si se establecen límites pero no se indican mínimos, K8s usa los límites como mínimos. Si no se especifican límites, K8s permite usar la capacidad completa del nodo.

La CPU se especifica en unidades de cpu. Un 1 es una CPU completa, y se puede usar el sufijo m para indicar milésimas (por ejemplo, cpu: 250m es equivalente a 0.250, un cuarto de CPU). La memoria se especifica en bytes, pero se pueden usar los sufijos habituales (T, Ti, G, Gi, M, Mi, K, Ki…​).

5.2. Contenedores de inicialización

Un pod puede tener uno o más contenedores de inicialización (init containers), que se ejecutan antes de lanzar los contenedores de aplicación (app containers). Se define dentro del array initContainers, que es similar al containers de los contenedores de aplicación. Se lanzan de forma secuencial y se espera a que terminen antes de pasar al siguiente. Cuando el último termina, se lanzan los contenedores de aplicación del pod. Pueden utilizarse para comprobar que el entorno es el adecuado antes de lanzar los trabajos principales del pod o para prepararlo (p. ej, creando bases de datos u otros servicios).

Los contenedores de inicialización deben devolver un código de resultado. En caso de que devuelvan un error, se considerará que el pod ha fallado y se aplicará su política de reinicio, aunque si está fijada a Always se tratará como si fuera OnFailure.

Los contenedores de inicialización se vuelven a ejecutar si hay que reiniciar el pod, por lo que deben escribirse de forma que no intenten volver a hacer un trabajo que ya esté hecho en ejecuciones previas.

5.3. Ciclo de vida de los pods

Los pods siguen un ciclo de vida bien definido, representado por el campo phase de su objeto PodStatus, que aparece en el apartado Status: de la salida de kubectl describe pod.

Los pods empiezan en estado Pending cuando son aceptados por el cluster de K8s, y pasan a estado Running cuando todos sus contenedores se han creado y al menos uno de ellos está corriendo. Los pod pueden terminar en los estados Succeeded (todos los contenedores han terminado bien y no deben ser reiniciados) o Failed (todos los contenedores han terminado, pero al menos uno fallando, con un estado distinto de 0 o terminado por el sistema). También pueden estar en estado Unknown, si por cualquier razón no se puede obtener su estado (por ejemplo, por no poder comunicarse con su nodo).

Los pods son efímeros. La ejecución de un pod se programa una sola vez en toda su vida, asignándole un nodo. Una vez que se asigna un nodo a un pod, se ejecuta en él hasta que termina o se elimina. Si un nodo falla, se programa la finalización de sus pods pasado un tiempo de espera.

Cada pod tiene su propio UID. Los pods no pueden reasignarse a otros nodos, pero pueden sustituirse por otro pod casi idéntico en otro nodo, con su propio UID.

5.3.1. Estado de los contenedores

Dentro de un pod, los contenedores pasan por los siguientes estados, que pueden verse con kubectl describe pod:

  • Waiting, cuando se está preparando el contenedor para que pase a alguno de los otros estados.

  • Running, cuando el contenedor está funcionando sin problemas. Si el contenedor tuviera un hook postStart, se habrá ejecutado antes de pasar a este estado, aunque no hay garantías de que se ejecute antes que el punto de entrada del contenedor (se ejecutan de forma asíncrona).

  • Terminated, cuando un contenedor que ha pasado a estado Running termina por cualquier motivo. Antes de pasar a este estado, se ejecuta cualquier hook preStop que tuviera configurado.

5.3.2. Política de reinicio de los contenedores

kubelet es capaz de reiniciar los contenedores de un pod ante cierto tipo de fallos y hacer que el pod vuelva a estar saludable (healthy). Esto depende de la política restartPolicy que tenga configurada el pod, que puede tener los valores Always (por defecto), OnFailure o Never. kubelet reinicia los contenedores incrementando el tiempo de espera de forma exponencial (10, 20, 40 segundos…​), hasta 5 minutos máximo. Si un contenedor lleva 10 minutos corriendo sin problemas, se reinicia el tiempo de espera a su valor inicial.

5.3.3. Condiciones de los pods

El PodStatus de los pods tiene un array de condiciones por las que el pod ha podido pasar:

  • PodScheduled, si se le ha asignado un nodo.

  • ContainersReady, si todos los contenedores del pod están en estado Ready.

  • Initialized, si todos los contenedores de inicialización han terminado correctamente.

  • Ready, si el pod puede atender peticiones y puede ser añadido a la pila de balanceadores de los Services pertinentes.

$ kubectl describe pod pod-2containers
Name:         pod-2containers
Namespace:    blas
Priority:     0
Node:         minikube-m03/192.168.49.4
Start Time:   Mon, 27 Jun 2022 18:18:56 +0200
Labels:       <none>
Annotations:  <none>
Status:       Running
IP:           10.244.4.2
IPs:
  IP:  10.244.4.2
Containers:
  nginx:
    Container ID:   docker://aaac06fcf79aa3f03f077c5043cda90caac73b4781db968593c8ee91fbcd894b
    Image:          nginx
    Image ID:       docker-pullable://nginx@sha256:10f14ffa93f8dedf1057897b745e5ac72ac5655c299dade0aa434c71557697ea
    Port:           80/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Mon, 27 Jun 2022 18:18:59 +0200
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-47sbt (ro)
  loop:
    Container ID:  docker://c4bf74188ceeaa6ae14cab8ecf0c4ad7356ed744870e68fb35894dee3e88aaf8
    Image:         nginx
    Image ID:      docker-pullable://nginx@sha256:10f14ffa93f8dedf1057897b745e5ac72ac5655c299dade0aa434c71557697ea
    Port:          <none>
    Host Port:     <none>
    Command:
      sh
      -c
      while true; do date; sleep 10s; done
    State:          Running
      Started:      Mon, 27 Jun 2022 18:19:00 +0200
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-47sbt (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  kube-api-access-47sbt:
    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:                      <none>

5.3.4. Condiciones personalizadas (readinessGates)

Podemos añadir a los pods condiciones adicionales que kubelet puede utilizar para determinar si están listos para recibir peticiones o no, usando readinessGates:

kind: Pod
...
spec:
  readinessGates:
    - conditionType: "BalancerReady"
status:
  conditions:
    - type: "BalancerReady"
      status: "False"
      lastProbeTime: null
      lastTransitionTime: 2022-01-01T00:00:00Z
...

Se trata de condiciones cuyo valor se actualiza mediante la API e K8s, no desde dentro del pod. Por ejemplo, podemos utilizar un programa externo que compruebe si un balanceador externo está listo para enviar tráfico a un pod y utilice la API para actualizar el estado de esa condición. Se puede ver un ejemplo de cómo hacer esto aquí.

Los pods que tengan condiciones personalizadas solo estarán en estado Ready cuando todos sus contenedores estén Ready y el status de todas sus readinessGates sea True. Si lo primero fuera cierto pero lo segundo no, el estado del pod sería ContainersReady.

5.3.5. Pruebas de estado

Podemos configurar hasta tres pruebas distintas que kubelet puede hacer sobre los contenedores de un pod:

  • livenessProbe, que indica si el contenedor está funcionando. Si esta prueba falla, kubelet elimina el contenedor y se aplica su política de reinicio. Si no se personaliza esta prueba, por defecto se considera que está en estado Success. Esta prueba no es necesaria en contenedores que terminan automáticamente cuando dejan de funcionar, porque se les aplicará directamente la política de reinicio que tengan configurada.

  • readinessProbe, que determina si el contenedor está listo para atender peticiones. Si la prueba falla, el controlador de endpoints quita la dirección IP del pod de los endpoints de todos los servicios que coincidan con el pod. El estado de esta prueba es Failure durante la pausa inicial que haya configurada, pasando después a Success si no se personaliza la prueba. Se considera que un pod está listo para atender peticiones cuando todos sus contenedores están listos.

  • startupProbe, que determina si la aplicación del contenedor ha arrancado. Si se personaliza esta prueba, las otras dos pruebas se mantienen deshabilitadas hasta que esta se pasa. Si la prueba falla, kubelet mata el contenedor y se le aplica la política de reinicio que tenga configurada. Si no se personaliza esta prueba, por defecto se considera que está en estado Success. Este tipo de pruebas son útiles para contenedores que tardan un tiempo considerable en arrancar, mayor que initialDelaySeconds+failureThreshold*periodSeconds.

kubelet es el encargado de lanzar las pruebas, que pueden ser de los siguientes tipos:

  • exec, que ejecuta una orden dentro del contenedor, y se supera si devuelve 0.

  • httpGet, que lanza un HTTP GET contra la URL especificada, y se pasa si se devuelve un código HTTP mayor o igual que 200 y menor que 400. En versiones de K8s anteriores o iguales a la 1.13, kubelet utilizará el proxy configurado en las variables de entorno http_proxy o HTTP_PROXY del nodo para comunicarse con el contenedor, pero a partir de esa versión lo hará directamente.

  • tcpSocket, que abre una conexión TCP contra el puerto especificado. La prueba se pasa si el puerto está abierto.

  • grpc, que utiliza llamadas a procedimiento remoto gRPC. Por el momento, esto está en estado alpha. Este tipo de pruebas está disponible a partir de la versión 1.24 de K8s.

El resultado de cualquiera de las pruebas anteriores puede ser Success, si se pasan, Failure, si no se pasan, o Unknown si ha habido problemas para lanzar la prueba, en cuyo caso se seguirá intentando.

Se puede modificar el comportamiento de las distintas pruebas de estado a través de los siguientes parámetros:

  • initialDelaySeconds, que por defecto es 0. Es el número de segundos a esperar desde que el contenedor arranca para empezar a lanzar las pruebas. No aplica a startupProbe.

  • periodSeconds, que por defecto es 10s. Cada cuánto se ejecuta la prueba.

  • timeoutSeconds, que por defecto es 1s. Tiempo máximo de espera para obtener un resultado de la prueba.

  • successThreshold, que por defecto es 1. Número de pruebas correctas consecutivas necesario para considerar que el contenedor pasa la prueba, después de haber fallado.

  • failureThreshold, que por defecto es 3. Número de reintentos que hace K8s cuando una prueba falla, antes de abandonar y actuar en consecuencia.

Además, las pruebas httpGet admiten los siguientes parámetros:

  • host, que por defecto es la IP del pod. Es el nombre del host al que lanzar las pruebas. Si se quiere cambiar el host de la cabecera HTTP, es mejor cambiar la cabecera Host con httpHeaders.

  • scheme, que por defecto es HTTP, pero podemos cambiarlo a HTTPS. No se valida el certificado.

  • path, que por defecto es /. Tiene la parte de ruta de la URL a usar.

  • httpHeaders, con las cabeceras HTTP que queramos personalizar. Por defecto, se envían las cabeceras User-Agent: kube-probe/1.24 y Accept: */*.

  • port, con el número de puerto del servidor.

En el siguiente ejemplo, lanzamos un pod con dos contenedores, y configuramos en uno de ellos una prueba que falla aproximadamente el 10% de las veces, además de configurar el valor failureThreshold a 1 para reiniciar el contenedor en cuanto se detecte un fallo:

pod-2containers-lp.yaml
---
apiVersion: v1
kind: Pod
metadata:
  name: pod-2containers-lp
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
    - containerPort: 80
  - name: loop
    image: nginx
    command:  ['sh', '-c', 'while true; do date; sleep 10s; done']
    livenessProbe:
     exec:
       command:
       - bash
       - -c
       - f() { return $(($RANDOM % 10 < 1)); }; f
     initialDelaySeconds: 5
     periodSeconds: 5
     failureThreshold: 1

Una vez aplicada esa configuración, vemos cómo el contenedor se reinicia cada vez que falla:

$ kubectl get events --field-selector involvedObject.name=pod-2containers-lp -o custom-columns=LATSEEN:.lastTimestamp,COUNT:.count,TYPE:.type,REASON:.reason,OBJECT:.involvedObject.name,MESSAGE:.message --watch
LATSEEN                COUNT   TYPE      REASON      OBJECT               MESSAGE
2022-07-07T15:31:07Z   1       Normal    Scheduled   pod-2containers-lp   Successfully assigned blas/pod-2containers-lp to minikube-m02
2022-07-07T15:31:08Z   1       Normal    Pulling     pod-2containers-lp   Pulling image "nginx"
2022-07-07T15:31:09Z   1       Normal    Pulled      pod-2containers-lp   Successfully pulled image "nginx" in 1.333973436s
2022-07-07T15:31:10Z   1       Normal    Created     pod-2containers-lp   Created container nginx
2022-07-07T15:31:10Z   1       Normal    Started     pod-2containers-lp   Started container nginx
2022-07-07T15:31:10Z   1       Normal    Pulling     pod-2containers-lp   Pulling image "nginx"
2022-07-07T15:31:11Z   1       Normal    Pulled      pod-2containers-lp   Successfully pulled image "nginx" in 1.405459696s
2022-07-07T15:31:11Z   1       Normal    Created     pod-2containers-lp   Created container loop
2022-07-07T15:31:11Z   1       Normal    Started     pod-2containers-lp   Started container loop
2022-07-07T15:31:17Z   1       Warning   Unhealthy   pod-2containers-lp   Liveness probe failed:
2022-07-07T15:31:17Z   1       Normal    Killing     pod-2containers-lp   Container loop failed liveness probe, will be restarted
2022-07-07T15:31:48Z   2       Normal    Pulling     pod-2containers-lp   Pulling image "nginx"
2022-07-07T15:31:49Z   1       Normal    Pulled      pod-2containers-lp   Successfully pulled image "nginx" in 1.371456844s
2022-07-07T15:31:49Z   2       Normal    Created     pod-2containers-lp   Created container loop
2022-07-07T15:31:49Z   2       Normal    Started     pod-2containers-lp   Started container loop
2022-07-07T15:31:57Z   2       Warning   Unhealthy   pod-2containers-lp   Liveness probe failed:
2022-07-07T15:31:57Z   2       Normal    Killing     pod-2containers-lp   Container loop failed liveness probe, will be restarted
2022-07-07T15:32:28Z   3       Normal    Pulling     pod-2containers-lp   Pulling image "nginx"
2022-07-07T15:32:29Z   1       Normal    Pulled      pod-2containers-lp   Successfully pulled image "nginx" in 1.34310106s
2022-07-07T15:32:29Z   3       Normal    Created     pod-2containers-lp   Created container loop
2022-07-07T15:32:29Z   3       Normal    Started     pod-2containers-lp   Started container loop
2022-07-07T15:33:17Z   3       Warning   Unhealthy   pod-2containers-lp   Liveness probe failed:
2022-07-07T15:33:17Z   3       Normal    Killing     pod-2containers-lp   Container loop failed liveness probe, will be restarted
2022-07-07T15:33:48Z   4       Normal    Pulling     pod-2containers-lp   Pulling image "nginx"
2022-07-07T15:33:49Z   1       Normal    Pulled      pod-2containers-lp   Successfully pulled image "nginx" in 1.327867822s
2022-07-07T15:33:49Z   4       Normal    Created     pod-2containers-lp   Created container loop
2022-07-07T14:34:20Z   8       Normal    Pulling     pod-2containers-lp   Pulling image "nginx"
2022-07-07T15:36:13Z   6       Normal    Pulling     pod-2containers-lp   Pulling image "nginx"
2022-07-07T15:41:19Z   8       Normal    Pulling     pod-2containers-lp   Pulling image "nginx"
2022-07-07T15:46:19Z   1       Warning   BackOff     pod-2containers-lp   Back-off restarting failed container
$ kubectl get pod pod-2containers-lp --watch
NAME                 READY   STATUS              RESTARTS   AGE
pod-2containers-lp   0/2     Pending             0             0s
pod-2containers-lp   0/2     Pending             0             0s
pod-2containers-lp   0/2     ContainerCreating   0             0s
pod-2containers-lp   2/2     Running             0             5s
pod-2containers-lp   2/2     Running             1 (2s ago)    43s
pod-2containers-lp   2/2     Running             2 (1s ago)    82s
pod-2containers-lp   2/2     Running             3 (1s ago)    2m42s
pod-2containers-lp   2/2     Running             4 (2s ago)    4m18s
pod-2containers-lp   2/2     Running             5 (1s ago)    5m7s
pod-2containers-lp   2/2     Running             6 (2s ago)    6m43s
pod-2containers-lp   1/2     CrashLoopBackOff    6 (1s ago)    7m32s
pod-2containers-lp   2/2     Running             7 (2m43s ago)   10m
pod-2containers-lp   2/2     Running             8 (2s ago)      12m
pod-2containers-lp   1/2     CrashLoopBackOff    8 (1s ago)      13m

El estado CrashLoopBackOff indica que uno de los pods está fallando intermitentemente y debe investigarse la causa.

5.3.6. Parada de los pods

Para detener un pod, kubelet envía la señal TERM a sus procesos principales (con PID 1), o, en los runtimes que lo soporten, la señal especificada en el valor STOPSIGNAL de la imagen de los contenedores, y espera un tiempo de gracia terminationGracePeriodSeconds (por defecto, de 30 segundos), tras el que mata todos los procesos de los contenedores con una señal KILL. Una vez muertos, se elimina el pod del servidor de API.

Se puede forzar la terminación inmediata de un pod poniendo el período de gracia a cero con kubectl delete <pod> --force --grace-period=0. Esto eliminará el pod directamente de la API, pero tendrán un pequeño tiempo de gracia en el nodo para terminar antes de ser eliminados con la señal KILL.

Los pods que fallan permanecen en la API hasta que se eliminan manualmente o a través de su controlador, hasta un máximo de terminated-pod-gc-threshold.

Para los contenedores que tengan definido el hook preStop, kubelet ejecuta el hook dentro de ellos antes de enviar la señal anterior. El objetivo de ese código es preparar el contenedor para ser eliminado, no tiene por qué terminar el proceso. Por ejemplo, puede usarse para pasar los datos que se mantengan en una caché a almacenamiento permanente.

El hook preStop tiene terminationGracePeriodSeconds más dos segundos para hacer su trabajo. Si no termina en ese tiempo, se mata y aparece un error en los eventos:

pod-prestop.yaml
---
apiVersion: v1
kind: Pod
metadata:
  name: pod-prestop
spec:
  containers:
  - name: loop
    image: nginx
    command:  ['sh', '-c', 'while [ ! -f /tmp/blas ]; do date; sleep 1; done; echo "My work is done. Waiting to be killed..."; sleep 3600s']
    lifecycle:
      preStop:
        exec:
          command: ['sh', '-c', 'touch /tmp/blas; sleep 3600s']

Los siguientes eventos aparecen desde que se aplica el código anterior hasta que se elimina con kubectl delete. Puede verse que se considera que el hook ha fallado por timeout:

$ kubectl get events -o custom-columns=LATSEEN:.lastTimestamp,COUNT:.count,TYPE:.type,REASON:.reason,OBJECT:.involvedObject.name,MESSAGE:.message --watch
LATSEEN                COUNT   TYPE      REASON              OBJECT        MESSAGE
2022-07-08T16:26:00Z   1       Normal    Scheduled           pod-prestop   Successfully assigned blas/pod-prestop to minikube-m02
2022-07-08T16:26:01Z   1       Normal    Pulling             pod-prestop   Pulling image "nginx"
2022-07-08T16:26:02Z   1       Normal    Pulled              pod-prestop   Successfully pulled image "nginx" in 1.410463062s
2022-07-08T16:26:02Z   1       Normal    Created             pod-prestop   Created container loop
2022-07-08T16:26:02Z   1       Normal    Started             pod-prestop   Started container loop
2022-07-08T16:26:20Z   1       Normal    Killing             pod-prestop   Stopping container loop
2022-07-08T16:27:20Z   1       Warning   FailedPreStopHook   pod-prestop   Exec lifecycle hook ([sh -c touch /tmp/blas; sleep 3600s]) for Container "loop" in Pod "pod-prestop_blas(80a91549-811c-48e1-97fa-4149528da8ed)" failed - error: command 'sh -c touch /tmp/blas; sleep 3600s' exited with 137: , message: ""

Si quitamos el sleep 3600s del final del código del hook, se elimina el contenedor sin ningún tipo de error, aunque el proceso principal no haya terminado por sí mismo:

$ kubectl get events -o custom-columns=LATSEEN:.lastTimestamp,COUNT:.count,TYPE:.type,REASON:.reason,OBJECT:.involvedObject.name,MESSAGE:.message --watch
LATSEEN                COUNT   TYPE      REASON              OBJECT        MESSAGE
2022-07-08T16:33:28Z   1       Normal    Scheduled           pod-prestop   Successfully assigned blas/pod-prestop to minikube-m02
2022-07-08T16:33:29Z   1       Normal    Pulling             pod-prestop   Pulling image "nginx"
2022-07-08T16:33:30Z   1       Normal    Pulled              pod-prestop   Successfully pulled image "nginx" in 1.495145804s
2022-07-08T16:33:30Z   1       Normal    Created             pod-prestop   Created container loop
2022-07-08T16:33:30Z   1       Normal    Started             pod-prestop   Started container loop
2022-07-08T16:33:47Z   1       Normal    Killing             pod-prestop   Stopping container loop

El proceso principal también puede terminar por sí mismo sin esperar a recibir una señal para terminar, pero en ese caso puede aparecer un error debido a que K8s no encuentra el contenedor que quiere eliminar, aunque no es nada grave:

---
apiVersion: v1
kind: Pod
metadata:
  name: pod-prestop
spec:
  containers:
  - name: loop
    image: nginx
    command:  ['sh', '-c', 'while [ ! -f /tmp/blas ]; do date; sleep 1; done; echo "My work is done."']
    lifecycle:
      preStop:
        exec:
          command: ['sh', '-c', 'touch /tmp/blas']
$ kubectl get events -o custom-columns=LATSEEN:.lastTimestamp,COUNT:.count,TYPE:.type,REASON:.reason,OBJECT:.involvedObject.name,MESSAGE:.message --watch
LATSEEN                COUNT   TYPE      REASON              OBJECT        MESSAGE
2022-07-08T16:43:48Z   1       Normal    Scheduled           pod-prestop   Successfully assigned blas/pod-prestop to minikube-m02
2022-07-08T16:43:48Z   1       Normal    Pulling             pod-prestop   Pulling image "nginx"
2022-07-08T16:43:50Z   1       Normal    Pulled              pod-prestop   Successfully pulled image "nginx" in 1.409953675s
2022-07-08T16:43:50Z   1       Normal    Created             pod-prestop   Created container loop
2022-07-08T16:43:50Z   1       Normal    Started             pod-prestop   Started container loop
2022-07-08T16:44:02Z   1       Normal    Killing             pod-prestop   Stopping container loop
2022-07-08T16:44:05Z   2       Normal    Killing             pod-prestop   Stopping container loop
2022-07-08T16:44:05Z   1       Warning   FailedKillPod       pod-prestop   error killing pod: failed to "KillContainer" for "loop" with KillContainerError: "rpc error: code = Unknown desc = Error response from daemon: No such container: 94da5b1db2e66dcb401de89732778a058eb6a91ba99a23c5f9f55ea63e2b19b3"

6. Control de los trabajos

La misión principal de K8s es asegurarse de los trabajos se ejecutan adecuadamente, monitorizándolos y asignándoles los recursos que necesiten. Para ello disponemos de workload resources, recursos que gestionan los trabajos, como Deployments, ReplicaSets, Jobs…​

Note
Aunque solo queramos tener una instancia de un pod, en vez de lanzarla manualmente es mejor utilizar siempre algún tipo de controlador para garantizar su funcionamiento.

Algunos controladores, como los deployments y los replica sets, permiten cambiar el número de réplicas que gestionan mediante la orden kubectl scale --replicas=<n>. Los que guardan información sobre las versiones desplegadas, como los deployments, permiten gestionar los despliegues con kubectl rollout.

6.1. Replica Sets

Los replica sets (ReplicaSet), garantizan que hay un número determinado de réplicas de un pod funcionando (levantados y disponibles), creando los que falten o eliminando los que sobren. Los pods se sustituyen automáticamente si fallan, se eliminan o terminan, utilizando para ello la plantilla del pod especificada en su definición. Se tiene en cuenta el estado de los pods en todos los nodos.

Note
Aunque podemos utilizar directamente los replica sets, es mejor utilizar deployments, que son conceptos de más alto nivel que utilizan replica sets y proporcionan más funcionalidades.

El siguiente ejemplo define un ReplicaSet con tres pods de nginx:

rs-nginx.yaml
---
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: nginx-rs
  labels:
    app: app-nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: app-nginx
  template:
    metadata:
      name: nginx
      labels:
        app: app-nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
$ kubectl apply -f rs-nginx.yaml
replicaset.apps/nginx-rs created

Estos son los eventos que se producen al ejecutar la orden anterior:

$ kubectl get events --watch
1s          Normal    Scheduled          pod/nginx-rs-t46pz    Successfully assigned blas/nginx-rs-t46pz to minikube-m03
1s          Normal    SuccessfulCreate   replicaset/nginx-rs   Created pod: nginx-rs-t46pz
1s          Normal    SuccessfulCreate   replicaset/nginx-rs   Created pod: nginx-rs-z87k5
0s          Normal    Scheduled          pod/nginx-rs-58npq    Successfully assigned blas/nginx-rs-58npq to minikube
0s          Normal    SuccessfulCreate   replicaset/nginx-rs   Created pod: nginx-rs-58npq
0s          Normal    Scheduled          pod/nginx-rs-z87k5    Successfully assigned blas/nginx-rs-z87k5 to minikube-m02
0s          Normal    Pulling            pod/nginx-rs-z87k5    Pulling image "nginx"
0s          Normal    Pulling            pod/nginx-rs-58npq    Pulling image "nginx"
0s          Normal    Pulling            pod/nginx-rs-t46pz    Pulling image "nginx"
0s          Normal    Pulled             pod/nginx-rs-z87k5    Successfully pulled image "nginx" in 1.354562976s
0s          Normal    Pulled             pod/nginx-rs-58npq    Successfully pulled image "nginx" in 1.317435738s
0s          Normal    Created            pod/nginx-rs-z87k5    Created container nginx
0s          Normal    Created            pod/nginx-rs-58npq    Created container nginx
0s          Normal    Pulled             pod/nginx-rs-t46pz    Successfully pulled image "nginx" in 1.379200875s
0s          Normal    Created            pod/nginx-rs-t46pz    Created container nginx
0s          Normal    Started            pod/nginx-rs-58npq    Started container nginx
0s          Normal    Started            pod/nginx-rs-z87k5    Started container nginx
0s          Normal    Started            pod/nginx-rs-t46pz    Started container nginx
$ kubectl get rs
NAME       DESIRED   CURRENT   READY   AGE
nginx-rs   3         3         3       76s
$ kubectl describe rs nginx-rs
Name:         nginx-rs
Namespace:    blas
Selector:     app=app-nginx
Labels:       app=app-nginx
Annotations:  <none>
Replicas:     3 current / 3 desired
Pods Status:  3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:  app=app-nginx
  Containers:
   nginx:
    Image:        nginx
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age   From                   Message
  ----    ------            ----  ----                   -------
  Normal  SuccessfulCreate  93s   replicaset-controller  Created pod: nginx-rs-7gprq
  Normal  SuccessfulCreate  93s   replicaset-controller  Created pod: nginx-rs-2hlpr
  Normal  SuccessfulCreate  92s   replicaset-controller  Created pod: nginx-rs-ltzt7
$ kubectl get pods
nginx-rs-2hlpr   1/1     Running   0          2m
nginx-rs-7gprq   1/1     Running   0          2m
nginx-rs-ltzt7   1/1     Running   0          2m

El seleccionador matchLabels del replica set identifica los pods que serán controlados por él. Un replica set está enlazado con sus pods mediante el campo metadata.ownerReferences de estos, que especifica qué recurso es el propietario de un objeto:

$ kubectl get pods nginx-rs-2hlpr -o yaml
kubectl get pods nginx-rs-6wxmb -o yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2022-06-17T12:10:39Z"
  generateName: nginx-rs-
  labels:
    app: app-nginx
  name: nginx-rs-6wxmb
  namespace: blas
  ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: ReplicaSet
    name: nginx-rs
    uid: 0ca66e0f-5951-47dd-a1d3-b4c22a1db7b6
  resourceVersion: "20754"
  uid: 279d249e-668a-4968-80fd-01a45942f805
...

Si un nuevo pod cumple con el seleccionador de un replica set, será adquirido por él, siempre que no tenga ya un propietario o su propietario no sea un controlador. Podemos ver esto con el siguiente ejemplo, donde creamos un nuevo pod manualmente con la etiqueta del seleccionador usado en nuestro replica set. El pod se crea, pero se destruye inmediatamente porque ya tenemos los tres pods del replica set funcionando:

rs-new-pod.yaml
---
apiVersion: v1
kind: Pod
metadata:
  name: new-pod
  labels:
    app: app-nginx
spec:
  containers:
  - name: new-nginx
    image: nginx
    ports:
    - containerPort: 80
$ kubectl apply -f rs-new-pod.yaml
pod/new-pod created
$ kubectl get events --watch
0s          Normal    Scheduled          pod/new-pod           Successfully assigned blas/new-pod to minikube-m02
0s          Normal    SuccessfulDelete   replicaset/nginx-rs   Deleted pod: new-pod
0s          Normal    Pulling            pod/new-pod           Pulling image "nginx"
0s          Normal    Pulled             pod/new-pod           Successfully pulled image "nginx" in 1.452625358s
0s          Normal    Created            pod/new-pod           Created container new-nginx
0s          Normal    Started            pod/new-pod           Started container new-nginx
0s          Normal    Killing            pod/new-pod           Stopping container new-nginx
$ kubectl get pods
NAME             READY   STATUS    RESTARTS   AGE
nginx-rs-2hlpr   1/1     Running   0          8m14s
nginx-rs-7gprq   1/1     Running   0          8m14s
nginx-rs-ltzt7   1/1     Running   0          8m14s

Si lo hacemos al revés, primero creando el pod y luego el replica set, pasa lo contrario, manteniéndose el pod que creamos manualmente y añadiéndose otros dos:

$ kubectl apply -f rs-new-pod.yaml
pod/new-pod created
$ kubectl get pods
NAME      READY   STATUS    RESTARTS   AGE
new-pod   1/1     Running   0          9s
$ kubectl apply -f rs-nginx.yaml
replicaset.apps/nginx-rs created
$ kubectl get pods
NAME             READY   STATUS    RESTARTS   AGE
new-pod          1/1     Running   0          35s
nginx-rs-9pg7q   1/1     Running   0          10s
nginx-rs-scjhn   1/1     Running   0          10s

Podemos comprobar que el pod creado manualmente ahora está controlado por el replica set:

$ kubectl describe pod/new-pod
Name:         new-pod
Namespace:    blas
Priority:     0
Node:         minikube-m03/192.168.49.4
Start Time:   Fri, 17 Jun 2022 14:03:13 +0200
Labels:       app=app-nginx
Annotations:  <none>
Status:       Running
IP:           10.244.2.10
IPs:
  IP:           10.244.2.10
Controlled By:  ReplicaSet/nginx-rs
...

Si eliminamos cualquiera de los pods controlados por el replica set, se sustituye por uno nuevo inmediatamente:

$ kubectl delete pod new-pod
pod "new-pod" deleted
$ kubectl get pods
NAME             READY   STATUS    RESTARTS   AGE
nginx-rs-6wxmb   1/1     Running   0          8s
nginx-rs-9pg7q   1/1     Running   0          7m9s
nginx-rs-scjhn   1/1     Running   0          7m9s

Al eliminar un replica set, se cambia el número de objetos controlados por él a 0 para terminarlos, y después se elimina el propio replica set:

$ kubectl delete replicaset/nginx-rs
replicaset.apps "nginx-rs" deleted

$ kubectl get pods
No resources found in blas namespace.

Se puede eliminar un replica set sin borrar los pods que controla usando la opción --cascade=orphan de kubectl delete. Esto permitiría, por ejemplo, sustituir un replica set por otro nuevo para controlar los mismos pods, aunque, si este tuviera una nueva plantilla para los pods, solo se utilizaría para los pods nuevos que hubiera que crear.

Otra cosa que puede ser útil es hacer que un pod deje de estar controlado por un replica set, cambiando sus etiquetas.

Se puede cambiar al vuelo el número de pods controlados por un replica set cambiando su campo .spec.replicas. Los pods se crearán o se destruirán según sea necesario. Se puede automatizar esto utilizando un horizontal pod autoscaler, como el siguiente:

---
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: nginx-scaler
spec:
  scaleTargetRef:
    kind: ReplicaSet
    name: nginx-rs
  minReplicas: 5
  maxReplicas: 10
  targetCPUUtilizationPercentage: 50
$ # Otra forma de crear el autoescalador sin usar un YAML.
$ kubectl autoscale rs nginx-rs --max=10 --min=5 --cpu-percent=50
horizontalpodautoscaler.autoscaling/nginx-rs autoscaled

Para que el ejemplo de autoescalado funcione, hace falta tener habilitado el metrics-server. Si no, podemos ver un error en autoescalador:

$ kubectl describe horizontalpodautoscalers.autoscaling nginx-rs
Warning: autoscaling/v2beta2 HorizontalPodAutoscaler is deprecated in v1.23+, unavailable in v1.26+; use autoscaling/v2 HorizontalPodAutoscaler
Name:                                                  nginx-rs
Namespace:                                             default
Labels:                                                <none>
Annotations:                                           <none>
CreationTimestamp:                                     Mon, 20 Jun 2022 12:10:56 +0200
Reference:                                             ReplicaSet/nginx-rs
Metrics:                                               ( current / target )
  resource cpu on pods  (as a percentage of request):  <unknown> / 50%
Min replicas:                                          5
Max replicas:                                          10
ReplicaSet pods:                                       5 current / 5 desired
Conditions:
  Type           Status  Reason                   Message
  ----           ------  ------                   -------
  AbleToScale    True    SucceededGetScale        the HPA controller was able to get the target's current scale
  ScalingActive  False   FailedGetResourceMetric  the HPA was unable to compute the replica count: failed to get cpu utilization: unable to get metrics for resource cpu: unable to fetch metrics from resource metrics API: the server could not find the requested resource (get pods.metrics.k8s.io)
Events:
  Type     Reason                        Age   From                       Message
  ----     ------                        ----  ----                       -------
  Normal   SuccessfulRescale             26s   horizontal-pod-autoscaler  New size: 5; reason: Current number of replicas below Spec.MinReplicas
  Warning  FailedGetResourceMetric       10s   horizontal-pod-autoscaler  failed to get cpu utilization: unable to get metrics for resource cpu: unable to fetch metrics from resource metrics API: the server could not find the requested resource (get pods.metrics.k8s.io)
  Warning  FailedComputeMetricsReplicas  10s   horizontal-pod-autoscaler  invalid metrics (1 invalid out of 1), first error is: failed to get cpu utilization: unable to get metrics for resource cpu: unable to fetch metrics from resource metrics API: the server could not find the requested resource (get pods.metrics.k8s.io)
$ kubectl get pods
NAME             READY   STATUS    RESTARTS   AGE
nginx-rs-2z8xx   1/1     Running   0          85s
nginx-rs-c5x48   1/1     Running   0          85s
nginx-rs-clx8v   1/1     Running   0          35s
nginx-rs-k4894   1/1     Running   0          85s
nginx-rs-znxwq   1/1     Running   0          35s

En este caso, se han creado dos pods adicionales porque no se cumplía con el mínimo pedido en el autoescalador, pero el escalado por uso de la CPU no funcionará.

6.2. Deployments

Los despliegues (deployments), son un método declarativo de gestionar pods utilizando por debajo replica sets. Es la forma recomendada de gestionar los pods en un cluster de K8s.

El siguiente es un ejemplo de un despliegue compuesto por tres pods de nginx:

dep-nginx.yaml
---
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
        ports:
        - containerPort: 80

Al aplicarlo, se crea el Deployment, los pods y el replica set que los gestiona:

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

$ kubectl rollout status deployment/nginx-deployment
Waiting for deployment "nginx-deployment" rollout to finish: 0 of 3 updated replicas are available...
Waiting for deployment "nginx-deployment" rollout to finish: 1 of 3 updated replicas are available...
Waiting for deployment "nginx-deployment" rollout to finish: 2 of 3 updated replicas are available...
deployment "nginx-deployment" successfully rolled out

$ kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-74d589986c-lhwsg   1/1     Running   0          5s
nginx-deployment-74d589986c-qvqfm   1/1     Running   0          5s
nginx-deployment-74d589986c-vsmbz   1/1     Running   0          5s

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-74d589986c   3         3         3       22s

$ kubectl get deployments
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     3            3           35s

Los pods y los replica sets gestionados con despliegues tienen en sus nombres el valor de la etiqueta pod-template-hash que el despliegue incluye en ellos. Esta etiqueta es un número aleatorio calculado usando como semilla el hash del PodTemplate:

$ kubectl describe pod/nginx-deployment-74d589986c-lhwsg
Name:         nginx-deployment-74d589986c-lhwsg
Namespace:    default
Priority:     0
Node:         minikube-m03/192.168.49.4
Start Time:   Mon, 20 Jun 2022 12:46:37 +0200
Labels:       app=nginx
              pod-template-hash=74d589986c
...

Al eliminar el despliegue, se eliminan todos los recursos que creó:

$ kubectl delete deployment nginx-deployment
deployment.apps "nginx-deployment" deleted

$ kubectl get pods
No resources found in default namespace.

Los despliegues permiten usar como selectores matchLabels y/o matchExpressions, con la condición de que la plantilla del pod cumpla con ellos.

6.2.1. Actualización de un despliegue

Cuando se cambia la plantilla de los pods de un despliegue, se lanza el despliegue para aplicar los cambios (se hace un rollout). La forma de aplicar los cambios depende de la estrategia configurada en el campo .spec.strategy del despliegue, que puede ser Recreate o RollingUpdate, que es el valor por defecto.

Con la estrategia Recreate, primero se eliminan todos los pods actuales y después se crean los nuevos. Esto no es muy recomendable, porque si falla la creación de los nuevos pods nos quedaremos sin servicio.

La estrategia RollingUpdate crea nuevos pods con la nueva plantilla y elimina los antiguos por tandas, hasta sustituirlos todos. Por defecto, y siempre sin contar los pods que estén en estado terminating, se permite tener hasta un 125% más de pods que el máximo permitido (un 25% de aumento), y se garantiza que al menos se tiene un 75% del número deseado levantados (un 25% no disponible). Estos valores pueden configurarse en los campos .spec.strategy.rollingUpdate.maxUnavailable y .spec.strategy.rollingUpdate.maxSurge del despliegue, que pueden ser valores absolutos o porcentajes sobre el número de pods deseados, que se redondean hacia abajo o hacia arriba, respectivamente, para calcular el valor final.

Las siguientes salidas se han obtenido justo después de editar la definición del despliegue anterior para añadir una etiqueta en los pods. Atención a cómo cambia el nombre de las etiquetas de los nuevos recursos creados:

$ kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-74d589986c-c287l   1/1     Running   0          9m21s
nginx-deployment-74d589986c-fgz2b   1/1     Running   0          9m24s
nginx-deployment-74d589986c-qtwlc   1/1     Running   0          9m18s
$ kubectl get pods
NAME                                READY   STATUS              RESTARTS   AGE
nginx-deployment-74d589986c-c287l   1/1     Running             0          9m36s
nginx-deployment-74d589986c-fgz2b   1/1     Running             0          9m39s
nginx-deployment-74d589986c-qtwlc   1/1     Running             0          9m33s
nginx-deployment-795bc797c7-xjh4p   0/1     ContainerCreating   0          2s
$ kubectl get pods
NAME                                READY   STATUS        RESTARTS   AGE
nginx-deployment-74d589986c-c287l   1/1     Terminating   0          9m37s
nginx-deployment-74d589986c-fgz2b   1/1     Running       0          9m40s
nginx-deployment-74d589986c-qtwlc   1/1     Running       0          9m34s
nginx-deployment-795bc797c7-xjh4p   1/1     Running       0          3s
$ kubectl get pods
NAME                                READY   STATUS              RESTARTS   AGE
nginx-deployment-74d589986c-fgz2b   1/1     Running             0          9m41s
nginx-deployment-74d589986c-qtwlc   1/1     Running             0          9m35s
nginx-deployment-795bc797c7-q7x74   0/1     ContainerCreating   0          1s
nginx-deployment-795bc797c7-xjh4p   1/1     Running             0          4s
$ kubectl get pods
NAME                                READY   STATUS              RESTARTS   AGE
nginx-deployment-74d589986c-fgz2b   1/1     Running             0          9m43s
nginx-deployment-74d589986c-qtwlc   1/1     Running             0          9m37s
nginx-deployment-795bc797c7-q7x74   0/1     ContainerCreating   0          3s
nginx-deployment-795bc797c7-xjh4p   1/1     Running             0          6s
$ kubectl get pods
NAME                                READY   STATUS              RESTARTS   AGE
nginx-deployment-74d589986c-fgz2b   1/1     Terminating         0          9m44s
nginx-deployment-74d589986c-qtwlc   1/1     Running             0          9m38s
nginx-deployment-795bc797c7-nmst8   0/1     ContainerCreating   0          1s
nginx-deployment-795bc797c7-q7x74   1/1     Running             0          4s
nginx-deployment-795bc797c7-xjh4p   1/1     Running             0          7s
$ kubectl get pods
NAME                                READY   STATUS              RESTARTS   AGE
nginx-deployment-74d589986c-qtwlc   1/1     Running             0          9m39s
nginx-deployment-795bc797c7-nmst8   0/1     ContainerCreating   0          2s
nginx-deployment-795bc797c7-q7x74   1/1     Running             0          5s
nginx-deployment-795bc797c7-xjh4p   1/1     Running             0          8s
$ kubectl get pods
NAME                                READY   STATUS        RESTARTS   AGE
nginx-deployment-74d589986c-qtwlc   1/1     Terminating   0          9m40s
nginx-deployment-795bc797c7-nmst8   1/1     Running       0          3s
nginx-deployment-795bc797c7-q7x74   1/1     Running       0          6s
nginx-deployment-795bc797c7-xjh4p   1/1     Running       0          9s
$ kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-795bc797c7-nmst8   1/1     Running   0          4s
nginx-deployment-795bc797c7-q7x74   1/1     Running   0          7s
nginx-deployment-795bc797c7-xjh4p   1/1     Running   0          10s

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-74d589986c   0         0         0       3h58m
nginx-deployment-795bc797c7   3         3         3       42s

Se puede ver cómo se van modificando los valores de los replica sets gestionados por los despliegues viendo los eventos "scaled up" y "scaled down" de estos:

Name:                   nginx-deployment
Namespace:              default
CreationTimestamp:      Mon, 20 Jun 2022 12:46:37 +0200
Labels:                 app=nginx
Annotations:            deployment.kubernetes.io/revision: 6
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
           etiqueta=blas
  Containers:
   nginx:
    Image:        nginx
    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-795bc797c7 (3/3 replicas created)
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  76s   deployment-controller  Scaled up replica set nginx-deployment-795bc797c7 to 1
  Normal  ScalingReplicaSet  73s   deployment-controller  Scaled down replica set nginx-deployment-74d589986c to 2
  Normal  ScalingReplicaSet  73s   deployment-controller  Scaled up replica set nginx-deployment-795bc797c7 to 2
  Normal  ScalingReplicaSet  70s   deployment-controller  Scaled up replica set nginx-deployment-795bc797c7 to 3
  Normal  ScalingReplicaSet  70s   deployment-controller  Scaled down replica set nginx-deployment-74d589986c to 1
  Normal  ScalingReplicaSet  67s   deployment-controller  Scaled down replica set nginx-deployment-74d589986c to 0

Si deshacemos el cambio, la plantilla del pod vuelve a quedar como estaba, y el valor del campo pod-template-hash de los nuevos recursos que se crean como parte del lanzamiento del despliegue coincide con el que teníamos originalmente.

6.2.2. Historia de los despliegues

K8s guarda la historia de los lanzamientos hechos con un despliegue, conservando los replica sets correspondientes:

$ kubectl rollout history deployment/nginx-deployment
deployment.apps/nginx-deployment
REVISION  CHANGE-CAUSE
2         <none>
5         <none>
6         <none>

La columna CHANGE-CAUSE se obtiene del campo kubernetes.io/change-cause del despliegue, que se puede establecer añadiendo la opción --record a la orden kubectl que provocó el cambio, en cuyo caso se guardará la orden en la descripción del cambio, o cambiando la anotación de la versión actual del despliegue con kubectl annotate deployment/XXXXX kubernetes.io/change-cause="blablabla".

Por defecto, se guardan 10 versiones, pero se puede cambiar este valor cambiando el campo del despliegue .spec.revisionHistoryLimit:

$ kubectl get deploy/nginx-deployment -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:
...
spec:
  progressDeadlineSeconds: 600
  replicas: 3
  revisionHistoryLimit: 10
...

Se puede ver cómo es cada versión añadiendo la opción --revision=<n>. En este caso, solo cambian las etiquetas entre versiones:

$ kubectl rollout history deployment/nginx-deployment --revision=2
deployment.apps/nginx-deployment with revision #2
Pod Template:
  Labels:       app=nginx
        otra=blas
        pod-template-hash=7678d86c77
  Containers:
   nginx:
    Image:      nginx
    Port:       80/TCP
    Host Port:  0/TCP
    Environment:        <none>
    Mounts:     <none>
  Volumes:      <none>

$ kubectl rollout history deployment/nginx-deployment --revision=5
deployment.apps/nginx-deployment with revision #5
Pod Template:
  Labels:       app=nginx
        pod-template-hash=74d589986c
  Containers:
   nginx:
    Image:      nginx
    Port:       80/TCP
    Host Port:  0/TCP
    Environment:        <none>
    Mounts:     <none>
  Volumes:      <none>

$ kubectl rollout history deployment/nginx-deployment --revision=6
deployment.apps/nginx-deployment with revision #6
Pod Template:
  Labels:       app=nginx
        etiqueta=blas
        pod-template-hash=795bc797c7
  Containers:
   nginx:
    Image:      nginx
    Port:       80/TCP
    Host Port:  0/TCP
    Environment:        <none>
    Mounts:     <none>
  Volumes:      <none>

Se puede volver a la versión anterior de un despliegue así:

$ kubectl rollout undo deployment/nginx-deployment
deployment.apps/nginx-deployment rolled back

Se puede volver a una versión concreta añadiendo --to-revision=<n> a la orden anterior.

6.2.3. Escalado

Se puede cambiar los parámetros de escalado de un despliegue con kubectl scale. Como eso con cambia la plantilla del pod del despliegue, no se generan nuevas versiones de la historia:

$ kubectl rollout history deployment/nginx-deployment
deployment.apps/nginx-deployment
REVISION  CHANGE-CAUSE
2         <none>
10        <none>
11        <none>

$ kubectl scale deployment/nginx-deployment --replicas=10
deployment.apps/nginx-deployment scaled

$ kubectl rollout history deployment/nginx-deployment
deployment.apps/nginx-deployment
REVISION  CHANGE-CAUSE
2         <none>
10        <none>
11        <none>

$ kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-74d589986c-4rwmk   1/1     Running   0          15s
nginx-deployment-74d589986c-8r858   1/1     Running   0          15s
nginx-deployment-74d589986c-9dncr   1/1     Running   0          4m26s
nginx-deployment-74d589986c-fvqgv   1/1     Running   0          15s
nginx-deployment-74d589986c-kvzwv   1/1     Running   0          15s
nginx-deployment-74d589986c-lmnlk   1/1     Running   0          4m23s
nginx-deployment-74d589986c-p5cpp   1/1     Running   0          15s
nginx-deployment-74d589986c-s7kpl   1/1     Running   0          15s
nginx-deployment-74d589986c-v8kdw   1/1     Running   0          15s
nginx-deployment-74d589986c-xg2jl   1/1     Running   0          4m20s

Si tenemos habilitado el autoescalado horizontal en el cluster, se puede configurar un autoescalado basado en el consumo de CPU:

$ kubectl autoscale deployment/nginx-deployment --min=3 --max=10 --cpu-percent=2
horizontalpodautoscaler.autoscaling/nginx-deployment autoscaled

$ kubectl get horizontalpodautoscalers.autoscaling
NAME               REFERENCE                     TARGETS        MINPODS   MAXPODS   REPLICAS   AGE
nginx-deployment   Deployment/nginx-deployment   <unknown>/2%   3         10        3          43s

Como puede verse, la orden anterior crea un HorizontalPodAutoscaler (abreviado, hpa), del que podemos ver los detalles con kubectl get, en YAML o en JSON:

$ kubectl get hpa nginx-deployment -o=yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  creationTimestamp: "2022-06-21T15:24:36Z"
  name: nginx-deployment
  namespace: blas
  resourceVersion: "64059"
  uid: 3652214e-da75-4848-aa3e-ef4b59a181f0
spec:
  maxReplicas: 10
  metrics:
  - resource:
      name: cpu
      target:
        averageUtilization: 2
        type: Utilization
    type: Resource
  minReplicas: 3
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: nginx-deployment
status:
  conditions:
  - lastTransitionTime: "2022-06-21T15:24:51Z"
    message: the HPA controller was able to get the target's current scale
    reason: SucceededGetScale
    status: "True"
    type: AbleToScale
  - lastTransitionTime: "2022-06-21T15:24:51Z"
    message: 'the HPA was unable to compute the replica count: failed to get cpu utilization:
      missing request for cpu'
    reason: FailedGetResourceMetric
    status: "False"
    type: ScalingActive
  currentMetrics: null
  currentReplicas: 3
  desiredReplicas: 0
$ kubectl get horizontalpodautoscalers/nginx-deployment -o=json
{
    "apiVersion": "autoscaling/v2",
    "kind": "HorizontalPodAutoscaler",
    "metadata": {
        "creationTimestamp": "2022-06-21T15:24:36Z",
        "name": "nginx-deployment",
        "namespace": "blas",
        "resourceVersion": "64059",
        "uid": "3652214e-da75-4848-aa3e-ef4b59a181f0"
    },
    "spec": {
        "maxReplicas": 10,
        "metrics": [
            {
                "resource": {
                    "name": "cpu",
                    "target": {
                        "averageUtilization": 2,
                        "type": "Utilization"
                    }
                },
                "type": "Resource"
            }
        ],
        "minReplicas": 3,
        "scaleTargetRef": {
            "apiVersion": "apps/v1",
            "kind": "Deployment",
            "name": "nginx-deployment"
        }
    },
    "status": {
        "conditions": [
            {
                "lastTransitionTime": "2022-06-21T15:24:51Z",
                "message": "the HPA controller was able to get the target's current scale",
                "reason": "SucceededGetScale",
                "status": "True",
                "type": "AbleToScale"
            },
            {
                "lastTransitionTime": "2022-06-21T15:24:51Z",
                "message": "the HPA was unable to compute the replica count: failed to get cpu utilization: missing request for cpu",
                "reason": "FailedGetResourceMetric",
                "status": "False",
                "type": "ScalingActive"
            }
        ],
        "currentMetrics": null,
        "currentReplicas": 3,
        "desiredReplicas": 0
    }
}

Podemos cambiar los parámetros del autoescalador en JSON:

$ kubectl patch hpa nginx-deployment --patch '{"spec":{"minReplicas":5}}'
horizontalpodautoscaler.autoscaling/nginx-deployment patched

$ kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-74d589986c-26ch9   1/1     Running   0          8s
nginx-deployment-74d589986c-fgz2t   1/1     Running   0          24m
nginx-deployment-74d589986c-ngph9   1/1     Running   0          24m
nginx-deployment-74d589986c-p7q86   1/1     Running   0          24m
nginx-deployment-74d589986c-r6dlq   1/1     Running   0          8s

6.2.4. Escalado proporcional

Si pidiéramos escalar un despliegue que estuviera en mitad de un lanzamiento, por ejemplo, incrementando el número de réplicas deseadas, las nuevas instancias se repartirían entre los ReplicaSet que estuvieran activos de manera proporcional al número de réplicas deseado en cada uno de ellos. A esto se le llama proportional scaling:

$ kubectl get deployments
No resources found in blas namespace.

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

$ kubectl get deployments
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     3            3           8s

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-74d589986c   3         3         3       17s

$ kubectl scale deployment nginx-deployment --replicas=10
deployment.apps/nginx-deployment scaled

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-74d589986c   10        10        10      57s

$ # Actualizamos el despliegue con una imagen que no existe para mantener
$ # activos dos ReplicaSets.
$ kubectl set image deployment/nginx-deployment nginx=nginx:blas
deployment.apps/nginx-deployment image updated

$ kubectl get deployments
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   8/10    5            8           91s

$ # Vemos los dos ReplicaSets activos, el antiguo con más réplicas deseadas
$ # que el nuevo, por estar en mitad de un rollout.
$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-74d589986c   8         8         8       95s
nginx-deployment-dd56879bf    5         5         0       16s

$ # Subimos el número de réplicas deseadas del despliegue a 20.
$ kubectl scale deployment nginx-deployment --replicas=20
deployment.apps/nginx-deployment scaled

$ # Y comprobamos que se han asignado a los dos ReplicaSets de forma
$ # propocional.
$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-74d589986c   15        15        15      2m4s
nginx-deployment-dd56879bf    10        10        0       45s

6.2.5. Pausa y reanudación de lanzamientos

Es posible poner en pausa los lanzamientos de un despliegue, lo que es útil si queremos hacer varios cambios en él y evitar que cada uno de los cambios provoque un lanzamiento. Para hacerlo, se usa la orden kubectl rollout pause <despliegue>, y para reanudarlos de nuevo se usa kubectl rollour resume <despliegue>.

7. El modelo de red de Kubernetes

K8s implanta un modelo de red en el que cada pod dispone de su propia dirección IP dentro del cluster, completamente independiente de las direcciones IP de los nodos. Los pods se pueden comunicar entre sí sin NAT, incluso estando en nodos distintos. Los nodos pueden comunicarse con los pods sin NAT, y los agentes del nodo, como kubelet o los demonios del sistema, pueden comunicarse libremente con todos los pods de su nodo.

Como los contenedores de un pod comparten el espacio de nombres de red, se pueden comunicar entre ellos utilizando la dirección IP del localhost (127.0.0.1). Para que haya comunicación entre pods, es necesario que los contenedores sean capaces de encontrar sus direcciones IP, para lo que se pueden usar services.

7.1. Servicios

Los servicios (services) permiten referirse a puertos de comunicaciones de un conjunto de pods, sin tener que conocer exactamente qué contenedores los componen ni qué direcciones IP tienen asignadas. Son abstracciones que definen servicios de red en un grupo lógico de pods y una política para acceder a ellos. Por lo general, los servicios utilizan seleccionadores para determinar a qué pods deben dirigirse las peticiones, aunque también podemos tener servicios sin seleccionadores.

Los servicios suelen utilizarse con balanceadores de carga. Los servicios se encargan de reconfigurar los balanceadores para que las solicitudes lleguen hasta los pods que estén disponibles en ese momento.

Los servicios se definen como cualquier otro objeto de K8s. Cuando se crean, K8s les asigna su propia dirección IP interna, conocida como la cluster IP, en la que escucha kube-proxy en los puertos especificados. Esta dirección solo está accesible desde los pods y los nodos. El controlador del servicio monitoriza los pods que concuerden con el seleccionador para reenviarles las peticiones que lleguen al servicio a los puertos configurados de alguno de ellos.

El protocolo por defecto es TCP, pero están soportados los siguientes:

  • TCP.

  • UDP.

  • SCTP, si el plugin de red lo soporta.

  • HTTP, para los proveedores cloud que lo soporten con balanceadores de carga. También se pueden utilizar [ingress] para esto.

  • PROXY, para los proveedores cloud que lo soporten con balanceadores de carga.

Lo siguiente es un ejemplo de un servicio básico que redirige las peticiones que le llegan al puerto TCP 3306 al mismo puerto de los pods que tengan las etiquetas app y service adecuadas. Al no especificar el targetPort de los pods, se utiliza para el puerto de destino el mismo valor de port:

---
apiVersion: v1
kind: Service
metadata:
  name: mysql-service
spec:
  selector:
    app: wp
    service: mysql
  ports:
  - protocol: TCP
    port: 3306

Como los puertos se pueden nombrar, en vez de un número de puerto en targetPort, se puede especificar un nombre que cada contenedor puede definir con un número distinto. Al evitar valores absolutos, se mejora la flexibilidad de las configuraciones.

Cuando definimos un servicio, dentro de los pods se definen unas variables de entorno cuyo nombre empieza por el nombre del servicio, y que permiten ubicar tanto su dirección como el puerto. La dirección también está accesible por DNS. Por ejemplo, con la definición de servicio anterior desplegado en el namespace blas, podemos ver las siguientes variables de entorno en cualquiera de los pods del namespace:

$ kubectl get services
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
mysql-service   ClusterIP   10.106.99.227   <none>        3306/TCP   26h

$ kubectl exec -ti dep-wordpress-02-76d5696858-h2bpz -- bash
root@dep-wordpress-02-76d5696858-h2bpz:/var/www/html# env | grep MYSQ
MYSQL_SERVICE_PORT_3306_TCP_ADDR=10.106.99.227
MYSQL_SERVICE_SERVICE_HOST=10.106.99.227
MYSQL_SERVICE_PORT=tcp://10.106.99.227:3306
MYSQL_SERVICE_PORT_3306_TCP=tcp://10.106.99.227:3306
MYSQL_SERVICE_PORT_3306_TCP_PORT=3306
MYSQL_SERVICE_SERVICE_PORT=3306
MYSQL_SERVICE_PORT_3306_TCP_PROTO=tcp

root@dep-wordpress-02-76d5696858-h2bpz:/var/www/html# dig +short  mysql-service.blas.svc.cluster.local
10.106.99.227

Los servicios utilizan otro tipo de objeto de K8s para decidir a dónde enviar el tráfico, los endpoints. Cada servicio tiene un endpoint asociado, que se llama como él, y que el servicio actualiza cuando cambian los pods que cumplen con el seleccionador. Los endpoints pueden verse con la orden kubectl get endpoints:

$ kubectl get services
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
mysql-service   ClusterIP   10.101.219.16   <none>        3306/TCP   16h

$ kubectl get endpoints
NAME            ENDPOINTS          AGE
mysql-service   10.244.3.22:3306   16h

Como puede verse en las salidas anteriores, las direcciones de red son distintas para el servicio, también llamadas cluster IP, que se implementan con kube-proxy, que para los endpoints, que usan las direcciones de los pods.

Podemos definir servicios sin seleccionadores y escribir directamente el endpoint asociado. Esto es útil para cosas como que los pod puedan invocar a servicios implantados fuera del cluster o en namespaces distintos a los suyos. Es necesario que el endpoint se llame exactamente igual que el servicio, como puede verse en este ejemplo:

---
apiVersion: v1
kind: Service
metadata:
  name: external-db
spec:
  ports:
    - protocol: TCP
      port: 3306

---
apiVersion: v1
kind: Endpoints
metadata:
  name: external-db
subsets:
  - addresses:
      - ip: 10.1.1.20
    ports:
      - port: 3306

Los endpoints son arrays y pueden tener hasta 1000 entradas.

Los endpoints no escalan bien y producen demasiada sobrecarga en el control plane de K8s, por lo que se implantó un tipo de objeto similar, los EndpointSlices, que también gestionan automáticamente los controladores de los servicios.

7.2. Funcionamiento de kube-proxy

En cada nodo de K8s hay un kube-proxy funcionando, responsable de implementar los puntos de entrada de los services. Se puede arrancar de varios modos, según su configuración guardada en un ConfigMap. En todos los modos, kube-proxy monitoriza la adición o eliminación de servicios y endpoints en el control plane para actuar en consecuencia.

En el modo user space proxy, ya obsoleto, kube-proxy abre un puerto aleatorio en el nodo por servicio para recibir las peticiones, y configura una regla de iptables para poder recibir todo lo que vaya hacia la IP y el puerto del cluster clusterIP:port. Esta IP es virtual, no es la IP del nodo.

8. ConfigMaps

Warning
TODO

9. CLI de kubectl

kubectl es el cliente más habitual para trabajar con la API de K8s. Funciona por línea de comandos, y su configuración se guarda en ~/.kube/config, incluyendo la URL del cluster y las credenciales de autenticación.

Los archivos de configuración de kubectl se conocen como kubeconfigs. Se puede decir a kubectl qué archivo usar con la opción global --kubeconfig=<archivo>.

9.1. Autocompletado

kubectl completion <shell> genera las órdenes necesarias para tener autocompletado con distintos shells. Para fish, basta con meter lo siguiente en ~/.config/fish/config.fish:

kubectl completion fish | source

9.2. Información sobre el cluster

9.2.1. kubectl api-resources

Muestra los recursos disponibles a través de la API del cluster:

$ kubectl api-resources
NAME                              SHORTNAMES   APIVERSION                             NAMESPACED   KIND
bindings                                       v1                                     true         Binding
componentstatuses                 cs           v1                                     false        ComponentStatus
configmaps                        cm           v1                                     true         ConfigMap
endpoints                         ep           v1                                     true         Endpoints
events                            ev           v1                                     true         Event
limitranges                       limits       v1                                     true         LimitRange
namespaces                        ns           v1                                     false        Namespace
nodes                             no           v1                                     false        Node
persistentvolumeclaims            pvc          v1                                     true         PersistentVolumeClaim
persistentvolumes                 pv           v1                                     false        PersistentVolume
pods                              po           v1                                     true         Pod
podtemplates                                   v1                                     true         PodTemplate
replicationcontrollers            rc           v1                                     true         ReplicationController
resourcequotas                    quota        v1                                     true         ResourceQuota
secrets                                        v1                                     true         Secret
serviceaccounts                   sa           v1                                     true         ServiceAccount
services                          svc          v1                                     true         Service
mutatingwebhookconfigurations                  admissionregistration.k8s.io/v1        false        MutatingWebhookConfiguration
validatingwebhookconfigurations                admissionregistration.k8s.io/v1        false        ValidatingWebhookConfiguration
customresourcedefinitions         crd,crds     apiextensions.k8s.io/v1                false        CustomResourceDefinition
apiservices                                    apiregistration.k8s.io/v1              false        APIService
controllerrevisions                            apps/v1                                true         ControllerRevision
daemonsets                        ds           apps/v1                                true         DaemonSet
deployments                       deploy       apps/v1                                true         Deployment
replicasets                       rs           apps/v1                                true         ReplicaSet
statefulsets                      sts          apps/v1                                true         StatefulSet
tokenreviews                                   authentication.k8s.io/v1               false        TokenReview
localsubjectaccessreviews                      authorization.k8s.io/v1                true         LocalSubjectAccessReview
selfsubjectaccessreviews                       authorization.k8s.io/v1                false        SelfSubjectAccessReview
selfsubjectrulesreviews                        authorization.k8s.io/v1                false        SelfSubjectRulesReview
subjectaccessreviews                           authorization.k8s.io/v1                false        SubjectAccessReview
horizontalpodautoscalers          hpa          autoscaling/v2                         true         HorizontalPodAutoscaler
cronjobs                          cj           batch/v1                               true         CronJob
jobs                                           batch/v1                               true         Job
certificatesigningrequests        csr          certificates.k8s.io/v1                 false        CertificateSigningRequest
leases                                         coordination.k8s.io/v1                 true         Lease
endpointslices                                 discovery.k8s.io/v1                    true         EndpointSlice
events                            ev           events.k8s.io/v1                       true         Event
flowschemas                                    flowcontrol.apiserver.k8s.io/v1beta2   false        FlowSchema
prioritylevelconfigurations                    flowcontrol.apiserver.k8s.io/v1beta2   false        PriorityLevelConfiguration
ingressclasses                                 networking.k8s.io/v1                   false        IngressClass
ingresses                         ing          networking.k8s.io/v1                   true         Ingress
networkpolicies                   netpol       networking.k8s.io/v1                   true         NetworkPolicy
runtimeclasses                                 node.k8s.io/v1                         false        RuntimeClass
poddisruptionbudgets              pdb          policy/v1                              true         PodDisruptionBudget
podsecuritypolicies               psp          policy/v1beta1                         false        PodSecurityPolicy
clusterrolebindings                            rbac.authorization.k8s.io/v1           false        ClusterRoleBinding
clusterroles                                   rbac.authorization.k8s.io/v1           false        ClusterRole
rolebindings                                   rbac.authorization.k8s.io/v1           true         RoleBinding
roles                                          rbac.authorization.k8s.io/v1           true         Role
priorityclasses                   pc           scheduling.k8s.io/v1                   false        PriorityClass
csidrivers                                     storage.k8s.io/v1                      false        CSIDriver
csinodes                                       storage.k8s.io/v1                      false        CSINode
csistoragecapacities                           storage.k8s.io/v1beta1                 true         CSIStorageCapacity
storageclasses                    sc           storage.k8s.io/v1                      false        StorageClass
volumeattachments                              storage.k8s.io/v1                      false        VolumeAttachment

9.2.2. kubectl api-versions

Muestra las API soportadas por un cluster de K8s:

$ kubectl api-versions
apiextensions.k8s.io/v1
apiregistration.k8s.io/v1
apps/v1
authentication.k8s.io/v1
authorization.k8s.io/v1
autoscaling/v1
autoscaling/v2
autoscaling/v2beta1
autoscaling/v2beta2
batch/v1
batch/v1beta1
certificates.k8s.io/v1
coordination.k8s.io/v1
discovery.k8s.io/v1
discovery.k8s.io/v1beta1
events.k8s.io/v1
events.k8s.io/v1beta1
flowcontrol.apiserver.k8s.io/v1beta1
flowcontrol.apiserver.k8s.io/v1beta2
metrics.k8s.io/v1beta1
networking.k8s.io/v1
node.k8s.io/v1
node.k8s.io/v1beta1
policy/v1
policy/v1beta1
rbac.authorization.k8s.io/v1
scheduling.k8s.io/v1
storage.k8s.io/v1
storage.k8s.io/v1beta1
v1

9.2.3. kubectl cluster-info [dump]

Muestra información sobre el cluster, incluyendo el punto de entrada a la API. Con la opción dump, se muestra información completa en formato JSON:

$ kubectl cluster-info
Kubernetes control plane is running at https://192.168.49.2:8443
CoreDNS is running at https://192.168.49.2:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

9.2.4. kubectl describe

Muestra los detalles de un recurso o de un grupo de recursos:

$ kubectl describe node minikube
Name:               minikube
Roles:              control-plane
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/os=linux
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=minikube
                    kubernetes.io/os=linux
                    minikube.k8s.io/commit=f4b412861bb746be73053c9f6d2895f12cf78565
                    minikube.k8s.io/name=minikube
                    minikube.k8s.io/primary=true
                    minikube.k8s.io/updated_at=2022_07_18T13_28_21_0700
                    minikube.k8s.io/version=v1.26.0
                    node-role.kubernetes.io/control-plane=
                    node.kubernetes.io/exclude-from-external-load-balancers=
Annotations:        kubeadm.alpha.kubernetes.io/cri-socket: unix:///var/run/cri-dockerd.sock
                    node.alpha.kubernetes.io/ttl: 0
                    volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp:  Mon, 18 Jul 2022 13:28:17 +0200
Taints:             <none>
Unschedulable:      false
Lease:
  HolderIdentity:  minikube
  AcquireTime:     <unset>
  RenewTime:       Mon, 18 Jul 2022 14:08:33 +0200
Conditions:
  Type             Status  LastHeartbeatTime                 LastTransitionTime                Reason                       Message
  ----             ------  -----------------                 ------------------                ------                       -------
  MemoryPressure   False   Mon, 18 Jul 2022 14:04:36 +0200   Mon, 18 Jul 2022 13:28:14 +0200   KubeletHasSufficientMemory   kubelet has sufficient memory available
  DiskPressure     False   Mon, 18 Jul 2022 14:04:36 +0200   Mon, 18 Jul 2022 13:28:14 +0200   KubeletHasNoDiskPressure     kubelet has no disk pressure
  PIDPressure      False   Mon, 18 Jul 2022 14:04:36 +0200   Mon, 18 Jul 2022 13:28:14 +0200   KubeletHasSufficientPID      kubelet has sufficient PID available
  Ready            True    Mon, 18 Jul 2022 14:04:36 +0200   Mon, 18 Jul 2022 13:28:51 +0200   KubeletReady                 kubelet is posting ready status
Addresses:
  InternalIP:  192.168.49.2
  Hostname:    minikube
Capacity:
  cpu:                4
  ephemeral-storage:  243998164Ki
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             16313940Ki
  pods:               110
Allocatable:
  cpu:                4
  ephemeral-storage:  243998164Ki
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             16313940Ki
  pods:               110
System Info:
  Machine ID:                 d8902d1345bb469697278da23257a8d2
  System UUID:                45be27bb-9f73-426a-a9e4-abb2dceca00d
  Boot ID:                    9782c3a5-9fa6-4e33-8e2b-969f9b81b3da
  Kernel Version:             5.18.0-2-amd64
  OS Image:                   Ubuntu 20.04.4 LTS
  Operating System:           linux
  Architecture:               amd64
  Container Runtime Version:  docker://20.10.17
  Kubelet Version:            v1.24.1
  Kube-Proxy Version:         v1.24.1
PodCIDR:                      10.244.0.0/24
PodCIDRs:                     10.244.0.0/24
Non-terminated Pods:          (8 in total)
  Namespace                   Name                                CPU Requests  CPU Limits  Memory Requests  Memory Limits  Age
  ---------                   ----                                ------------  ----------  ---------------  -------------  ---
  kube-system                 coredns-6d4b75cb6d-bmghz            100m (2%)     0 (0%)      70Mi (0%)        170Mi (1%)     40m
  kube-system                 etcd-minikube                       100m (2%)     0 (0%)      100Mi (0%)       0 (0%)         40m
  kube-system                 kindnet-ppdxr                       100m (2%)     100m (2%)   50Mi (0%)        50Mi (0%)      40m
  kube-system                 kube-apiserver-minikube             250m (6%)     0 (0%)      0 (0%)           0 (0%)         40m
  kube-system                 kube-controller-manager-minikube    200m (5%)     0 (0%)      0 (0%)           0 (0%)         40m
  kube-system                 kube-proxy-rrf8b                    0 (0%)        0 (0%)      0 (0%)           0 (0%)         40m
  kube-system                 kube-scheduler-minikube             100m (2%)     0 (0%)      0 (0%)           0 (0%)         40m
  kube-system                 storage-provisioner                 0 (0%)        0 (0%)      0 (0%)           0 (0%)         40m
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource           Requests    Limits
  --------           --------    ------
  cpu                850m (21%)  100m (2%)
  memory             220Mi (1%)  220Mi (1%)
  ephemeral-storage  0 (0%)      0 (0%)
  hugepages-1Gi      0 (0%)      0 (0%)
  hugepages-2Mi      0 (0%)      0 (0%)
Events:
  Type    Reason                   Age                From             Message
  ----    ------                   ----               ----             -------
  Normal  Starting                 40m                kube-proxy
  Normal  NodeHasSufficientMemory  40m (x5 over 40m)  kubelet          Node minikube status is now: NodeHasSufficientMemory
  Normal  NodeHasNoDiskPressure    40m (x5 over 40m)  kubelet          Node minikube status is now: NodeHasNoDiskPressure
  Normal  NodeHasSufficientPID     40m (x4 over 40m)  kubelet          Node minikube status is now: NodeHasSufficientPID
  Normal  NodeHasNoDiskPressure    40m                kubelet          Node minikube status is now: NodeHasNoDiskPressure
  Normal  Starting                 40m                kubelet          Starting kubelet.
  Normal  NodeHasSufficientMemory  40m                kubelet          Node minikube status is now: NodeHasSufficientMemory
  Normal  NodeHasSufficientPID     40m                kubelet          Node minikube status is now: NodeHasSufficientPID
  Normal  NodeAllocatableEnforced  40m                kubelet          Updated Node Allocatable limit across pods
  Normal  RegisteredNode           40m                node-controller  Node minikube event: Registered Node minikube in Controller
  Normal  NodeReady                39m                kubelet          Node minikube status is now: NodeReady
$ kubectl -n kube-system describe coredns-6d4b75cb6d-bmghz
Name:                 coredns-6d4b75cb6d-bmghz
Namespace:            kube-system
Priority:             2000000000
Priority Class Name:  system-cluster-critical
Node:                 minikube/192.168.49.2
Start Time:           Mon, 18 Jul 2022 13:28:51 +0200
Labels:               k8s-app=kube-dns
                      pod-template-hash=6d4b75cb6d
Annotations:          <none>
Status:               Running
IP:                   10.244.0.2
IPs:
  IP:           10.244.0.2
Controlled By:  ReplicaSet/coredns-6d4b75cb6d
Containers:
  coredns:
    Container ID:  docker://cdc91589b7f6e409c2d499f3fd4b62cd6fc7e85aeef319814d541966d2bfc4e4
    Image:         k8s.gcr.io/coredns/coredns:v1.8.6
    Image ID:      docker-pullable://k8s.gcr.io/coredns/coredns@sha256:5b6ec0d6de9baaf3e92d0f66cd96a25b9edbce8716f5f15dcd1a616b3abd590e
    Ports:         53/UDP, 53/TCP, 9153/TCP
    Host Ports:    0/UDP, 0/TCP, 0/TCP
    Args:
      -conf
      /etc/coredns/Corefile
    State:          Running
      Started:      Mon, 18 Jul 2022 13:28:52 +0200
    Ready:          True
    Restart Count:  0
    Limits:
      memory:  170Mi
    Requests:
      cpu:        100m
      memory:     70Mi
    Liveness:     http-get http://:8080/health delay=60s timeout=5s period=10s #success=1 #failure=5
    Readiness:    http-get http://:8181/ready delay=0s timeout=1s period=10s #success=1 #failure=3
    Environment:  <none>
    Mounts:
      /etc/coredns from config-volume (ro)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-f99hc (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  config-volume:
    Type:      ConfigMap (a volume populated by a ConfigMap)
    Name:      coredns
    Optional:  false
  kube-api-access-f99hc:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   Burstable
Node-Selectors:              kubernetes.io/os=linux
Tolerations:                 CriticalAddonsOnly op=Exists
                             node-role.kubernetes.io/control-plane:NoSchedule
                             node-role.kubernetes.io/master:NoSchedule
                             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
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  42m   default-scheduler  0/1 nodes are available: 1 node(s) had untolerated taint {node.kubernetes.io/not-ready: }. preemption: 0/1 nodes are available: 1 Preemption is not helpful for scheduling.
  Normal   Scheduled         42m   default-scheduler  Successfully assigned kube-system/coredns-6d4b75cb6d-bmghz to minikube
  Normal   Pulled            42m   kubelet            Container image "k8s.gcr.io/coredns/coredns:v1.8.6" already present on machine
  Normal   Created           42m   kubelet            Created container coredns
  Normal   Started           42m   kubelet            Started container coredns

9.2.5. kubectl get

Devuelve distinta información sobre el cluster, como los nodos, los pods que hay corriendo…​

$ kubectl get nodes -o wide
NAME           STATUS   ROLES           AGE   VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION   CONTAINER-RUNTIME
minikube       Ready    control-plane   43m   v1.24.1   192.168.49.2   <none>        Ubuntu 20.04.4 LTS   5.18.0-2-amd64   docker://20.10.17
minikube-m02   Ready    <none>          43m   v1.24.1   192.168.49.3   <none>        Ubuntu 20.04.4 LTS   5.18.0-2-amd64   docker://20.10.17
minikube-m03   Ready    <none>          42m   v1.24.1   192.168.49.4   <none>        Ubuntu 20.04.4 LTS   5.18.0-2-amd64   docker://20.10.17
$ kubectl get pods --all-namespaces
NAMESPACE     NAME                               READY   STATUS    RESTARTS   AGE
kube-system   coredns-6d4b75cb6d-bmghz           1/1     Running   0          43m
kube-system   etcd-minikube                      1/1     Running   0          44m
kube-system   kindnet-c88sw                      1/1     Running   0          43m
kube-system   kindnet-gw87k                      1/1     Running   0          43m
kube-system   kindnet-ppdxr                      1/1     Running   0          43m
kube-system   kube-apiserver-minikube            1/1     Running   0          44m
kube-system   kube-controller-manager-minikube   1/1     Running   0          44m
kube-system   kube-proxy-j6wvq                   1/1     Running   0          43m
kube-system   kube-proxy-rrf8b                   1/1     Running   0          43m
kube-system   kube-proxy-t8b25                   1/1     Running   0          43m
kube-system   kube-scheduler-minikube            1/1     Running   0          44m
kube-system   metrics-server-58d4b776f5-v6l7c    1/1     Running   0          29m
kube-system   storage-provisioner                1/1     Running   0          44m
$ kubectl get pods --all-namespaces -o wide
NAMESPACE     NAME                               READY   STATUS    RESTARTS   AGE   IP             NODE           NOMINATED NODE   READINESS GATES
kube-system   coredns-6d4b75cb6d-bmghz           1/1     Running   0          44m   10.244.0.2     minikube       <none>           <none>
kube-system   etcd-minikube                      1/1     Running   0          44m   192.168.49.2   minikube       <none>           <none>
kube-system   kindnet-c88sw                      1/1     Running   0          43m   192.168.49.4   minikube-m03   <none>           <none>
kube-system   kindnet-gw87k                      1/1     Running   0          44m   192.168.49.3   minikube-m02   <none>           <none>
kube-system   kindnet-ppdxr                      1/1     Running   0          44m   192.168.49.2   minikube       <none>           <none>
kube-system   kube-apiserver-minikube            1/1     Running   0          44m   192.168.49.2   minikube       <none>           <none>
kube-system   kube-controller-manager-minikube   1/1     Running   0          44m   192.168.49.2   minikube       <none>           <none>
kube-system   kube-proxy-j6wvq                   1/1     Running   0          43m   192.168.49.4   minikube-m03   <none>           <none>
kube-system   kube-proxy-rrf8b                   1/1     Running   0          44m   192.168.49.2   minikube       <none>           <none>
kube-system   kube-proxy-t8b25                   1/1     Running   0          44m   192.168.49.3   minikube-m02   <none>           <none>
kube-system   kube-scheduler-minikube            1/1     Running   0          44m   192.168.49.2   minikube       <none>           <none>
kube-system   metrics-server-58d4b776f5-v6l7c    1/1     Running   0          29m   10.244.2.2     minikube-m03   <none>           <none>
kube-system   storage-provisioner                1/1     Running   0          44m   192.168.49.2   minikube       <none>           <none>
$  kubectl -n kube-system get pod coredns-6d4b75cb6d-bmghz
NAME                       READY   STATUS    RESTARTS   AGE
coredns-6d4b75cb6d-bmghz   1/1     Running   0          160m
$ kubectl get services
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   16h

Por defecto, hay algunos tipos de recursos dentro de los namespaces que no se muestran en la salida de kubectl get all. Se puede utilizar lo siguiente para verlos todos:

$ kubectl api-resources --verbs=list --namespaced -o name | xargs -n1 kubectl get --show-kind --ignore-not-found --all-namespaces
NAMESPACE         NAME                                           DATA   AGE
default           configmap/kube-root-ca.crt                     1      162m
kube-node-lease   configmap/kube-root-ca.crt                     1      162m
kube-public       configmap/cluster-info                         4      162m
kube-public       configmap/kube-root-ca.crt                     1      162m
kube-system       configmap/coredns                              1      162m
kube-system       configmap/extension-apiserver-authentication   6      162m
kube-system       configmap/kube-proxy                           2      162m
kube-system       configmap/kube-root-ca.crt                     1      162m
kube-system       configmap/kubeadm-config                       1      162m
kube-system       configmap/kubelet-config                       1      162m
NAMESPACE     NAME                                 ENDPOINTS                                     AGE
default       endpoints/kubernetes                 192.168.49.2:8443                             162m
kube-system   endpoints/k8s.io-minikube-hostpath   <none>                                        161m
kube-system   endpoints/kube-dns                   10.244.0.2:53,10.244.0.2:53,10.244.0.2:9153   162m
kube-system   endpoints/metrics-server             10.244.2.2:4443                               147m
NAMESPACE     LAST SEEN   TYPE      REASON                    OBJECT                                 MESSAGE
default       161m        Normal    NodeHasSufficientMemory   node/minikube-m02                      Node minikube-m02 status is now: NodeHasSufficientMemory
default       161m        Normal    NodeHasNoDiskPressure     node/minikube-m02                      Node minikube-m02 status is now: NodeHasNoDiskPressure
default       161m        Normal    RegisteredNode            node/minikube-m02                      Node minikube-m02 event: Registered Node minikube-m02 in Controller
default       161m        Normal    Starting                  node/minikube-m02
default       161m        Normal    NodeHasSufficientMemory   node/minikube-m03                      Node minikube-m03 status is now: NodeHasSufficientMemory
default       161m        Normal    NodeHasNoDiskPressure     node/minikube-m03                      Node minikube-m03 status is now: NodeHasNoDiskPressure
default       161m        Normal    RegisteredNode            node/minikube-m03                      Node minikube-m03 event: Registered Node minikube-m03 in Controller
default       161m        Normal    Starting                  node/minikube-m03
...
kubectl get events

Muestra los eventos que se han producido en el cluster:

$ kubectl get events
LAST SEEN   TYPE     REASON                    OBJECT              MESSAGE
163m        Normal   NodeHasSufficientMemory   node/minikube-m02   Node minikube-m02 status is now: NodeHasSufficientMemory
163m        Normal   NodeHasNoDiskPressure     node/minikube-m02   Node minikube-m02 status is now: NodeHasNoDiskPressure
163m        Normal   RegisteredNode            node/minikube-m02   Node minikube-m02 event: Registered Node minikube-m02 in Controller
163m        Normal   Starting                  node/minikube-m02
163m        Normal   NodeHasSufficientMemory   node/minikube-m03   Node minikube-m03 status is now: NodeHasSufficientMemory
163m        Normal   NodeHasNoDiskPressure     node/minikube-m03   Node minikube-m03 status is now: NodeHasNoDiskPressure
163m        Normal   RegisteredNode            node/minikube-m03   Node minikube-m03 event: Registered Node minikube-m03 in Controller
163m        Normal   Starting                  node/minikube-m03
164m        Normal   NodeHasSufficientMemory   node/minikube       Node minikube status is now: NodeHasSufficientMemory
164m        Normal   NodeHasNoDiskPressure     node/minikube       Node minikube status is now: NodeHasNoDiskPressure
164m        Normal   NodeHasSufficientPID      node/minikube       Node minikube status is now: NodeHasSufficientPID
164m        Normal   Starting                  node/minikube       Starting kubelet.
164m        Normal   NodeHasSufficientMemory   node/minikube       Node minikube status is now: NodeHasSufficientMemory
164m        Normal   NodeHasNoDiskPressure     node/minikube       Node minikube status is now: NodeHasNoDiskPressure
164m        Normal   NodeHasSufficientPID      node/minikube       Node minikube status is now: NodeHasSufficientPID
164m        Normal   NodeAllocatableEnforced   node/minikube       Updated Node Allocatable limit across pods
164m        Normal   RegisteredNode            node/minikube       Node minikube event: Registered Node minikube in Controller
164m        Normal   Starting                  node/minikube
163m        Normal   NodeReady                 node/minikube       Node minikube status is now: NodeReady

Podemos filtrar los eventos utilizando la opción --field-selector:

$ kubectl get event --field-selector involvedObject.name=pod-2containers-lp --watch
LAST SEEN   TYPE      REASON      OBJECT                    MESSAGE
15m         Normal    Pulling     pod/pod-2containers-lp   Pulling image "nginx"
10m         Warning   BackOff     pod/pod-2containers-lp   Back-off restarting failed container
7m26s       Normal    Scheduled   pod/pod-2containers-lp   Successfully assigned blas/pod-2containers-lp to minikube-m03
7m25s       Normal    Pulling     pod/pod-2containers-lp   Pulling image "nginx"
7m23s       Normal    Pulled      pod/pod-2containers-lp   Successfully pulled image "nginx" in 1.386338018s
7m23s       Normal    Created     pod/pod-2containers-lp   Created container nginx
7m23s       Normal    Started     pod/pod-2containers-lp   Started container nginx
70s         Normal    Pulling     pod/pod-2containers-lp   Pulling image "nginx"
7m22s       Normal    Pulled      pod/pod-2containers-lp   Successfully pulled image "nginx" in 1.398490586s
69s         Normal    Created     pod/pod-2containers-lp   Created container loop
69s         Normal    Started     pod/pod-2containers-lp   Started container loop
101s        Warning   Unhealthy   pod/pod-2containers-lp   Liveness probe failed:
101s        Normal    Killing     pod/pod-2containers-lp   Container loop failed liveness probe, will be restarted
3m29s       Normal    Pulled      pod/pod-2containers-lp   Successfully pulled image "nginx" in 1.403924337s
2m39s       Normal    Pulled      pod/pod-2containers-lp   Successfully pulled image "nginx" in 1.35347142s
69s         Normal    Pulled      pod/pod-2containers-lp   Successfully pulled image "nginx" in 1.342802789s
0s          Normal    Pulling     pod/pod-2containers-lp   Pulling image "nginx"
0s          Warning   BackOff     pod/pod-2containers-lp   Back-off restarting failed container
0s          Warning   BackOff     pod/pod-2containers-lp   Back-off restarting failed container
0s          Warning   BackOff     pod/pod-2containers-lp   Back-off restarting failed container

Para ver los campos disponibles, podemos ver la salida de la orden en formato YAML o JSON:

$ kubectl get events --output yaml
apiVersion: v1
items:
- apiVersion: v1
  count: 8
  eventTime: null
  firstTimestamp: "2022-07-18T11:28:37Z"
  involvedObject:
    kind: Node
    name: minikube-m02
    uid: minikube-m02
  kind: Event
  lastTimestamp: "2022-07-18T11:28:50Z"
  message: 'Node minikube-m02 status is now: NodeHasSufficientMemory'
  metadata:
    creationTimestamp: "2022-07-18T11:28:50Z"
    name: minikube-m02.1702e8ecfd61ab67
    namespace: default
    resourceVersion: "395"
    uid: fbe3fd76-837c-4d62-9c2b-5482c356ccbf
  reason: NodeHasSufficientMemory
  reportingComponent: ""
  reportingInstance: ""
  source:
    component: kubelet
    host: minikube-m02
  type: Normal
...

9.3. Manipulación del cluster

9.3.1. kubectl apply

Aplica al cluster la configuración indicada en el archivo YAML o JSON especificado con -f (o desde la entrada estándar, con -f -), haciendo los cambios necesarios sobre la configuración actual.

También se puede utilizar la opción -k para especificar un archivo kustomization.yaml, que permite hacer referencia a varios archivos donde especificar los recursos, y asignarles valores comunes, como el namespace o etiquetas. Los siguientes ejemplos son de la documentación de kubectl:

# kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

# list of Resource Config to be Applied
resources:
- deployment.yaml

# namespace to deploy all Resources to
namespace: default

# labels added to all Resources
commonLabels:
  app: example
  env: test
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: the-deployment
spec:
  replicas: 5
  template:
    containers:
      - name: the-container
        image: registry/container:latest

Se puede usar la orden edit-last-applied para editar la última configuración aplicada, y view-last-applied para mostrarla.

Con la opción --prune, se eliminan los objetos del cluster que no estén en la configuración aplicada.

9.3.2. kubectl create

Crea los recursos especificados en el archivo YAML o JSON pasado con la opción -f (o desde la entrada estándar, con -f -).

9.3.3. kubectl delete

Elimina los recursos especificados en el archivo YAML o JSON pasado con la opción -f (o desde la entrada estándar, con -f -), o los indicados por nombre o por etiqueta.

9.3.4. kubectl edit

Edita el objeto especificado en el archivo YAML o JSON pasado con la opción -f (o desde la entrada estándar, con -f -), o los indicados por nombre o por etiqueta. Utiliza el editor especificado en las variables de entorno EDITOR o KUBE_EDITOR, o con vi si no están definidas. Puede editar varios objetos, pero de uno en uno.

9.4. Namespaces

Los namespaces son una forma de hacer compartimentos dentro de Kubernetes, de manera que se puede limitar la visibilidad de los recursos.

Los nombres de los recursos deben de ser únicos dentro de un namespace, pero se pueden repetir entre namespaces.

El prefijo kube- está reservado para uso interno de K8s.

Por defecto, hay cuatro namespaces:

  • default, para los objetos que no están en ningún otro namespace.

  • kube-system, para los objetos creados y gestionados por K8s.

  • kube-public, para objetos públicos que puede ver cualquier usuario, incluso sin estar autenticado, y para los recursos que deban ser vistos por el cluster completo.

  • kube-node-lease, para guardar información sobre los heartbeats de los nodos, de manera que el plano de control pueda detectar su caída.

El nombre que los servicios tienen en el DNS de K8s incluye el namespace (<servicio>.<namespace>.svc.cluster.local), por lo que los contenedores solo verán los servicios que tengan en su propio namespace, a menos que especifiquen el dominio DNS completo. Como el nombre de los namespaces se usa en el DNS, solo deben de tener caracteres válidos para DNS (63 caracteres máximo, solo letras minúsculas o guiones, y empezar y terminar con un carácter alfanumérico).

No todos los tipos de recursos pueden estar dentro de un namespace, como los nodos o los propios namespaces (no se pueden anidar). Se puede ver la lista completa así:

$ kubectl api-resources --namespaced=false
NAME                              SHORTNAMES   APIVERSION                             NAMESPACED   KIND
componentstatuses                 cs           v1                                     false        ComponentStatus
namespaces                        ns           v1                                     false        Namespace
nodes                             no           v1                                     false        Node
persistentvolumes                 pv           v1                                     false        PersistentVolume
mutatingwebhookconfigurations                  admissionregistration.k8s.io/v1        false        MutatingWebhookConfiguration
validatingwebhookconfigurations                admissionregistration.k8s.io/v1        false        ValidatingWebhookConfiguration
customresourcedefinitions         crd,crds     apiextensions.k8s.io/v1                false        CustomResourceDefinition
apiservices                                    apiregistration.k8s.io/v1              false        APIService
tokenreviews                                   authentication.k8s.io/v1               false        TokenReview
selfsubjectaccessreviews                       authorization.k8s.io/v1                false        SelfSubjectAccessReview
selfsubjectrulesreviews                        authorization.k8s.io/v1                false        SelfSubjectRulesReview
subjectaccessreviews                           authorization.k8s.io/v1                false        SubjectAccessReview
certificatesigningrequests        csr          certificates.k8s.io/v1                 false        CertificateSigningRequest
flowschemas                                    flowcontrol.apiserver.k8s.io/v1beta2   false        FlowSchema
prioritylevelconfigurations                    flowcontrol.apiserver.k8s.io/v1beta2   false        PriorityLevelConfiguration
nodes                                          metrics.k8s.io/v1beta1                 false        NodeMetrics
ingressclasses                                 networking.k8s.io/v1                   false        IngressClass
runtimeclasses                                 node.k8s.io/v1                         false        RuntimeClass
podsecuritypolicies               psp          policy/v1beta1                         false        PodSecurityPolicy
clusterrolebindings                            rbac.authorization.k8s.io/v1           false        ClusterRoleBinding
clusterroles                                   rbac.authorization.k8s.io/v1           false        ClusterRole
priorityclasses                   pc           scheduling.k8s.io/v1                   false        PriorityClass
csidrivers                                     storage.k8s.io/v1                      false        CSIDriver
csinodes                                       storage.k8s.io/v1                      false        CSINode
storageclasses                    sc           storage.k8s.io/v1                      false        StorageClass
volumeattachments                              storage.k8s.io/v1                      false        VolumeAttachment

9.4.1. Namespace por defecto

Se utiliza la opción global --namespace para indicar a kubectl el namespace sobre el que queremos actuar. Podemos especificar el namespace por defecto sobre el que queremos actuar en el contexto actual haciendo kubectl config set-context --current --namespace=<namespace>.

9.4.2. kubectl create namespace

Permite crear un namespace desde la línea de comandos, sin necesidad de utilizar un archivo YAML o JSON:

$ kubectl create namespace blas
namespace/blas created

$ kubectl config set-context --current --namespace=blas
Context "minikube" modified.

$ kubectl get pods
No resources found in blas namespace.

9.5. Pods

9.5.1. kubectl attach (POD | TYPE/NAME) -c CONTAINER [options]

Conecta los stdout y stderr del terminal actual con uno de los contenedores de un pod en ejecución. Se puede especificar el contenedor con la opción --container (si no se especifica ninguno, se elige el que tenga el nombre contenido en la anotación kubectl.kubernetes.io/default-container del pod, o el primer contenedor del pod si esa anotación no está definida).

La forma de interrumpir la conexión dependerá del runtime de contenedores que estemos usando, pero normalmente se hace pulsando Ctrl-P+Ctrl-Q, aunque suele ser configurable.

Por defecto no conecta la entrada estándar, pero puede hacerse con la opción --stdin, con la que podemos usar además --tty para indicar que queremos que funcione en modo interactivo, como una terminal, para poder pasar las señales de control generadas con el teclado.

9.5.2. kubectl exec (POD | TYPE/NAME) [-c CONTAINER] [flags] — COMMAND [args…​]

Ejecuta una orden en uno de los contenedores de un pod. Como ocurre con kubectl attach, solo conecta las corrientes stdout y stderr del terminal a la orden, a menos que se ejecute con la opción --stdin y, opcionalmente, con --tty si queremos conectar la terminal actual.

La elección del contenedor donde se ejecuta la orden se hace igual que con kubectl attach.

9.5.3. kubectl logs [-f] [-p] (POD | TYPE/NAME) [-c CONTAINER] [options]

Muestra los registros de uno de los contenedores de un pod o del recurso que se especifique.

Podemos ver los registros de todos los contenedores de un pod con la opción --all-containers.

Con -f, el proceso seguirá mostrando los registros a medida que se vayan generando.

Con -p, podemos ver los registros de la instancia previa del contenedor, si es que hubo una.

Con -l, podemos elegir los contenedores utilizando seleccionadores de igualdad (ver seleccionadores).

Con --since, podemos especificar que queremos ver los registros desde el tiempo relativo que especifiquemos (p. ej, --since=10m para ver los de los útlimos 10 minutos).

9.5.4. kubectl top (node | pod) [NAME | -l label]

Muestra el uso de los recursos de un nodo o de un pod. Solo funciona si tenemos corriendo en el cluster la API de métricas proporcionada por metrics-server.

$ kubectl top node
NAME           CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
minikube       196m         4%     1456Mi          9%
minikube-m02   51m          1%     405Mi           2%
minikube-m03   44m          1%     357Mi           2%

9.6. Contextos

La información de la configuración de kubectl se agrupa en contextos con nombre. kubectl permite consultar el contexto actual y cambiar de contexto.

9.6.1. kubectl config current-context

Muestra el contexto que usa kubectl:

$ kubectl config current-context
minikube

9.6.2. kubectl config get-contexts <contexto>

Muestra los contextos disponibles en la configuración, o la información de uno concreto:

$ kubectl config get-contexts
CURRENT   NAME       CLUSTER    AUTHINFO   NAMESPACE
*         minikube   minikube   minikube   default

$ kubectl config get-contexts minikube
CURRENT   NAME       CLUSTER    AUTHINFO   NAMESPACE
*         minikube   minikube   minikube   default

9.6.3. kubectl config use-context <contexto>

Alias: kubectl config use.

Cambia el contexto actual.

9.6.4. kubectl config set-context <contexto>

Modifica un contexto:

$ kubectl config set-context minikube --namespace=blas
Context "minikube" modified.

9.6.5. kubectl config view

Muestra el archivo kubeconfig actual:

$ kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority: /home/jcouto/.minikube/ca.crt
    extensions:
    - extension:
        last-update: Thu, 16 Jun 2022 16:57:10 CEST
        provider: minikube.sigs.k8s.io
        version: v1.25.2
      name: cluster_info
    server: https://192.168.49.2:8443
  name: minikube
contexts:
- context:
    cluster: minikube
    extensions:
    - extension:
        last-update: Thu, 16 Jun 2022 16:57:10 CEST
        provider: minikube.sigs.k8s.io
        version: v1.25.2
      name: context_info
    namespace: default
    user: minikube
  name: minikube
current-context: minikube
kind: Config
preferences: {}
users:
- name: minikube
  user:
    client-certificate: /home/jcouto/.minikube/profiles/minikube/client.crt
    client-key: /home/jcouto/.minikube/profiles/minikube/client.key

$ diff ~/.kube/config (kubectl config view | psub)
$

10. Troubleshooting

Las órdenes más útiles para ver lo que ocurre dentro del cluster de K8s son kubectl get, kubectl_get events y kubectl describe.

11. Ejemplos

11.1. WordPress

WordPress es un sistema de gestión de contenidos web que necesita una base de datos MySQL para funcionar. Vamos a ver varias formas de desplegar este servicio en K8s, evolucionando la configuración inicial hasta tener un diseño robusto.

11.1.1. Versión inicial

La siguiente configuración básica muestra cómo lanzar WordPress en un pod con los dos contenedores necesarios, monitorizando que WordPress esté levantado mediante el código HTTP devuelto por su página de login:

dep-wordpress-01.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dep-wordpress-01
  labels:
    app: wp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wp
  template:
    metadata:
      labels:
        app: wp
    spec:
      containers:
      - name: wp
        image: wordpress:6.0-apache
        ports:
        - containerPort: 80
        env:
        - name: WORDPRESS_DB_HOST
          value: 127.0.0.1
        - name: WORDPRESS_DB_USER
          value: blas
        - name: WORDPRESS_DB_PASSWORD
          value: estonticiassupernoiasexuperaciones
        - name: WORDPRESS_DB_NAME
          value: wp
        readinessProbe:
          httpGet:
            port: 80
            path: /wp-login.php
        livenessProbe:
          httpGet:
            port: 80
            path: /wp-login.php
      - name: db
        image: mysql:8.0
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: computacionalmenteinversapolarizacionesmutan
        - name: MYSQL_USER
          value: blas
        - name: MYSQL_PASSWORD
          value: estonticiassupernoiasexuperaciones
        - name: MYSQL_DATABASE
          value: wp

Este ejemplo tiene varios problemas:

  1. No esperamos a que la base de datos esté levantada para lanzar WordPress, aunque es necesario para que funcione. WordPress reintentará la conexión con la base de datos varias veces hasta desistir.

  2. El servicio de base de datos y WordPress están corriendo en el mismo pod, lo que sería equivalente a que los dos servicios corrieran en el mismo servidor. Esto no es recomendable porque hacemos que los dos servicios estén fuertemente acoplados, lo que nos impide hacer cosas como escalarlos de forma independiente.

  3. No tenemos persistencia del almacenamiento para ninguno de los dos servicios.

11.1.2. Base de datos separada del frontal

El siguiente ejemplo mejora algunos de los problemas indicados antes, separando la base de datos MySQL y el frontal de WordPress en dos pods distintos, y utilizando un servicio para permitir que WordPress localice la base de datos por nombre:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dep-wordpress-02
  labels:
    app: wp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wp
      service: wordpress
  template:
    metadata:
      labels:
        app: wp
        service: wordpress
    spec:
      containers:
      - name: wp
        image: wordpress:6.0-apache
        ports:
        - containerPort: 80
        env:
        - name: WORDPRESS_DB_HOST
          value: mysql-service
        - name: WORDPRESS_DB_USER
          value: blas
        - name: WORDPRESS_DB_PASSWORD
          value: estonticiassupernoiasexuperaciones
        - name: WORDPRESS_DB_NAME
          value: wp
        readinessProbe:
          httpGet:
            port: 80
            path: /wp-login.php
        livenessProbe:
          httpGet:
            port: 80
            path: /wp-login.php

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dep-db-02
  labels:
    app: wp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wp
      service: mysql
  template:
    metadata:
      labels:
        app: wp
        service: mysql
    spec:
      containers:
      - name: db
        image: mysql:8.0
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: computacionalmenteinversapolarizacionesmutan
        - name: MYSQL_USER
          value: blas
        - name: MYSQL_PASSWORD
          value: estonticiassupernoiasexuperaciones
        - name: MYSQL_DATABASE
          value: wp

---
apiVersion: v1
kind: Service
metadata:
  name: mysql-service
spec:
  selector:
    app: wp
    service: mysql
  ports:
  - protocol: TCP
    port: 3306

12. Otras herramientas

12.1. kustomize

kustomize sirve para aplicar cambios a plantillas YAML de K8s sin tener que modificar los archivos originales, combinando funcionalidades de herramientas como make y sed.

12.2. helm

Warning
TODO

14. Glosario

kubeconfig

Archivo de configuración de kubectl, generalmente ubicado en ~/.kube/config.

label

Las etiquetas son parejas de clave/valor que se asignan a los objetos de K8s, y se pueden utilizar en los seleccionadores para hacer referencia a los objetos que tengan determinadas etiquetas.

pod

Unidad mínima de proceso de Kubernetes, consistente en un entorno para ejecutar contenedores donde comparten volúmenes, namespaces y cgroups. El contenido de un pod se lanza en un único nodo, y se gestiona como un todo. Todos los contenedores de un pod comparten la dirección IP 127.0.0.1 y la pueden usar para comunicarse entre ellos.

selector

Filtro que utiliza etiquetas para elegir objetos. Por ejemplo, se puede utilizar nodeSelector en la definición de un pod para indicar que solo debe ejecutarse en los nodos que tengan las etiquetas indicadas.

kubernetes's People

Contributors

coquec avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.