Валидация Kubernetes YAML на соответствие лучшим практикам и политикам

Прим. перев.: С ростом числа YAML-конфигураций для K8s-окружений всё более актуальной становится потребность в их автоматизированной проверке. Автор этого обзора не просто отобрал существующие решения для этой задачи, но и на примере Deployment’а посмотрел, как они работают. Получилось весьма информативно для тех, кому эта тема интересна.

Валидация Kubernetes YAML на соответствие лучшим практикам и политикам

TL;DR: В статье сравниваются шесть статических инструментов проверки и оценки YAML-файлов Kubernetes на соответствие лучшим практикам и требованиям.

Рабочие нагрузки Kubernetes, как правило, определяются в форме YAML-документов. Одна из проблем с YAML’ом — сложность задания ограничений или взаимоотношений между файлами манифестов.

Что, если нам надо удостовериться, что все образы, развертываемые в кластере, берутся из доверенного реестра?

Как предотвратить отправку в кластер Deployment’ов, для которых не заданы PodDisruptionBudgets?

Интеграция статического тестирования позволяет выявлять ошибки и нарушения политик еще на стадии разработки. Тем самым повышаются гарантии правильности и безопасности определений ресурсов и повышается вероятность того, что production-нагрузки будут следовать лучшим практикам.

Экосистему статической проверки YAML-файлов Kubernetes можно разделить на следующие категории:

  • API-валидаторы. Инструменты в этой категории проверяют YAML-манифест на соответствие требованиям API-сервера Kubernetes.
  • Готовые тестеры. Инструменты из данной категории идут с готовыми тестами на безопасность, соответствие лучшим практикам и т.п.
  • Кастомные валидаторы. Представители этой категории позволяют создавать пользовательские тесты на различных языках, например, на Rego и Javascript.

В этой статье мы опишем и сравним шесть различных инструментов:

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

Что ж, давайте приступим!

Проверка Deployment’ов

Прежде чем приступить к сравнению инструментов, давайте создадим некоторую основу, на которой будем их тестировать.

Приведенный ниже манифест содержит ряд ошибок и несоответствий лучшим практикам: сколько из них вы сможете найти?

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)

Мы будем использовать этот YAML для сравнения различных инструментов.

Вышеприведенный манифест base-valid.yaml и другие манифесты из этой статьи можно найти в Git-репозитории.

Манифест описывает веб-приложение, основная задача которого — отвечать сообщением «Hello World» на порт 5678. Его можно развернуть следующей командой:

kubectl apply -f hello-world.yaml

А так — проверить работу:

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

Теперь перейдите на http://localhost:8080 и подтвердите, что приложение работает. Но следует ли оно лучшим практикам? Давайте проверим.

1. Kubeval

В основе kubeval лежит идея о том, что любое взаимодействие с Kubernetes происходит через его REST API. Другими словами, можно использовать схему API для проверки того, соответствует ли ей данный YAML. Давайте рассмотрим пример.

Инструкции по установке kubeval доступны на сайте проекта.

На момент написания оригинальной статьи была доступна версия 0.15.0.

После установки давайте «скормим» ему манифест, приведенный выше:

$ kubeval base-valid.yaml
PASS - base-valid.yaml contains a valid Deployment (http-echo)
PASS - base-valid.yaml contains a valid Service (http-echo)

В случае успеха kubeval завершит работу с exit-кодом 0. Проверить его можно следующим образом:

$ echo $?
0

Давайте теперь попробуем kubeval с другим манифестом:

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)

Можете на глаз определить проблему? Запускаем:

$ kubeval kubeval-invalid.yaml
WARN - kubeval-invalid.yaml contains an invalid Deployment (http-echo) - selector: selector is required
PASS - kubeval-invalid.yaml contains a valid Service (http-echo)

# проверим код возврата
$ echo $?
1

Ресурс не проходит проверку.

Deployment’ы, использующие версию API apps/v1, должны включать селектор, соответствующий метке pod’а. Манифест выше не включает селектор, поэтому kubeval сообщил об ошибке и вышел с ненулевым кодом.

Интересно, что произойдет, если выполнить kubectl apply -f с этим манифестом?

Что ж, давайте попробуем:

$ kubectl apply -f kubeval-invalid.yaml
error: error validating "kubeval-invalid.yaml": error validating data: ValidationError(Deployment.spec):
missing required field "selector" in io.k8s.api.apps.v1.DeploymentSpec; if you choose to ignore these errors,
turn validation off with --validate=false

Именно та ошибка, о которой предупреждал kubeval. Исправить ее можно, добавив селектор:

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)

Преимущество инструментов вроде kubeval в том, что подобные ошибки можно отлавливать на ранних стадиях цикла развертывания.

Кроме того, для этих проверок не нужен доступ в кластер: их можно проводить оффлайн.

По умолчанию kubeval проверяет ресурсы на соответствие самой последней схеме Kubernetes API. Однако в большинстве случаев вам может потребоваться провести проверку на соответствие конкретному релизу Kubernetes. Сделать это можно с помощью флага --kubernetes-version:

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

Обратите внимание, что версия должна указываться в формате Major.Minor.Patch.

Чтобы просмотреть список версий, для которых поддерживается проверка, обратитесь к JSON-схеме на GitHub, которую kubeval использует для валидации. Если нужно запустить kubeval оффлайн, скачайте схемы и укажите их локальное местоположение с помощью флага --schema-location.

Кроме отдельных YAML-файлов, kubeval также может работать с директориями и stdin.

Кроме того, Kubeval легко интегрируется в пайплайн CI. Желающие проводить тесты перед отправкой манифестов в кластер будут рады узнать, что kubeval поддерживает три формата вывода:

  1. Обычный текст;
  2. JSON;
  3. Test Anything Protocol (TAP).

И любой из форматов можно использовать для дальнейшего парсинга вывода, чтобы формировать сводку результатов желаемого вида.

Один из недостатков kubeval — в настоящее время он не умеет проверять на соответствие Custom Resource Definitions (CRDs). Однако можно настроить kubeval игнорировать их.

Kubeval — отличный инструмент для проверки и оценки ресурсов; правда, следует подчеркнуть, что успешное прохождение теста не гарантирует, что ресурс соответствует лучшим практикам.

Например, использование тега latest в контейнере не соответствует лучшим практикам. Однако kubeval не считает это ошибкой и не сообщает о ней. То есть проверка такого YAML завершится без предупреждений.

Но что, если нужно оценить YAML и выявить нарушения вроде тега latest? Как проверить YAML-файл на соответствие лучшим практикам?

2. Kube-score

Kube-score анализирует манифесты YAML и оценивает их по встроенным тестам. Эти тесты выбираются на основе рекомендаций по безопасности и лучших практик, например:

  • Запуск контейнера не под root’ом.
  • Наличие проверок здоровья pod’ов.
  • Задание request’ов и limit’ов ресурсов.

По итогам теста выдается три результата: OK, WARNING и CRITICAL.

Kube-score можно попробовать онлайн или установить его локально.

На момент написания оригинальной статьи самой свежей версией kube-score была 1.7.0.

Давайте испытаем его на нашем манифесте base-valid.yaml:

$ kube-score score base-valid.yaml

apps/v1/Deployment http-echo
[CRITICAL] Container Image Tag
  · http-echo -> Image with latest tag
      Using a fixed tag is recommended to avoid accidental upgrades
[CRITICAL] Pod NetworkPolicy
  · The pod does not have a matching network policy
      Create a NetworkPolicy that targets this pod
[CRITICAL] Pod Probes
  · Container is missing a readinessProbe
      A readinessProbe should be used to indicate when the service is ready to receive traffic.
      Without it, the Pod is risking to receive traffic before it has booted. It is also used during
      rollouts, and can prevent downtime if a new version of the application is failing.
      More information: https://github.com/zegl/kube-score/blob/master/README_PROBES.md
[CRITICAL] Container Security Context
  · http-echo -> Container has no configured security context
      Set securityContext to run the container in a more secure context.
[CRITICAL] Container Resources
  · http-echo -> CPU limit is not set
      Resource limits are recommended to avoid resource DDOS. Set resources.limits.cpu
  · http-echo -> Memory limit is not set
      Resource limits are recommended to avoid resource DDOS. Set resources.limits.memory
  · http-echo -> CPU request is not set
      Resource requests are recommended to make sure that the application can start and run without
      crashing. Set resources.requests.cpu
  · http-echo -> Memory request is not set
      Resource requests are recommended to make sure that the application can start and run without crashing.
      Set resources.requests.memory
[CRITICAL] Deployment has PodDisruptionBudget
  · No matching PodDisruptionBudget was found
      It is recommended to define a PodDisruptionBudget to avoid unexpected downtime during Kubernetes
      maintenance operations, such as when draining a node.
[WARNING] Deployment has host PodAntiAffinity
  · Deployment does not have a host podAntiAffinity set
      It is recommended to set a podAntiAffinity that stops multiple pods from a deployment from
      being scheduled on the same node. This increases availability in case the node becomes unavailable.

YAML проходит проверки kubeval, в то время как kube-score указывает на следующие недостатки:

  • Не настроены проверки готовности.
  • Отсутствуют request’ы и limit’ы на ресурсы CPU и память.
  • Не заданы Pod disruption budgets.
  • Отсутствуют правила раздельного существования (anti-affinity) для максимизации доступности.
  • Контейнер выполняется под root’ом.

Всё это — резонные замечания о недостатках, которые следует устранить, чтобы Deployment стал более эффективным и надежным.

Команда kube-score выводит информацию в удобочитаемой форме с включением всех нарушений типа WARNING и CRITICAL, что очень помогает во время разработки.

Желающие использовать этот инструмент в рамках пайплайна CI могут включить более сжатый вывод с помощью флага --output-format ci (в этом случае выводятся также тесты с результатом 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) CPU limit is not set
[CRITICAL] http-echo apps/v1/Deployment: (http-echo) Memory limit is not set
[CRITICAL] http-echo apps/v1/Deployment: (http-echo) CPU request is not set
[CRITICAL] http-echo apps/v1/Deployment: (http-echo) Memory request is not set
[CRITICAL] http-echo apps/v1/Deployment: (http-echo) Image with latest tag
[OK] http-echo apps/v1/Deployment
[CRITICAL] http-echo apps/v1/Deployment: The pod does not have a matching network policy
[CRITICAL] http-echo apps/v1/Deployment: Container is missing a readinessProbe
[CRITICAL] http-echo apps/v1/Deployment: (http-echo) Container has no configured security context
[CRITICAL] http-echo apps/v1/Deployment: No matching PodDisruptionBudget was found
[WARNING] http-echo apps/v1/Deployment: Deployment does not have a host podAntiAffinity set
[OK] http-echo v1/Service
[OK] http-echo v1/Service
[OK] http-echo v1/Service
[OK] http-echo v1/Service

По аналогии с kubeval, kube-score возвращает ненулевой код выхода при наличии теста, завершившегося ошибкой CRITICAL. Также можно включить подобную обработку и для WARNING.

Кроме того, имеется возможность проверки ресурсов на соответствие различным версиям API (как и в kubeval). Однако эта информация за’hardcode’на в самом kube-score: выбрать другую версию Kubernetes нельзя. Подобное ограничение может стать большой проблемой, если вы намерены обновить кластер или у вас есть несколько кластеров с различными версиями K8s.

Обратите внимание, что уже есть issue с предложением реализовать эту возможность.

Подробнее о kube-score можно узнать на официальном сайте.

Тесты kube-score — отличный инструмент для внедрения лучших практик, но что, если необходимо внести изменения в тест или добавить собственные правила? Увы, этого не получится сделать.

Kube-score не расширяем: в него нельзя добавить политики или подстроить их.

Если требуется писать пользовательские тесты для проверок на соответствие политикам, принятым в компании, можно использовать один из следующих четырех инструментов: config-lint, copper, conftest или polaris.

3. Config-lint

Config-lint — это инструмент для валидации конфигурационных файлов формата YAML, JSON, Terraform, CSV и манифестов Kubernetes.

Установить его можно с помощью инструкций на сайте проекта.

Текущий релиз по состоянию на момент написания оригинальной статьи — 1.5.0.

Config-lint не содержит встроенных тестов для проверки манифестов Kubernetes.

Для проведения любых тестов необходимо создавать соответствующие правила. Они записываются в YAML-файлы, называемые «наборами правил» (rulesets), и имеют следующую структуру:

version: 1
description: Rules for Kubernetes spec files
type: Kubernetes
files:
  - "*.yaml"
rules:
   # список правил

(rule.yaml)

Давайте изучим ее повнимательнее:

  • Поле type указывает, какой тип конфигурации будет использовать config-lint. Для манифестов K8s это всегда Kubernetes.
  • В поле files кроме самих файлов можно указать директорию.
  • Поле rules предназначено для задания пользовательских тестов.

Допустим, вы хотите убедиться, что образы в Deployment’е всегда скачиваются из доверенного репозитория вроде my-company.com/myapp:1.0. Правило для config-lint, осуществляющее подобную проверку, будет выглядеть следующим образом:

- id: MY_DEPLOYMENT_IMAGE_TAG
  severity: FAILURE
  message: Deployment must use a valid image tag
  resource: Deployment
  assertions:
    - every:
        key: spec.template.spec.containers
        expressions:
          - key: image
            op: starts-with
            value: "my-company.com/"

(rule-trusted-repo.yaml)

Для каждого правила должны быть указаны следующие атрибуты:

  • id — уникальный идентификатор правила;
  • severity — может быть FAILURE, WARNING и NON_COMPLIANT;
  • message — при нарушении правила отображается содержимое этой строки;
  • resource — тип ресурса, к которому применяется это правило;
  • assertions — список условий, которые будут оцениваться в отношении данного ресурса.

В правиле выше assertion под названием every проверяет, что все контейнеры в Deployment’е (key: spec.templates.spec.containers) используют доверенные образы (т.е., начинающиеся с my-company.com/).

Полный ruleset выглядит следующим образом:

version: 1
description: Rules for Kubernetes spec files
type: Kubernetes
files:
  - "*.yaml"
rules:

 - id: DEPLOYMENT_IMAGE_REPOSITORY # !!!
    severity: FAILURE
    message: Deployment must use a valid image repository
    resource: Deployment
    assertions:
      - every:
          key: spec.template.spec.containers
          expressions:
            - key: image
              op: starts-with
              value: "my-company.com/"

(ruleset.yaml)

Чтобы испытать тест, давайте сохраним его как check_image_repo.yaml. Запустим проверку над файлом 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"
  }
]

Проверка завершилась неудачно. Теперь давайте проверим следующий манифест с правильным репозиторием образов:

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)

Запускаем тот же самый тест с приведенным выше манифестом. Проблем не обнаружено:

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

Config-lint — перспективный фреймворк, позволяющий создавать собственные тесты для проверки YAML-манифестов Kubernetes с помощью YAML DSL.

Но что делать, если требуется более сложная логика и тесты? Разве возможности YAML не слишком малы для этого? Что, если можно было бы создавать тесты на полноценном языке программирования?

4. Copper

Copper V2 — это фреймворк для валидации манифестов с помощью пользовательских тестов (аналог config-lint).

Однако от последнего он отличается тем, что не использует YAML для описания тестов. Вместо этого тесты можно создавать на JavaScript. Copper предоставляет библиотеку с несколькими базовыми инструментами, помогающими считывать информацию об объектах Kubernetes и сообщать об ошибках.

Последовательность шагов для установки Copper можно найти в официальной документации.

2.0.1 — самый свежий релиз этой утилиты на момент написания оригинальной статьи.

Как и config-lint, Copper не имеет встроенных тестов. Давайте напишем один. Пусть он проверяет, что deployment’ы используют контейнерные образы исключительно из доверенных репозиториев вроде my-company.com.

Создайте файл check_image_repo.js со следующим содержимым:

$$.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)
            }
        });
    }
});

Теперь, чтобы проверить наш манифест base-valid.yaml, используйте команду 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

Понятно, что с помощью copper можно проводить более сложные тесты — например, проверять доменные имена в манифестах Ingress или отвергать pod’ы, работающие в привилегированном режиме.

В Copper встроены различные служебные функции:

  • DockerImage считывает указанный входной файл и создает объект со следующими атрибутами:
    • name — имя образа,
    • tag — тег образа,
    • registry — реестр образов,
    • registry_url — протокол (https://) и реестр образов,
    • fqin — полное местоположение образа.
  • Функция findByName помогает найти ресурс по заданному типу (kind) и имени (name) из входного файла.
  • Функция findByLabels помогает найти ресурс по указанному типу (kind) и меткам (labels).

Со всеми доступными служебными функциями можно ознакомиться здесь.

По умолчанию он загружает весь входной YAML-файл в переменную $$ и делает ее доступной для скриптов (знакомый метод для тех, кто имеет опыт работы с jQuery).

Главный плюс Copper очевиден: вам не нужно осваивать специализированный язык и можно пользоваться различными возможностями JavaScript для создания собственных тестов, такими как интерполяция строк, функции и т.д.

Следует также отметить, что текущая версия Copper работает с версией ES5 движка JavaScript, а не с ES6.

Подробности доступны на официальном сайте проекта.

Впрочем, если вы не очень любите JavaScript и предпочитаете язык, специально предназначенный для создания запросов и описания политик, вам следует обратить внимание на conftest.

5. Conftest

Conftest — это фреймворк для проверки конфигурационных данных. Подходит и для тестирования/верификации манифестов Kubernetes. Тесты описываются с помощью специализированного языка запросов Rego.

Установить conftest можно с помощью инструкций, приведенных на сайте проекта.

На момент написания оригинальной статьи самой последней доступной версией была 0.18.2.

По аналогии с config-lint и copper, conftest идет без каких-либо встроенных тестов. Давайте попробуем его и напишем собственную политику. Как и в предыдущих примерах, будем проверять, берутся ли образы контейнеров из надежного источника.

Создайте директорию conftest-checks, а в ней — файл с именем check_image_registry.rego со следующим содержимым:

package main

deny[msg] {

  input.kind == "Deployment"
  image := input.spec.template.spec.containers[_].image
  not startswith(image, "my-company.com/")
  msg := sprintf("image '%v' doesn't come from my-company.com repository", [image])
}

Теперь давайте протестируем base-valid.yaml через conftest:

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

FAIL - base-valid.yaml - image 'hashicorp/http-echo' doesn't come from my-company.com repository
1 tests, 1 passed, 0 warnings, 1 failure

Тест ожидаемо провалился, поскольку образы поступают из недоверенного источника.

В файле Rego мы задаем блок deny. Его истинность рассматривается как нарушение. Если блоков deny несколько, conftest проверяет их независимо друг от друга, и истинность любого из блоков трактуется как нарушение.

Помимо вывода по умолчанию conftest поддерживает JSON, TAP и табличный формат — крайне полезная возможность, если нужно встроить отчеты в существующий пайплайн CI. Задать нужный формат можно с помощью флага --output.

Для облегчения отладки политик в conftest имеется флаг --trace. Он выводит трассировку того, как conftest парсит указанные файлы политик.

Политики conftest можно публиковать и делиться ими в OCI-реестрах (Open Container Initiative) в виде артефактов.

Команды push и pull позволяют опубликовать артефакт или извлечь существующий артефакт из удаленного реестра. Давайте попробуем опубликовать созданную нами политику в локальный реестр Docker с помощью conftest push.

Запустите локальный реестр Docker:

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

В другом терминале перейдите в созданную ранее директорию conftest-checks и выполните следующую команду:

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

Если команда прошла успешно, вы увидите сообщение следующего типа:

2020/06/10 14:25:43 pushed bundle with digest: sha256:e9765f201364c1a8a182ca637bc88201db3417bacc091e7ef8211f6c2fd2609c

Теперь создайте временную директорию и выполните в ней команду conftest pull. Она скачает в нее пакет, созданный предыдущей командой:

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

Во временной директории появится подкаталог policy, содержащий наш файл с политикой:

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

Тесты можно проводить напрямую из репозитория:

$ conftest test --update 127.0.0.1:5000/amitsaha/opa-bundle-example:latest base-valid.yaml
..
FAIL - base-valid.yaml - image 'hashicorp/http-echo' doesn't come from my-company.com repository
2 tests, 1 passed, 0 warnings, 1 failure

К сожалению, DockerHub пока не поддерживается. Поэтому считайте, что вам повезло, если вы используете Azure Container Registry (ACR) или свой собственный реестр.

Формат артефактов — такой же, как у пакетов Open Policy Agent (OPA), что позволяет использовать conftest для запуска тестов из существующих пакетов OPA.

Больше о совместном использовании политик и других особенностях conftest можно узнать на официальном сайте проекта.

6. Polaris

Последний инструмент, о котором пойдет речь в этой статье, — это Polaris. (Его прошлогодний анонс мы уже переводилиприм. перев.)

Polaris можно установить в кластер или использовать в режиме командной строки. Как вы уже догадались, он позволяет статически анализировать манифесты Kubernetes.

При работе в режиме командной строки доступны встроенные тесты, охватывающие такие области, как безопасность и лучшие практики (по аналогии с kube-score). Кроме того, можно создавать собственные тесты (как в config-lint, copper и conftest).

Другими словами, Polaris сочетает в себе плюсы обеих категорий инструментов: со встроенными и пользовательскими тестами.

Для установки Polaris в режиме командной строки воспользуйтесь инструкциями на сайте проекта.

На момент написания оригинальной статьи доступна версия 1.0.3.

После завершения установки можно запустить polaris на манифесте base-valid.yaml с помощью следующей команды:

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

Она выведет строку в формате JSON с подробным описанием выполненных тестов и их результатами. Вывод будет иметь следующую структуру:

{
  "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": [
    /* длинный список */
  ]
}

Полный вывод доступен здесь.

Как и kube-score, Polaris выявляет проблемы в тех областях, в которых манифест не соответствует лучшим практикам:

  • Отсутствуют проверки здоровья pod’ов.
  • Не указаны теги для контейнерных образов.
  • Контейнер выполняется под root’ом.
  • Не указаны request’ы и limit’ы для памяти и CPU.

Каждому тесту в зависимости от его результатов присваивается степень критичности: warning или danger. Чтобы узнать больше об имеющихся встроенных тестах, обратитесь к документации.

Если подробности не нужны, можно указать флаг --format score. В этом случае Polaris выведет число в диапазоне от 1 до 100 — score (т.е. оценку):

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

Чем оценка ближе к 100, тем выше степень соответствия. Если проверить exit-код команды polaris audit, окажется, что он равен 0.

Заставить polaris audit завершать работу с ненулевым кодом можно с помощью двух флагов:

  • Флаг --set-exit-code-below-score принимает в качестве аргумента пороговое значение в диапазоне 1-100. В этом случае команда завершится с exit-кодом 4, если оценка окажется ниже пороговой. Это очень удобно, когда у вас есть некое пороговое значение (скажем, 75), и вам необходимо получить alert, если оценка опустится ниже.
  • Флаг --set-exit-code-on-danger приведет к тому, что команда завершится с кодом 3, если один из danger-тестов завершится неудачно.

Теперь давайте попробуем создать пользовательский тест, проверяющий, берется ли образ из доверенного репозитория. Пользовательские тесты задаются в формате YAML, а сам тест описывается с помощью JSON Schema.

Следующий фрагмент YAML-кода описывает новый тест, называемый checkImageRepo:

checkImageRepo:
  successMessage: Image registry is valid
  failureMessage: Image registry is not valid
  category: Images
  target: Container
  schema:
    '$schema': http://json-schema.org/draft-07/schema
    type: object
    properties:
      image:
        type: string
        pattern: ^my-company.com/.+$

Давайте посмотрим на него поближе:

  • successMessage — эта строка будет выведена, если тест завершится успешно;
  • failureMessage — это сообщение будет показано в случае неудачи;
  • category — указывает на одну из категорий: Images, Health Checks, Security, Networking и Resources;
  • target—- определяет, к какому типу объекту (spec) применяется тест. Возможные значения: Container, Pod или Controller;
  • Сам тест задается в объекте schema с помощью JSON schema. В данном тесте ключевое слово pattern используется для сравнения источника образа с требуемым.

Для запуска вышеприведенного теста необходимо создать следующую конфигурацию Polaris:

checks:
  checkImageRepo: danger
customChecks:
  checkImageRepo:
    successMessage: Image registry is valid
    failureMessage: Image registry is not valid
    category: Images
    target: Container
    schema:
      '$schema': http://json-schema.org/draft-07/schema
      type: object
      properties:
        image:
          type: string
          pattern: ^my-company.com/.+$

(polaris-conf.yaml)

Разберем файл:

  • В поле checks прописываются тесты и их уровень критичности. Поскольку желательно получать предупреждение, когда образ берется из ненадежного источника, ставим здесь уровень danger.
  • Сам тест checkImageRepo затем прописывается в объекте customChecks.

Сохраните файл как custom_check.yaml. Теперь можно запустить polaris audit с YAML-манифестом, требующим проверки.

Протестируем наш манифест base-valid.yaml:

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

Команда polaris audit выполнила только пользовательский тест, заданный выше, и он не увенчался успехом.

Если исправить образ на my-company.com/http-echo:1.0, Polaris завершится успешно. Манифест с изменениями уже есть в репозитории, так что вы можете проверить предыдущую команду на манифесте image-valid-mycompany.yaml.

Теперь возникает вопрос: как запускать встроенные тесты совместно с пользовательскими? Легко! Просто надо добавить идентификаторы встроенных тестов в файл конфигурации. В результате он приобретет следующий вид:

checks:
  cpuRequestsMissing: warning
  cpuLimitsMissing: warning
  # Other inbuilt checks..
  # ..
  # custom checks
  checkImageRepo: danger # !!!
customChecks:
  checkImageRepo:        # !!!
    successMessage: Image registry is valid
    failureMessage: Image registry is not valid
    category: Images
    target: Container
    schema:
      '$schema': http://json-schema.org/draft-07/schema
      type: object
      properties:
        image:
          type: string
          pattern: ^my-company.com/.+$

(config_with_custom_check.yaml)

Пример полного файла конфигурации доступен здесь.

Проверить манифест base-valid.yaml, используя встроенные и пользовательские тесты, можно с помощью команды:

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

Polaris дополняет встроенные тесты пользовательскими, тем самым сочетая лучшее из двух миров.

С другой стороны, невозможность использовать более мощные языки, такие как Rego или JavaScript, может стать ограничивающим фактором, препятствующим созданию более изощренных тестов.

Дополнительная информация о Polaris доступна на сайте проекта.

Резюме

Хотя существует множество инструментов для проверки и оценки YAML-файлов Kubernetes, важно иметь четкое представление о том, как тесты будут проектироваться и выполняться.

Например, если взять манифесты Kubernetes, проходящие через пайплайн, kubeval мог бы стать первым шагом в таком пайплайне. Он бы следил за тем, соответствуют ли определения объектов схеме API Kubernetes.

После завершения подобной проверки можно было бы перейти к более изощренным тестам, таким как соответствие стандартным лучшим практикам и особым политикам. И здесь бы пригодились kube-score и Polaris.

Тем, у кого сложные требования и есть необходимость детально настраивать тесты, подошли бы copper, config-lint и conftest.

Conftest и config-lint используют YAML для задания пользовательских тестов, а copper дает доступ к полноценному языку программирования, что делает его довольно привлекательным выбором.

С другой стороны, стоит ли воспользоваться одним из этих инструментов и, следовательно, создавать все тесты вручную, или предпочесть Polaris, и дописать в него только то, что нужно? Однозначного ответа на этот вопрос нет.

Таблица ниже содержит краткое описание каждого инструмента:

Инструмент
Предназначение
Недостатки
Пользовательские тесты

kubeval
Проверяет YAML-манифесты на соответствие определенной версии схемы API
Не умеет работать с CRD
Нет

kube-score
Анализирует манифесты YAML на соответствие лучшим практикам
Нельзя выбрать свою версию API Kubernetes для проверки ресурсов
Нет

copper
Общий фреймфорк для создания собственных JavaScript-тестов для YAML-манифестов
Нет встроенных тестов. Скудная документация
Да

config-lint
Общий фреймворк для создания тестов на предметно-ориентированном языке, встроенном в YAML. Поддерживает различные форматы конфигураций (например, Terraform)
Нет готовых тестов. Встроенных assertions и функций может оказаться недостаточно
Да

conftest
Фреймворк для создания собственных тестов на Rego (специализированном языке запросов). Позволяет делиться политиками через OCI bundles
Нет встроенных тестов. Приходится изучать Rego. Docker Hub не поддерживается при публикации политик
Да

Polaris
Анализирует YAML-манифесты на соответствие стандартным лучшим практикам. Позволяет создавать собственные тесты с помощью JSON Schema
Возможностей тестов, основанных на JSON Schema, может не хватить
Да

Поскольку эти инструменты не зависят от доступа в кластер Kubernetes, их легко устанавливать. Они позволяют фильтровать исходные файлы и обеспечивают быструю обратную связь авторам pull request’ов в проектах.

P.S. от переводчика

Читайте также в нашем блоге:

Источник: habr.com