Validação e padronização no K8S com admission controllers
Aqui no GrupoZAP, usamos o Kubernetes na grande maioria de serviços, com certeza mais que 80% de apps rodam no sábio capitão de containers. Quando começamos essa jornada, em 2016, pouco sabíamos e muitas foram as descobertas e surpresas, e uma coisa certamente ficou clara: como é difícil manter padrões e normas.
Começamos a perceber que muitas apps não seguiam os padrões que pedimos e precisávamos, como as labels dos deployments ou os recursos de limits e requests que cada serviço utilizava. Isso tornava difícil o gerenciamento do cluster. Usamos o Grafana para ter a visualização de utilização do Kubernetes, separada por namespaces ou apps, mas sem padronização, era impossível criar filtros eficientes e algo fácil de ver.
Percebemos também, que não usando limits e requests, o desperdício de CPU e memória aumentava, o que se traduz diretamente em perda de dinheiro. Aí já é demais!
Além disso, explicar as regras para todo time ou integrante novo da engenharia, era um trabalho árduo e sem fim, pela própria natureza.
Queríamos algo que fizesse o trabalho 'sujo' no nosso lugar, que fosse austero e implacável, mas com amor.
Admission controllers; era a nossa resposta.
Caso não saiba o que são admission controllers, recomendo a leitura: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/
Cogitamos usar o OPA Gatekeeper, mas a DSL dele não é simples e isso incomodou, parecia coisa demais para o que precisávamos.
Assim, surgiu dos mares um titã chamado: Aegir
Feito em casa, Aegir é um Admission Controller que valida campos de recursos kubernetianos de acordo com regras definidas usando uma linguagem comum chamada LIVR.
Chega de contexto, vamos aos exemplos:
Para forçar que todos os deployments tivessem as labels product
, app
e o mínimo de requests para memória e CPU, utilizamos a seguinte regra:
rules:
- name: labels_app_product_and_requests_are_required
namespace: "*"
resource_type: "Deployment"
rules_definitions:
- field: "metadata.labels"
livr_rule:
description: "product and app labels are required"
rule:
labels:
nested_object:
app: required
product: required
- field: "spec.template.spec.containers.#.resources.requests.cpu"
livr_rule:
description: "ensure minimum cpu request"
rule:
cpu: required
- field: "spec.template.spec.containers.#.resources.requests.memory"
livr_rule:
description: "ensure minimum memory request"
rule:
memory: required
slack_notification_channel: "#alerts-aegir"
Cada regra é composta dos seguintes campos:
- Name: Nome da regra
- Namespace: Namespace no qual a regra é aplicada,
*
a aplicará em todos os namespaces - ResourceType: O tipo de objeto que se enquadra na regra
- RuleDefinitions: Uma lista com os 3 seguintes campos:
- Field: O campo que será validado
- Description: Uma descrição da regra LIVR
- Rule: A regra LIVR em si
- SlackNotificationChannel: canal do slack onde será enviada uma notificação de violação da regra
Caso a regra seja violada, o objeto não será criado, e uma mensagem aparecerá no console, com a descrição da regra que foi violada:
É possível validar campos de vários objetos nativos como: Deployments, Services, Ingresses e etc.
O LIVR aceita muitas regras e é possível fazer muitas coisas, como exigir que as imagens dos containers sejam apenas de um repositório específico, exigir que o usuário que executa o container não seja root, entre outras coisas. O limite é o LIVR, que é bem completo.
Assim conseguimos criar um bom nível de padronização no nosso K8S, de forma automatizada e simples.
Caso haja interesse no projeto, segue o repositório, com uma documentação mais completa e exemplos: https://github.com/grupozap/aegir.