Skip to main content

Comandos Avançados do Kubectl

Os comandos mais avançados no kubectl utilizam queries json para filtrar a saída. Veja sobre jsonpath em jsonpath.

Quando trabalhamos com um cluster produtivo teremos centenas ou milhares de pods, dezenas de nodes, deployments, replicaset, services, secrets e outros objetos do Kubernetes e usamos o kubectl para ver esses objetos.

Muitas vezes você terá requisitos nos quais precisará imprimir um resumo geral ou imprimir campos específicos de vários recursos ao mesmo tempo.

Você não vai querer ficar executando describe para cada objeto fazendo um grep em loop em um script e manipulando a saída o tempo todo com awk, cut, etc. Primeiro que demanda tempo de criar o script segundo que temos ferramentas para isso que no próprio kubectl que suporta o uso de jsonpath.

O kubectl faz requisições para o kube-apiserver e tem como resposta um json. Por baixo dos panos o próprio kubectl manipula esses dados e exibe informações e organizada, mas por baixo do capô ele recebeu um json.

kubectl create deployment nginx --image nginx
# Aqui mostra uma saía 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

# Mas o que recebeu foi isso
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": ""
}
}

Quando executamos o -o yaml nos é exibido a saída em yaml convertida do json. Se fôssemos usar uma ferramenta externa para esse propósito seria o yq.

# O kubectl usa bibliotecas em go que faz essa conversão, mas o que quero deixar claro aqui é que ele recebeu um json
kubectl get deploy -o yaml
## Mesma saída abaixo

## Usando ferramentas externas para converter
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: ""

# compare a saída do describe com a saída do yaml
kubectl describe deploy nginx

kubectl JSONPath

Quando fazemos o describe a saída é formatada para fácil leitura e muitas informações são ocultadas. Sabendo disso, e se quisesse criar uma saída específica ao invés do que já temos com o -o wide.

Se vamos manipular a saída, precisamos conhecer o json recebido, logo é bom se familiarizar com o -o json que está presente em praticamente todos os comandos.

Vamos gerar uma saída usando o jsonpath. Queremos uma lista com toda as imagens usadas. A expressão usada no jsonpath precisa estar em '{}' e inicia com . ao invés 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%

As imagens vieram tudo numa linha só. Não está bom não, vamos melhorar isso.

Se fossemos pensar em uma lógica seria assim.

FOR EACH item
PRINT ITEM DATA + NEW LINE
END EACH

E podemos transcrever para:

  • FOR EACH item {range.items[*]}
    • PRINT ITEM DATA + NEW LINE {.spec.containers[*].image}{"\n"}
  • 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

Poderíamos acrescentar mais coisas como o nome do container um tab e depois a imagem.

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

Mas isso não fica alinhado certo?

Então poderíamos fazer.

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

E vamos colocar o nome das colunas para ficar mais apresentável.

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

Veja esse outro exemplo.

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

Quanto mais você estiver familiarizado com jsonpath mais você conseguirá gerar saídas específicas.

Mas, existem forma mais fáceis de fazer isso.

kubectl Custom Columns

O kubectl faz essas formatações que fizemos acima para você usando o -o=custom-columns. Por baixo dos panos o custom-columns faz o jsonpath, mas facilitando a vida deixando o foco na expressão de cada que irá para cada coluna.

A sintaxe é KUBECTL COMMAND -o=custom-columns=<COLUMNNAME>:<jsonpath>,<COLUMNNAME>:<jsonpath>,<COLUMNNAME>:<jsonpath>...

O comando abaixo é a mesma coisa 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

Mais fácil que isso só isso ao quadrado.

Ordenação

Para ordenar podemos usar --sort-by e passar qual o jsonpath que iremos usar para ordenar. Por padrão ordena-se pelo .metadata.name, mas podemos mudar.

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

O sort-by apenas ordena em forma crescente, se quiséssemos decrescente precisaríamos de alguns recursos do 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