Presentamos Shell-operator: crear operadores para Kubernetes ahora es más fácil

Ya ha habido artículos en nuestro blog hablando sobre capacidades del operador en Kubernetes y cómo escribe un operador simple tú mismo. Esta vez nos gustaría presentarles nuestra solución Open Source, que lleva la creación de operadores a un nivel súper fácil - consulte operador de shell!

¿Por qué?

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:

Presentamos Shell-operator: crear operadores para Kubernetes ahora es más fácil

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:

$ cat Dockerfile
FROM flant/shell-operator:v1.0.0-beta.1-alpine3.9
ADD namespace-hook.sh /hooks

$ docker build -t registry.example.com/my-operator:v1 . 
$ docker push registry.example.com/my-operator:v1

Ejecutando en un clúster

Miremos nuevamente el gancho y esta vez anotemos qué acciones y con qué objetos realiza en el cluster:

  1. se suscribe a eventos de creación de espacios de nombres;
  2. 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.

La descripción final en YAML se verá así:

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: monitor-namespaces-acc

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: monitor-namespaces
rules:
- apiGroups: [""]
  resources: ["namespaces"]
  verbs: ["get", "watch", "list"]
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "list", "create", "patch"]

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: monitor-namespaces
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: monitor-namespaces
subjects:
  - kind: ServiceAccount
    name: monitor-namespaces-acc
    namespace: example-monitor-namespaces

Puede iniciar la imagen ensamblada como una implementación simple:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: my-operator
spec:
  template:
    spec:
      containers:
      - name: my-operator
        image: registry.example.com/my-operator:v1
      serviceAccountName: monitor-namespaces-acc

Para mayor comodidad, se crea un espacio de nombres separado donde se iniciará el operador de shell y se aplicarán los manifiestos creados:

$ kubectl create ns example-monitor-namespaces
$ kubectl -n example-monitor-namespaces apply -f rbac.yaml
$ kubectl -n example-monitor-namespaces apply -f deployment.yaml

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.

Presentamos Shell-operator: crear operadores para Kubernetes ahora es más fácil

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:

Presentamos Shell-operator: crear operadores para Kubernetes ahora es más fácil

Hay otro ejemplo sobre este asunto...Presentamos Shell-operator: crear operadores para Kubernetes ahora es más fácil

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í:

cat <<EOF
{
"onKubernetesEvent": [
{ "kind": "deployment",
  "event":["update"],
  "jqFilter": ".metadata.labels"
}
]}
EOF

Esta expresión jqFilter convierte el manifiesto JSON largo de Deployment en JSON corto con etiquetas:

Presentamos Shell-operator: crear operadores para Kubernetes ahora es más fácil

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:

{"onKubernetesEvent":[
  {"name":"OnCreatePod",
  "kind": "pod",
  "event":["add"]
  },
  {"name":"OnModifiedNamespace",
  "kind": "namespace",
  "event":["update"],
  "jqFilter": ".metadata.labels"
  }
],
"schedule": [
{ "name":"every 10 min",
  "crontab":"* */10 * * * *"
}, {"name":"on Mondays at 12:10",
"crontab": "* 10 12 * * 1"
]}

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:

[{ "binding": "every 10 min"}]

... y el lunes empezará con esto:

[{ "binding": "every 10 min"}, { "binding": "on Mondays at 12:10"}]

para onKubernetesEvent Habrá más activadores JSON, porque contiene una descripción del objeto:

[
 {
 "binding": "onCreatePod",
 "resourceEvent": "add",
 "resourceKind": "pod",
 "resourceName": "foo",
 "resourceNamespace": "bar"
 }
]

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.

¡Manténganse al tanto!

PS

Lea también en nuestro blog:

Fuente: habr.com

Añadir un comentario