Walidacja YAML Kubernetes zgodnie z najlepszymi praktykami i politykami

Przyp. tłum.: W miarę rosnącej liczby konfiguracji YAML dla środowisk K8s, coraz ważniejsza staje się potrzeba ich automatycznego sprawdzania. Autor tego przeglądu nie tylko wybrał istniejące rozwiązania do tego zadania, ale również na przykładzie Deploymentu przyjrzał się ich działaniu. Okazało się to bardzo pouczające dla tych, których temat interesuje.

Walidacja YAML Kubernetes zgodnie z najlepszymi praktykami i politykami

TL;DR: W artykule porównano sześć statycznych narzędzi do sprawdzania i oceny plików YAML Kubernetes pod kątem zgodności z najlepszymi praktykami i wymaganiami.

Obciążenia robocze Kubernetes zazwyczaj definiowane są w postaci dokumentów YAML. Jednym z problemów z YAML-em jest złożoność określania ograniczeń lub relacji między plikami manifestów.

Co zrobić, jeśli musimy upewnić się, że wszystkie obrazy wdrażane w klastrze pochodzą z zaufanego rejestru?

Jak zapobiec wysyłaniu do klastra Deploymentów, dla których nie zostały określone PodDisruptionBudgets?

Integracja testowania statycznego pozwala wykrywać błędy i naruszenia polityki już na etapie rozwoju. W ten sposób zwiększa się pewność poprawności i bezpieczeństwa definicji zasobów, a także rośnie prawdopodobieństwo, że obciążenia produkcyjne będą przestrzegały najlepszych praktyk.

Ekosystem statycznej weryfikacji plików YAML Kubernetes można podzielić na następujące kategorie:

  • Weryfikatory API. Narzędzia w tej kategorii sprawdzają manifest YAML pod kątem zgodności z wymaganiami serwera API Kubernetes.
  • Gotowe testery. Narzędzia z tej kategorii mają gotowe testy dotyczące bezpieczeństwa, zgodności z najlepszymi praktykami itd.
  • Niestandardowe weryfikatory. Przedstawiciele tej kategorii pozwalają na tworzenie niestandardowych testów w różnych językach, np. w Rego i Javascript.

W tym artykule opiszemy i porównamy sześć różnych narzędzi:

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

Cóż, zaczynajmy!

Sprawdzanie Deploymentów

Zanim przejdziemy do porównania narzędzi, stwórzmy pewną podstawę, na której będziemy je testować.

Podany poniżej manifest zawiera szereg błędów i niezgodności z najlepszymi praktykami: ile z nich możesz znaleźć?

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)

Będziemy używać tego YAML do porównania różnych narzędzi.

Powyższy manifest base-valid.yaml i inne manifesty z tego artykułu można znaleźć w repozytorium Git.

Manifest opisuje aplikację internetową, której głównym zadaniem jest odpowiedzieć komunikatem „Hello World” na porcie 5678. Można go wdrożyć następującą komendą:

kubectl apply -f hello-world.yaml

A oto jak sprawdzić działanie:

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

Teraz przejdź do http://localhost:8080 i potwierdź, że aplikacja działa. Ale czy stosuje najlepsze praktyki? Sprawdźmy to.

1. Kubeval

Na podstawie kubeval leży idea, że każde interakcje z Kubernetes odbywają się przez jego REST API. Innymi słowy, można użyć schematu API do weryfikacji, czy dany YAML jest zgodny. Przyjrzyjmy się przykładowi.

Instrukcje instalacji kubeval są dostępne na stronie projektu.

W momencie pisania oryginalnego artykułu dostępna była wersja 0.15.0.

Po instalacji „podajmy” mu manifest podany powyżej:

$ kubeval base-valid.yaml
PASS - base-valid.yaml zawiera ważny Deployment (http-echo)
PASS - base-valid.yaml zawiera ważny Service (http-echo)

W przypadku sukcesu kubeval zakończy pracę z kodem wyjścia 0. Można to sprawdzić w następujący sposób:

$ echo $?
0

Teraz spróbujmy kubeval z innym manifestem:

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)

Czy możesz dostrzec problem gołym okiem? Uruchamiamy:

$ kubeval kubeval-invalid.yaml
WARN - kubeval-invalid.yaml zawiera nieprawidłowy Deployment (http-echo) - selektor: selektor jest wymagany
PASS - kubeval-invalid.yaml zawiera ważny Service (http-echo)

# sprawdźmy kod zwrotu
$ echo $?
1

Zasób nie przechodzi weryfikacji.

Deploymenty korzystające z wersji API apps/v1, muszą zawierać selektor odpowiadający etykiecie podu. Powyższy manifest nie zawiera selektora, dlatego kubeval zgłosił błąd i zakończył działanie z kodem różnym od zera.

Ciekawe, co się stanie, jeśli wykonasz kubectl apply -f z tym manifestem?

Cóż, spróbujmy:

$ kubectl apply -f kubeval-invalid.yaml
błąd: błąd walidacji "kubeval-invalid.yaml": błąd walidacji danych: ValidationError(Deployment.spec):
brak wymaganego pola "selector" w io.k8s.api.apps.v1.DeploymentSpec; jeśli chcesz zignorować te błędy,
wyłącz walidację za pomocą --validate=false

To właśnie ten błąd, o którym ostrzega kubeval. Można go naprawić, dodając selektor:

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)

Zaletą narzędzi takich jak kubeval jest to, że takie błędy można wychwytywać na wczesnych etapach cyklu wdrożenia.

Ponadto, do tych sprawdzeń nie jest wymagany dostęp do klastra: można je przeprowadzać offline.

Domyślnie kubeval sprawdza zasoby pod kątem zgodności z najnowszym schematem API Kubernetes. Jednak w wielu przypadkach może być konieczne sprawdzenie zgodności z określoną wersją Kubernetes. Można to zrobić za pomocą flagi --kubernetes-version:

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

Zauważ, że wersja musi być podana w formacie Major.Minor.Patch.

Aby zobaczyć listę wersji, dla których dostępne jest sprawdzenie zgodności, odwiedź schematu JSON na GitHubie, który kubeval używa do walidacji. Jeśli potrzebujesz uruchomić kubeval offline, pobierz schemy i podaj ich lokalizację lokalną za pomocą flagi --schema-location.

Oprócz pojedynczych plików YAML, kubeval może również pracować z katalogami i stdin.

Ponadto, Kubeval łatwo integruje się z pipeline'ami CI. Ciągle testujących przed przesłaniem manifestów do klastra ucieszy informacja, że kubeval obsługuje trzy formaty wyjściowe:

  1. Zwykły tekst;
  2. JSON;
  3. Test Anything Protocol (TAP).

I dowolny z formatów można wykorzystać do dalszego parsowania wyjścia, aby utworzyć podsumowanie wyników w pożądanym formacie.

Jedną z wad kubeval jest to, że obecnie nie potrafi on sprawdzać zgodności z Custom Resource Definitions (CRDs). Można jednak skonfigurować kubeval, aby je ignorować..

Kubeval to świetne narzędzie do sprawdzania i oceny zasobów; jednak należy podkreślić, że pomyślne przejście testu nie gwarantuje, że zasób spełnia najlepsze praktyki.

Na przykład użycie tagu latest W kontenerze nie spełnia najlepszych praktyk. Jednak kubeval nie uznaje tego za błąd i nie zgłasza. Oznacza to, że sprawdzenie takiego YAML zakończy się bez ostrzeżeń.

Ale co, jeśli trzeba ocenić YAML i wykryć naruszenia, takie jak tag latest? Как проверить YAML-файл на соответствие лучшим практикам?

2. Kube-score

Kube-score analizuje manifesty YAML i ocenia je na podstawie wbudowanych testów. Te testy są wybierane na podstawie zaleceń dotyczących bezpieczeństwa i najlepszych praktyk, na przykład:

  • Uruchomienie kontenera nie jako root.
  • Obecność kontroli zdrowia dla podów.
  • Określenie requestów i limitów zasobów.

Po zakończeniu testu wydawane są trzy wyniki: OK, WARNING i CRITICAL.

Kube-score można wypróbować online lub zainstalować lokalnie.

W momencie pisania oryginalnego artykułu najnowsza wersja kube-score wynosiła 1.7.0.

Wypróbujmy go na naszym manifeście base-valid.yaml:

$ kube-score score base-valid.yaml

apps/v1/Deployment http-echo
[CRITICAL] Tag obrazu kontenera
  · http-echo -> Obraz z tagiem latest
      Zaleca się używanie stałego tagu, aby uniknąć niezamierzonych aktualizacji
[CRITICAL] Pod NetworkPolicy
  · Pod nie ma dopasowanej polityki sieciowej
      Utwórz NetworkPolicy, która będzie celować w ten pod
[CRITICAL] Pod Probes
  · Kontener nie ma readinessProbe
      readinessProbe powinien być używany, aby wskazać, kiedy usługa jest gotowa na przyjmowanie ruchu.
      Bez niego Pod ryzykuje otrzymanie ruchu zanim się załaduje. Jest to także używane podczas
      wdrożeń, a może zapobiec przestojowi, jeśli nowa wersja aplikacji zawiedzie.
      Więcej informacji: https://github.com/zegl/kube-score/blob/master/README_PROBES.md
[CRITICAL] Kontekst zabezpieczeń kontenera
  · http-echo -> Kontener nie ma skonfigurowanego kontekstu zabezpieczeń
      Ustaw securityContext, aby uruchomić kontener w bardziej zabezpieczonym kontekście.
[CRITICAL] Zasoby kontenera
  · http-echo -> Limit CPU nie jest ustawiony
      Zaleca się ustawienie limitów zasobów, aby uniknąć DDOS zasobów. Ustaw resources.limits.cpu
  · http-echo -> Limit pamięci nie jest ustawiony
      Zaleca się ustawienie limitów zasobów, aby uniknąć DDOS zasobów. Ustaw resources.limits.memory
  · http-echo -> Żądanie CPU nie jest ustawione
      Zaleca się ustawienie żądań zasobów, aby upewnić się, że aplikacja może wystartować i działać bez
      awarii. Ustaw resources.requests.cpu
  · http-echo -> Żądanie pamięci nie jest ustawione
      Zaleca się ustawienie żądań zasobów, aby upewnić się, że aplikacja może wystartować i działać bez awarii.
      Ustaw resources.requests.memory
[CRITICAL] Wdrożenie ma PodDisruptionBudget
  · Nie znaleziono dopasowanego PodDisruptionBudget
      Zaleca się zdefiniowanie PodDisruptionBudget, aby uniknąć niespodziewanych przestojów podczas Kubernetes
      operacji konserwacyjnych, takich jak opróżnianie węzła.
[WARNING] Wdrożenie ma host PodAntiAffinity
  · Wdrożenie nie ma ustawionej polityki podAntiAffinity
      Zaleca się ustawienie podAntiAffinity, aby zapobiec planowaniu wielu podów z wdrożenia na
      tym samym węźle. To zwiększa dostępność w przypadku, gdy węzeł stanie się niedostępny.

YAML przechodzi kontrole kubeval, podczas gdy kube-score wskazuje na następujące niedociągnięcia:

  • Brak są skonfigurowane kontrole gotowości.
  • Brakują requesty i limitów dla zasobów CPU i pamięci.
  • Nie określono budżetów zakłóceń podów.
  • Brak zasad podziału (anti-affinity) aby maksymalizować dostępność.
  • Kontener działa z uprawnieniami roota.

Wszystkie te uwagi to uzasadnione spostrzeżenia dotyczące wad, które należy wyeliminować, aby wdrożenie było bardziej efektywne i niezawodne.

Zespół kube-score wyświetla informacje w czytelnej formie, uwzględniając wszelkie naruszenia typu WARNING i CRITICAL, co bardzo pomocne podczas rozwoju.

Osoby chcące używać tego narzędzia w ramach CI mogą włączyć bardziej zwięzły format wyjścia za pomocą flagi --output-format ci (w tym przypadku wyświetlane są również testy z wynikami 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) limit CPU nie jest ustawiony
[CRITICAL] http-echo apps/v1/Deployment: (http-echo) limit pamięci nie jest ustawiony
[CRITICAL] http-echo apps/v1/Deployment: (http-echo) żądanie CPU nie jest ustawione
[CRITICAL] http-echo apps/v1/Deployment: (http-echo) żądanie pamięci nie jest ustawione
[CRITICAL] http-echo apps/v1/Deployment: (http-echo) obraz z najnowszym tagiem
[OK] http-echo apps/v1/Deployment
[CRITICAL] http-echo apps/v1/Deployment: Pod nie ma pasującej polityki sieciowej
[CRITICAL] http-echo apps/v1/Deployment: Kontener nie zawiera readinessProbe
[CRITICAL] http-echo apps/v1/Deployment: (http-echo) Kontener nie ma skonfigurowanego kontekstu bezpieczeństwa
[CRITICAL] http-echo apps/v1/Deployment: Nie znaleziono pasującego PodDisruptionBudget
[WARNING] http-echo apps/v1/Deployment: Wdrożenie nie ma ustawionej polityki podAntiAffinity
[OK] http-echo v1/Service
[OK] http-echo v1/Service
[OK] http-echo v1/Service
[OK] http-echo v1/Service

Podobnie jak kubeval, kube-score zwraca niezerowy kod wyjścia w przypadku, gdy któryś z testów zakończył się błędem CRITICAL. Można również włączyć podobne przetwarzanie dla WARNING.

Ponadto istnieje możliwość sprawdzenia zasobów pod kątem zgodności z różnymi wersjami API (tak jak w kubeval). Jednak te informacje są 'hardcodowane' w samym kube-score: nie można wybrać innej wersji Kubernetes. Tego rodzaju ograniczenie może być dużym problemem, jeśli planujesz zaktualizować klaster lub masz kilka klastrów z różnymi wersjami K8s.

Zauważ, że już istnieje problem z propozycją wdrożenia tej możliwości.

Więcej informacji o kube-score można znaleźć na oficjalnej stronie.

Testy kube-score to doskonałe narzędzie do wprowadzania najlepszych praktyk, ale co jeśli potrzebujesz wprowadzić zmiany w teście lub dodać własne zasady? Niestety, nie jest to możliwe.

Kube-score nie jest rozszerzalny: nie można dodać ani dostosować polityk.

Jeśli potrzebujesz pisać niestandardowe testy do sprawdzenia zgodności z politykami przyjętymi w firmie, możesz skorzystać z jednego z czterech narzędzi: config-lint, copper, conftest lub polaris.

3. Config-lint

Config-lint to narzędzie do walidacji plików konfiguracyjnych w formacie YAML, JSON, Terraform, CSV oraz manifestów Kubernetes.

Można je zainstalować za pomocą instrukcji na stronie projektu.

Aktualna wersja w momencie pisania oryginalnego artykułu to 1.5.0.

Config-lint nie zawiera wbudowanych testów do sprawdzania manifestów Kubernetes.

Aby przeprowadzić jakiekolwiek testy, należy stworzyć odpowiednie reguły. Zapisuje się je w plikach YAML, zwanych "zestawami reguł" (rulesets), i mają one następującą strukturę:

version: 1
description: Reguły dla plików specyfikacji Kubernetes
type: Kubernetes
files:
  - "*.yaml"
rules:
   # lista reguł

(rule.yaml)

Przyjrzyjmy się jej bliżej:

  • Pole type określa, jaki typ konfiguracji będzie używać config-lint. Dla manifestów K8s jest to zawsze Kubernetes.
  • W polu files oprócz samych plików można określić katalog.
  • Pole rules służy do definiowania testów użytkownika.

Załóżmy, że chcesz upewnić się, że obrazy w Deployment zawsze są pobierane z zaufanego repozytorium, takiego jak my-company.com/myapp:1.0. Reguła dla config-lint, która wykonuje podobną kontrolę, będzie wyglądać następująco:

- id: MY_DEPLOYMENT_IMAGE_TAG
  severity: FAILURE
  message: Deployment musi używać prawidłowego tagu obrazu
  resource: Deployment
  assertions:
    - every:
        key: spec.template.spec.containers
        expressions:
          - key: image
            op: starts-with
            value: "my-company.com/"

(rule-trusted-repo.yaml)

Dla każdej reguły należy podać następujące atrybuty:

  • id — unikalny identyfikator reguły;
  • severity — może być FAILURE, WARNING i NON_COMPLIANT;
  • message — w przypadku naruszenia reguły wyświetla się treść tej linii;
  • resource — typ zasobu, do którego stosuje się tę regułę;
  • assertions — lista warunków, które będą oceniane w odniesieniu do danego zasobu.

W powyższej regule assertion o nazwie every sprawdza, że wszystkie kontenery w Deployment (key: spec.templates.spec.containers) używają zaufanych obrazów (czyli zaczynających się od my-company.com/).

Pełny zestaw reguł wygląda następująco:

version: 1
description: Reguły dla plików specyfikacji Kubernetes
type: Kubernetes
files:
  - "*.yaml"
rules:

 - id: DEPLOYMENT_IMAGE_REPOSITORY # !!!
    severity: FAILURE
    message: Deployment musi używać prawidłowego repozytorium obrazów
    resource: Deployment
    assertions:
      - every:
          key: spec.template.spec.containers
          expressions:
            - key: image
              op: starts-with
              value: "my-company.com/"

(ruleset.yaml)

Aby przetestować regułę, zapiszmy ją jako check_image_repo.yaml. Uruchomimy sprawdzenie na pliku base-valid.yaml:

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

[
  {
  "AssertionMessage": "Każdy wyrażenie zawiodło: Wyrażenie 'And' zawiodło: obraz nie zaczyna się od 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": "Wdrożenie musi używać prawidłowego repozytorium obrazów",
  "Status": "FAILURE"
  }
]

Sprawdzenie zakończyło się niepowodzeniem. Teraz sprawdźmy następny manifest z poprawnym repozytorium obrazów:

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)

Uruchamiamy ten sam test z powyższym manifestem. Nie wykryto problemów:

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

Config-lint to obiecujący framework, który pozwala tworzyć własne testy do weryfikacji manifestów YAML Kubernetes za pomocą YAML DSL.

Ale co zrobić, jeśli potrzebna jest bardziej złożona logika i testy? Czy możliwości YAML nie są na to zbyt ograniczone? Co jeśli można by było tworzyć testy w pełnoprawnym języku programowania?

4. Copper

Copper V2 to framework do walidacji manifestów z użyciem niestandardowych testów (analogicznie do config-lint).

Jednak różni się od niego tym, że nie używa YAML do opisu testów. Zamiast tego testy można tworzyć w JavaScript. Copper zapewnia bibliotekę z wieloma podstawowymi narzędziami, które pomagają odczytywać informacje o obiektach Kubernetes i zgłaszać błędy.

Kolejność kroków do zainstalowania Copper można znaleźć w oficjalnej dokumentacji.

2.0.1 — najnowsza wersja tego narzędzia w chwili pisania oryginalnego artykułu.

Jak w przypadku config-lint, Copper nie ma wbudowanych testów. Napiszmy jeden. Niech sprawdza, czy wdrożenia używają obrazów kontenerowych wyłącznie z zaufanych repozytoriów takich jak my-company.com.

Utwórz plik check_image_repo.js z następującą zawartością:

$$.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',"Obraz " + $.metadata.name + " nie pochodzi z repozytorium my-company.com", 1)
            }
        });
    }
});

Teraz, aby sprawdzić nasz manifest base-valid.yaml, użyj polecenia copper validate:

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

Weryfikacja no_company_repo nie powiodła się z powodu Obraz http-echo nie pochodzi z repozytorium my-company.com
Weryfikacja nie powiodła się

Jasne, że za pomocą copper można przeprowadzać bardziej złożone testy — na przykład sprawdzać nazwy domen w manifestach Ingress lub odrzucać pod’y działające w trybie uprzywilejowanym.

W Copper wbudowane są różne funkcje pomocnicze:

  • DockerImage odczytuje podany plik wejściowy i tworzy obiekt z następującymi atrybutami:
    • name — nazwa obrazu,
    • tag — tag obrazu,
    • registry — rejestr obrazów,
    • registry_url — protokół (https://) i rejestr obrazów,
    • fqin — pełna lokalizacja obrazu.
  • veth_xdp_flush_bq() findByName pomaga znaleźć zasób według podanego typu (kind) i nazwy (name) z pliku wejściowego.
  • veth_xdp_flush_bq() findByLabels pomaga znaleźć zasób według określonego typu (kind) i etykiet (labels).

Ze wszystkimi dostępnymi funkcjami pomocniczymi można zapoznać się tutaj.

Domyślnie ładuje cały plik YAML do zmiennej $$ i udostępnia ją dla skryptów (znany sposób dla tych, którzy mają doświadczenie w pracy z jQuery).

Główną zaletą Copper jest to, że nie trzeba uczyć się specjalistycznego języka i można korzystać z różnych możliwości JavaScript do tworzenia własnych testów, takich jak interpolacja łańcuchów, funkcje itp.

Należy również zauważyć, że obecna wersja Copper działa z wersją ES5 silnika JavaScript, a nie z ES6.

Szczegóły są dostępne na oficjalnej stronie projektu.

Jednak jeśli nie lubisz zbytnio JavaScript i wolisz język specjalnie przeznaczony do tworzenia zapytań i opisywania polityk, powinieneś zwrócić uwagę na conftest.

5. Conftest

Conftest to framework do weryfikacji danych konfiguracyjnych. Nadaje się również do testowania/weryfikacji manifestów Kubernetes. Testy opisywane są za pomocą specjalistycznego języka zapytań Rego.

Instalację conftest można przeprowadzić przy użyciu instrukcji, wskazówek na stronie projektu.

W momencie pisania oryginalnego artykułu najnowszą dostępną wersją była 0.18.2.

Na wzór config-lint i copper, conftest nie zawiera żadnych wbudowanych testów. Spróbujmy go i napiszmy własną politykę. Jak w poprzednich przykładach, będziemy sprawdzać, czy obrazy kontenerów pochodzą z zaufanego źródła.

Utwórz katalog conftest-checks, a w nim plik o nazwie check_image_registry.rego z następującą zawartością:

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])
}

Teraz przetestujmy base-valid.yaml przez conftest:

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

FAIL - base-valid.yaml - obraz 'hashicorp/http-echo' nie pochodzi z repozytorium my-company.com
1 test, 1 zaliczony, 0 ostrzeżeń, 1 błąd

Test nieoczekiwanie nie przeszedł, ponieważ obrazy pochodzą z niezweryfikowanego źródła.

W pliku Rego definiujemy blok deny. Jego prawdziwość traktowana jest jako naruszenie. Jeśli bloków deny jest więcej, conftest sprawdza je niezależnie od siebie, a prawdziwość dowolnego z bloków interpretowana jest jako naruszenie.

Oprócz domyślnego wyjścia, conftest wspiera formaty JSON, TAP i tabelaryczny — bardzo przydatna funkcjonalność, jeśli trzeba wkomponować raporty w istniejący pipeline CI. Odpowiedni format można ustawić za pomocą flagi --output.

Aby ułatwić debugowanie polityk, w conftest znajdziesz flagę --trace. Wyświetli ślad, jak conftest analizuje wskazane pliki polityk.

Polityki conftest można publikować i dzielić się nimi w rejestrach OCI (Open Container Initiative) w postaci artefaktów.

Polecenia push i pull pozwalają publikować artefakt lub pobrać istniejący artefakt z dalekiego rejestru. Spróbujmy opublikować stworzoną przez nas politykę w lokalnym rejestrze Docker przy pomocy conftest push.

Uruchom lokalny rejestr Docker:

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

W innym terminalu przejdź do wcześniej utworzonego katalogu conftest-checks i wykonaj następującą komendę:

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

Jeśli komenda przebiegła pomyślnie, zobaczysz wiadomość podobną do:

2020/06/10 14:25:43 przesłano zestaw z hashem: sha256:e9765f201364c1a8a182ca637bc88201db3417bacc091e7ef8211f6c2fd2609c

Teraz stwórz tymczasowy katalog i wykonaj w nim polecenie conftest pull. Pobrał on do niego pakiet stworzony poprzednią komendą:

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

W tymczasowym katalogu pojawi się podkatalog policy, zawierający nasz plik z polityką:

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

Testy można przeprowadzać bezpośrednio z repozytorium:

$ conftest test --update 127.0.0.1:5000/amitsaha/opa-bundle-example:latest base-valid.yaml
..
FAIL - base-valid.yaml - obraz 'hashicorp/http-echo' nie pochodzi z repozytorium my-company.com

Niestety, DockerHub na razie nie jest wspierany. Więc uznaj, że masz szczęście, jeśli używasz Azure Container Registry (ACR) lub własnego rejestru.

Format artefaktów jest taki sam, jak pakietów Open Policy Agent (OPA), co umożliwia korzystanie z conftest do uruchamiania testów z istniejących pakietów OPA.

Więcej o współdzieleniu polityk i innych cechach conftest można znaleźć na oficjalnej stronie projektu.

6. Polaris

Ostatnim narzędziem, o którym mowa w tym artykule, jest Polaris. (Jego zeszłoroczna zapowiedź została już przetłumaczonaprzyp. tłum.)

Polaris można zainstalować w klastrze lub używać w trybie wiersza poleceń. Jak już się domyśliliście, pozwala on na statyczną analizę manifestów Kubernetes.

Pracując w trybie wiersza poleceń, dostępne są wbudowane testy obejmujące takie obszary, jak bezpieczeństwo i najlepsze praktyki (podobnie jak w kube-score). Dodatkowo można tworzyć własne testy (jak w config-lint, copper i conftest).

Innymi słowy, Polaris łączy w sobie zalety obu kategorii narzędzi: z wbudowanymi i użytkownikami testami.

Aby zainstalować Polaris w trybie wiersza poleceń, skorzystaj z instrukcji na stronie projektu.

W momencie pisania oryginalnego artykułu dostępna była wersja 1.0.3.

Po zakończeniu instalacji można uruchomić polaris na manifeście base-valid.yaml za pomocą następującego polecenia:

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

Zostanie wyświetlona linia w formacie JSON z szczegółowym opisem wykonanych testów i ich wynikami. Wyjście będzie miało następującą strukturę:

{
  "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": [
    /* długa lista */
  ]
}

Pełne wyjście jest dostępne tutaj.

Podobnie jak kube-score, Polaris wykrywa problemy w tych obszarach, w których manifest nie odpowiada najlepszym praktykom:

  • Brak kontroli zdrowia podów.
  • Nie określono tagów dla obrazów kontenerów.
  • Kontener działa z uprawnieniami roota.
  • Nie określono requestów i limitów dla pamięci i CPU.

Każdemu testowi w zależności od jego wyników przypisywana jest stopień krytyczności: warning lub danger. Aby dowiedzieć się więcej o dostępnych wbudowanych testach, zapoznaj się z dokumentacji.

Jeśli szczegóły nie są potrzebne, można użyć flagi --format score. W takim przypadku Polaris wyświetli liczbę w zakresie od 1 do 100 — score (czyli ocenę):

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

Im ocena jest bliższa 100, tym wyższy poziom zgodności. Jeśli sprawdzisz kod wyjścia polecenia polaris audit, okaże się, że wynosi 0.

Wymusić polaris audit zakończenie pracy z kodem różnym od zera można za pomocą dwóch flag:

  • Flaga --set-exit-code-below-score przyjmuje jako argument próg w zakresie 1-100. W takim przypadku polecenie zakończy się kodem wyjścia 4, jeśli ocena będzie poniżej progu. To bardzo wygodne, gdy masz pewną wartość progową (powiedzmy 75) i potrzebujesz uzyskać alert, jeśli ocena spadnie poniżej.
  • Flaga --set-exit-code-on-danger spowoduje, że polecenie zakończy się kodem 3, jeśli jeden z testów typu danger się nie powiedzie.

Teraz spróbujmy stworzyć test niestandardowy, który sprawdza, czy obraz pochodzi z zaufanego repozytorium. Testy niestandardowe definiuje się w formacie YAML, a sam test opisuje się za pomocą schemy JSON.

Następujący fragment kodu YAML opisuje nowy test, nazywany checkImageRepo:

checkImageRepo:
  successMessage: Rejestr obrazów jest ważny
  failureMessage: Rejestr obrazów jest nieważny
  category: Obrazy
  target: Kontener
  schema:
    '$schema': http://json-schema.org/draft-07/schema
    type: object
    properties:
      image:
        type: string
        pattern: ^my-company.com/.+$

Przyjrzyjmy się mu bliżej:

  • successMessage — ten komunikat zostanie wyświetlony, jeśli test zakończy się sukcesem;
  • failureMessage — ten komunikat zostanie wyświetlony w przypadku niepowodzenia;
  • category — wskazuje na jedną z kategorii: Obrazy, Kontrole stanu, Bezpieczeństwo, Networking i Zasoby;
  • target— określa, do jakiego rodzaju obiektu (spec) stosuje się test. Możliwe wartości: Kontener, Pod lub Kontroler;
  • Sam test jest definiowany w obiekcie schema za pomocą schemy JSON. W tym teście słowo kluczowe wzorzec jest używane do porównania źródła obrazu z wymaganym.

Aby uruchomić powyższy test, należy stworzyć następującą konfigurację Polaris:

checks:
  checkImageRepo: danger
customChecks:
  checkImageRepo:
    successMessage: Rejestr obrazów jest ważny
    failureMessage: Rejestr obrazów jest nieważny
    category: Obrazy
    target: Kontener
    schema:
      '$schema': http://json-schema.org/draft-07/schema
      type: object
      properties:
        image:
          type: string
          pattern: ^my-company.com/.+$

(polaris-conf.yaml)

Rozbijmy plik:

  • W polu checks określają testy i poziom ich krytyczności. Ponieważ pożądane jest otrzymywanie ostrzeżenia, gdy obraz pochodzi z niepewnego źródła, ustawiamy tutaj poziom danger.
  • Test checkImageRepo następnie definiuje się w obiekcie customChecks.

Zapisz plik jako custom_check.yaml. Teraz można uruchomić polaris audit z manifestem YAML, który wymaga weryfikacji.

Przetestujemy nasz manifest base-valid.yaml:

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

Zespół polaris audit wykonał tylko test niestandardowy, który został określony powyżej, i nie zakończył się sukcesem.

Jeśli poprawisz obraz na my-company.com/http-echo:1.0, Polaris zakończy się pomyślnie. Manifest z wprowadzonymi zmianami już istnieje w repozytorium, więc możesz sprawdzić poprzednie polecenie w manifeście image-valid-mycompany.yaml.

Teraz pojawia się pytanie: jak uruchamiać wbudowane testy razem z użytkownikami? To proste! Wystarczy dodać identyfikatory wbudowanych testów do pliku konfiguracyjnego. W rezultacie przybierze on następującą formę:

checks:
  cpuRequestsMissing: warning
  cpuLimitsMissing: warning
  # Inne wbudowane kontrole..
  # ..
  # kontrole użytkownika
  checkImageRepo: danger # !!!
customChecks:
  checkImageRepo:        # !!!
    successMessage: Rejestr obrazu jest ważny
    failureMessage: Rejestr obrazu nie jest ważny
    category: Obrazy
    target: Kontener
    schema:
      '$schema': http://json-schema.org/draft-07/schema
      type: object
      properties:
        image:
          type: string
          pattern: ^my-company.com/.+$

(config_with_custom_check.yaml)

Przykład pełnego pliku konfiguracyjnego jest dostępny tutaj.

Sprawdź manifest base-valid.yaml, korzystając z wbudowanych i użytkowników testów, można za pomocą polecenia:

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

Polaris uzupełnia wbudowane testy o testy użytkowników, łącząc to, co najlepsze z obu światów.

Z drugiej strony, niemożność korzystania z bardziej zaawansowanych języków, takich jak Rego czy JavaScript, może być ograniczeniem dla tworzenia bardziej wyszukanych testów.

Dodatkowe informacje o Polaris są dostępne na stronie projektu.

Podsumowanie

Chociaż istnieje wiele narzędzi do sprawdzania i oceniania plików YAML Kubernetes, ważne jest, aby mieć jasny obraz tego, jak testy będą projektowane i wykonywane.

Na przykład, jeśli zacząć od manifestów Kubernetes przechodzących przez pipeline, kubeval mógłby być pierwszym krokiem w takim pipeline. Będzie on monitorował, czy definicje obiektów są zgodne ze schemą API Kubernetes.

Po zakończeniu takiej kontroli można by przejść do bardziej zaawansowanych testów, takich jak zgodność z najlepszymi praktykami oraz specjalnymi politykami. I tutaj przydałyby się kube-score i Polaris.

Osobom z złożonymi wymaganiami i potrzebą szczegółowego konfigurowania testów odpowiadałyby copper, config-lint oraz conftest.

Conftest i config-lint wykorzystują YAML do definiowania testów użytkowników, a copper daje dostęp do pełnoprawnego języka programowania, co sprawia, że jest dość atrakcyjnym wyborem.

Z drugiej strony, czy warto skorzystać z jednego z tych narzędzi i w ten sposób tworzyć wszystkie testy ręcznie, czy raczej wybrać Polaris i dodać tylko to, co potrzebne? Na to pytanie nie ma jednoznacznej odpowiedzi.

Tabela poniżej zawiera krótkie podsumowanie każdego narzędzia:

Narzędzie
Przeznaczenie
Wady
Testy użytkowników

kubeval
Sprawdza manifesty YAML pod kątem zgodności z określoną wersją schematu API
Nie obsługuje CRD
Nie

kube-score
Analizuje manifesty YAML pod kątem zgodności z najlepszymi praktykami
Nie można wybrać własnej wersji API Kubernetes do weryfikacji zasobów
Nie

miedź
Ogólny framework do tworzenia własnych testów JavaScript dla manifestów YAML
Brak wbudowanych testów. Uboga dokumentacja
Tak

config-lint
Ogólny framework do tworzenia testów w obiektowym języku wbudowanym w YAML. Obsługuje różne formaty konfiguracji (np. Terraform)
Brak gotowych testów. Wbudowane asercje i funkcje mogą być niewystarczające
Tak

conftest
Framework do tworzenia własnych testów w Rego (specjalizowanym języku zapytań). Umożliwia dzielenie się politykami za pośrednictwem pakietów OCI
Brak wbudowanych testów. Konieczność nauki Rego. Docker Hub nie jest obsługiwany podczas publikacji polityk
Tak

Polaris
Analizuje manifesty YAML pod kątem zgodności ze standardowymi najlepszymi praktykami. Umożliwia tworzenie własnych testów za pomocą JSON Schema
Możliwości testów opartych na JSON Schema mogą być niewystarczające
Tak

Ponieważ te narzędzia nie są zależne od dostępu do klastra Kubernetes, łatwo je zainstalować. Umożliwiają filtrowanie plików źródłowych i zapewniają szybką informację zwrotną dla autorów pull requestów w projektach.

P.S. od tłumacza

Przeczytaj także na naszym blogu:

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster