Vai? Golpe! Coñece o operador de shell (revisión e informe de vídeo de KubeCon EU'2020)

Este ano, a principal conferencia europea de Kubernetes - KubeCon + CloudNativeCon Europe 2020 - foi virtual. Non obstante, tal cambio de formato non nos impediu entregar o noso informe "Vai? Golpe! Meet the Shell-operator” dedicado ao noso proxecto de código aberto operador de shell.

Este artigo, inspirado na charla, presenta un enfoque para simplificar o proceso de creación de operadores para Kubernetes e mostra como pode facer o seu propio cun mínimo esforzo usando un operador de shell.

Vai? Golpe! Coñece o operador de shell (revisión e informe de vídeo de KubeCon EU'2020)

Presentando vídeo do informe (~23 minutos en inglés, notablemente máis informativo que o artigo) e o extracto principal deste en forma de texto. Vaia!

En Flant optimizamos e automatizamos todo constantemente. Hoxe falaremos doutro concepto interesante. Coñecer: script de shell nativo da nube!

Non obstante, empecemos polo contexto no que sucede todo isto: Kubernetes.

API e controladores de Kubernetes

A API en Kubernetes pódese representar como unha especie de servidor de ficheiros con directorios para cada tipo de obxecto. Os obxectos (recursos) deste servidor están representados por ficheiros YAML. Ademais, o servidor ten unha API básica que che permite facer tres cousas:

  • recibir recurso polo seu tipo e nome;
  • cambio recurso (neste caso, o servidor almacena só obxectos "correctos" - descartanse todos os formados incorrectamente ou destinados a outros directorios);
  • pista para o recurso (neste caso, o usuario recibe inmediatamente a súa versión actual/actualizada).

Así, Kubernetes actúa como unha especie de servidor de ficheiros (para manifestos YAML) con tres métodos básicos (si, en realidade hai outros, pero omitirémolos por agora).

Vai? Golpe! Coñece o operador de shell (revisión e informe de vídeo de KubeCon EU'2020)

O problema é que o servidor só pode almacenar información. Para que funcione necesitas controlador - o segundo concepto máis importante e fundamental no mundo de Kubernetes.

Hai dous tipos principais de controladores. O primeiro toma información de Kubernetes, procesa segundo a lóxica aniñada e devólvaa a K8s. O segundo toma información de Kubernetes, pero, a diferenza do primeiro tipo, cambia o estado dalgúns recursos externos.

Vexamos máis de cerca o proceso de creación dunha implementación en Kubernetes:

  • Controlador de implementación (incluído en kube-controller-manager) recibe información sobre a implementación e crea un ReplicaSet.
  • ReplicaSet crea dúas réplicas (dous pods) en función desta información, pero estes pods aínda non están programados.
  • O planificador programa pods e engade información de nodos aos seus YAML.
  • Kubelets realiza cambios nun recurso externo (por exemplo, Docker).

A continuación, repítese toda esta secuencia en orde inversa: o kubelet comproba os recipientes, calcula o estado da vaina e envíao de volta. O controlador ReplicaSet recibe o estado e actualiza o estado do conxunto de réplicas. O mesmo ocorre co controlador de implementación e o usuario finalmente obtén o estado actualizado (actual).

Vai? Golpe! Coñece o operador de shell (revisión e informe de vídeo de KubeCon EU'2020)

Operador Shell

Resulta que Kubernetes baséase no traballo conxunto de varios controladores (os operadores de Kubernetes tamén son controladores). Xorde a pregunta, como crear o teu propio operador cun mínimo esforzo? E aquí o que desenvolvemos vén ao rescate operador de shell. Permite aos administradores do sistema crear as súas propias declaracións utilizando métodos coñecidos.

Vai? Golpe! Coñece o operador de shell (revisión e informe de vídeo de KubeCon EU'2020)

Exemplo sinxelo: copiar segredos

Vexamos un exemplo sinxelo.

Digamos que temos un clúster de Kubernetes. Ten un espazo de nomes default con algún Segredo mysecret. Ademais, hai outros espazos de nomes no clúster. Algúns deles teñen unha etiqueta específica adherida. O noso obxectivo é copiar Secret en espazos de nomes cunha etiqueta.

A tarefa complícase polo feito de que poden aparecer novos espazos de nomes no clúster e algúns deles poden ter esta etiqueta. Por outra banda, cando se elimina a etiqueta, tamén se debe eliminar Secret. Ademais disto, o propio Secreto tamén pode cambiar: neste caso, o novo Secreto debe copiarse en todos os espazos de nomes con etiquetas. Se Secret se elimina accidentalmente nalgún espazo de nomes, o noso operador debería restauralo inmediatamente.

Agora que se formulou a tarefa, é hora de comezar a implementala mediante o operador shell. Pero primeiro paga a pena dicir algunhas palabras sobre o propio operador de shell.

Como funciona o shell-operator

Do mesmo xeito que outras cargas de traballo en Kubernetes, o shell-operator execútase no seu propio pod. Neste pod do directorio /hooks gárdanse ficheiros executables. Estes poden ser scripts en Bash, Python, Ruby, etc. Chamamos a estes ficheiros executables ganchos (garras).

Vai? Golpe! Coñece o operador de shell (revisión e informe de vídeo de KubeCon EU'2020)

O operador Shell subscríbese aos eventos de Kubernetes e executa estes ganchos en resposta aos eventos que necesitamos.

Vai? Golpe! Coñece o operador de shell (revisión e informe de vídeo de KubeCon EU'2020)

Como sabe o operador de shell que gancho executar e cando? A cuestión é que cada gancho ten dúas etapas. Durante o inicio, o operador de shell executa todos os hooks cun argumento --config Esta é a fase de configuración. E despois diso, os ganchos lánzanse de forma normal, en resposta aos eventos aos que están ligados. Neste último caso, o gancho recibe o contexto de unión (contexto vinculante) - datos en formato JSON, dos que falaremos con máis detalle a continuación.

Facendo un operador en Bash

Agora estamos preparados para a implementación. Para iso, necesitamos escribir dúas funcións (por certo, recomendamos biblioteca shell_lib, o que simplifica moito os ganchos de escritura en Bash):

  • o primeiro é necesario para a fase de configuración: mostra o contexto de vinculación;
  • o segundo contén a lóxica principal do gancho.

#!/bin/bash

source /shell_lib.sh

function __config__() {
  cat << EOF
    configVersion: v1
    # BINDING CONFIGURATION
EOF
}

function __main__() {
  # THE LOGIC
}

hook::run "$@"

O seguinte paso é decidir que obxectos necesitamos. No noso caso, necesitamos seguir:

  • segredo da fonte para os cambios;
  • todos os espazos de nomes do clúster, para que saibas cales teñen unha etiqueta adxunta a eles;
  • segredos de destino para asegurarse de que están todos sincronizados co segredo de orixe.

Subscríbete á fonte secreta

A configuración de vinculación para iso é bastante sinxela. Indicamos que nos interesa Secreto co nome mysecret no espazo de nomes default:

Vai? Golpe! Coñece o operador de shell (revisión e informe de vídeo de KubeCon EU'2020)

function __config__() {
  cat << EOF
    configVersion: v1
    kubernetes:
    - name: src_secret
      apiVersion: v1
      kind: Secret
      nameSelector:
        matchNames:
        - mysecret
      namespace:
        nameSelector:
          matchNames: ["default"]
      group: main
EOF

Como resultado, o gancho activarase cando o segredo de orixe cambie (src_secret) e recibe o seguinte contexto vinculante:

Vai? Golpe! Coñece o operador de shell (revisión e informe de vídeo de KubeCon EU'2020)

Como podes ver, contén o nome e todo o obxecto.

Realizar un seguimento dos espazos de nomes

Agora tes que subscribirte aos espazos de nomes. Para iso, especificamos a seguinte configuración vinculante:

- name: namespaces
  group: main
  apiVersion: v1
  kind: Namespace
  jqFilter: |
    {
      namespace: .metadata.name,
      hasLabel: (
       .metadata.labels // {} |  
         contains({"secret": "yes"})
      )
    }
  group: main
  keepFullObjectsInMemory: false

Como podes ver, apareceu un novo campo na configuración co nome jqFilter. Como o seu nome indica, jqFilter filtra toda a información innecesaria e crea un novo obxecto JSON cos campos que nos interesan. Un gancho cunha configuración similar recibirá o seguinte contexto de vinculación:

Vai? Golpe! Coñece o operador de shell (revisión e informe de vídeo de KubeCon EU'2020)

Contén unha matriz filterResults para cada espazo de nomes do clúster. Variable booleana hasLabel indica se unha etiqueta está anexa a un espazo de nomes determinado. Selector keepFullObjectsInMemory: false indica que non é necesario gardar obxectos completos na memoria.

Seguimento dos segredos de destino

Subscribímonos a todos os segredos que teñan unha anotación especificada managed-secret: "yes" (estes son os nosos obxectivos dst_secrets):

- name: dst_secrets
  apiVersion: v1
  kind: Secret
  labelSelector:
    matchLabels:
      managed-secret: "yes"
  jqFilter: |
    {
      "namespace":
        .metadata.namespace,
      "resourceVersion":
        .metadata.annotations.resourceVersion
    }
  group: main
  keepFullObjectsInMemory: false

Neste caso jqFilter filtra toda a información excepto o espazo de nomes e o parámetro resourceVersion. O último parámetro pasou á anotación ao crear o segredo: permite comparar versións dos segredos e mantelos actualizados.

Un gancho configurado deste xeito recibirá, cando se execute, os tres contextos de vinculación descritos anteriormente. Pódense considerar como unha especie de instantánea (instantáneo) cluster.

Vai? Golpe! Coñece o operador de shell (revisión e informe de vídeo de KubeCon EU'2020)

A partir de toda esta información pódese desenvolver un algoritmo básico. Itera sobre todos os espazos de nomes e:

  • se hasLabel asuntos true para o espazo de nomes actual:
    • compara o segredo global co local:
      • se son iguais, non fai nada;
      • se difiren - executa kubectl replace ou create;
  • se hasLabel asuntos false para o espazo de nomes actual:
    • asegúrese de que Secret non estea no espazo de nomes indicado:
      • se o segredo local está presente, elimínao usando kubectl delete;
      • se non se detecta o segredo local, non fai nada.

Vai? Golpe! Coñece o operador de shell (revisión e informe de vídeo de KubeCon EU'2020)

Implementación do algoritmo en Bash podes descargar no noso repositorios con exemplos.

Así é como puidemos crear un controlador Kubernetes sinxelo usando 35 liñas de configuración YAML e aproximadamente a mesma cantidade de código Bash. O traballo do operador de shell é vinculalos entre si.

Non obstante, copiar segredos non é a única área de aplicación da utilidade. Aquí tes algúns exemplos máis para mostrar do que é capaz.

Exemplo 1: facer cambios en ConfigMap

Vexamos unha implementación formada por tres módulos. Os pods usan ConfigMap para almacenar algunha configuración. Cando se lanzaron os pods, ConfigMap estaba nun determinado estado (chamémoslle v.1). En consecuencia, todos os pods usan esta versión particular de ConfigMap.

Agora supoñamos que o ConfigMap cambiou (v.2). Non obstante, os pods usarán a versión anterior de ConfigMap (v.1):

Vai? Golpe! Coñece o operador de shell (revisión e informe de vídeo de KubeCon EU'2020)

Como podo conseguir que cambien ao novo ConfigMap (v.2)? A resposta é sinxela: usa un modelo. Engademos unha anotación de suma de verificación á sección template Configuracións de implantación:

Vai? Golpe! Coñece o operador de shell (revisión e informe de vídeo de KubeCon EU'2020)

Como resultado, esta suma de verificación rexistrarase en todos os pods e será a mesma que a de Implementación. Agora só precisa actualizar a anotación cando cambie o ConfigMap. E o operador de shell é útil neste caso. Todo o que tes que facer é programar un gancho que se subscribirá ao ConfigMap e actualizará a suma de verificación.

Se o usuario fai cambios no ConfigMap, o operador de shell notaraos e volverá calcular a suma de verificación. Despois diso, a maxia de Kubernetes entrará en xogo: o orquestrador matará a vaina, creará unha nova, agardará a que se converta. Ready, e pasa ao seguinte. Como resultado, Deployment sincronizarase e cambiará á nova versión de ConfigMap.

Vai? Golpe! Coñece o operador de shell (revisión e informe de vídeo de KubeCon EU'2020)

Exemplo 2: Traballar con definicións de recursos personalizadas

Como sabes, Kubernetes permíteche crear tipos de obxectos personalizados. Por exemplo, podes crear tipo MysqlDatabase. Digamos que este tipo ten dous parámetros de metadatos: name и namespace.

apiVersion: example.com/v1alpha1
kind: MysqlDatabase
metadata:
  name: foo
  namespace: bar

Temos un clúster de Kubernetes con diferentes espazos de nomes no que podemos crear bases de datos MySQL. Neste caso, o shell-operator pódese usar para rastrexar recursos MysqlDatabase, conectándoos ao servidor MySQL e sincronizando os estados desexados e observados do clúster.

Vai? Golpe! Coñece o operador de shell (revisión e informe de vídeo de KubeCon EU'2020)

Exemplo 3: Monitorización da rede de clústeres

Como sabes, usar ping é a forma máis sinxela de supervisar unha rede. Neste exemplo, mostraremos como implementar tal monitorización usando o shell-operator.

Primeiro de todo, terás que subscribirte aos nodos. O operador de shell necesita o nome e o enderezo IP de cada nodo. Coa súa axuda, fará un ping a estes nodos.

configVersion: v1
kubernetes:
- name: nodes
  apiVersion: v1
  kind: Node
  jqFilter: |
    {
      name: .metadata.name,
      ip: (
       .status.addresses[] |  
        select(.type == "InternalIP") |
        .address
      )
    }
  group: main
  keepFullObjectsInMemory: false
  executeHookOnEvent: []
schedule:
- name: every_minute
  group: main
  crontab: "* * * * *"

Parámetro executeHookOnEvent: [] impide que o gancho se execute en resposta a calquera evento (é dicir, en resposta a cambiar, engadir ou eliminar nós). Porén, el correrá (e actualizar a lista de nodos) Programado - cada minuto, segundo o prescrito polo campo schedule.

Agora xorde a pregunta, como sabemos exactamente sobre problemas como a perda de paquetes? Vexamos o código:

function __main__() {
  for i in $(seq 0 "$(context::jq -r '(.snapshots.nodes | length) - 1')"); do
    node_name="$(context::jq -r '.snapshots.nodes['"$i"'].filterResult.name')"
    node_ip="$(context::jq -r '.snapshots.nodes['"$i"'].filterResult.ip')"
    packets_lost=0
    if ! ping -c 1 "$node_ip" -t 1 ; then
      packets_lost=1
    fi
    cat >> "$METRICS_PATH" <<END
      {
        "name": "node_packets_lost",
        "add": $packets_lost,
        "labels": {
          "node": "$node_name"
        }
      }
END
  done
}

Recorremos a lista de nodos, obtemos os seus nomes e enderezos IP, fai un ping a eles e enviamos os resultados a Prometheus. O operador Shell pode exportar métricas a Prometheus, gardándoos nun ficheiro localizado segundo o camiño especificado na variable de ambiente $METRICS_PATH.

Entón pode facer un operador para a simple vixilancia da rede nun clúster.

Mecanismo de cola

Este artigo estaría incompleto sen describir outro mecanismo importante integrado no shell-operator. Imaxina que executa algún tipo de gancho en resposta a un evento no clúster.

  • Que pasa se, ao mesmo tempo, ocorre algo no clúster? un máis evento?
  • O shell-operator executará outra instancia do gancho?
  • E se, por exemplo, ocorren cinco eventos no clúster á vez?
  • Procesaraos o operador de shell en paralelo?
  • Que pasa cos recursos consumidos como memoria e CPU?

Afortunadamente, o shell-operator ten un mecanismo de cola incorporado. Todos os eventos están en cola e procesan secuencialmente.

Ilustremos isto con exemplos. Digamos que temos dous ganchos. O primeiro evento vai para o primeiro gancho. Unha vez finalizado o seu procesamento, a cola avanza. Os seguintes tres eventos son redirixidos ao segundo gancho: elimínanse da cola e introdúcense nel nun "paquete". É dicir gancho recibe unha serie de eventos — ou, máis precisamente, unha variedade de contextos vinculantes.

Tamén estes os eventos pódense combinar nun só grande. O parámetro é o responsable diso group na configuración de vinculación.

Vai? Golpe! Coñece o operador de shell (revisión e informe de vídeo de KubeCon EU'2020)

Podes crear calquera número de colas/ganchos e as súas diversas combinacións. Por exemplo, unha cola pode funcionar con dous ganchos ou viceversa.

Vai? Golpe! Coñece o operador de shell (revisión e informe de vídeo de KubeCon EU'2020)

Todo o que tes que facer é configurar o campo en consecuencia queue na configuración de vinculación. Se non se especifica un nome de cola, o gancho execútase na cola predeterminada (default). Este mecanismo de cola permítelle resolver completamente todos os problemas de xestión de recursos ao traballar con ganchos.

Conclusión

Explicamos o que é un operador de shell, mostramos como se pode usar para crear operadores Kubernetes de forma rápida e sen esforzo e demos varios exemplos do seu uso.

A información detallada sobre o operador de shell, así como un tutorial rápido sobre como usalo, está dispoñible no correspondente repositorios en GitHub. Non dubides en contactar connosco para as dúbidas: podes comentalas nun especial Grupo de Telegram (en ruso) ou en este foro (en inglés).

E se che gustou, sempre estamos encantados de ver novos números/PR/estrelas en GitHub, onde, por certo, podes atopar outros proxectos interesantes. Entre eles cabe destacar operador-complemento, que é o irmán maior de shell-operator. Esta utilidade usa gráficos Helm para instalar complementos, pode entregar actualizacións e supervisar varios parámetros/valores de gráficos, controla o proceso de instalación dos gráficos e tamén pode modificalos en resposta a eventos no clúster.

Vai? Golpe! Coñece o operador de shell (revisión e informe de vídeo de KubeCon EU'2020)

Vídeos e diapositivas

Vídeo da actuación (~23 minutos):


Presentación do informe:

PS

Lea tamén no noso blog:

Fonte: www.habr.com

Engadir un comentario