Validação do YAML do Kubernetes de acordo com as melhores práticas e políticas

Nota do tradutor.: Com o aumento do número de configurações em YAML para ambientes K8s, a necessidade de verificação automatizada se torna cada vez mais relevante. O autor desta revisão não apenas selecionou soluções existentes para essa tarefa, mas também analisou, com base em um Deployment, como elas funcionam. O resultado foi bastante informativo para aqueles interessados no assunto.

Validação do YAML do Kubernetes de acordo com as melhores práticas e políticas

TL;DR: O artigo compara seis ferramentas estáticas de verificação e avaliação de arquivos YAML do Kubernetes em relação às melhores práticas e requisitos.

As cargas de trabalho do Kubernetes geralmente são definidas na forma de documentos YAML. Um dos problemas com YAML é a complexidade em definir restrições ou relacionamentos entre os arquivos de manifestos.

E se precisarmos garantir que todas as imagens implantadas no cluster vêm de um registro confiável?

Como evitar a implantação de Deployments no cluster para os quais não foram definidos PodDisruptionBudgets?

A integração de testes estáticos permite identificar erros e violações de políticas ainda na fase de desenvolvimento. Isso aumenta as garantias de correção e segurança nas definições de recursos, além de aumentar a probabilidade de que as cargas de produção sigam as melhores práticas.

O ecossistema de verificação estática de arquivos YAML do Kubernetes pode ser dividido nas seguintes categorias:

  • Validadores de API. Ferramentas nesta categoria verificam se o manifesto YAML está em conformidade com os requisitos do servidor API do Kubernetes.
  • Testadores prontos. As ferramentas desta categoria vêm com testes prontos sobre segurança, conformidade com as melhores práticas, etc.
  • Validadores personalizados. Os representantes desta categoria permitem criar testes personalizados em várias linguagens, como Rego e Javascript.

Neste artigo, descreveremos e compararemos seis ferramentas diferentes:

  1. kubeval;
  2. kube-score;
  3. config-lint;
  4. copper;
  5. conftest;
  6. Polaris.

Bem, vamos começar!

Verificação de Deployments

Antes de começarmos a comparar as ferramentas, vamos estabelecer alguma base sobre a qual iremos testá-las.

O manifesto abaixo contém uma série de erros e não conformidades com as melhores práticas: quantos deles você consegue encontrar?

apiVersion: apps/v1
kind: Deployment
metadata:
  name: http-echo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: http-echo
  template:
    metadata:
      labels:
        app: http-echo
    spec:
      containers:
      - name: http-echo
        image: hashicorp/http-echo
        args: ["-text", "hello-world"]
        ports:
        - containerPort: 5678
---
apiVersion: v1
kind: Service
metadata:
  name: http-echo
spec:
  ports:
  - port: 5678
    protocol: TCP
    targetPort: 5678
  selector:
    app: http-echo

(base-valid.yaml)

Vamos usar este YAML para comparar várias ferramentas.

O manifesto acima base-valid.yaml e outros manifestos deste artigo podem ser encontrados em repositório Git.

O manifesto descreve um aplicativo web cuja principal função é responder com a mensagem "Hello World" na porta 5678. Ele pode ser implantado com o seguinte comando:

kubectl apply -f hello-world.yaml

E assim — para verificar o funcionamento:

kubectl port-forward svc/http-echo 8080:5678

Agora acesse http://localhost:8080 e confirme que o aplicativo está funcionando. Mas ele está seguindo as melhores práticas? Vamos verificar.

1. Kubeval

A base de kubeval é a ideia de que qualquer interação com o Kubernetes ocorre através de sua API REST. Em outras palavras, você pode usar o esquema da API para verificar se o YAML atende a ele. Vamos ver um exemplo.

Instruções para instalação do kubeval estão disponíveis no site do projeto.

No momento da redação do artigo original, a versão 0.15.0 estava disponível.

Após a instalação, vamos "dar" o manifesto acima a ele:

$ kubeval base-valid.yaml
PASS - base-valid.yaml contém um Deployment válido (http-echo)
PASS - base-valid.yaml contém um Service válido (http-echo)

Em caso de sucesso, o kubeval sairá com o código de saída 0. Podemos verificar assim:

$ echo $?
0

Vamos agora tentar o kubeval com outro manifesto:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: http-echo
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: http-echo
    spec:
      containers:
      - name: http-echo
        image: hashicorp/http-echo
        args: ["-text", "hello-world"]
        ports:
        - containerPort: 5678
---
apiVersion: v1
kind: Service
metadata:
  name: http-echo
spec:
  ports:
  - port: 5678
    protocol: TCP
    targetPort: 5678
  selector:
    app: http-echo

(kubeval-invalid.yaml)

Você consegue identificar o problema a olho nu? Vamos executar:

$ kubeval kubeval-invalid.yaml
WARN - kubeval-invalid.yaml contém um Deployment inválido (http-echo) - selector: selector é obrigatório
PASS - kubeval-invalid.yaml contém um Service válido (http-echo)

# vamos verificar o código de retorno
$ echo $?
1

O recurso não passa na verificação.

Deployments que usam a versão da API apps/v1, devem incluir um seletor que corresponda ao rótulo do pod. O manifesto acima não inclui um seletor, então o kubeval gerou um erro e saiu com um código diferente de zero.

É interessante ver o que acontece se executarmos kubectl apply -f com este manifesto?

Bem, vamos tentar:

$ kubectl apply -f kubeval-invalid.yaml
error: erro ao validar "kubeval-invalid.yaml": erro ao validar dados: ValidationError(Deployment.spec):
falta o campo obrigatório "selector" em io.k8s.api.apps.v1.DeploymentSpec; se você optar por ignorar esses erros,
vire a validação desativando --validate=false

É exatamente o erro que o kubeval avisou. Você pode corrigi-lo adicionando um seletor:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: http-echo
spec:
  replicas: 2
  selector:          # !!!
    matchLabels:     # !!!
      app: http-echo # !!!
  template:
    metadata:
      labels:
        app: http-echo
    spec:
      containers:
      - name: http-echo
        image: hashicorp/http-echo
        args: ["-text", "hello-world"]
        ports:
        - containerPort: 5678
---
apiVersion: v1
kind: Service
metadata:
  name: http-echo
spec:
  ports:
  - port: 5678
    protocol: TCP
    targetPort: 5678
  selector:
    app: http-echo

(base-valid.yaml)

A vantagem de ferramentas como kubeval é que esses erros podem ser detectados nas fases iniciais do ciclo de implantação.

Além disso, para essas verificações não é necessário acesso ao cluster: elas podem ser realizadas offline.

Por padrão, kubeval verifica os recursos quanto à conformidade com o esquema mais recente da API Kubernetes. No entanto, na maioria dos casos, pode ser necessário realizar a verificação para uma versão específica do Kubernetes. Isso pode ser feito com a flag --kubernetes-version:

$ kubeval --kubernetes-version 1.16.1 base-valid.yaml

Observe que a versão deve ser especificada no formato Major.Minor.Patch.

Para ver a lista de versões para as quais a verificação é suportada, consulte a esquema JSON no GitHub, que kubeval utiliza para validação. Se você precisar executar kubeval offline, baixe os esquemas e indique sua localização local usando a flag --schema-location.

Além de arquivos YAML individuais, kubeval também pode trabalhar com diretórios e stdin.

Além disso, Kubeval se integra facilmente ao pipeline CI. Aqueles que desejam realizar testes antes de enviar manifestos ao cluster ficarão felizes em saber que kubeval suporta três formatos de saída:

  1. Texto simples;
  2. JSON;
  3. Test Anything Protocol (TAP).

E qualquer um dos formatos pode ser usado para análise posterior da saída, a fim de gerar um resumo dos resultados desejados.

Uma das desvantagens do kubeval é que, atualmente, ele não consegue verificar a conformidade com Custom Resource Definitions (CRDs). No entanto, é possível configurar kubeval para ignorar esses.

Kubeval é uma ótima ferramenta para verificar e avaliar recursos; no entanto, deve-se enfatizar que a aprovação no teste não garante que o recurso esteja em conformidade com as melhores práticas.

Por exemplo, o uso da tag latest O conteúdo do container não corresponde às melhores práticas. No entanto, o kubeval não considera isso um erro e não reporta. Ou seja, a validação desse YAML será concluída sem avisos.

Mas e se for necessário avaliar o YAML e identificar violações como a tag latest? Как проверить YAML-файл на соответствие лучшим практикам?

2. Kube-score

Kube-score analisa os manifestos YAML e os avalia com base em testes embutidos. Esses testes são selecionados com base em recomendações de segurança e melhores práticas, como:

  • Executar containers não como root.
  • A presença de verificações de saúde dos pods.
  • Definição de requests e limits de recursos.

Os resultados dos testes incluem três níveis: OK, WARNING e CRITICAL.

O Kube-score pode ser testado online ou instalado localmente.

No momento da redação do artigo original, a versão mais recente do kube-score era 1.7.0.

Vamos testá-lo em nosso manifesto base-valid.yaml:

$ kube-score score base-valid.yaml

apps/v1/Deployment http-echo
[CRITICAL] Container Image Tag
  · http-echo -> Image com tag latest
      É recomendável usar uma tag fixa para evitar atualizações acidentais
[CRITICAL] Pod NetworkPolicy
  · O pod não possui uma política de rede correspondente
      Crie uma NetworkPolicy que direciona para este pod
[CRITICAL] Pod Probes
  · O container não possui um readinessProbe
      Um readinessProbe deve ser utilizado para indicar quando o serviço está pronto para receber tráfego.
      Sem isso, o Pod corre o risco de receber tráfego antes de ser iniciado. Também é usado durante
      rollouts, e pode evitar downtime se uma nova versão da aplicação falhar.
      Mais informações: https://github.com/zegl/kube-score/blob/master/README_PROBES.md
[CRITICAL] Container Security Context
  · http-echo -> Container não possui contexto de segurança configurado
      Defina securityContext para executar o container em um contexto mais seguro.
[CRITICAL] Container Resources
  · http-echo -> Limite de CPU não está definido
      Limites de recursos são recomendados para evitar DDOS de recursos. Defina resources.limits.cpu
  · http-echo -> Limite de memória não está definido
      Limites de recursos são recomendados para evitar DDOS de recursos. Defina resources.limits.memory
  · http-echo -> Request de CPU não está definido
      Requests de recursos são recomendados para garantir que a aplicação possa iniciar e funcionar sem
      falhar. Defina resources.requests.cpu
  · http-echo -> Request de memória não está definido
      Requests de recursos são recomendados para garantir que a aplicação possa iniciar e funcionar sem falhar.
      Defina resources.requests.memory
[CRITICAL] Deployment has PodDisruptionBudget
  · Nenhum PodDisruptionBudget correspondente foi encontrado
      É recomendável definir um PodDisruptionBudget para evitar downtime inesperado durante operações
      de manutenção do Kubernetes, como ao esvaziar um nó.
[WARNING] Deployment has host PodAntiAffinity
  · O deployment não tem um podAntiAffinity definido
      É recomendável definir um podAntiAffinity que impeça que múltiplos pods de um deployment sejam
      agendados no mesmo nó. Isso aumenta a disponibilidade caso o nó se torne indisponível.

O YAML passa nas verificações do kubeval, enquanto o kube-score aponta as seguintes deficiências:

  • Verificações de prontidão não estão configuradas.
  • Faltam requests e limits para recursos de CPU e memória.
  • Não estão definidos budgets de interrupção de pods.
  • Faltam regras de afinidade de pods. (anti-affinity) para maximizar a disponibilidade.
  • O contêiner é executado sob root.

Todas essas são observações razoáveis sobre as deficiências que devem ser corrigidas para que o Deployment se torne mais eficiente e confiável.

Time kube-score exibe informações em um formato legível, incluindo todos os tipos de violação WARNING e CRITICAL, o que ajuda muito durante o desenvolvimento.

Os interessados em usar esta ferramenta dentro do pipeline CI podem incluir uma saída mais compacta usando a flag --output-format ci (neste caso, os testes com resultado também são exibidos OK):

$ kube-score score base-valid.yaml --output-format ci

[OK] http-echo apps/v1/Deployment
[OK] http-echo apps/v1/Deployment
[CRITICAL] http-echo apps/v1/Deployment: (http-echo) O limite de CPU não está definido
[CRITICAL] http-echo apps/v1/Deployment: (http-echo) O limite de memória não está definido
[CRITICAL] http-echo apps/v1/Deployment: (http-echo) A solicitação de CPU não está definida
[CRITICAL] http-echo apps/v1/Deployment: (http-echo) A solicitação de memória não está definida
[CRITICAL] http-echo apps/v1/Deployment: (http-echo) Imagem com tag latest
[OK] http-echo apps/v1/Deployment
[CRITICAL] http-echo apps/v1/Deployment: O pod não possui uma política de rede correspondente
[CRITICAL] http-echo apps/v1/Deployment: O contêiner está faltando um readinessProbe
[CRITICAL] http-echo apps/v1/Deployment: (http-echo) O contêiner não tem um contexto de segurança configurado
[CRITICAL] http-echo apps/v1/Deployment: Nenhum PodDisruptionBudget correspondente foi encontrado
[WARNING] http-echo apps/v1/Deployment: O Deployment não tem um host podAntiAffinity definido
[OK] http-echo v1/Service
[OK] http-echo v1/Service
[OK] http-echo v1/Service
[OK] http-echo v1/Service

Semelhante ao kubeval, o kube-score retorna um código de saída diferente de zero quando um teste falha CRITICAL. Também é possível incluir esse tipo de tratamento para WARNING.

Além disso, há a possibilidade de verificar os recursos quanto à conformidade com várias versões da API (assim como no kubeval). No entanto, essa informação é 'hardcoded' dentro do kube-score: não é possível escolher outra versão do Kubernetes. Essa limitação pode se tornar um grande problema se você planeja atualizar o cluster ou se possui vários clusters com versões diferentes do K8s.

Observe que já existe uma issue com a proposta de implementar essa funcionalidade.

Mais informações sobre o kube-score podem ser encontradas em site oficial.

Os testes do kube-score são uma ótima ferramenta para implementar as melhores práticas, mas e se for necessário fazer alterações no teste ou adicionar suas próprias regras? Infelizmente, isso não pode ser feito.

O kube-score não é extensível: não é possível adicionar ou ajustar políticas.

Se você precisa escrever testes personalizados para verificar a conformidade com as políticas adotadas pela empresa, pode usar uma das seguintes quatro ferramentas: config-lint, copper, conftest ou polaris.

3. Config-lint

Config-lint é uma ferramenta para validação de arquivos de configuração nos formatos YAML, JSON, Terraform, CSV e manifestos do Kubernetes.

Você pode instalá-lo seguindo instruções no site do projeto.

A versão atual no momento da redação deste artigo é 1.5.0.

O Config-lint não contém testes embutidos para verificar manifestos do Kubernetes.

Para realizar quaisquer testes, é necessário criar regras correspondentes. Elas são registradas em arquivos YAML, chamados de 'conjuntos de regras' (rulesets), e têm a seguinte estrutura:

version: 1
description: Regras para arquivos de especificação do Kubernetes
type: Kubernetes
files:
  - "*.yaml"
rules:
   # lista de regras

(rule.yaml)

Vamos examiná-lo mais de perto:

  • Campo tipo indica qual tipo de configuração será utilizado pelo config-lint. Para manifestos K8s, isso é sempre Kubernetes.
  • No campo files além dos próprios arquivos, é possível indicar o diretório.
  • Campo rules é destinado a definir testes personalizados.

Suponha que você queira garantir que as imagens no Deployment sejam sempre baixadas de um repositório confiável como my-company.com/myapp:1.0. A regra para o config-lint que realiza essa verificação seria a seguinte:

- id: MY_DEPLOYMENT_IMAGE_TAG
  severity: FAILURE
  message: O Deployment deve usar uma tag de imagem válida
  resource: Deployment
  assertions:
    - every:
        key: spec.template.spec.containers
        expressions:
          - key: image
            op: starts-with
            value: "my-company.com/"

(rule-trusted-repo.yaml)

Para cada regra, devem ser especificados os seguintes atributos:

  • id — um identificador único para a regra;
  • severity — pode ser FAILURE, WARNING e NON_COMPLIANT;
  • message — se a regra for violada, o conteúdo desta linha será exibido;
  • resource — tipo de recurso ao qual esta regra se aplica;
  • assertions — uma lista de condições que serão avaliadas em relação a este recurso.

Na regra acima, a assertion chamada every verifica se todos os contêineres no Deployment (key: spec.templates.spec.containers) usam imagens confiáveis (ou seja, que começam com my-company.com/).

O ruleset completo é o seguinte:

version: 1
description: Regras para arquivos de especificação do Kubernetes
type: Kubernetes
files:
  - "*.yaml"
rules:

 - id: DEPLOYMENT_IMAGE_REPOSITORY # !!!
    severity: FAILURE
    message: O Deployment deve usar um repositório de imagem válido
    resource: Deployment
    assertions:
      - every:
          key: spec.template.spec.containers
          expressions:
            - key: image
              op: starts-with
              value: "my-company.com/"

(ruleset.yaml)

Para testar a regra, vamos salvá-la como check_image_repo.yaml. Vamos executar a verificação no arquivo base-valid.yaml:

$ config-lint -rules check_image_repo.yaml base-valid.yaml

[
  {
  "AssertionMessage": "Every expression fails: And expression fails: image does not start with my-company.com/",
  "Category": "",
  "CreatedAt": "2020-06-04T01:29:25Z",
  "Filename": "test-data/base-valid.yaml",
  "LineNumber": 0,
  "ResourceID": "http-echo",
  "ResourceType": "Deployment",
  "RuleID": "DEPLOYMENT_IMAGE_REPOSITORY",
  "RuleMessage": "Deployment must use a valid image repository",
  "Status": "FAILURE"
  }
]

A verificação falhou. Agora vamos verificar o próximo manifesto com um repositório de imagem correto:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: http-echo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: http-echo
  template:
    metadata:
      labels:
        app: http-echo
    spec:
      containers:
      - name: http-echo
         image: my-company.com/http-echo:1.0 # !!!
         args: ["-text", "hello-world"]
         ports:
         - containerPort: 5678

(image-valid-mycompany.yaml)

Executando o mesmo teste com o manifesto acima. Nenhum problema encontrado:

$ config-lint -rules check_image_repo.yaml image-valid-mycompany.yaml
[]

Config-lint é uma estrutura promissora que permite criar testes personalizados para verificar manifestos YAML do Kubernetes usando YAML DSL.

Mas e se for necessária uma lógica e testes mais complexos? As capacidades do YAML não são muito limitadas para isso? E se pudéssemos criar testes em uma linguagem de programação completa?

4. Copper

Copper V2 é uma estrutura para validação de manifestos usando testes personalizados (semelhante ao config-lint).

No entanto, ao contrário deste último, ele não utiliza YAML para descrever os testes. Em vez disso, os testes podem ser criados em JavaScript. Copper oferece uma biblioteca com várias ferramentas básicas, ajudando a ler informações sobre objetos Kubernetes e relatar erros.

A sequência de passos para instalar o Copper pode ser encontrada em documentação oficial.

2.0.1 — a versão mais recente deste utilitário no momento da redação do artigo original.

Assim como o config-lint, o Copper não tem testes embutidos. Vamos escrever um. Que ele verifique se os deployments usam imagens de contêineres exclusivamente de repositórios confiáveis como my-company.com.

Crie um arquivo check_image_repo.js com o seguinte conteúdo:

$$.forEach(function($){
    if ($.kind === 'Deployment') {
        $.spec.template.spec.containers.forEach(function(container) {
            var image = new DockerImage(container.image);
            if (image.registry.lastIndexOf('my-company.com/') != 0) {
                errors.add_error('no_company_repo',"Image " + $.metadata.name + " is not from my-company.com repo", 1)
            }
        });
    }
});

Agora, para verificar nosso manifesto base-valid.yaml, use o comando copper validate:

$ copper validate --in=base-valid.yaml --validator=check_image_tag.js

Check no_company_repo failed with severity 1 due to Image http-echo is not from my-company.com repo
Validation failed

É claro que com o Copper é possível realizar testes mais complexos — por exemplo, verificar nomes de domínio em manifests do Ingress ou rejeitar pods que estão sendo executados em modo privilegiado.

O Copper possui várias funções auxiliares:

  • DockerImage lê o arquivo de entrada especificado e cria um objeto com os seguintes atributos:
    • nome — nome da imagem,
    • tag — tag da imagem,
    • registry — registro de imagens,
    • registry_url — protocolo (https://) e registro de imagens,
    • fqin — localização completa da imagem.
  • Função findByName ajuda a encontrar um recurso pelo tipo especificado (kind) e pelo nome (nome) do arquivo de entrada.
  • Função findByLabels ajuda a encontrar um recurso pelo tipo especificado (kind) e pelos rótulos (labels).

Você pode encontrar todas as funções auxiliares disponíveis aqui.

Por padrão, ele carrega todo o arquivo YAML de entrada em uma variável $$ e a torna disponível para scripts (método familiar para aqueles que têm experiência com jQuery).

A principal vantagem do Copper é evidente: você não precisa dominar uma linguagem especializada e pode usar várias funcionalidades do JavaScript para criar seus próprios testes, como interpolação de strings, funções, etc.

Vale também mencionar que a versão atual do Copper funciona com a versão ES5 do motor JavaScript, e não com a ES6.

Mais detalhes estão disponíveis em página oficial do projeto.

No entanto, se você não gosta muito de JavaScript e prefere uma linguagem projetada especificamente para criar consultas e descrever políticas, você deve considerar o conftest.

5. Conftest

Conftest é um framework para verificar dados de configuração. Adequado para testar/verificar manifests do Kubernetes. Os testes são descritos usando uma linguagem de consulta especializada Rego.

Você pode instalar o conftest usando instruções, disponíveis no site do projeto.

No momento da redação deste artigo, a versão mais recente disponível era a 0.18.2.

Analogamente ao config-lint e ao copper, o conftest vem sem testes embutidos. Vamos experimentá-lo e escrever nossa própria política. Como nos exemplos anteriores, vamos verificar se as imagens de contêineres vêm de uma fonte confiável.

Crie um diretório conftest-checks, e dentro dele — um arquivo chamado check_image_registry.rego com o seguinte conteúdo:

package main

deny[msg] {

  input.kind == "Deployment"
  image := input.spec.template.spec.containers[_].image
  not startswith(image, "my-company.com/")
  msg := sprintf("imagem '%v' não vem do repositório my-company.com", [image])
}

Agora vamos testar base-valid.yaml via conftest:

$ conftest test --policy ./conftest-checks base-valid.yaml

FAIL - base-valid.yaml - imagem 'hashicorp/http-echo' não vem do repositório my-company.com
1 testes, 1 aprovado, 0 avisos, 1 falha

O teste falhou como esperado, pois as imagens vêm de uma fonte não confiável.

No arquivo Rego, definimos um bloco deny. Sua veracidade é considerada uma violação. Se houver vários blocos, deny o conftest os verifica de forma independente, e a veracidade de qualquer um dos blocos é interpretada como uma violação.

Além da saída padrão, o conftest suporta JSON, TAP e formato de tabela — uma possibilidade extremamente útil, se você precisa integrar relatórios em um pipeline CI existente. O formato desejado pode ser definido usando a flag --output.

Para facilitar o depuração de políticas, o conftest possui uma flag --trace. Ela exibe um rastreamento de como o conftest faz o parsing dos arquivos de políticas especificados.

As políticas do conftest podem ser publicadas e compartilhadas em repositórios OCI (Open Container Initiative) como artefatos.

Equipes push e pull permitindo publicar um artefato ou extrair um artefato existente de um repositório remoto. Vamos tentar publicar a política que criamos em um repositório local do Docker usando conftest push.

Inicie um repositório local do Docker:

$ docker run -it --rm -p 5000:5000 registry

Em outro terminal, vá até o diretório criado anteriormente conftest-checks e execute o seguinte comando:

$ conftest push 127.0.0.1:5000/amitsaha/opa-bundle-example:latest

Se o comando for bem-sucedido, você verá uma mensagem do seguinte tipo:

2020/06/10 14:25:43 empurrou o bundle com digest: sha256:e9765f201364c1a8a182ca637bc88201db3417bacc091e7ef8211f6c2fd2609c

Agora crie um diretório temporário e execute nele o comando conftest pull. Ele fará o download do pacote criado pelo comando anterior:

$ cd $(mktemp -d)
$ conftest pull 127.0.0.1:5000/amitsaha/opa-bundle-example:latest

No diretório temporário, aparecerá um subdiretório policy, contendo nosso arquivo de política:

$ tree
.
└── policy
  └── check_image_registry.rego

Os testes podem ser realizados diretamente do repositório:

$ conftest test --update 127.0.0.1:5000/amitsaha/opa-bundle-example:latest base-valid.yaml
..
FALHA - base-valid.yaml - a imagem 'hashicorp/http-echo' não vem do repositório my-company.com
2 testes, 1 aprovado, 0 avisos, 1 falha

Infelizmente, o DockerHub ainda não é suportado. Portanto, considere-se sortudo se você estiver usando Azure Container Registry (ACR) ou seu próprio repositório.

O formato dos artefatos é o mesmo que o dos pacotes Open Policy Agent (OPA), o que permite usar o conftest para executar testes a partir de pacotes OPA existentes.

Mais sobre compartilhamento de políticas e outras características do conftest pode ser encontrado em página oficial do projeto.

6. Polaris

A última ferramenta que será tratada neste artigo é Polaris. (O anúncio dele no ano passado já o traduzimos) já traduziu — nota do tradutor.)

O Polaris pode ser instalado em um cluster ou usado no modo de linha de comando. Como você já deve ter imaginado, ele permite a análise estática dos manifests do Kubernetes.

Ao operar no modo de linha de comando, testes integrados estão disponíveis, cobrindo áreas como segurança e melhores práticas (semelhante ao kube-score). Além disso, você pode criar seus próprios testes (como no config-lint, copper e conftest).

Em outras palavras, o Polaris combina os benefícios de ambas as categorias de ferramentas: com testes integrados e personalizados.

Para instalar o Polaris no modo de linha de comando, utilize as instruções disponíveis no site do projeto..

No momento da redação deste artigo, a versão 1.0.3 está disponível.

Após a instalação, você pode executar o polaris em um manifest base-valid.yaml usando o seguinte comando:

$ polaris audit --audit-path base-valid.yaml

Isso resultará em uma string no formato JSON com uma descrição detalhada dos testes realizados e seus resultados. A saída terá a seguinte estrutura:

{
  "PolarisOutputVersion": "1.0",
  "AuditTime": "0001-01-01T00:00:00Z",
  "SourceType": "Path",
  "SourceName": "test-data/base-valid.yaml",
  "DisplayName": "test-data/base-valid.yaml",
  "ClusterInfo": {
    "Version": "unknown",
    "Nodes": 0,
    "Pods": 2,
    "Namespaces": 0,
    "Controllers": 2
  },
  "Results": [
    /* lista longa */
  ]
}

A saída completa está disponível aqui.

Assim como o kube-score, o Polaris identifica problemas nas áreas em que o manifest não segue as melhores práticas:

  • Faltam verificações de saúde para os pods.
  • As tags para as imagens dos contêineres não foram especificadas.
  • O contêiner é executado sob root.
  • As solicitações e limites para memória e CPU não foram especificados.

Cada teste recebe um nível de criticidade dependendo de seus resultados: warning ou danger. Para saber mais sobre os testes integrados disponíveis, consulte documentação.

Se os detalhes não forem necessários, você pode especificar a flag --format score. Nesse caso, o Polaris retornará um número entre 1 e 100 — score (ou seja, a pontuação):

$ polaris audit --audit-path test-data/base-valid.yaml --format score
68

Quanto mais próxima a pontuação estiver de 100, maior será a conformidade. Se você verificar o código de saída do comando polaris audit, você verá que ele é igual a 0.

Fazer com que polaris audit o programa termine com um código diferente de zero pode ser feito usando duas flags:

  • A bandeira --set-exit-code-below-score aceita um valor limiar como argumento, variando de 1 a 100. Nesse caso, o comando terminará com código de saída 4 se a pontuação estiver abaixo do limiar. Isso é muito útil quando você tem um determinado valor limiar (digamos, 75) e precisa receber um alerta se a pontuação cair abaixo disso.
  • A bandeira --set-exit-code-on-danger resultará em que a equipe termine com o código 3 se um dos testes de danger falhar.

Agora, vamos tentar criar um teste personalizado que verifique se a imagem está sendo obtida de um repositório confiável. Os testes personalizados são definidos no formato YAML, e o teste em si é descrito usando JSON Schema.

O trecho a seguir de código YAML descreve um novo teste chamado checkImageRepo:

checkImageRepo:
  successMessage: O registro de imagens é válido
  failureMessage: O registro de imagens não é válido
  category: Imagens
  target: Contêiner
  schema:
    '$schema': http://json-schema.org/draft-07/schema
    type: object
    properties:
      image:
        type: string
        pattern: ^my-company.com/.+$

Vamos dar uma olhada mais de perto:

  • successMessage — esta linha será exibida se o teste for bem-sucedido;
  • failureMessage — esta mensagem será mostrada em caso de falha;
  • category — indica uma das categorias: Imagens, Verificações de Saúde, Segurança, Rede e Recursos;
  • alvo— define a qual tipo de objeto (spec) o teste se aplica. Valores possíveis: Contêiner, Pod ou Controlador;
  • O teste em si é definido dentro do objeto schema usando JSON schema. Neste teste, a palavra-chave pattern é usada para comparar a origem da imagem com o requerido.

Para executar o teste mencionado acima, é necessário criar a seguinte configuração do Polaris:

checks:
  checkImageRepo: danger
customChecks:
  checkImageRepo:
    successMessage: O registro de imagens é válido
    failureMessage: O registro de imagens não é válido
    category: Imagens
    target: Contêiner
    schema:
      '$schema': http://json-schema.org/draft-07/schema
      type: object
      properties:
        image:
          type: string
          pattern: ^my-company.com/.+$

(polaris-conf.yaml)

Vamos analisar o arquivo:

  • No campo checks definem os testes e seu nível de criticidade. Como é desejável receber um alerta quando a imagem é obtida de uma fonte não confiável, definimos aqui o nível danger.
  • O teste checkImageRepo é então definido dentro do objeto customChecks.

Salve o arquivo como custom_check.yaml. Agora você pode executar polaris audit com o manifesto YAML que requer verificação.

Vamos testar nosso manifesto base-valid.yaml:

$ polaris audit --config custom_check.yaml --audit-path base-valid.yaml

Time polaris audit executou apenas o teste personalizado definido acima, e ele não foi bem-sucedido.

Se corrigir a imagem para my-company.com/http-echo:1.0, o Polaris será bem-sucedido. Já há um manifesto com as alterações em repositório, então você pode verificar o comando anterior no manifesto image-valid-mycompany.yaml.

Agora surge a questão: como executar testes embutidos juntamente com os personalizados? É fácil! Basta adicionar os identificadores dos testes embutidos ao arquivo de configuração. Como resultado, ele terá a seguinte aparência:

verificações:
  cpuRequestsMissing: aviso
  cpuLimitsMissing: aviso
  # Outras verificações integradas..
  # ..
  # verificações personalizadas
  checkImageRepo: perigo # !!!
verificaçõesPersonalizadas:
  checkImageRepo:        # !!!
    successMessage: O registro de imagens é válido
    failureMessage: O registro de imagens não é válido
    category: Imagens
    target: Contêiner
    schema:
      '$schema': http://json-schema.org/draft-07/schema
      type: object
      properties:
        image:
          type: string
          pattern: ^my-company.com/.+$

(config_with_custom_check.yaml)

Um exemplo completo de arquivo de configuração está disponível aqui.

Verificar manifestos base-valid.yaml, usando testes integrados e personalizados, pode ser feito com o comando:

$ polaris audit --config config_with_custom_check.yaml --audit-path base-valid.yaml

Polaris complementa os testes integrados com os personalizados, combinando o melhor dos dois mundos.

Por outro lado, a incapacidade de usar linguagens mais poderosas, como Rego ou JavaScript, pode ser um fator limitante para criar testes mais sofisticados.

Mais informações sobre Polaris estão disponíveis em site do projeto.

Resumo

Embora haja muitas ferramentas para verificar e avaliar arquivos YAML do Kubernetes, é importante ter uma visão clara de como os testes serão projetados e executados.

Por exemplo, se pegarmos os manifestos do Kubernetes passando pelo pipeline, o kubeval poderia ser o primeiro passo nesse pipeline. Ele garantiria se as definições dos objetos estão de acordo com o esquema da API do Kubernetes.

Após a conclusão de tal verificação, seria possível passar para testes mais sofisticados, como conformidade com as melhores práticas e políticas específicas. E aqui o kube-score e o Polaris seriam úteis.

Para aqueles com requisitos complexos e a necessidade de configurar testes detalhadamente, o copper, config-lint e conftest seriam adequados..

Conftest e config-lint usam YAML para definir testes personalizados, enquanto o copper oferece acesso a uma linguagem de programação completa, tornando-o uma escolha bastante atraente.

Por outro lado, vale a pena usar uma dessas ferramentas e, portanto, criar todos os testes manualmente, ou preferir o Polaris e apenas adicionar o que for necessário? Não existe uma resposta definitiva para essa pergunta..

A tabela abaixo contém uma breve descrição de cada ferramenta:

A ferramenta
O propósito
Desvantagens
Testes personalizados

kubeval
Verifica manifestos YAML quanto à conformidade com uma versão específica do esquema da API
Não consegue lidar com CRD
Não

kube-score
Analisa manifestos YAML quanto à conformidade com as melhores práticas
Não é possível escolher sua versão da API do Kubernetes para verificar os recursos
Não

copper
Framework geral para criar testes JavaScript personalizados para manifestos YAML
Sem testes embutidos. Documentação pobre
Sim

config-lint
Framework geral para criar testes em uma linguagem orientada a objetos, embutida em YAML. Suporta vários formatos de configuração (por exemplo, Terraform)
Sem testes prontos. Asserções e funções embutidas podem ser insuficientes
Sim

conftest
Framework para criar testes personalizados em Rego (linguagem de consulta especializada). Permite compartilhar políticas através de pacotes OCI
Sem testes embutidos. É necessário aprender Rego. O Docker Hub não é suportado ao publicar políticas
Sim

Polaris
Analisa manifestos YAML para conformidade com as melhores práticas padrão. Permite criar testes personalizados usando JSON Schema
As capacidades de testes baseados em JSON Schema podem não ser suficientes
Sim

Como essas ferramentas não dependem de acesso ao cluster Kubernetes, elas são fáceis de instalar. Elas permitem filtrar arquivos de origem e fornecem feedback rápido aos autores de pull requests em projetos.

P.S. do tradutor

Confira também em nosso blog:

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster