Comandos Avanzados de Kubectl
Los comandos más avanzados en kubectl utilizan queries json para filtrar la salida. Consulta sobre jsonpath en jsonpath.
Cuando trabajamos con un cluster productivo tendremos cientos o miles de pods, decenas de nodos, deployments, replicaset, services, secrets y otros objetos de Kubernetes y usamos kubectl para ver esos objetos.
Muchas veces tendrás requisitos en los que necesitarás imprimir un resumen general o imprimir campos específicos de varios recursos al mismo tiempo.
No vas a querer estar ejecutando describe para cada objeto haciendo un grep en loop en un script y manipulando la salida todo el tiempo con awk, cut, etc. Primero que demanda tiempo crear el script, segundo que tenemos herramientas para esto que en el propio kubectl que soporta el uso de jsonpath.
El kubectl hace peticiones al kube-apiserver y tiene como respuesta un json. Por debajo del capó el propio kubectl manipula esos datos y muestra información organizada, pero por debajo del capó recibió un json.
kubectl create deployment nginx --image nginx
# Aquí muestra una salida manipulada
kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nginx 1/1 1 1 7s
kubectl get deploy -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
nginx 1/1 1 1 31m nginx nginx app=nginx
# Pero lo que recibió fue esto
kubectl get deploy -o json
{
"apiVersion": "v1",
"items": [
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"annotations": {
"deployment.kubernetes.io/revision": "1"
},
"creationTimestamp": "2024-02-20T08:35:03Z",
"generation": 1,
"labels": {
"app": "nginx"
},
"name": "nginx",
"namespace": "default",
"resourceVersion": "1430016",
"uid": "e4ecc32a-7d98-48ff-8bce-ba8af52e27e2"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app": "nginx"
}
},
"strategy": {
"rollingUpdate": {
"maxSurge": "25%",
"maxUnavailable": "25%"
},
"type": "RollingUpdate"
},
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"app": "nginx"
}
},
"spec": {
"containers": [
{
"image": "nginx",
"imagePullPolicy": "Always",
"name": "nginx",
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File"
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {},
"terminationGracePeriodSeconds": 30
}
}
},
"status": {
"availableReplicas": 1,
"conditions": [
{
"lastTransitionTime": "2024-02-20T08:35:07Z",
"lastUpdateTime": "2024-02-20T08:35:07Z",
"message": "Deployment has minimum availability.",
"reason": "MinimumReplicasAvailable",
"status": "True",
"type": "Available"
},
{
"lastTransitionTime": "2024-02-20T08:35:03Z",
"lastUpdateTime": "2024-02-20T08:35:07Z",
"message": "ReplicaSet \"nginx-7854ff8877\" has successfully progressed.",
"reason": "NewReplicaSetAvailable",
"status": "True",
"type": "Progressing"
}
],
"observedGeneration": 1,
"readyReplicas": 1,
"replicas": 1,
"updatedReplicas": 1
}
}
],
"kind": "List",
"metadata": {
"resourceVersion": ""
}
}
Cuando ejecutamos -o yaml se nos muestra la salida en yaml convertida del json. Si fuéramos a usar una herramienta externa para este propósito sería el yq.
# El kubectl usa bibliotecas en go que hace esta conversión, pero lo que quiero dejar claro aquí es que recibió un json
kubectl get deploy -o yaml
## Misma salida abajo
## Usando herramientas externas para convertir
kubectl get deploy -o json | yq -p json
apiVersion: v1
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
creationTimestamp: "2024-02-20T08:35:03Z"
generation: 1
labels:
app: nginx
name: nginx
namespace: default
resourceVersion: "1430016"
uid: e4ecc32a-7d98-48ff-8bce-ba8af52e27e2
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: nginx
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
containers:
- image: nginx
imagePullPolicy: Always
name: nginx
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
status:
availableReplicas: 1
conditions:
- lastTransitionTime: "2024-02-20T08:35:07Z"
lastUpdateTime: "2024-02-20T08:35:07Z"
message: Deployment has minimum availability.
reason: MinimumReplicasAvailable
status: "True"
type: Available
- lastTransitionTime: "2024-02-20T08:35:03Z"
lastUpdateTime: "2024-02-20T08:35:07Z"
message: ReplicaSet "nginx-7854ff8877" has successfully progressed.
reason: NewReplicaSetAvailable
status: "True"
type: Progressing
observedGeneration: 1
readyReplicas: 1
replicas: 1
updatedReplicas: 1
kind: List
metadata:
resourceVersion: ""
# compara la salida del describe con la salida del yaml
kubectl describe deploy nginx
kubectl JSONPath
Cuando hacemos el describe la salida está formateada para fácil lectura y muchas informaciones están ocultas. Sabiendo esto, y si quisiéramos crear una salida específica en vez de lo que ya tenemos con -o wide.
Si vamos a manipular la salida, necesitamos conocer el json recibido, luego es bueno familiarizarse con -o json que está presente en prácticamente todos los comandos.
Vamos a generar una salida usando jsonpath. Queremos una lista con todas las imágenes usadas.
La expresión usada en jsonpath necesita estar en '{}' e inicia con . en vez de $..
kubectl get pods -A -o=jsonpath='{.items[*].spec.containers[*].image}'
nginx registry.k8s.io/coredns/coredns:v1.11.1 registry.k8s.io/coredns/coredns:v1.11.1 registry.k8s.io/etcd:3.5.10-0 docker.io/kindest/kindnetd:v20240202-8f1494ea docker.io/kindest/kindnetd:v20240202-8f1494ea docker.io/kindest/kindnetd:v20240202-8f1494ea docker.io/kindest/kindnetd:v20240202-8f1494ea registry.k8s.io/kube-apiserver:v1.29.1 registry.k8s.io/kube-controller-manager:v1.29.1 registry.k8s.io/kube-proxy:v1.29.1 registry.k8s.io/kube-proxy:v1.29.1 registry.k8s.io/kube-proxy:v1.29.1 registry.k8s.io/kube-proxy:v1.29.1 registry.k8s.io/kube-scheduler:v1.29.1 docker.io/alpine:3.13 docker.io/alpine:3.13 kubernetesui/dashboard:v2.7.0 docker.io/kindest/local-path-provisioner:v20240202-8f1494ea%
Las imágenes vinieron todas en una sola línea. No está bien, vamos a mejorar eso.
Si fuéramos a pensar en una lógica sería así.
FOR EACH item
PRINT ITEM DATA + NEW LINE
END EACH
Y podemos transcribir para:
- FOR EACH item
{range.items[*]}- PRINT ITEM DATA + NEW LINE
{.spec.containers[*].image}{"\n"}
- PRINT ITEM DATA + NEW LINE
- END EACH
{end}
Aplicando...
kubectl get pods -A -o=jsonpath='{range.items[*]}{.spec.containers[*].image}{"\n"}{end}'
nginx
registry.k8s.io/coredns/coredns:v1.11.1
registry.k8s.io/coredns/coredns:v1.11.1
registry.k8s.io/etcd:3.5.10-0
docker.io/kindest/kindnetd:v20240202-8f1494ea
docker.io/kindest/kindnetd:v20240202-8f1494ea
docker.io/kindest/kindnetd:v20240202-8f1494ea
docker.io/kindest/kindnetd:v20240202-8f1494ea
registry.k8s.io/kube-apiserver:v1.29.1
registry.k8s.io/kube-controller-manager:v1.29.1
registry.k8s.io/kube-proxy:v1.29.1
registry.k8s.io/kube-proxy:v1.29.1
registry.k8s.io/kube-proxy:v1.29.1
registry.k8s.io/kube-proxy:v1.29.1
registry.k8s.io/kube-scheduler:v1.29.1
docker.io/alpine:3.13
docker.io/alpine:3.13
kubernetesui/dashboard:v2.7.0
docker.io/kindest/local-path-provisioner:v20240202-8f1494ea
Podríamos añadir más cosas como el nombre del contenedor, un tab y después la imagen.
kubectl get pods -A -o=jsonpath='{range.items[*]}{.spec.containers[*].name}{"\t"}{.spec.containers[*].image}{"\n"}{end}'
nginx nginx
coredns registry.k8s.io/coredns/coredns:v1.11.1
coredns registry.k8s.io/coredns/coredns:v1.11.1
etcd registry.k8s.io/etcd:3.5.10-0
kindnet-cni docker.io/kindest/kindnetd:v20240202-8f1494ea
kindnet-cni docker.io/kindest/kindnetd:v20240202-8f1494ea
kindnet-cni docker.io/kindest/kindnetd:v20240202-8f1494ea
kindnet-cni docker.io/kindest/kindnetd:v20240202-8f1494ea
kube-apiserver registry.k8s.io/kube-apiserver:v1.29.1
kube-controller-manager registry.k8s.io/kube-controller-manager:v1.29.1
kube-proxy registry.k8s.io/kube-proxy:v1.29.1
kube-proxy registry.k8s.io/kube-proxy:v1.29.1
kube-proxy registry.k8s.io/kube-proxy:v1.29.1
kube-proxy registry.k8s.io/kube-proxy:v1.29.1
kube-scheduler registry.k8s.io/kube-scheduler:v1.29.1
shell docker.io/alpine:3.13
shell docker.io/alpine:3.13
kubernetes-dashboard kubernetesui/dashboard:v2.7.0
local-path-provisioner docker.io/kindest/local-path-provisioner:v20240202-8f1494ea
Pero esto no queda alineado ¿verdad?
Entonces podríamos hacer.
kubectl get pods -A -o=jsonpath='{range .items[*]}{.spec.containers[*].name}{"\t"}{.spec.containers[*].image}{"\n"}{end}' | column -t
nginx nginx
coredns registry.k8s.io/coredns/coredns:v1.11.1
coredns registry.k8s.io/coredns/coredns:v1.11.1
etcd registry.k8s.io/etcd:3.5.10-0
kindnet-cni docker.io/kindest/kindnetd:v20240202-8f1494ea
kindnet-cni docker.io/kindest/kindnetd:v20240202-8f1494ea
kindnet-cni docker.io/kindest/kindnetd:v20240202-8f1494ea
kindnet-cni docker.io/kindest/kindnetd:v20240202-8f1494ea
kube-apiserver registry.k8s.io/kube-apiserver:v1.29.1
kube-controller-manager registry.k8s.io/kube-controller-manager:v1.29.1
kube-proxy registry.k8s.io/kube-proxy:v1.29.1
kube-proxy registry.k8s.io/kube-proxy:v1.29.1
kube-proxy registry.k8s.io/kube-proxy:v1.29.1
kube-proxy registry.k8s.io/kube-proxy:v1.29.1
kube-scheduler registry.k8s.io/kube-scheduler:v1.29.1
shell docker.io/alpine:3.13
shell docker.io/alpine:3.13
kubernetes-dashboard kubernetesui/dashboard:v2.7.0
local-path-provisioner docker.io/kindest/local-path-provisioner:v20240202-8f1494ea
Y vamos a colocar el nombre de las columnas para que quede más presentable.
kubectl get pods -A -o=jsonpath='{"NAME\tIMAGE\n\n"}{range .items[*]}{.spec.containers[*].name}{"\t"}{.spec.containers[*].image}{"\n"}{end}' | column -t
NAME IMAGE
nginx nginx
coredns registry.k8s.io/coredns/coredns:v1.11.1
coredns registry.k8s.io/coredns/coredns:v1.11.1
etcd registry.k8s.io/etcd:3.5.10-0
kindnet-cni docker.io/kindest/kindnetd:v20240202-8f1494ea
kindnet-cni docker.io/kindest/kindnetd:v20240202-8f1494ea
kindnet-cni docker.io/kindest/kindnetd:v20240202-8f1494ea
kindnet-cni docker.io/kindest/kindnetd:v20240202-8f1494ea
kube-apiserver registry.k8s.io/kube-apiserver:v1.29.1
kube-controller-manager registry.k8s.io/kube-controller-manager:v1.29.1
kube-proxy registry.k8s.io/kube-proxy:v1.29.1
kube-proxy registry.k8s.io/kube-proxy:v1.29.1
kube-proxy registry.k8s.io/kube-proxy:v1.29.1
kube-proxy registry.k8s.io/kube-proxy:v1.29.1
kube-scheduler registry.k8s.io/kube-scheduler:v1.29.1
shell docker.io/alpine:3.13
shell docker.io/alpine:3.13
kubernetes-dashboard kubernetesui/dashboard:v2.7.0
local-path-provisioner docker.io/kindest/local-path-provisioner:v20240202-8f1494ea
Vea este otro ejemplo.
kubectl get nodes -o=jsonpath='{"NAME\tCPU\tARCH\n"}{range .items[*]}{.metadata.name}{"\t"}{.status.capacity.cpu}{"\t"}{.status.nodeInfo.architecture}{"\n"}{end}' | column -t
NAME CPU ARCH
kind-cluster-control-plane 16 amd64
kind-cluster-worker 16 amd64
kind-cluster-worker2 16 amd64
kind-cluster-worker3 16 amd64
Cuanto más familiarizado esté con jsonpath más podrá generar salidas específicas.
Pero, existen formas más fáciles de hacer esto.
kubectl Custom Columns
El kubectl hace esos formatos que hicimos arriba para ti usando -o=custom-columns. Por debajo del capó custom-columns hace el jsonpath, pero facilitando la vida dejando el foco en la expresión de cada que irá para cada columna.
La sintaxis es KUBECTL COMMAND -o=custom-columns=<COLUMNNAME>:<jsonpath>,<COLUMNNAME>:<jsonpath>,<COLUMNNAME>:<jsonpath>...
El comando abajo es lo mismo que kubectl get nodes -o=jsonpath='{"NAME\tCPU\tARCH\n"}{range .items[*]}{.metadata.name}{"\t"}{.status.capacity.cpu}{"\t"}{.status.nodeInfo.architecture}{"\n"}{end}' | column -t.
kubectl get nodes -o=custom-columns=NAME:.metadata.name,CPU:.status.capacity.cpu,ARCH:.status.nodeInfo.architecture
NAME CPU ARCH
kind-cluster-control-plane 16 amd64
kind-cluster-worker 16 amd64
kind-cluster-worker2 16 amd64
kind-cluster-worker3 16 amd64
Más fácil que eso solo eso al cuadrado.
Ordenación
Para ordenar podemos usar --sort-by y pasar cual el jsonpath que vamos a usar para ordenar. Por defecto se ordena por .metadata.name, pero podemos cambiar.
kubectl get nodes -o wide --sort-by='{.status.addresses[0].address}'
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
kind-cluster-control-plane Ready control-plane 11d v1.29.1 172.18.0.2 <none> Debian GNU/Linux 12 (bookworm) 6.5.0-17-generic containerd://1.7.13
kind-cluster-worker2 Ready <none> 11d v1.29.1 172.18.0.3 <none> Debian GNU/Linux 12 (bookworm) 6.5.0-17-generic containerd://1.7.13
kind-cluster-worker Ready <none> 11d v1.29.1 172.18.0.4 <none> Debian GNU/Linux 12 (bookworm) 6.5.0-17-generic containerd://1.7.13
kind-cluster-worker3 Ready <none> 11d v1.29.1 172.18.0.5 <none> Debian GNU/Linux 12 (bookworm) 6.5.0-17-generic containerd://1.7.13
El sort-by solo ordena en forma creciente, si quisiéramos decreciente necesitaríamos algunos recursos de linux.
kubectl get nodes -o wide --sort-by='{.status.addresses[0].address}' --no-headers| tac
kind-cluster-worker3 Ready <none> 11d v1.29.1 172.18.0.5 <none> Debian GNU/Linux 12 (bookworm) 6.5.0-17-generic containerd://1.7.13
kind-cluster-worker Ready <none> 11d v1.29.1 172.18.0.4 <none> Debian GNU/Linux 12 (bookworm) 6.5.0-17-generic containerd://1.7.13
kind-cluster-worker2 Ready <none> 11d v1.29.1 172.18.0.3 <none> Debian GNU/Linux 12 (bookworm) 6.5.0-17-generic containerd://1.7.13
kind-cluster-control-plane Ready control-plane 11d v1.29.1 172.18.0.2 <none> Debian GNU/Linux 12 (bookworm) 6.5.0-17-generic containerd://1.7.13