Extraindo informações úteis do seu cluster Kubernetes com custom-columns e jq

Extraindo informações úteis do seu cluster Kubernetes com custom-columns e jq

Como montar consultas customizadas para os objetos do seu cluster Kubernetes e como criar um repertório de consultas.

Imagem por: max_duz em: unsplash.com/photos/qAjJk-un3BI

É comum ao trabalharmos com k8s realizarmos diversas consultas aos nossos objetos do cluster como nodes, deployments, builds, pods e nem sempre temos o conjunto de informações que necessitamos, expostas por padrão via kubeclt get, tendo que nesses casos recorrer a busca de todo o objeto trazendo informações além das desejadas.

Usando a opção de saída custom-columns do kubectl e a ferramenta jq podemos criar consultas que trazem especificamente o que desejamos. Nesse artigo vamos explorar os dois e aprender como criar um repertório de consultas customizadas.

Problema

Vamos considerar dois cenários comuns em que precisamos buscar informações de um cluster kubernetes:

  • Recuperar informações da saúde dos nós do cluster como gargalos de memória, cpu, disco.
  • Recuperar informações de variáveis de ambiente (env) e limites de recursos (resource e limits) de deployments no cluster.

Para recuperar informações dos objetos do cluster, de maneira geral, podemos utilizar o comando:

kubectl get <OBJETO>

Para consultar os nodes podemos executar o comando:

~ kubectl get nodes -o wide

NAME                                         STATUS   ROLES    AGE   VERSION           INTERNAL-IP    EXTERNAL-IP   OS-IMAGE                                                       KERNEL-VERSION                 CONTAINER-RUNTIME
ip-10-0-135-204.xyz.compute.internal   Ready    master   11d   v1.21.1+6438632   10.0.135.204   <none>        RHEL CoreOS 48.84.202110270303-0    4.18.0-305.19.1.el8_4.x86_64   cri-o://1.21.3-8.rhaos4.8.git7415a53.el8
ip-10-0-142-176.xyz.compute.internal   Ready    worker   11d   v1.21.1+6438632   10.0.142.176   <none>        RHEL CoreOS 48.84.202110270303-0    4.18.0-305.19.1.el8_4.x86_64   cri-o://1.21.3-8.rhaos4.8.git7415a53.el8
ip-10-0-160-187.xyz.compute.internal   Ready    master   11d   v1.21.1+6438632   10.0.160.187   <none>        RHEL CoreOS 48.84.202110270303-0    4.18.0-305.19.1.el8_4.x86_64   cri-o://1.21.3-8.rhaos4.8.git7415a53.el8
ip-10-0-176-188.xyz.compute.internal   Ready    worker   11d   v1.21.1+6438632   10.0.176.188   <none>        RHEL CoreOS 48.84.202110270303-0    4.18.0-305.19.1.el8_4.x86_64   cri-o://1.21.3-8.rhaos4.8.git7415a53.el8
ip-10-0-214-226.xyz.compute.internal   Ready    master   11d   v1.21.1+6438632   10.0.214.226   <none>        RHEL CoreOS 48.84.202110270303-0    4.18.0-305.19.1.el8_4.x86_64   cri-o://1.21.3-8.rhaos4.8.git7415a53.el8
ip-10-0-219-74.xyz.compute.internal    Ready    worker   11d   v1.21.1+6438632   10.0.219.74    <none>        RHEL CoreOS 48.84.202110270303-0    4.18.0-305.19.1.el8_4.x86_64   cri-o://1.21.3-8.rhaos4.8.git7415a53.el8

Para consultar os deployments podemos utilizar o comando:

# também é possível usar o -o wide para recuperar mais informações
~ kubectl get deployments --all-namespaces


NAMESPACE                                          NAME                                     READY   UP-TO-DATE   AVAILABLE   AGE
openshift-apiserver-operator                       openshift-apiserver-operator             1/1     1            1           11d
openshift-apiserver                                apiserver                                3/3     3            3           11d
openshift-cluster-storage-operator                 cluster-storage-operator                 1/1     1            1           11d
openshift-cluster-storage-operator                 csi-snapshot-controller                  2/2     2            2           11d
openshift-cluster-version                          cluster-version-operator                 1/1     1            1           11d
openshift-console-operator                         console-operator                         1/1     1            1           11d
openshift-console                                  console                                  2/2     2            2           11d
openshift-image-registry                           cluster-image-registry-operator          1/1     1            1           11d
openshift-ingress-operator                         ingress-operator                         1/1     1            1           11d
openshift-ingress                                  router-default                           2/2     2            2           11d
openshift-insights                                 insights-operator                        1/1     1            1           11d

Os dois comandos, apesar de trazerem bastante informações, não contém as informações que estamos buscando. Para recuperar as informações que precisamos, podemos recuperar os objetos completos, no formato yaml ou json, através do comando: kubectl get deployments --all-namespaces -o json

A saída do comando é a que segue:

{
    "apiVersion": "v1",
    "items": [
        {
            "apiVersion": "apps/v1",
            "kind": "Deployment",
            "metadata": {
                "name": "openshift-apiserver-operator",
                "namespace": "openshift-apiserver-operator"
            },
            "spec": {
                "template": {
                    "spec": {
                        "containers": [
                            {
                                "env": [
                                    {
                                        "name": "IMAGE",
                                        "value": "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:f532d4e20932e1e6664b1b7003691d44a511bb626bc339fd883a624f020ff399"
                                    },
                                    {
                                        "name": "OPERATOR_IMAGE",
                                        "value": "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:a24bdc7bae31584af5a7e0cb0629dda9bb2b1d613a40e92e227e0d13cb326ef4"
                                    },
                                    {
                                        "name": "OPERATOR_IMAGE_VERSION",
                                        "value": "4.8.19"
                                    },
                                    {
                                        "name": "OPERAND_IMAGE_VERSION",
                                        "value": "4.8.19"
                                    },
                                    {
                                        "name": "KUBE_APISERVER_OPERATOR_IMAGE",
                                        "value": "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:0e56e34f980552a7ce3d55429a9a265307dc89da11c29f6366b34369cc2a9ba0"
                                    }
                                ],
                                "resources": {
                                    "requests": {
                                        "cpu": "10m",
                                        "memory": "50Mi"
                                    }
                                }
                            }
                        ]
                    }
                }
            }
        },
        // outras informações...
    ],
    "kind": "List",
    "metadata": {
        "resourceVersion": "",
        "selfLink": ""
    }
}

Utilizando custom-columns para consultar nodes

Vamos explorar a opção de saída custom-columns do comando kubectl get para recuperar apenas informações que precisamos. A opção custom-columns nos permite definir quais dados serão extraidos através do mapeamento do título da coluna e o campo desejado.

Utilizando o json do node como base para montar nossa consulta

{
  "apiVersion": "v1",
  "kind": "Node",
  "metadata": {
    "name": "ip-10-0-219-74.xyz.compute.internal"
  },
  "status": {
    "addresses": [
      {
        "address": "10.0.219.74",
        "type": "InternalIP"
      },
      {
        "address": "ip-10-0-219-74.xyz.compute.internal",
        "type": "Hostname"
      },
      {
        "address": "ip-10-0-219-74.xyz.compute.internal",
        "type": "InternalDNS"
      }
    ],
    "conditions": [
      {
        "message": "kubelet has sufficient memory available",
        "reason": "KubeletHasSufficientMemory",
        "status": "False",
        "type": "MemoryPressure"
      },
      {
        "message": "kubelet has no disk pressure",
        "reason": "KubeletHasNoDiskPressure",
        "status": "False",
        "type": "DiskPressure"
      },
      {
        "message": "kubelet has sufficient PID available",
        "reason": "KubeletHasSufficientPID",
        "status": "False",
        "type": "PIDPressure"
      },
      {
        "message": "kubelet is posting ready status",
        "reason": "KubeletReady",
        "status": "True",
        "type": "Ready"
      }
    ],
    "nodeInfo": {
      "architecture": "amd64",
      "bootID": "327671fc-3d6f-4bc4-ab5f-fa012687e839",
      "containerRuntimeVersion": "cri-o://1.21.3-8.rhaos4.8.git7415a53.el8",
      "kernelVersion": "4.18.0-305.19.1.el8_4.x86_64",
      "kubeProxyVersion": "v1.21.1+6438632",
      "kubeletVersion": "v1.21.1+6438632",
      "machineID": "ec2e23b2f3d554c78f67dc2e30ba230a",
      "operatingSystem": "linux",
      "osImage": "Red Hat Enterprise Linux CoreOS 48.84.202110270303-0 (Ootpa)",
      "systemUUID": "ec2e23b2-f3d5-54c7-8f67-dc2e30ba230a"
    }
  }
}

Uma consulta simples utilizando custom-columns para retornar o nome dos nodes do cluster:

~ kubectl get nodes -o custom-columns="Name:.metadata.name"

Name
ip-10-0-135-204.xyz.compute.internal
ip-10-0-142-176.xyz.compute.internal
ip-10-0-160-187.xyz.compute.internal
ip-10-0-176-188.xyz.compute.internal
ip-10-0-214-226.xyz.compute.internal
ip-10-0-219-74.xyz.compute.internal

Para consultar valores de um grupo, como por exemplo, os endereços dos nodes (InternalIP, Hostname, InternalDNS) podemos usar a notação .status.addresses[*].address

~ kubectl get nodes -o custom-columns="Name:.metadata.name,Addresses:.status.addresses[*].address"

Name                                         Addresses
ip-10-0-135-204.xyz.compute.internal   10.0.135.204,ip-10-0-135-204.xyz.compute.internal,ip-10-0-135-204.xyz.compute.internal
ip-10-0-142-176.xyz.compute.internal   10.0.142.176,ip-10-0-142-176.xyz.compute.internal,ip-10-0-142-176.xyz.compute.internal
ip-10-0-160-187.xyz.compute.internal   10.0.160.187,ip-10-0-160-187.xyz.compute.internal,ip-10-0-160-187.xyz.compute.internal
ip-10-0-176-188.xyz.compute.internal   10.0.176.188,ip-10-0-176-188.xyz.compute.internal,ip-10-0-176-188.xyz.compute.internal
ip-10-0-214-226.xyz.compute.internal   10.0.214.226,ip-10-0-214-226.xyz.compute.internal,ip-10-0-214-226.xyz.compute.internal
ip-10-0-219-74.xyz.compute.internal    10.0.219.74,ip-10-0-219-74.xyz.compute.internal,ip-10-0-219-74.xyz.compute.internal

Caso queiramos valores específicos de um grupo, podemos utilizar o índice desejado para tal, logo, para montar nossa consulta de saúde dos nodes:

~ kubectl get nodes -o custom-columns="Name:.metadata.name,InternalIP:.status.addresses[0].address,Kernel:.status.nodeInfo.kernelVersion,MemoryPressure:.status.conditions[0].status,DiskPressure:.status.conditions[1].status,PIDPressure:.status.conditions[2].status,Ready:.status.conditions[3].status"
Name                                         Kernel                         InternalIP     MemoryPressure   DiskPressure   PIDPressure   Ready
ip-10-0-135-204.xyz.compute.internal   4.18.0-305.19.1.el8_4.x86_64   10.0.135.204   False            False          False         True
ip-10-0-142-176.xyz.compute.internal   4.18.0-305.19.1.el8_4.x86_64   10.0.142.176   False            False          False         True
ip-10-0-160-187.xyz.compute.internal   4.18.0-305.19.1.el8_4.x86_64   10.0.160.187   False            False          False         True
ip-10-0-176-188.xyz.compute.internal   4.18.0-305.19.1.el8_4.x86_64   10.0.176.188   False            False          False         True
ip-10-0-214-226.xyz.compute.internal   4.18.0-305.19.1.el8_4.x86_64   10.0.214.226   False            False          False         True
ip-10-0-219-74.xyz.compute.internal    4.18.0-305.19.1.el8_4.x86_64   10.0.219.74    False            False          False         True

Criando um repertório de consultas

Com nossa consulta customizada pronta, podemos guardar o mapeamento dos campos para facilitar o reuso. O arquivo segue um formato específico de cabeçalhos e valores:

HEADER1       HEADER2       HEADER3
.field.value1 .field.value2 .field.value3

Para nossa consulta, o arquivo, que vamos chamar de cluster-nodes-health.txt, seria:

Name Kernel InternalIP MemoryPressure DiskPressure PIDPressure Ready
.metadata.name .status.nodeInfo.kernelVersion .status.addresses[0].address .status.conditions[0].status .status.conditions[1].status .status.conditions[2].status .status.conditions[3].status

E podemos realizar a consulta utilizando a opção custom-columns-file:

~ kubectl get nodes -o custom-columns-file=cluster-nodes-health.txt

Name                                         Kernel                         InternalIP     MemoryPressure   DiskPressure   PIDPressure   Ready
ip-10-0-135-204.xyz.compute.internal   4.18.0-305.19.1.el8_4.x86_64   10.0.135.204   False            False          False         True
ip-10-0-142-176.xyz.compute.internal   4.18.0-305.19.1.el8_4.x86_64   10.0.142.176   False            False          False         True
ip-10-0-160-187.xyz.compute.internal   4.18.0-305.19.1.el8_4.x86_64   10.0.160.187   False            False          False         True
ip-10-0-176-188.xyz.compute.internal   4.18.0-305.19.1.el8_4.x86_64   10.0.176.188   False            False          False         True
ip-10-0-214-226.xyz.compute.internal   4.18.0-305.19.1.el8_4.x86_64   10.0.214.226   False            False          False         True
ip-10-0-219-74.xyz.compute.internal    4.18.0-305.19.1.el8_4.x86_64   10.0.219.74    False            False          False         True

Utilizando jq para consultar env de deployments

Para consultar os envs vamos explorar o utilitário jq, com ele iremos buscar os objetos como json e filtrá-los para mostrar apenas as informações que desejamos.

Sobre o jq

O jq é um processador JSON de linha de comando leve e flexível.

Conforme o própria página do jq o descreve:

"jq é como o sed para dados JSON - você pode usá-lo para dividir, filtrar, mapear e transformar dados estruturados com a mesma facilidade que sed, awk, grep."

Ele pode ser encontrado em: stedolan.github.io/jq

Estruturando o comando jq

Vamos mostrar uma consulta básica com o jq. Que intera sobre os deployments .items[] e extrair apenas o nome deles .metadata.name.

~ kubectl get deployments --all-namespaces -o json | jq -r '.items[] | .metadata.name '

openshift-apiserver-operator
apiserver
authentication-operator
# outros projetos...

Vamos evoluir nossa consulta para montar um json com as informações de name, namespace e env:

~ kubectl get deployments --all-namespaces -o json | jq -r '.items[] | { namespace: .metadata.namespace, name: .metadata.name, env: .spec.template.spec.containers[].env}'
{
  "namespace": "openshift-apiserver-operator",
  "name": "openshift-apiserver-operator",
  "env": [
    {
      "name": "IMAGE",
      "value": "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:f532d4e20932e1e6664b1b7003691d44a511bb626bc339fd883a624f020ff399"
    },
    {
      "name": "OPERATOR_IMAGE",
      "value": "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:a24bdc7bae31584af5a7e0cb0629dda9bb2b1d613a40e92e227e0d13cb326ef4"
    },
    {
      "name": "OPERATOR_IMAGE_VERSION",
      "value": "4.8.19"
    },
    {
      "name": "OPERAND_IMAGE_VERSION",
      "value": "4.8.19"
    },
    {
      "name": "KUBE_APISERVER_OPERATOR_IMAGE",
      "value": "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:0e56e34f980552a7ce3d55429a9a265307dc89da11c29f6366b34369cc2a9ba0"
    }
  ]
}
{
  "namespace": "openshift-apiserver",
  "name": "apiserver",
  "env": [
    {
      "name": "POD_NAME",
      "valueFrom": {
        "fieldRef": {
          "apiVersion": "v1",
          "fieldPath": "metadata.name"
        }
      }
    },
    {
      "name": "POD_NAMESPACE",
      "valueFrom": {
        "fieldRef": {
          "apiVersion": "v1",
          "fieldPath": "metadata.namespace"
        }
      }
    }
  ]
}
{
  "namespace": "openshift-apiserver",
  "name": "apiserver",
  "env": [
    {
      "name": "POD_NAME",
      "valueFrom": {
        "fieldRef": {
          "apiVersion": "v1",
          "fieldPath": "metadata.name"
        }
      }
    },
    {
      "name": "POD_NAMESPACE",
      "valueFrom": {
        "fieldRef": {
          "apiVersion": "v1",
          "fieldPath": "metadata.namespace"
        }
      }
    }
  ]
}

// campos omitidos....

Para deixar nosso json num formato válido, vamos encapsular os resultados em um array [] e usar função map do jq.

~ kubectl get deployments --all-namespaces -o json | jq -r '.items | [ map(.) | .[] | { namespace: .metadata.namespace, name: .metadata.name, env: .spec.template.spec.containers[].env }]'
// exemplo resumido de output

[
  {
    "namespace": "openshift-operator-lifecycle-manager",
    "name": "catalog-operator",
    "env": [
      {
        "name": "RELEASE_VERSION",
        "value": "4.8.19"
      }
    ]
  },
  {
    "namespace": "openshift-operator-lifecycle-manager",
    "name": "olm-operator",
    "env": [
      {
        "name": "RELEASE_VERSION",
        "value": "4.8.19"
      },
      {
        "name": "OPERATOR_NAMESPACE",
        "valueFrom": {
          "fieldRef": {
            "apiVersion": "v1",
            "fieldPath": "metadata.namespace"
          }
        }
      },
      {
        "name": "OPERATOR_NAME",
        "value": "olm-operator"
      }
    ]
  },
  {
    "namespace": "openshift-operator-lifecycle-manager",
    "name": "packageserver",
    "env": [
      {
        "name": "OPERATOR_CONDITION_NAME",
        "value": "packageserver"
      }
    ]
  }
]

Consulta com jq file

Assim como no custom-columns, com o jq temos a opção de passar um arquivo contendo nosso filtro ao invés de dados inline. Desse modo, vamos criar um arquivo chamado jq-deployments-envs.txt com o conteúdo:

.items | [ map(.) | .[] | { namespace: .metadata.namespace, name: .metadata.name, env: .spec.template.spec.containers[].env }]

E nossa consulta pode ser executada com o comando:

~ kubectl deployments --all-namespaces -o json | jq -f jq-deployments-envs.txt

Conclusão

Com a opção nativa do kubectl, custom-columns, e o utilitário jq é possível extrair informações customizadas de um cluster Kubernetes. Além disso, com a opção de usar arquivos para montar as consultas podemos criar diversas visualizações úteis para o cluster e armazená-los no controle de versão para compartilhamento com outros integrantes do time ou para a comunidade.

Referências

kubernetes.io/docs/reference/kubectl/overvi..

kubernetes.io/pt-br/docs/reference/kubectl/..

stedolan.github.io/jq/tutorial

kubernetes.io/docs/tasks/access-application..

kubernetes.io/docs/reference/kubectl/jsonpath

gist.github.com/so0k/42313dbb3b547a0f51a547..

starkandwayne.com/blog/silly-kubectl-trick-..

michalwojcik.com.pl/2021/07/04/yaml-jsonpat..

laury.dev/snippets/combine-kubectl-jsonpath..

access.redhat.com/articles/2988581

sferich888.blogspot.com/2017/01/learning-us..