Escribiendo plugins para kubectl

  • September 7, 2019
  • tuxotron
  • Kubectl

    Kubectl Plugin

    Como posiblemente ya sabrás kubectl es la herramienta de línea de comandos oficial para interactuar con Kubernetes. Desde ésta puedes hacer prácticamente cualquier cosa en Kubernetes siempre y cuando, obviamente, tengas permiso para ello. Además de todas las posibilidades que esta herramienta ofrece, también tienes la posibilidad de expandirla a través del sistema de plugins.

    Un plugin para kubectl no es más que un fichero que cumpla las siguientes tres características:

    • Que sea ejecutable (binario o script)
    • Que se encuentre en el PATH del sistema
    • Que el nombre del fichero empierce por kubectl- (¡guión incluído!)

    Los sistema de plugins en kubectl se implementó en la versión 1.8.0 como versión alpha, y luego hubo una reescritura de esta característica en la versión 1.12.0, cuya versión es la mínima recomendada.

    Vamos a escribir nuestro primer plugin. Para ello vamos a escribirlo en bash. Así que creemos un fichero llamada kubectl-hello con el siguiente contenido:

    #!/bin/bash
    
    echo "Hello, Hello!"
    

    Prefijando el nombre del fichero con kubectl-, cumplimos con uno de los requisitos. Otro de los requisitos es que el fichero sea ejecutable. En nuestro caso:

    chmod +x kubectl-hello
    

    El último requisito es que el fichero se encuentre en el PATH del sistema. Para ello puedes copiar el fichero a cualquier directorio que ya se encuentre en el PATH, o en nuestro caso lo que voy a hacer es añadir el directorio donde tengo creado el fichero al PATH. En mi caso el directorio donde he creado el plugin es ~/tmp/kplugin. Por lo tanto (esta acción sólo sobrevive a la sesión desde donde la ejecutes. Si quieres que dicha acción sea permanente tendrás que añadirla a tu ~/.bashrc, ~/.zshrc, o algún otro fichero):

    export PATH=$PATH:~/tmp/kplugin
    

    Una vez tenemos hecho esto, podemos ejecutar nuestro script:

    kubectl hello
    Hello, Hello!
    

    Este comando lo podemos ejecutar desde cualquier parte del sistema, siempre y cuando kubectl esté instalado correctamente.

    Vamos a crear ahora un plugin que sea un poco más útil. En este caso vamos a crear otro script (en bash de nuevo), pero éste va a hacer uso del propio comando kubectl para crear un fichero de configuración que pueda ser usado con el propio kubectl. Algunas veces queremos automatizar alguna tarea desde un servidor, por ejemplo desde un pipeline CI/CD, donde usemos kubectl para hacer algo en un clúster de Kubernetes. Por ejemplo la actualización o despliegue de un servcio. Para que el servidor de automatización sea capaz de poder comunicarse con Kubernetes a través de kubectl, éste necesita ciertos datos (dirección del clúster, credenciales, etc) en un fichero de configuración para dicha comunicación. Este fichero de configuración es el que nuestro plugin va a generar.

    El contenido de nuestro fichero es el siguiente:

    #!/bin/bash
    
    set -e
    
    usage="
    USAGE: 
      kubectl kubeconf-generator -a SERVICE_ACCOUNT -n NAMESPACE
    "
    
    while getopts a:n: option
    do
    case "${option}"
    in
    a) SA=${OPTARG};;
    n) NAMESPACE=${OPTARG};;
    esac
    done
    
    [[ -z "$SA" ]] && { echo "Service account is required" ; echo "$usage" ; exit 1; }
    [[ -z "$NAMESPACE" ]] && { echo "Namespace is required" ; echo "$usage" ; exit 1; }
    
    # Get secret name
    SECRET_NAME=($(kubectl get sa $SA -n $NAMESPACE -o jsonpath='{.secrets[0].name}'))
    
    # Get secret value
    SECRET=$(kubectl get secret $SECRET_NAME -n $NAMESPACE -o jsonpath='{.data.token}' | base64 -D)
    
    # Get cluster server name
    SERVER=$(kubectl config view --minify -o json | jq -r '.clusters[].cluster.server')
    # Get cluster name
    CLUSTER_NAME=$(kubectl config view --minify -o json | jq -r '.clusters[].name')
    
    # Get cluster certs
    CERTS=$(kubectl config view --raw --minify -o json | jq -r '.clusters[].cluster."certificate-authority-data"')
    
    cat << EOM
    apiVersion: v1
    kind: Config
    users:
    - name: $SA
      user:
        token: $SECRET
    clusters:
    - cluster:
        certificate-authority-data: $CERTS
        server: $SERVER
      name: $CLUSTER_NAME
    contexts:
    - context:
        cluster: $CLUSTER_NAME
        user: $SAS
      name: svcs-acct-context
    current-context: svcs-acct-context
    EOM
    

    Este script usa además de kubectl, la herramienta jp. Si no la tienes instalada tendrás que instalarla.

    Cómo se puede observar este plugin espera dos parámetros, una cuenta de servicio y el espacio de nombres de dicha cuenta. El fichero de configuración generado, cuando sea usado, interactuará con el clúster con dicha cuenta de servicio, por lo que estará limitado a los permisos de la misma.

    Vamos a crearnos un fichero llamado kubectl-kubeconf_generator con el contenido mostrado anteriormente. Fíjate bien que hemos usado el guión necesario después de kubectl y un carácter de subrayado en la segunda parte del nombre. Hablaremos de este detalle más tarde. Por ahora vamos a ejecutar nuestro plugin sin parámetros:

    kubectl kubeconf_generator
    Service account is required
    
    USAGE:
      kubectl-kubeconf_generator -a SERVICE_ACCOUNT -n NAMESPACE
    

    Ahora pasemos los parámetros. En este caso necesitas tener acceso a un clúster de Kubernetes. En nuestro ejemplo usaremos minikube (asegúrate que está arrancado):

    kubectl kubeconf_generator -a default -n default
    apiVersion: v1
    kind: Config
    users:
    - name: default
      user:
        token: REDACTADO
    clusters:
    - cluster:
        certificate-authority-data: null
        server: https://192.168.99.110:8443
      name: minikube
    contexts:
    - context:
        cluster: minikube
        user:
      name: svcs-acct-context
    current-context: svcs-acct-context
    

    Ese fichero de configuración nos permitirá acceder a nuestro clúster (minikube), con la cuenta de servicio default del espacio de nombres default. Este no es el tema de esta entrada, así que lo dejamos aquí.

    Volvamos al tema del nombre del plugin. En nuestro último ejemplo nombramos el fichero kubectl-kubeconf_generator, de esta forma kubectl reconoce el plugin como kubeconf_generator o kubeconf-generator, es decir podemos llamar a nuestro plugin de estas dos formas:

    kubectl kubeconf_generator -a default -n default
    ...
    

    o kubectl kubeconf-generator -a default -n default …

    Ambas formas funcionan. Ahora, si renombramos nuestro plugin a kubectl-kubeconf-generator, con guión en la segunda separación, para llamarlo desde kubectl lo haremos con un espacio entre los nombres:

    kubectl kubeconf generator -a default -n default
    ...
    

    Siguiendo este patrón podemos crear subcomandos. Por ejemplo podríamos crear dos plugins, uno que genere la configuración en formato yaml y otro en json, de esta forma los nombraríamos:

    kubectl-kubeconf-generator-yaml
    kubectl-kubeconf-generator-json
    

    Así podríamos llamarlos desde kubectl:

    kubectl kubeconf generator json ...
    ...
    kubectl kubeconf generator yaml ...
    ...
    

    Esto es simplemente un ejemplo para mostrar esta particularidad de cómo kubectl trata el tema de los nombres. Si quieres tener un plugin que permita crear la salida en distintos formatos, lo suyo sería hacerlo a través de un parámetro, y posiblente, usando el parámetro -o para que sea consistente con kubectl.

    Para ir terminando, una limitación que existe, es que no puedes sobreescribir un subcomando de kubectl, por ejemplo, kubectl tiene el subcomando version, por lo que si creas un plugin que se llame kubectl-version, cuando invoques kubectl version, se ejecutará el subcomando de kubectl y no tu plugin. También existen una serie de reglas a la hora de resolver conflictos de nombres entre plugins, en las que no vamos a tratar en esta entrada, pero puedes consultarlas en la documentación a través del enlace que tienes al principio de la entrada.

    Por último mencionar que kubectl nos ofrece el subcomando plugin list, el cual nos permite ver los plugins que tenemos en el sistema.

    kubectl plugin list
    The following compatible plugins are available:
    
    /Users/tuxotron/tmp/kplugin/kubectl-hello
    /Users/tuxotron/tmp/kplugin/kubectl-kubeconf-generator
    

    En otra entrada hablaremos de cómo manejar los plugins de una forma más limpia y ordenada.

Herramienta para entender cómo corren las aplicaciones en Kubernetes

  • September 2, 2019
  • tuxotron
  • Octant

    Octant

    Kubernetes es una plataforma, que ya trabajes en desarrollo, seguridad u operaciones, debes conocer porque en algún momento, no muy lejano, te tocará tratar con ella, sino lo haces ya.

    Octant es una herramienta de VMWare que nos ofrece información en tiempo de real sobre un clúster de Kubernetes al que tengamos acceso. Su objetivo es que ésta sea parte del conjunto de herramientas del desarrollador, aunque evidentemente puede ser usada por cualquiera.

    Entre sus características podemos encontrar:

    • Visualización de recursos de forma visual y las dependencias entre estos.
    • Nos permite hacer port-forwarding a un pod. Es decir, podemos mapear un puerto local, a un puerto remoto de un pod. De esta forma podemos acceder al mismo sin tener que crear un servicio.
    • Ampliable a través de plugins.
    • Acceso a logs de forma continua.
    • Fácil navegación por el clúster.
    • Filtrado de búsquedas por etiquetas.

    En Kubernetes existe una interfaz gráfica que práticamente permite hacer todo lo mencionado anteriormente, a excepción del port-forwarding y el uso de plugins. De hecho el panel de control de Kubernetes, nos permite crear objetos, borrarlos, modificarlos, etc. Mientras que Octant, sin la instalación de plugins, actualmente sólo nos permite navegar por el clúster y ver el estado del mismo, es decir, no nos permite modificarlo. Aquí la gran diferencia entre estos es que el panel de control de Kubernetes se instala como parte del clúster, mientras que Octant corre de forma local, es decir, que siempre y cuando tengas acceso a un cluster de Kubernetes, puedes correr Octant y poder navegar por el mismo de forma gráfica.

    Recuerda que el panel de control gráfico de Kubernetes, no es un componente requerido. Es opcional, y no es el único que existe. De hecho, cada producto construido encima de Kubernete, suele tener su propio panel de control gráfico: Openshift, GKE, EKS, etc.

    Si quieres ver la diferencia entre el panel de control de Kubernetes y Octant puedes ejecutar ambos en tu sistema.

    Usando minikube sólo tienes que ejecutar (minikube debe estar corriendo):

    minikube dashboard
    

    Este comando abrirá el panel de control de minikube (oficial de Kubernetes) en el navegador.

    Para ver Octant, lo primero es instalarlo, cuyas instrucciones están disponibles en el enlace de arriba, así el cómo ejecutarlo. Por defecto Octant escucha por el puerto 7777, pero éste se puede cambiar a través de la variable de entorno OCTANT_LISTENER_ADDR, por ejemplo:

    OCTANT_LISTENER_ADDR=0.0.0.0:8900 octant
    

    Ó

    export OCTANT_LISTENER_ADDR=0.0.0.0:8900 
    octant
    

    Si estás interesado en la creación de plugins para Octant, puedes visitar la documentación, que curiosamente tienes que correr localmente usando hugo, misma plataforma sobre la que corre este blog :).

Cómo kubectl usa la API de Kubernetes

  • August 19, 2019
  • tuxotron
  • kubectl

    kubectl Fuente: https://blog.risingstack.com/what-is-kubernetes-how-to-get-started/

    Como ya sabes, y si no lo sabes te lo cuento ahora, en Kubernetes, cualquier tipo de consulta o comando se lleva a cabo a través de llamadas a la API del mismo. El componente encargado de recibir y procesar dichas peticiones se llama: API Server. Este componente se encuentra en el nodo o nodos maestros del cluster.

    API Server

    Fuente: https://blog.openshift.com/kubernetes-deep-dive-api-server-part-1/

    Mi compañero y amigo Cybercaronte y yo ya hemos publicado varios artículos relacionados con Kubernetes, en los cuales si has leído, sabrás que existe una herramienta con la que podemos intereactuar con nuestro clúster desde la línea de comandos: kubectl. Aunque existen interfaces gráficas, la forma más común y usada es a través de dicha herramienta.

    Las opciones que esta utilidad provee son bastante amplias. En esta entrada vamos a hablar sólo de un parámetro común a prácticamente todas las opciones ofrecidas por kubectl: la verbosidad (verbosity).

    Para saber que ocurre cuando ejecutamos un comando con kubectl, podemos usar la opción -v o –v. Esta opción acepta a su vez un número, el cual indica el nivel de verbosidad que queremos obtener. Dicho número va desde el 0 al 9, y la información que reporta kubectl con cada número es la siguiente:

    Verbosity

    Fuente: https://kubernetes.io/docs/reference/kubectl/cheatsheet/

    Si ejecutamos el sisguiente comando:

    kubectl get pods
    NAME                     READY   STATUS    RESTARTS   AGE
    nginx-7bb7cd8db5-8z6t8   1/1     Running   0          33s
    

    Vemos que kubectl nos devuelve los pods que tenemos corriendo en el espacio de nombres default. Ahora hagamos los mismo con vervosidad de nivel 5:

    kubectl get pods -v=5
    I0819 17:02:54.174578   30833 get.go:564] no kind "Table" is registered for version "meta.k8s.io/v1beta1" in scheme "k8s.io/kubernetes/pkg/api/legacyscheme/scheme.go:30"
    NAME                     READY   STATUS    RESTARTS   AGE
    nginx-7bb7cd8db5-8z6t8   1/1     Running   0          26s
    

    Como se puede observar, en este caso, además de los pods que hay corriendo, obtenemos información extra. Los niveles del 0 al 5 nos vienen muy bien para depurar o ver que acciones lleva a cabo kubectl, pero los níveles en los que quiero hacer hincapié en esta entrada son del 6 al 9.

    Activando estos niveles, no sólo son muy útiles para depurar y ver que hace kubectl, pero también son muy educativos, sobre todo si nos queremos familiarizar con su API, ya que estos niveles nos muestran que llamadas a la API hace kubectl.

    Veamos un ejemplo en nivel 6: (sólo voy a dejar las líneas que nos interesa)

    kubectl get pods -v=6
    ...
    I0819 17:11:39.565753   30923 round_trippers.go:438] GET https://192.168.99.110:8443/api/v1/namespaces/default/pods?limit=500 200 OK in 12 milliseconds
    ...
    

    Como se puede ver, para obtener los pods del espacio de nombre actual (default), kubectl hace una llamada a https://192.168.99.110:8443/api/v1/namespaces/default/pods?limit=500 que además, podemos apreciar que limita el número de pods a 500, con lo que si tienes más de 500 pods corriendo en tu espacio de nombre y kubectl sólo te devuelve 500, ya sabes el porqué ;).

    Ahora subamos de nivel:

    kubectl get pods -v=7
    ...
    I0819 17:22:29.600084   31029 round_trippers.go:416] GET https://192.168.99.110:8443/api/v1/namespaces/default/pods?limit=500
    I0819 17:22:29.600108   31029 round_trippers.go:423] Request Headers:
    I0819 17:22:29.600118   31029 round_trippers.go:426]     Accept: application/json;as=Table;v=v1beta1;g=meta.k8s.io, application/json
    I0819 17:22:29.600132   31029 round_trippers.go:426]     User-Agent: kubectl/v1.15.2 (darwin/amd64) kubernetes/f627830
    I0819 17:22:29.612086   31029 round_trippers.go:441] Response Status: 200 OK in 11 milliseconds
    ...
    

    La diferencia con el nivel 6, es que además de los recursos que son llamados, también podemos ver las cabeceras HTTP con las que se llaman a dichos recursos.

    El nivel 8 y el 9, además de las cabeceras, nos muestra el contenido del cuerpo de la petición (si existe) y respuesta (el nivel 9 muestra el contenido sin truncar), veamos otro ejemplo:

    kubectl get pods -v=8
    ...
    I0819 17:22:22.188395   31000 request.go:947] Response Body: {"kind":"Table","apiVersion":"meta.k8s.io/v1beta1","metadata":{"selfLink":"/api/v1/namespaces/default/pods","resourceVersion":"70162"},"columnDefinitions":[{"name":"Name","type":"string","format":"name","description":"Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names","priority":0},{"name":"Ready","type":"string","format":"","description":"The aggregate readiness state of this pod for accepting traffic.","priority":0},{"name":"Status","type":"string","format":"","description":"The aggregate status of the containers in this pod.","priority":0},{"name":"Restarts","type":"integer","format":"","description":"The number of times the containers in this pod have been restarted.","priority":0},{"name":"Age","type":"string [truncated 2611 chars]
    ...
    

    Veamos un ejemplo de un comando un poco más complejo dónde se requiera el uso de varias llamadas:

    kubectl describe pod nginx-7bb7cd8db5-8z6t8 -v=6
    ...
    I0819 17:26:27.770772   31121 round_trippers.go:438] GET https://192.168.99.110:8443/api/v1/namespaces/default/pods/nginx-7bb7cd8db5-8z6t8 200 OK in 12 milliseconds
    I0819 17:26:27.777728   31121 round_trippers.go:438] GET https://192.168.99.110:8443/api/v1/namespaces/default/pods/nginx-7bb7cd8db5-8z6t8 200 OK in 2 milliseconds
    I0819 17:26:27.786906   31121 round_trippers.go:438] GET https://192.168.99.110:8443/api/v1/namespaces/default/events?fieldSelector=involvedObject.name%3Dnginx-7bb7cd8db5-8z6t8%2CinvolvedObject.namespace%3Ddefault%2CinvolvedObject.uid%3D9e77227d-cc08-4365-aeab-c0bbbfc1c1d8 200 OK in 2 milliseconds
    ...
    

    Como se puede observar el comando describe requiere más de una llamada a la API. Por último veamos que ocurre durante la creación de un deployment con el comando run:

    kubectl run nginx2 --image nginx -v=8
    ...
    I0819 17:29:23.727063   31398 round_trippers.go:416] GET https://192.168.99.110:8443/apis/apps/v1?timeout=32s
    I0819 17:29:23.727097   31398 round_trippers.go:423] Request Headers:
    ...
    I0819 17:29:23.749539   31398 request.go:947] Request Body: {"kind":"Deployment","apiVersion":"apps/v1","metadata":{"name":"nginx2","creationTimestamp":null,"labels":{"run":"nginx2"}},"spec":{"replicas":1,"selector":{"matchLabels":{"run":"nginx2"}},"template":{"metadata":{"creationTimestamp":null,"labels":{"run":"nginx2"}},"spec":{"containers":[{"name":"nginx2","image":"nginx","resources":{}}]}},"strategy":{}},"status":{}}
    I0819 17:29:23.749618   31398 round_trippers.go:416] POST https://192.168.99.110:8443/apis/apps/v1/namespaces/default/deployments
    I0819 17:29:23.749631   31398 round_trippers.go:423] Request Headers:
    I0819 17:29:23.749638   31398 round_trippers.go:426]     Content-Type: application/json
    I0819 17:29:23.749645   31398 round_trippers.go:426]     User-Agent: kubectl/v1.15.2 (darwin/amd64) kubernetes/f627830
    ...
    

    En este caso vemos como no sólo se hacen peticiones GET, sino también POST, y gracias al nivel 8 podemos ver el contenido de dichas peticiones.

    Así puedes ir probando comandos y jugando con los niveles de verbosidad. Si quieres aprender más sobre la API de Kuberntes, además de su documentación obviamente, usando la verbosidad puedes aprender un poco más de forma algo más interactiva.

    Happy Hacking!