Przedstawiamy operatora powłoki: tworzenie operatorów dla Kubernetesa właśnie stało się łatwiejsze

Na naszym blogu pojawiły się już artykuły nt możliwości operatora w Kubernetesie i jak napisz sam prosty operator. Tym razem chcielibyśmy zaprezentować Państwu nasze rozwiązanie Open Source, które przenosi tworzenie operatorów na superprosty poziom - sprawdź operator powłoki!

Dlaczego?

Idea operatora powłoki jest dość prosta: subskrybuj zdarzenia z obiektów Kubernetesa, a po odebraniu tych zdarzeń uruchom zewnętrzny program, dostarczając mu informacji o zdarzeniu:

Przedstawiamy operatora powłoki: tworzenie operatorów dla Kubernetesa właśnie stało się łatwiejsze

Potrzeba tego pojawiła się, gdy w trakcie działania klastrów zaczęły pojawiać się drobne zadania, które bardzo chcieliśmy w odpowiedni sposób zautomatyzować. Wszystkie te drobne zadania rozwiązano za pomocą prostych skryptów bashowych, chociaż, jak wiadomo, operatory lepiej pisać w Golangu. Oczywiście inwestowanie w pełnowymiarowy rozwój operatora dla każdego tak małego zadania byłoby nieefektywne.

Operator w 15 minut

Przyjrzyjmy się przykładowi tego, co można zautomatyzować w klastrze Kubernetes i jak operator powłoki może w tym pomóc. Przykładem może być następujący: replikacja klucza tajnego w celu uzyskania dostępu do rejestru dokera.

Pody korzystające z obrazów z rejestru prywatnego muszą zawierać w swoim manifeście link do sekretu z danymi umożliwiającymi dostęp do rejestru. Ten sekret należy utworzyć w każdej przestrzeni nazw przed utworzeniem zasobników. Można to zrobić ręcznie, ale jeśli skonfigurujemy środowiska dynamiczne, to przestrzeni nazw dla jednej aplikacji będzie sporo. A jeśli nie ma też 2-3 aplikacji... liczba sekretów staje się bardzo duża. I jeszcze jedno odnośnie sekretów: chciałbym od czasu do czasu zmienić klucz dostępu do rejestru. W końcu, operacje ręczne jako rozwiązanie całkowicie nieskuteczne — musimy zautomatyzować tworzenie i aktualizację sekretów.

Prosta automatyzacja

Napiszmy skrypt powłoki, który będzie uruchamiany co N sekund i sprawdza przestrzenie nazw pod kątem obecności sekretu, a jeśli sekretu nie ma, to zostanie on utworzony. Zaletą tego rozwiązania jest to, że wygląda jak skrypt powłoki w cron - klasyczne i zrozumiałe dla każdego podejście. Minusem jest to, że w przerwie między jego uruchomieniami może zostać utworzona nowa przestrzeń nazw i przez jakiś czas pozostanie ona bez tajemnicy, co będzie prowadzić do błędów w uruchamianiu podów.

Automatyka z operatorem powłoki

Aby nasz skrypt działał poprawnie, klasyczne uruchomienie crona należy zastąpić uruchomieniem po dodaniu przestrzeni nazw: w tym przypadku możesz utworzyć sekret przed jego użyciem. Zobaczmy, jak to zaimplementować za pomocą operatora powłoki.

Najpierw spójrzmy na skrypt. Skrypty w terminologii operatora powłoki nazywane są hakami. Każdy hak, gdy jest uruchamiany z flagą --config informuje operatora powłoki o jego powiązaniach, tj. na jakich wydarzeniach powinien zostać uruchomiony. W naszym przypadku skorzystamy onKubernetesEvent:

#!/bin/bash
if [[ $1 == "--config" ]] ; then
cat <<EOF
{
"onKubernetesEvent": [
  { "kind": "namespace",
    "event":["add"]
  }
]}
EOF
fi

Tutaj opisano, że jesteśmy zainteresowani dodaniem wydarzeń (add) obiekty typu namespace.

Teraz musisz dodać kod, który zostanie wykonany po wystąpieniu zdarzenia:

#!/bin/bash
if [[ $1 == "--config" ]] ; then
  # конфигурация
cat <<EOF
{
"onKubernetesEvent": [
{ "kind": "namespace",
  "event":["add"]
}
]}
EOF
else
  # реакция:
  # узнать, какой namespace появился
  createdNamespace=$(jq -r '.[0].resourceName' $BINDING_CONTEXT_PATH)
  # создать в нём нужный секрет
  kubectl create -n ${createdNamespace} -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  ...
data:
  ...
EOF
fi

Świetnie! W rezultacie powstał mały, piękny scenariusz. Aby go „ożywić” pozostały dwa kroki: przygotować obraz i uruchomić go w klastrze.

Przygotowanie obrazu za pomocą haka

Jeśli spojrzysz na skrypt, zobaczysz, że polecenia są używane kubectl и jq. Oznacza to, że obraz musi zawierać następujące elementy: nasz hak, operator powłoki, który będzie monitorował zdarzenia i uruchamiał hak, oraz polecenia używane przez hak (kubectl i jq). Hub.docker.com ma już gotowy obraz, w którym spakowany jest operator powłoki, kubectl i jq. Pozostaje tylko dodać prosty haczyk Dockerfile:

$ cat Dockerfile
FROM flant/shell-operator:v1.0.0-beta.1-alpine3.9
ADD namespace-hook.sh /hooks

$ docker build -t registry.example.com/my-operator:v1 . 
$ docker push registry.example.com/my-operator:v1

Bieganie w klastrze

Przyjrzyjmy się hakowi jeszcze raz i tym razem napiszmy jakie akcje i z jakimi obiektami wykonuje w klastrze:

  1. subskrybuje zdarzenia tworzenia przestrzeni nazw;
  2. tworzy sekret w przestrzeniach nazw innych niż ta, w której jest uruchamiany.

Okazuje się, że pod, w którym zostanie uruchomiony nasz obraz, musi mieć uprawnienia do wykonywania tych czynności. Można tego dokonać tworząc własne konto ServiceAccount. Zezwolenie musi zostać wydane w formie ClusterRole i ClusterRoleBinding, ponieważ interesują nas obiekty z całej gromady.

Ostateczny opis w YAML będzie wyglądał mniej więcej tak:

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: monitor-namespaces-acc

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: monitor-namespaces
rules:
- apiGroups: [""]
  resources: ["namespaces"]
  verbs: ["get", "watch", "list"]
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "list", "create", "patch"]

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: monitor-namespaces
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: monitor-namespaces
subjects:
  - kind: ServiceAccount
    name: monitor-namespaces-acc
    namespace: example-monitor-namespaces

Możesz uruchomić złożony obraz w ramach prostego wdrożenia:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: my-operator
spec:
  template:
    spec:
      containers:
      - name: my-operator
        image: registry.example.com/my-operator:v1
      serviceAccountName: monitor-namespaces-acc

Dla wygody tworzona jest osobna przestrzeń nazw, w której zostanie uruchomiony operator powłoki i zastosowane zostaną utworzone manifesty:

$ kubectl create ns example-monitor-namespaces
$ kubectl -n example-monitor-namespaces apply -f rbac.yaml
$ kubectl -n example-monitor-namespaces apply -f deployment.yaml

To wszystko: operator powłoki uruchomi się, zasubskrybuje zdarzenia tworzenia przestrzeni nazw i uruchomi hak, jeśli zajdzie taka potrzeba.

Przedstawiamy operatora powłoki: tworzenie operatorów dla Kubernetesa właśnie stało się łatwiejsze

Tak więc, prosty skrypt powłoki zamieniony w prawdziwego operatora dla Kubernetesa i działa w ramach klastra. A wszystko to bez skomplikowanego procesu rozwijania operatorów w Golang:

Przedstawiamy operatora powłoki: tworzenie operatorów dla Kubernetesa właśnie stało się łatwiejsze

Jest jeszcze jedna ilustracja na ten temat...Przedstawiamy operatora powłoki: tworzenie operatorów dla Kubernetesa właśnie stało się łatwiejsze

Jego znaczenie zdradzimy szerzej w jednej z kolejnych publikacji.

filtracja

Śledzenie obiektów jest dobre, ale często trzeba na nie reagować zmianę niektórych właściwości obiektuna przykład, aby zmienić liczbę replik we wdrożeniu lub zmienić etykiety obiektów.

Po nadejściu zdarzenia operator powłoki otrzymuje manifest JSON obiektu. Możemy wybrać właściwości, które nas interesują w tym JSON-ie i uruchomić hak tylko kiedy się zmieniają. Jest na to pole jqFilter, gdzie należy określić wyrażenie jq, które zostanie zastosowane do manifestu JSON.

Na przykład, aby odpowiedzieć na zmiany w etykietach obiektów wdrożenia, należy przefiltrować pole labels poza polem metadata. Konfiguracja będzie następująca:

cat <<EOF
{
"onKubernetesEvent": [
{ "kind": "deployment",
  "event":["update"],
  "jqFilter": ".metadata.labels"
}
]}
EOF

To wyrażenie jqFilter zamienia długi manifest JSON wdrożenia na krótki JSON z etykietami:

Przedstawiamy operatora powłoki: tworzenie operatorów dla Kubernetesa właśnie stało się łatwiejsze

Operator powłoki uruchomi hak tylko wtedy, gdy ten krótki JSON ulegnie zmianie, a zmiany innych właściwości zostaną zignorowane.

Kontekst uruchamiania haka

Konfiguracja hooka pozwala określić kilka opcji dla zdarzeń - na przykład 2 opcje dla zdarzeń z Kubernetesa i 2 harmonogramy:

{"onKubernetesEvent":[
  {"name":"OnCreatePod",
  "kind": "pod",
  "event":["add"]
  },
  {"name":"OnModifiedNamespace",
  "kind": "namespace",
  "event":["update"],
  "jqFilter": ".metadata.labels"
  }
],
"schedule": [
{ "name":"every 10 min",
  "crontab":"* */10 * * * *"
}, {"name":"on Mondays at 12:10",
"crontab": "* 10 12 * * 1"
]}

Mała dygresja: tak, obsługuje operatora powłoki uruchamianie skryptów w stylu crontab. Więcej szczegółów można znaleźć w dokumentacja.

Aby rozróżnić, dlaczego hak został uruchomiony, operator powłoki tworzy plik tymczasowy i przekazuje ścieżkę do niego w zmiennej do haka BINDING_CONTEXT_TYPE. Plik zawiera opis JSON powodu uruchomienia haka. Na przykład co 10 minut będzie uruchamiany hak z następującą zawartością:

[{ "binding": "every 10 min"}]

...a w poniedziałek zacznie się tak:

[{ "binding": "every 10 min"}, { "binding": "on Mondays at 12:10"}]

dla onKubernetesEvent Będzie więcej wyzwalaczy JSON, ponieważ zawiera opis obiektu:

[
 {
 "binding": "onCreatePod",
 "resourceEvent": "add",
 "resourceKind": "pod",
 "resourceName": "foo",
 "resourceNamespace": "bar"
 }
]

Zawartość pól można zrozumieć na podstawie ich nazw, a więcej szczegółów można odczytać dokumentacja. Przykład pobrania nazwy zasobu z pola resourceName użycie jq zostało już pokazane w haku replikującym sekrety:

jq -r '.[0].resourceName' $BINDING_CONTEXT_PATH

W podobny sposób możesz uzyskać inne pola.

Co dalej?

W repozytorium projektu w /przykładowe katalogi, istnieją przykłady haków, które są gotowe do uruchomienia w klastrze. Pisząc własne hooki, możesz wykorzystać je jako bazę.

Istnieje wsparcie dla zbierania metryk za pomocą Prometheusa - dostępne metryki opisano w sekcji METRYKA.

Jak można się domyślić, operator powłoki jest napisany w Go i rozpowszechniany na licencji Open Source (Apache 2.0). Będziemy wdzięczni za każdą pomoc rozwojową projekt na GitHubie: i gwiazdki, problemy i żądania ściągnięcia.

Podnosząc zasłonę tajemnicy, poinformujemy Cię również, że Shell-operator jest mały część naszego systemu, która może na bieżąco aktualizować dodatki zainstalowane w klastrze Kubernetes i wykonywać różne automatyczne akcje. Przeczytaj więcej o tym systemie powiedział dosłownie w poniedziałek na targach HighLoad++ 2019 w St. Petersburgu – wkrótce opublikujemy wideo i transkrypcję tej relacji.

Mamy plan otwarcia reszty tego systemu: operatora dodatków oraz naszej kolekcji haków i modułów. Nawiasem mówiąc, operator dodatków już jest dostępne na github, ale dokumentacja do niego jest wciąż w drodze. Wydanie kolekcji modułów planowane jest na lato.

Bądźcie czujni!

PS

Przeczytaj także na naszym blogu:

Źródło: www.habr.com

Dodaj komentarz