La idea de un operador de shell es bastante simple: suscríbase a eventos de objetos de Kubernetes y, cuando se reciban estos eventos, inicie un programa externo, proporcionándole información sobre el evento:
La necesidad surgió cuando, durante el funcionamiento de los clusters, empezaron a aparecer pequeñas tareas que realmente queríamos automatizar de la forma correcta. Todas estas pequeñas tareas se resolvieron utilizando scripts bash simples, aunque, como sabes, es mejor escribir operadores en Golang. Obviamente, invertir en el desarrollo a gran escala de un operador para cada tarea tan pequeña sería ineficaz.
Operador en 15 minutos
Veamos un ejemplo de lo que se puede automatizar en un clúster de Kubernetes y cómo puede ayudar el operador de shell. Un ejemplo sería el siguiente: replicar un secreto para acceder al registro de Docker.
Los pods que utilizan imágenes de un registro privado deben contener en su manifiesto un enlace a un secreto con datos para acceder al registro. Este secreto debe crearse en cada espacio de nombres antes de crear pods. Esto se puede hacer manualmente, pero si configuramos entornos dinámicos, el espacio de nombres para una aplicación será mucho. Y si además no hay 2-3 aplicaciones... el número de secretos se vuelve muy grande. Y una cosa más sobre los secretos: me gustaría cambiar la clave de acceso al registro de vez en cuando. Eventualmente, operaciones manuales como solución completamente ineficaz - Necesitamos automatizar la creación y actualización de secretos.
Automatización sencilla
Escribamos un script de shell que se ejecute una vez cada N segundos y verifique los espacios de nombres para detectar la presencia de un secreto y, si no hay ningún secreto, se crea. La ventaja de esta solución es que parece un script de shell en cron: un enfoque clásico y comprensible para todos. La desventaja es que en el intervalo entre sus lanzamientos se puede crear un nuevo espacio de nombres y durante algún tiempo permanecerá sin secreto, lo que provocará errores en el lanzamiento de pods.
Automatización con operador shell
Para que nuestro script funcione correctamente, el inicio cron clásico debe reemplazarse con un inicio cuando se agrega un espacio de nombres: en este caso, puede crear un secreto antes de usarlo. Veamos cómo implementar esto usando el operador Shell.
Primero, veamos el guión. Los scripts en términos de operadores de shell se denominan ganchos. Cada gancho cuando se ejecuta con una bandera. --config informa al operador de shell sobre sus enlaces, es decir sobre qué eventos debería lanzarse. En nuestro caso usaremos onKubernetesEvent:
#!/bin/bash
if [[ $1 == "--config" ]] ; then
cat <<EOF
{
"onKubernetesEvent": [
{ "kind": "namespace",
"event":["add"]
}
]}
EOF
fi
Aquí se describe que estamos interesados en agregar eventos (add) objetos de tipo namespace.
Ahora necesitas agregar el código que se ejecutará cuando ocurra el evento:
#!/bin/bash
if [[ $1 == "--config" ]] ; then
# конфигурация
cat <<EOF
{
"onKubernetesEvent": [
{ "kind": "namespace",
"event":["add"]
}
]}
EOF
else
# реакция:
# узнать, какой namespace появился
createdNamespace=$(jq -r '.[0].resourceName' $BINDING_CONTEXT_PATH)
# создать в нём нужный секрет
kubectl create -n ${createdNamespace} -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
...
data:
...
EOF
fi
¡Excelente! El resultado fue un guión pequeño y hermoso. Para “revivirla” quedan dos pasos: preparar la imagen y ejecutarla en el cluster.
Preparando una imagen con un gancho.
Si observa el script, puede ver que se utilizan los comandos. kubectl и jq. Esto significa que la imagen debe tener lo siguiente: nuestro gancho, un operador de shell que escuchará los eventos y ejecutará el gancho, y los comandos utilizados por el gancho (kubectl y jq). Hub.docker.com ya tiene una imagen preparada en la que están empaquetados shell-operator, kubectl y jq. Ya sólo queda añadir un simple gancho. Dockerfile:
Miremos nuevamente el gancho y esta vez anotemos qué acciones y con qué objetos realiza en el cluster:
se suscribe a eventos de creación de espacios de nombres;
crea un secreto en espacios de nombres distintos de aquel donde se lanza.
Resulta que el pod en el que se lanzará nuestra imagen debe tener permisos para realizar estas acciones. Esto se puede hacer creando su propia cuenta de servicio. El permiso debe realizarse en forma de ClusterRole y ClusterRoleBinding, porque Estamos interesados en objetos de todo el grupo.
Eso es todo: el operador de shell se iniciará, se suscribirá a los eventos de creación del espacio de nombres y ejecutará el enlace cuando sea necesario.
Por lo tanto, la un simple script de shell convertido en un operador real para Kubernetes y funciona como parte de un clúster. Y todo ello sin el complejo proceso de desarrollar operadores en Golang:
Hay otro ejemplo sobre este asunto...
Revelaremos su significado con más detalle en una de las siguientes publicaciones.
filtración
El seguimiento de objetos es bueno, pero a menudo es necesario reaccionar ante ellos. cambiando algunas propiedades del objeto, por ejemplo, para cambiar el número de réplicas en Implementación o cambiar las etiquetas de los objetos.
Cuando llega un evento, el operador de shell recibe el manifiesto JSON del objeto. Podemos seleccionar las propiedades que nos interesen en este JSON y ejecutar el gancho sólo cuando cambian. Hay un campo para esto jqFilter, donde debe especificar la expresión jq que se aplicará al manifiesto JSON.
Por ejemplo, para responder a cambios en las etiquetas de los objetos de implementación, debe filtrar el campo labels fuera del campo metadata. La configuración será así:
Esta expresión jqFilter convierte el manifiesto JSON largo de Deployment en JSON corto con etiquetas:
El operador de shell solo ejecutará el enlace cuando este JSON corto cambie y se ignorarán los cambios en otras propiedades.
Contexto de lanzamiento del gancho
La configuración del enlace le permite especificar varias opciones para eventos, por ejemplo, 2 opciones para eventos de Kubernetes y 2 programaciones:
Una pequeña digresión: sí, el operador shell lo admite ejecutando scripts de estilo crontab. Más detalles se pueden encontrar en documentación.
Para distinguir por qué se lanzó el gancho, el operador de shell crea un archivo temporal y le pasa la ruta en una variable al gancho. BINDING_CONTEXT_TYPE. El archivo contiene una descripción JSON del motivo para ejecutar el enlace. Por ejemplo, cada 10 minutos se ejecutará el gancho con el siguiente contenido:
El contenido de los campos se puede entender por sus nombres y se pueden leer más detalles en documentación. Un ejemplo de cómo obtener un nombre de recurso de un campo resourceName El uso de jq ya se ha mostrado en un gancho que replica secretos:
jq -r '.[0].resourceName' $BINDING_CONTEXT_PATH
Puede obtener otros campos de forma similar.
¿Qué será lo próximo?
En el repositorio del proyecto, en /directorios de ejemplos, hay ejemplos de ganchos que están listos para ejecutarse en un clúster. Al escribir tus propios ganchos, puedes utilizarlos como base.
Hay soporte para recopilar métricas usando Prometheus; las métricas disponibles se describen en la sección MÉTRICA.
Como puedes imaginar, el operador shell está escrito en Go y distribuido bajo una licencia de código abierto (Apache 2.0). Estaremos agradecidos por cualquier ayuda para el desarrollo. proyecto en GitHub: y estrellas, problemas y solicitudes de extracción.
Levantando el velo del secreto, también le informaremos que el operador shell es pequeño parte de nuestro sistema que puede mantener actualizados los complementos instalados en el clúster de Kubernetes y realizar diversas acciones automáticas. Leer más sobre este sistema dicho literalmente, el lunes en HighLoad++ 2019 en San Petersburgo; pronto publicaremos el video y la transcripción de este informe.
Tenemos un plan para abrir el resto de este sistema: el operador adicional y nuestra colección de ganchos y módulos. Por cierto, el operador adicional ya está disponible en github, pero la documentación aún está en camino. El lanzamiento de la colección de módulos está previsto para el verano.