- 1. Kubernetes
- 2. Instalación de un cluster de pruebas
- 3. API de Kubernetes
- 4. Etiquetas
- 5. Pods
- 6. Control de los trabajos
- 7. El modelo de red de Kubernetes
- 8. ConfigMaps
- 9. CLI de kubectl
- 10. Troubleshooting
- 11. Ejemplos
- 12. Otras herramientas
- 13. Para saber más
- 14. Glosario
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 ordenkubectl
.
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…
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..
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.
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
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
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
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
, comov1alpha2
. 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
, comov2beta1
. 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
nibeta
.
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
.
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: {}
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. |
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.
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. |
selector: matchExpressions: - key: key1 operator: In values: - value1 - value2
Este tipo de seleccionadores admite los operadores In
, NotIn
, Exists
,
DoesNotExist
, Gt
y Lt
.
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:
---
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.
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…).
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.
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.
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 hookpostStart
, 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 estadoRunning
termina por cualquier motivo. Antes de pasar a este estado, se ejecuta cualquier hookpreStop
que tuviera configurado.
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.
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 estadoReady
. -
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 losServices
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>
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
.
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 estadoSuccess
. 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 esFailure
durante la pausa inicial que haya configurada, pasando después aSuccess
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 estadoSuccess
. Este tipo de pruebas son útiles para contenedores que tardan un tiempo considerable en arrancar, mayor queinitialDelaySeconds
+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 entornohttp_proxy
oHTTP_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 astartupProbe
. -
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 cabeceraHost
conhttpHeaders
. -
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 cabecerasUser-Agent: kube-probe/1.24
yAccept: */*
. -
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:
---
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.
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:
---
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"
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
.
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:
---
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:
---
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á.
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:
---
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.
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.
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.
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
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
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>
.
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.
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.
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.
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>
.
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
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
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
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'.
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
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
...
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
...
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.
Crea los recursos especificados en el archivo YAML o JSON pasado con la opción
-f
(o desde la entrada estándar, con -f -
).
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.
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.
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
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>
.
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.
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.
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
.
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).
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%
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.
Muestra el contexto que usa kubectl
:
$ kubectl config current-context
minikube
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
Modifica un contexto:
$ kubectl config set-context minikube --namespace=blas
Context "minikube" modified.
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)
$
Las órdenes más útiles para ver lo que ocurre dentro del cluster de K8s son
kubectl get
, kubectl_get events
y kubectl describe
.
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.
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:
---
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:
-
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.
-
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.
-
No tenemos persistencia del almacenamiento para ninguno de los dos servicios.
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
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
.
- 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.