Shkoni? Bashkë! Njihuni me shell-operatorin (përmbledhje dhe video e raportit nga KubeCon EU’2020)

Këtë vit, konferenca kryesore evropiane për Kubernetes - KubeCon + CloudNativeCon Europe 2020 - ishte virtuale. Megjithatë, ky ndryshim formati nuk na pengoi të japim një ligjëratë të planifikuar prej kohësh me titullin «Go? Bash! Meet the Shell-operator», e cila është përkushtuar projektit tonë Open Source. shell-operator.

Në këtë artikull, i shkruar mbi bazën e kësaj ligjërate, paraqitet një qasje për të thjeshtuar procesin e krijimit të operatorëve për Kubernetes dhe tregohet se si me përpjekje minimale, nëpërmjet shell-operator, mund të krijoni tuajin.

Go? Bash! Takoni shell-operatorin (përmbledhje dhe video e prezantimit nga KubeCon EU'2020)

Prezantimi video me referatin (~23 minuta në anglisht, dukshëm më informuese se artikulli) dhe përmbledhja kryesore e tij në formë tekstuale. Le të fillojmë!

Ne në «Flant» vazhdimisht optimizojmë dhe automatizojmë gjithçka. Sot, do të flasim për një koncept tjetër emocionues. Prezentoni: shell-skriptimi cloud-native!

Megjithatë, le të fillojmë me kontekstin në të cilin gjithë kjo ndodh - me Kubernetes.

API dhe kontrolluesit e Kubernetes

API në Kubernetes mund të paraqitet si një server skedarësh me kataloge për çdo lloj objekti. Objeketet (burimet) në këtë server janë përfaqësuar si skedarë YAML. Për më tepër, serveri ka një API bazë, i cili lejon kryerjen e tre gjërave:

  • të marrin burimi sipas llojit dhe emrit të tij;
  • ndrysho burimin (në këtë rast, serveri ruan vetëm objektet "të sakta" - të gjitha ato që janë formuar gabimisht ose të destinuara për drejtoritë e tjera janë të hequra);
  • ndjekë për burimin (në këtë rast përdoruesi menjëherë merr versionin e tij aktual/të përditësuar).

Kështu, Kubernetes vepron si një server skedarësh (për manifestet YAML) me tre metoda bazë (po, në të vërtetë ka dhe të tjera, por për tani do t'i anashkalojmë).

Go? Bash! Takoni shell-operatorin (përmbledhje dhe video e prezantimit nga KubeCon EU'2020)

Problemi është se serveri di vetëm të ruajë informacionin. Për ta bërë atë të funksionojë, është e nevojshme kontrolleri — e dyta në rëndësi dhe themel në botën e Kubernetes.

Nodhen dy lloje kryesore të kontrolerëve. I pari merr informacion nga Kubernetes, e përpunon atë sipas logjikës së brendshme dhe e kthen në K8s. I dyti merr informacion nga Kubernetes, por, ndryshe nga lloji i parë, ndryshon gjendjen e disa burimeve ekstere.

Le të shqyrtojmë më në detaje procesin e krijimit të një Deployment-i në Kubernetes:

  • Kontrolleri i Deployment-it (i përfshirë në kube-controller-manager) merr informacionin mbi Deployment-in dhe krijon ReplicaSet.
  • ReplicaSet, bazuar në këtë informacion, krijon dy replika (dy pod-e), por këto pod-e ende nuk janë planifikuar.
  • Planifikuesi planifikon pod’ë dhe shton informacion për nyjat në YAML’ët e tyre.
  • Kubelet’ët bëjnë ndryshime në burimin e jashtëm (p.sh., Docker).

Pastaj, e gjithë kjo sekuencë përsëritet në rendin e kundërt: kubelet kontrollon kontejnerët, llogarit statusin e pod’it dhe e dërgon atë prapa. Kontrolleri i ReplicaSet merr statusin dhe përditëson gjendjen e grupit të replikave. E njëjta gjë ndodh me Kontrollerin e Deployment, dhe përdoruesi në fund merr statusin e përditësuar (aktual).

Go? Bash! Takoni shell-operatorin (përmbledhje dhe video e prezantimit nga KubeCon EU'2020)

Shell-operator

Kështu, në themel të Kubernetes qëndron bashkëpunimi i ndryshëm i kontrollevë (operatorët e Kubernetes janë gjithashtu kontrollevë). Lind pyetja, si të krijojmë operatorin tonë me përpjekje minimale? Dhe këtu ndihmon projekti ynë i zhvilluar shell-operator. Ai u lejon administratorëve të sistemit të krijojnë operatorë të vetë, duke përdorur metoda të njohura.

Go? Bash! Takoni shell-operatorin (përmbledhje dhe video e prezantimit nga KubeCon EU'2020)

Një shembull i thjeshtë: kopjimi i sekreteve

Le të shqyrtojmë një shembull të thjeshtë.

Supozoni se kemi një klaster Kubernetes. Në të ka një hapësirë emërimi default me një Secret mysecret. Përveç kësaj, në klaster ka edhe emra të tjerë hapësinor. Disa prej tyre kanë një etiketë të caktuar. Qëllimi ynë është të kopjojmë Secret në hapësira të emrave me etiketën.

Detyra komplikohet nga fakti se në klaster mund të shfaqen hapësira të reja emrash, dhe disa prej tyre mund të kenë këtë etiketë. Nga ana tjetër, kur etiketa fshihet, Secret gjithashtu duhet të fshihet. Përveç kësaj, vetë Secret mund të ndryshojë: në këtë rast, Secret i ri duhet të kopjohet në të gjitha hapësirat e emrave me etiketat. Nëse Secret fshihet rastësisht në ndonjë hapësirë emri, operatori ynë duhet ta rikthejë atë menjëherë.

Tani që detyra është formuluar, është koha për të filluar zbatimin e saj me ndihmën e shell-operator. Por, fillimisht, është e rëndësishme të themi disa fjalë rreth vetë shell-operator’s.

Parimet e punës së shell-operator

Si ngarkesa të tjera në Kubernetes, shell-operator funksionon në pod’in e tij. Në këtë pod, në katalogun /hooks ruhen skedarët ekzekutivë. Këto mund të jenë skripte në Bash, Python, Ruby, etj. Këto skedarë ekzekutivë ne i quajmë hook (hooks).

Go? Bash! Takoni shell-operatorin (përmbledhje dhe video e prezantimit nga KubeCon EU'2020)

Shell-operator subscribohet në ngjarjet e Kubernetes dhe ekzekuton këto hook-a në përgjigje të atyre ngjarjeve që na nevojiten.

Go? Bash! Takoni shell-operatorin (përmbledhje dhe video e prezantimit nga KubeCon EU'2020)

Si e di shell-operator merr vesht se cilin hook dhe kur ta thërrasë? Kjo ka të bëjë me faktin se çdo hook ka dy faza. Gjatë fillimit, shell-operator aktivizon të gjitha hook-et me argumentin --config — kjo është faza e konfigurimit. Pas kësaj, hook-et aktivizohen siç duhet — në përgjigje të ngjarjeve të lidhura me ta. Në këtë rast të fundit, hook-u merr kontekstin e lidhjes (binding context) — të dhëna në formatin JSON, për të cilat do të flasim më poshtë.

Krijojmë operatorin në Bash

Tani jemi të gatshëm për zbatuar. Për këtë, na nevojitet të shkruajmë dy funksione (për më tepër, rekomandojmë bibliotekën shell_lib, e cila e thjeshton ndjeshëm shkrimin e hook-eve në Bash):

  • funksioni i parë është i nevojshëm për fazën e konfigurimit — ai shfaq kontekstin e lidhjes;
  • i dyti përmban logjikën kryesore të hook-ut.

#!/bin/bash

source /shell_lib.sh

function __config__() {
  cat << EOF
    configVersion: v1
    # BINDING CONFIGURATION
EOF
}

function __main__() {
  # THE LOGIC
}

hook::run "$@"

Hapi tjetër është të përcaktojmë se cilat objekte na nevojiten. Në rastin tonë, duhet të ndjekim:

  • sekretin-burim për ndonjë ndryshim;
  • të gjithë namespace-t në klaster, për të ditur se cilëve iu është lidhur etiketa;
  • sekretet-celës, për të siguruar se të gjitha janë të sinkronizuara me sekretin-burim.

Abonoheni në sekretin-burim

Konfigurimi i lidhjes për të është mjaft i thjeshtë. Ne përcaktojmë se na intereson Sekreti me emrin mysecret në hapësirën e emrave default:

Go? Bash! Takoni shell-operatorin (përmbledhje dhe video e prezantimit nga KubeCon EU'2020)

function __config__() {
  cat << EOF
    configVersion: v1
    kubernetes:
    - name: src_secret
      apiVersion: v1
      kind: Secret
      nameSelector:
        matchNames:
        - mysecret
      namespace:
        nameSelector:
          matchNames: ["default"]
      group: main
EOF

Si rezultat, hook-u do të aktivizohet kur të ndryshojë sekreti burim (src_secret) dhe do të marrë këtë kontekst lidhjeje:

Go? Bash! Takoni shell-operatorin (përmbledhje dhe video e prezantimit nga KubeCon EU'2020)

Siç e shihni, përmban emrin dhe objektin e plotë.

Monitorojmë hapësirat emërtuese

Tani duhet të abonoheni në hapësirat emërtuese. Për këtë, do të përcaktojmë konfigurimin e mëposhtëm të lidhjes:

- name: namespaces
  group: main
  apiVersion: v1
  kind: Namespace
  jqFilter: |
    {
      namespace: .metadata.name,
      hasLabel: (
       .metadata.labels // {} |  
         contains({"secret": "yes"})
      )
    }
  group: main
  keepFullObjectsInMemory: false

Siç e shihni, në konfigurim është shtuar një fushë e re me emrin jqFilter. Siç sugjeron emri i tij, jqFilter filtron të gjitha informacionet e panevojshme dhe krijon një objekt të ri JSON me fushat që na interesojnë. Hook-u me këtë konfigurim do të marrë këtë kontekst lidhjeje:

Go? Bash! Takoni shell-operatorin (përmbledhje dhe video e prezantimit nga KubeCon EU'2020)

Ai përmban një array filterResults për çdo hapësirë emërtuese në klaster. Variabla boolean hasLabel tregon nëse etiketa është e lidhur me këtë hapësirë emri. Seletori keepFullObjectsInMemory: false tregon se nuk është e nevojshme të mbani objekte të plota në memorje.

Monitorojmë sekretet-cela

Ne abonomohemi në të gjitha Secret’ët, të cilët kanë një annotim të caktuar managed-secret: "yes" (këto janë qëllimet tona dst_secrets):

- name: dst_secrets
  apiVersion: v1
  kind: Secret
  labelSelector:
    matchLabels:
      managed-secret: "yes"
  jqFilter: |
    {
      "namespace":
        .metadata.namespace,
      "resourceVersion":
        .metadata.annotations.resourceVersion
    }
  group: main
  keepFullObjectsInMemory: false

Në këtë rast jqFilter filtron të gjithë informacionin përveç hapësirës emri dhe parametrave resourceVersion. Parametri i fundit u kalua në annotim gjatë krijimit të sekretit: ai lejon të krahasohen versionet e sekreteve dhe të mbahen të azhurnuara.

Një hook i konfiguruar në këtë mënyrë, kur ekzekutohet, do të marrë tre kontekste lidhëse, të përshkruara më sipër. Ato mund të përfaqësohen si një lloj pamje tësnapshot) kështjellës.

Go? Bash! Takoni shell-operatorin (përmbledhje dhe video e prezantimit nga KubeCon EU'2020)

Bazuar në të gjithë këtë informacion, mund të zhvillohet një algoritëm bazik. Ai kalon përmes të gjitha hapësirave emri dhe:

  • nëse hasLabel ka rëndësi e vërtetë për hapësirën aktuale të emrit:
    • krahason sekretin global me atë lokal:
      • nëse ato janë të njëjta — nuk bën asgjë;
      • nëse ato ndryshojnë - ekzekuton kubectl zëvendëso ose krijo;
  • nëse hasLabel ka rëndësi false për hapësirën aktuale të emrit:
    • siguron që Sekreti nuk është i pranishëm në këtë hapësirë emri:
      • nëse Sekreti lokal është i pranishëm - e heq atë me kubectl delete;
      • nëse Sekreti lokal nuk gjendet - nuk bën asgjë.

Go? Bash! Takoni shell-operatorin (përmbledhje dhe video e prezantimit nga KubeCon EU'2020)

Implementimi i algoritmit në Bash mund ta shkarkoni në tonë repo me shembuj.

Ja si arritëm të krijonim një kontrollues të thjeshtë Kubernetes, duke përdorur 35 rreshta YAML-konfigurimesh dhe afërsisht të njëjtën sasi kodi në Bash! Detyra e operatorit shell është të lidhë ato së bashku.

Megjithatë, kopjimi i sekreteve nuk është fushë e vetme e aplikimit të utilitetit. Ja edhe disa shembuj të tjerë që do të tregojnë se çfarë është në gjendje të bëjë.

Shembulli 1: ndryshimi i ConfigMap

Le të shqyrtojmë një Deployment që përbëhet nga tri pod. Pod-t përdorin ConfigMap për të ruajtur disa konfigurata. Gjatë nisjes së pod-ëve, ConfigMap ishte në një gjendje të caktuar (ta quajmë v.1). Prandaj, të gjitha pod-et përdorin këtë version të ConfigMap.

Tani le të supozojmë se ConfigMap ka ndryshuar (v.2). Megjithatë, pod-et do të përdorin versionin e mëparshëm të ConfigMap (v.1):

Go? Bash! Takoni shell-operatorin (përmbledhje dhe video e prezantimit nga KubeCon EU'2020)

Si të bëni që ata të kalojnë në ConfigMap të ri (v.2)? Përgjigjja është e thjeshtë: të përdorni modelin. Le të shtojmë një annotim me checksum në seksionin template e konfigurimit të Deployment-it:

Go? Bash! Takoni shell-operatorin (përmbledhje dhe video e prezantimit nga KubeCon EU'2020)

Si rezultat, në të gjitha pod-et do të shkruhet kjo checksum, dhe ajo do të jetë e njëjtë me atë të Deployment-it. Tani thjesht duhet të përditësojmë annotimin me çdo ndryshim të ConfigMap. Dhe shell-operatori është i përshtatshëm në këtë rast. E gjithë çfarë nevojitet është të programoni një hook, i cili do të nënshkruhet për ConfigMap dhe do të përditësojë checksum-in.

Nëse përdoruesi bën ndryshime në ConfigMap, shell-operatori do t'i vërejë ato dhe do të rregullojë checksum-in. Pas kësaj, magjia e Kubernetes fillon: orkestratori do të vrasë pod-in, do të krijojë një të ri, do të presë derisa ai të bëhet Gati, dhe do të kalojë në të next. Si rezultat, Deployment-i do të sinkronizohet dhe do të kalojë në versionin e ri të ConfigMap.

Go? Bash! Takoni shell-operatorin (përmbledhje dhe video e prezantimit nga KubeCon EU'2020)

Shembulli 2: puna me Custom Resource Definitions

Siç dihet, Kubernetes lejon krijimin e llojeve të personalizuara (kinds) të objekteve. Për shembull, mund të krijoni një kind MysqlDatabase. Supozoni se ky tip ka dy parametra metadata: emri dhe namespace.

apiVersion: example.com/v1alpha1
kind: MysqlDatabase
metadata:
  name: foo
  namespace: bar

Kemi një kluster Kubernetes me hapësira të ndryshme emërtimi, në të cilat mund të krijojmë baza të të dhënave MySQL. Në këtë rast, shell-operator mund të përdoret për të ndjekur burimet MysqlDatabase, lidhjet e tyre me serverin MySQL dhe për të sinkronizuar gjendjet e dëshiruara dhe ato të vëzhguara të klusterit.

Go? Bash! Takoni shell-operatorin (përmbledhje dhe video e prezantimit nga KubeCon EU'2020)

Shembulli 3: monitorimi i rrjetit të klusterit

Siç dihet, përdorimi i ping është mënyra më e thjeshtë për të monitoruar rrjetin. Në këtë shembull do të tregojmë se si të realizojmë një monitorim të tillë me anë të shell-operator.

Së pari, do të nevojitet të abonoheni te nodet. Shell-operatori ka nevojë për emrin dhe adresën IP të çdo nodi. Me ndihmën e tyre, ai do të pingojë këto node.

configVersion: v1
kubernetes:
- name: nodes
  apiVersion: v1
  kind: Node
  jqFilter: |
    {
      name: .metadata.name,
      ip: (
       .status.addresses[] |  
        select(.type == "InternalIP") |
        .address
      )
    }
  group: main
  keepFullObjectsInMemory: false
  executeHookOnEvent: []
schedule:
- name: every_minute
  group: main
  crontab: "* * * * *"

Parametri executeHookOnEvent: [] parandalon ekzekutimin e hooks në përgjigje të ndonjë eventi (pra, në përgjigje të ndryshimit, shtimit, ose fshirjes së nodeve). Sidoqoftë, ai do të ekzekutohet (dhe do të përditësojë listën e nodeve) sipër orarit — çdo minutë, siç parashikohet në fushën schedule.

Tani tani është pyetja, si do ta dimë për probleme si humbja e paketimeve? Le të shikojmë kodin:

function __main__() {
  for i in $(seq 0 "$(context::jq -r '(.snapshots.nodes | length) - 1')"); do
    node_name="$(context::jq -r '.snapshots.nodes['"$i"'].filterResult.name')"
    node_ip="$(context::jq -r '.snapshots.nodes['"$i"'].filterResult.ip')"
    packets_lost=0
    if ! ping -c 1 "$node_ip" -t 1 ; then
      packets_lost=1
    fi
    cat >> "$METRICS_PATH" <<END
      {
        "name": "node_packets_lost",
        "add": $packets_lost,
        "labels": {
          "node": "$node_name"
        }
      }
END
  done
}

Ne kalojmë nëpër listën e nyjeve, marrim emrat dhe IP adresat e tyre, bëjmë ping dhe dërgojmë rezultatet në Prometheus. Shell-operatori di të eksportojë metrikat në Prometheus, duke i ruajtur ato në një skedar, i vendosur sipas rrugës, e cila është përcaktuar në variablën mjedisore $METRICS_PATH.

Kështu mund të bëni një operator për monitorimin e thjeshtë të rrjetit në klasër.

Mekanizmi i radhëve

Ky artikull do të ishte i papërfunduar pa përshkrimin e një mekanizmi tjetër të rëndësishëm, i integruar në shell-operator. Imagjinoni se ai kryen një hook në përgjigje të një ngjarjeje në klasër.

  • Çfarë do të ndodhë nëse në të njëjtën kohë në klasër ndodh një ngjarje tjetër? A do të nisë shell-operatori një ekzemplar tjetër të hook-ut?
  • Dhe çfarë nëse në klasër ndodhin menjëherë, le të themi, pesë ngjarje?
  • А что, если в кластере сразу произойдут, скажем, пять событий?
  • A do shell-operator do t'i përpunojë ato paralelisht?
  • Si qëndron puna me resurset konsumuese, siç janë memoria dhe CPU?

Fatmirësisht, shell-operator ka një mekanizëm të ngjitur për radhë. Të gjithë ngjarjet vendosen në një radhë dhe përpunohen një pas një.

Le të ilustrojmë këtë me shembuj. Supozoni se kemi dy hooks. Ngjarja e parë i jepet hook-ut të parë. Pasi përpunimi i saj të përfundojë, radhë avancohet përpara. Tre ngjarjet e ardhshme dërgohen në hook-un e dytë - ato nxirren nga radhë dhe i jepen atij 'në grup'. Domethënë, hook-u merr një array ngjarjesh ose, më saktësisht, një array kontekstesh të lidhjes.

Po ashtu, këto ngjarje mund të bashkohen në një ngjarje të madhe. Kjo kontrollohet nga parametri grup në konfigurimin e lidhjes.

Go? Bash! Takoni shell-operatorin (përmbledhje dhe video e prezantimit nga KubeCon EU'2020)

Mund të krijoni çdo numër radhësh/hook-esh dhe kombinime të ndryshme të tyre. Për shembull, një radhë mund të funksionojë me dy hooks, ose e kundërta.

Go? Bash! Takoni shell-operatorin (përmbledhje dhe video e prezantimit nga KubeCon EU'2020)

E gjitha çfarë duhet bërë është të konfigurohet siç duhet fusha rendit në konfigurimin e lidhjes. Nëse nuk caktohet emri i radhës, hook-u aktivizohet në radhën e paracaktuar (default). Një mekanizëm i tillë radhitje lejon zgjidhjen e plotë të të gjitha problemeve të menaxhimit të burimeve kur punohet me hooks.

Përfundimi

Ne e shpjeguam se ç'është shell-operator, treguam se si mund të krijoni shpejt dhe pa shumë përpjekje operatorë Kubernetes me të, dhe ofruam disa shembuj të përdorimit të tij.

Informacion i detajuar mbi shell-operator, si dhe një udhëzues i shkurtër për përdorimin e tij, janë në dispozicion në repo në GitHub. Mos hezitoni të na kontaktoni me pyetje: mund t'i diskutojmë ato në grupin e Telegramit (në rusisht) ose në këtë forum (në anglisht).

Dhe nëse ju pëlqeu - ne gjithmonë jemi të lumtur për issues/PR/yjet e reja në GitHub, ku, mes të tjerash, mund të gjeni edhe projekte të tjera . Ndër to, veçojmë, i cili është vëllai më i madh i shell-operator addon-operator. Ky mjet përdor chart-ët Helm për të instaluar shtesat, mund të dorëzojë përditësime dhe të monitorojë parametrat/vlerat e chart-ëve, kontrollon procesin e instalimit të chart-ëve dhe gjithashtu mund t'i modifikojë ato si përgjigje ndaj ngjarjeve në klaster.. Эта утилита использует чарты Helm для установки дополнений, умеет доставлять обновления и следить за различными параметрами/значениями чартов, контролирует процесс инсталляции чартов, а также может модифицировать их в ответ на события в кластере.

Go? Bash! Takoni shell-operatorin (përmbledhje dhe video e prezantimit nga KubeCon EU'2020)

Video dhe prezentime

Video nga prezantimi (~23 minuta):

Luaj videon

Prezantimi i referatit:

P.S.

Lexoni gjithashtu në blogun tonë:

Burimi: habr.com

Bli një hosting të besueshëm për faqet me mbrojtje DDoS, VPS VDS serverë 🔥 Bli një hosting të besueshëm për faqet me mbrojtje DDoS, VPS VDS serverë | ProHoster