Já existem artigos em nosso blog sobre os
recursos dos operadores no Kubernetes e sobre como
escrever um operador simples . Desta vez, queremos chamar a atenção para a nossa solução de código aberto, que leva a criação de operadores a um nível super fácil - familiarize-se com o
operador de shell !
Porque
A idéia de um operador de shell é bastante simples: assine eventos de objetos Kubernetes e, ao receber esses eventos, inicie um programa externo, fornecendo informações sobre o evento:

A necessidade disso surgiu quando, durante a operação de clusters, começaram a aparecer pequenas tarefas que realmente queríamos automatizar da maneira correta. Todas essas pequenas tarefas foram resolvidas com a ajuda de scripts simples do bash, embora, como você sabe, os operadores sejam melhor escritos em Golang. Obviamente, investir em um desenvolvimento de operador em larga escala para cada tarefa tão pequena seria ineficiente.
Operador em 15 minutos
Vejamos um exemplo do que pode ser automatizado em um cluster Kubernetes e como o operador de shell ajudará. Um exemplo seria o seguinte: duplicar um segredo para acessar o registro do docker.
Os pods que usam imagens do registro privado devem conter no manifesto um link para um segredo com dados para acessar o registro. Esse segredo deve ser criado em cada espaço para nome antes da criação dos pods. É bem possível fazer isso manualmente, mas se configurarmos ambientes dinâmicos, haverá muito espaço para nome para um aplicativo. E se os aplicativos também não forem 2-3, o número de segredos se torna muito grande. E mais uma coisa sobre segredos: gostaria de alterar a chave para acessar o registro de tempos em tempos. Como resultado,
operações manuais como solução são
completamente ineficazes - você precisa automatizar a criação e atualização de segredos.
Fácil automação
Escreveremos um script de shell que será executado uma vez a cada N segundos e verificará o espaço de nomes em busca de um segredo e, se não houver nenhum segredo, ele será criado. A vantagem desta solução é que ela se parece com um script de shell no cron - uma abordagem clássica e compreensível. A desvantagem é que, no intervalo entre os lançamentos, um novo espaço para nome pode ser criado e, por algum tempo, permanecerá sem segredo, o que levará a erros no lançamento dos pods.
Automação com operador de shell
Para que nosso script funcione corretamente, o lançamento clássico do cron precisa ser substituído pelo lançamento quando o evento de espaço para nome é adicionado: nesse caso, você pode criar um segredo antes de usá-lo. Vamos ver como implementar isso usando o operador de shell.
Primeiro, vamos analisar o script. Os scripts em termos de operador de shell são chamados ganchos. Cada gancho na inicialização com o sinalizador
--config
informa o operador shell sobre suas ligações, ou seja, por quais eventos ele precisa ser lançado. No nosso caso, usaremos
onKubernetesEvent
:
É descrito aqui que estamos interessados em eventos para adicionar (
add
) objetos do tipo
namespace
.
Agora você precisa adicionar o código que será executado quando o evento ocorrer:
Ótimo! O resultado é um script pequeno e bonito. Para "revivê-la", restam duas etapas: preparar a imagem e executá-la no cluster.
Preparando uma imagem com um gancho
Se você olhar o script, poderá ver que os
jq
kubectl
e
jq
. Isso significa que a imagem deve ter o seguinte: our hook, um operador de shell que monitorará eventos e ativará o hook, bem como os comandos usados pelo hook (kubectl e jq).
O Hub.docker.com já possui uma imagem pronta na qual o operador shell, kubectl e jq são empacotados. Resta adicionar o gancho com um
Dockerfile
simples:
$ 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
Lançamento de cluster
Mais uma vez, vamos dar uma olhada no gancho e desta vez escrever quais ações e com quais objetos ele executa no cluster:
- Inscreve-se em eventos de espaço para nome
- cria um segredo em espaços para nome diferentes daquele em que está sendo executado.
Acontece que o pod em que nossa imagem será lançada deve ter permissões para essas ações. Isso pode ser feito criando sua própria ServiceAccount. A permissão deve ser feita na forma de ClusterRole e ClusterRoleBinding, porque estamos interessados em objetos de todo o cluster.
A descrição final no YAML é mais ou menos assim:
--- 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
Você pode executar a imagem montada na forma de uma implantação simples:
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
Por conveniência, um espaço para nome separado é criado onde o operador shell será iniciado e os manifestos criados serão aplicados:
$ kubectl create ns example-monitor-namespaces $ kubectl -n example-monitor-namespaces apply -f rbac.yaml $ kubectl -n example-monitor-namespaces apply -f deployment.yaml
Isso é tudo: o operador do shell iniciará, se inscreverá nos eventos de criação do espaço para nome e iniciará o gancho quando necessário.

Assim, um
simples script de shell se transformou em um operador real para o Kubernetes e funciona como parte integrante do cluster. E tudo isso - sem o processo complicado de desenvolver operadores na Golang:

Filtragem
O rastreamento de objetos é bom, mas geralmente é necessário responder a
alterações em algumas propriedades de um objeto , por exemplo, a uma alteração no número de réplicas em uma Implantação ou a uma alteração nos rótulos de um objeto.
Quando um evento chega, o operador de shell recebe o manifesto JSON do objeto. Você pode selecionar as propriedades de interesse para nós neste JSON e executar o gancho
apenas quando elas mudarem. Para fazer isso, o campo
jqFilter
é
jqFilter
, onde você precisa especificar a expressão jq que será aplicada ao manifesto JSON.
Por exemplo, para responder às alterações de rótulo nos objetos Deployment, você precisa filtrar o campo de
labels
campo de
metadata
. A configuração será assim:
cat <<EOF { "onKubernetesEvent": [ { "kind": "deployment", "event":["update"], "jqFilter": ".metadata.labels" } ]} EOF
Essa expressão no jqFilter transforma o manifesto JSON longo do Deployment em JSON curto com rótulos:

O operador shell só acionará um gancho quando esse JSON curto for alterado e as alterações em outras propriedades forem ignoradas.
Contexto de lançamento do gancho
A configuração do gancho permite especificar várias opções para eventos - por exemplo, 2 opções para eventos do Kubernetes e 2 planejamentos:
{"onKubernetesEvent":[ {"name":"OnCreatePod", "kind": "pod", "event":["add"] }, {"name":"OnModifiedNamespace", "kind": "namespace", "event":["update"], "jqFilter": ".metadata.labels" } ], "schedule": [ { "name":"every 10 min", "crontab":"0 */10 * * * *" }, {"name":"on Mondays at 12:10", "crontab": "0 10 12 * * 1" ]}
Uma pequena digressão: sim, o operador shell suporta a execução de scripts no estilo crontab . Você pode ler mais na documentação .Para distinguir por que o gancho foi iniciado, o operador de shell cria um arquivo temporário e passa o caminho para ele até o
BINDING_CONTEXT_TYPE
na variável
BINDING_CONTEXT_TYPE
. O arquivo contém uma descrição JSON do motivo pelo qual o gancho foi iniciado. Por exemplo, a cada 10 minutos, um gancho começará com o seguinte conteúdo:
[{ "binding": "every 10 min"}]
... e na segunda-feira começará com isso:
[{ "binding": "every 10 min"}, { "binding": "on Mondays at 12:10"}]
Para
onKubernetesEvent
haverá mais disparos JSON desde contém uma descrição do objeto:
[ { "binding": "onCreatePod", "resourceEvent": "add", "resourceKind": "pod", "resourceName": "foo", "resourceNamespace": "bar" } ]
O conteúdo dos campos pode ser entendido a partir de seus nomes e, em mais detalhes, leia a
documentação . Um exemplo de como obter o nome do
resourceName
campo
resourceName
usando jq já foi mostrado em um gancho que replica segredos:
jq -r '.[0].resourceName' $BINDING_CONTEXT_PATH
Da mesma forma, você pode obter os campos restantes.
O que vem a seguir?
No repositório do projeto, no
diretório / examples , existem exemplos de ganchos prontos para execução no cluster. Ao escrever seus ganchos, você pode tomá-los como base.
Há suporte para a coleta de métricas usando o Prometheus - as métricas disponíveis são gravadas na seção
METRICS .
Como você pode imaginar, o operador de shell é escrito em Go e distribuído sob a licença Open Source (Apache 2.0). Seremos gratos por qualquer ajuda no desenvolvimento do
projeto no GitHub : asteriscos, problemas e solicitações de recebimento.
Abrindo o véu de sigilo, também informamos que o operador de shell é uma
pequena parte do nosso sistema, que pode manter atualizados os complementos instalados no cluster Kubernetes e executar várias ações automáticas.
Falamos sobre esse sistema com mais detalhes na segunda-feira no HighLoad ++ 2019 em São Petersburgo - o vídeo e a transcrição deste relatório serão publicados em breve.
Temos um plano para abrir o restante deste sistema: addon-operator e nossa coleção de ganchos e módulos. A propósito, o addon-operator já está
disponível no GitHub , mas a documentação para ele ainda está a caminho. O lançamento da coleção de módulos está planejado para o verão.
Fique atento!
PS
Leia também em nosso blog: