Pumunta ka? Bash! Kilalanin ang shell-operator (review at video report mula sa KubeCon EU'2020)

Ngayong taon, ang pangunahing European Kubernetes conference - KubeCon + CloudNativeCon Europe 2020 - ay virtual. Gayunpaman, ang gayong pagbabago sa format ay hindi naging hadlang sa amin sa paghahatid ng aming matagal nang binalak na ulat na β€œGo? Bash! Kilalanin ang Shell-operator” na nakatuon sa aming Open Source na proyekto shell-operator.

Ang artikulong ito, na inspirasyon ng usapan, ay nagpapakita ng diskarte sa pagpapasimple sa proseso ng paglikha ng mga operator para sa Kubernetes at ipinapakita kung paano ka makakagawa ng sarili mo nang may kaunting pagsisikap gamit ang isang shell-operator.

Pumunta ka? Bash! Kilalanin ang shell-operator (review at video report mula sa KubeCon EU'2020)

Pagpapakilala video ng ulat (~23 minuto sa Ingles, kapansin-pansing mas nagbibigay-kaalaman kaysa sa artikulo) at ang pangunahing katas mula rito sa anyo ng teksto. Go!

Sa Flant, patuloy naming ino-optimize at ino-automate ang lahat. Ngayon ay pag-uusapan natin ang tungkol sa isa pang kapana-panabik na konsepto. Kilalanin: cloud-native shell scripting!

Gayunpaman, magsimula tayo sa konteksto kung saan nangyayari ang lahat ng ito: Kubernetes.

Kubernetes API at mga controller

Ang API sa Kubernetes ay maaaring katawanin bilang isang uri ng file server na may mga direktoryo para sa bawat uri ng object. Ang mga bagay (mga mapagkukunan) sa server na ito ay kinakatawan ng mga YAML file. Bilang karagdagan, ang server ay may pangunahing API na nagbibigay-daan sa iyong gawin ang tatlong bagay:

  • tumanggap mapagkukunan ayon sa uri at pangalan nito;
  • magbago mapagkukunan (sa kasong ito, ang server ay nag-iimbak lamang ng "tama" na mga bagay - lahat ng hindi wastong nabuo o inilaan para sa iba pang mga direktoryo ay itinapon);
  • subaybayan para sa mapagkukunan (sa kasong ito, agad na natatanggap ng user ang kasalukuyan/na-update na bersyon nito).

Kaya, kumikilos ang Kubernetes bilang isang uri ng file server (para sa mga manifest ng YAML) na may tatlong pangunahing pamamaraan (oo, talagang may iba pa, ngunit aalisin namin ang mga ito sa ngayon).

Pumunta ka? Bash! Kilalanin ang shell-operator (review at video report mula sa KubeCon EU'2020)

Ang problema ay ang server ay maaari lamang mag-imbak ng impormasyon. Upang gawin itong gumana kailangan mo magsusupil - ang pangalawang pinakamahalaga at pangunahing konsepto sa mundo ng Kubernetes.

Mayroong dalawang pangunahing uri ng mga controllers. Ang una ay kumukuha ng impormasyon mula sa Kubernetes, pinoproseso ito ayon sa nested logic, at ibinalik ito sa K8s. Ang pangalawa ay kumukuha ng impormasyon mula sa Kubernetes, ngunit, hindi tulad ng unang uri, binabago ang estado ng ilang panlabas na mapagkukunan.

Tingnan natin ang proseso ng paglikha ng Deployment sa Kubernetes:

  • Deployment Controller (kasama sa kube-controller-manager) tumatanggap ng impormasyon tungkol sa Deployment at lumilikha ng ReplicaSet.
  • Gumagawa ang ReplicaSet ng dalawang replika (dalawang pod) batay sa impormasyong ito, ngunit hindi pa nakaiskedyul ang mga pod na ito.
  • Ang scheduler ay nag-iskedyul ng mga pod at nagdaragdag ng impormasyon ng node sa kanilang mga YAML.
  • Gumagawa ang mga Kubelets ng mga pagbabago sa isang panlabas na mapagkukunan (sabihin ang Docker).

Pagkatapos ang buong sequence na ito ay paulit-ulit sa reverse order: sinusuri ng kubelet ang mga container, kinakalkula ang status ng pod at ibinalik ito. Ang ReplicaSet controller ay tumatanggap ng status at ina-update ang estado ng replica set. Ang parehong bagay ay nangyayari sa Deployment Controller at sa wakas ay nakuha ng user ang na-update (kasalukuyang) status.

Pumunta ka? Bash! Kilalanin ang shell-operator (review at video report mula sa KubeCon EU'2020)

Shell-operator

Lumalabas na ang Kubernetes ay batay sa magkasanib na gawain ng iba't ibang mga controllers (ang mga operator ng Kubernetes ay mga controllers din). Ang tanong ay lumitaw, kung paano lumikha ng iyong sariling operator na may kaunting pagsisikap? At narito ang aming binuo ay dumating sa pagsagip shell-operator. Pinapayagan nito ang mga administrator ng system na lumikha ng kanilang sariling mga pahayag gamit ang mga pamilyar na pamamaraan.

Pumunta ka? Bash! Kilalanin ang shell-operator (review at video report mula sa KubeCon EU'2020)

Simpleng halimbawa: pagkopya ng mga lihim

Tingnan natin ang isang simpleng halimbawa.

Sabihin nating mayroon tayong Kubernetes cluster. Mayroon itong namespace default na may ilang Lihim mysecret. Bilang karagdagan, mayroong iba pang mga namespace sa cluster. Ang ilan sa kanila ay may partikular na label na naka-attach sa kanila. Ang aming layunin ay kopyahin ang Lihim sa mga namespace na may label.

Ang gawain ay kumplikado sa katotohanan na ang mga bagong namespace ay maaaring lumitaw sa cluster, at ang ilan sa mga ito ay maaaring may ganitong label. Sa kabilang banda, kapag ang label ay tinanggal, ang Secret ay dapat ding tanggalin. Bilang karagdagan dito, ang Lihim mismo ay maaari ding magbago: sa kasong ito, ang bagong Lihim ay dapat makopya sa lahat ng mga namespace na may mga label. Kung hindi sinasadyang natanggal ang Secret sa anumang namespace, dapat itong ibalik kaagad ng aming operator.

Ngayon na ang gawain ay nabalangkas na, oras na upang simulan ang pagpapatupad nito gamit ang shell-operator. Ngunit una ay nagkakahalaga ng pagsasabi ng ilang mga salita tungkol sa shell-operator mismo.

Paano gumagana ang shell-operator

Tulad ng iba pang mga workload sa Kubernetes, ang shell-operator ay tumatakbo sa sarili nitong pod. Sa pod na ito sa direktoryo /hooks Ang mga maipapatupad na file ay naka-imbak. Ang mga ito ay maaaring mga script sa Bash, Python, Ruby, atbp. Tinatawag namin ang mga executable na file na hook (Hooks).

Pumunta ka? Bash! Kilalanin ang shell-operator (review at video report mula sa KubeCon EU'2020)

Nag-subscribe ang Shell-operator sa mga kaganapan sa Kubernetes at pinapatakbo ang mga hook na ito bilang tugon sa mga kaganapang iyon na kailangan namin.

Pumunta ka? Bash! Kilalanin ang shell-operator (review at video report mula sa KubeCon EU'2020)

Paano malalaman ng shell-operator kung aling hook ang tatakbo at kailan? Ang punto ay ang bawat kawit ay may dalawang yugto. Sa panahon ng pagsisimula, pinapatakbo ng shell-operator ang lahat ng mga kawit na may isang argumento --config Ito ang yugto ng pagsasaayos. At pagkatapos nito, ang mga kawit ay inilunsad sa normal na paraan - bilang tugon sa mga kaganapan kung saan sila ay nakalakip. Sa huling kaso, natatanggap ng hook ang nagbubuklod na konteksto (umiiral na konteksto) - data sa format na JSON, na pag-uusapan natin nang mas detalyado sa ibaba.

Gumagawa ng operator sa Bash

Ngayon ay handa na kami para sa pagpapatupad. Upang gawin ito, kailangan naming magsulat ng dalawang pag-andar (sa pamamagitan ng paraan, inirerekumenda namin ang library shell_lib, na lubos na nagpapasimple sa pagsusulat ng mga kawit sa Bash):

  • ang una ay kinakailangan para sa yugto ng pagsasaayos - ipinapakita nito ang konteksto na nagbubuklod;
  • ang pangalawa ay naglalaman ng pangunahing lohika ng kawit.

#!/bin/bash

source /shell_lib.sh

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

function __main__() {
  # THE LOGIC
}

hook::run "$@"

Ang susunod na hakbang ay ang magpasya kung anong mga bagay ang kailangan natin. Sa aming kaso, kailangan naming subaybayan:

  • lihim ng pinagmulan para sa mga pagbabago;
  • lahat ng namespace sa cluster, para malaman mo kung alin ang may label na naka-attach sa kanila;
  • i-target ang mga lihim upang matiyak na ang lahat ng ito ay naka-sync sa pinagmulang sikreto.

Mag-subscribe sa lihim na pinagmulan

Ang pagsasaayos ng pagbubuklod para dito ay medyo simple. Ipinapahiwatig namin na interesado kami sa Lihim na may pangalan mysecret sa namespace default:

Pumunta ka? Bash! Kilalanin ang shell-operator (review at video report mula sa 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

Bilang resulta, ma-trigger ang hook kapag nagbago ang source secret (src_secret) at tumanggap ng sumusunod na konteksto na nagbubuklod:

Pumunta ka? Bash! Kilalanin ang shell-operator (review at video report mula sa KubeCon EU'2020)

Tulad ng nakikita mo, naglalaman ito ng pangalan at ang buong bagay.

Sinusubaybayan ang mga namespace

Ngayon ay kailangan mong mag-subscribe sa mga namespace. Upang gawin ito, tinukoy namin ang sumusunod na pagsasaayos na nagbubuklod:

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

Tulad ng nakikita mo, isang bagong patlang ang lumitaw sa pagsasaayos na may pangalan jqFilter. Gaya ng ipinahihiwatig ng pangalan nito, jqFilter sinasala ang lahat ng hindi kinakailangang impormasyon at lumilikha ng bagong object ng JSON na may mga field na interesado sa amin. Ang isang hook na may katulad na configuration ay makakatanggap ng sumusunod na konteksto na nagbubuklod:

Pumunta ka? Bash! Kilalanin ang shell-operator (review at video report mula sa KubeCon EU'2020)

Naglalaman ito ng array filterResults para sa bawat namespace sa cluster. Boolean variable hasLabel ay nagpapahiwatig kung ang isang label ay naka-attach sa isang ibinigay na namespace. Tagapili keepFullObjectsInMemory: false ay nagpapahiwatig na hindi na kailangang panatilihin ang kumpletong mga bagay sa memorya.

Pagsubaybay sa mga lihim ng target

Nag-subscribe kami sa lahat ng Secrets na may tinukoy na anotasyon managed-secret: "yes" (ito ang target natin 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

Sa kasong ito jqFilter sinasala ang lahat ng impormasyon maliban sa namespace at parameter resourceVersion. Ang huling parameter ay naipasa sa anotasyon kapag gumagawa ng lihim: nagbibigay-daan ito sa iyong paghambingin ang mga bersyon ng mga lihim at panatilihing napapanahon ang mga ito.

Ang hook na na-configure sa ganitong paraan ay, kapag naisakatuparan, ay makakatanggap ng tatlong umiiral na konteksto na inilarawan sa itaas. Maaari silang isipin bilang isang uri ng snapshot (retrato) kumpol.

Pumunta ka? Bash! Kilalanin ang shell-operator (review at video report mula sa KubeCon EU'2020)

Batay sa lahat ng impormasyong ito, maaaring bumuo ng isang pangunahing algorithm. Ito ay umuulit sa lahat ng mga namespace at:

  • kung hasLabel mga bagay true para sa kasalukuyang namespace:
    • inihahambing ang pandaigdigang lihim sa lokal:
      • kung sila ay pareho, ito ay walang ginagawa;
      • kung magkaiba sila - executes kubectl replace o create;
  • kung hasLabel mga bagay false para sa kasalukuyang namespace:
    • tinitiyak na ang Secret ay wala sa ibinigay na namespace:
      • kung ang lokal na Lihim ay naroroon, tanggalin ito gamit kubectl delete;
      • kung ang lokal na Lihim ay hindi nakita, wala itong gagawin.

Pumunta ka? Bash! Kilalanin ang shell-operator (review at video report mula sa KubeCon EU'2020)

Pagpapatupad ng algorithm sa Bash maaari mong i-download sa aming mga repositoryo na may mga halimbawa.

Iyon ay kung paano kami nakagawa ng isang simpleng Kubernetes controller gamit ang 35 linya ng YAML config at halos kaparehong dami ng Bash code! Ang trabaho ng shell-operator ay iugnay ang mga ito.

Gayunpaman, ang pagkopya ng mga lihim ay hindi lamang ang lugar ng aplikasyon ng utility. Narito ang ilang higit pang mga halimbawa upang ipakita kung ano ang kaya niya.

Halimbawa 1: Paggawa ng mga pagbabago sa ConfigMap

Tingnan natin ang isang Deployment na binubuo ng tatlong pod. Gumagamit ang mga pod ng ConfigMap para mag-imbak ng ilang configuration. Noong inilunsad ang mga pod, ang ConfigMap ay nasa isang partikular na estado (tawagin natin itong v.1). Alinsunod dito, ginagamit ng lahat ng pod ang partikular na bersyong ito ng ConfigMap.

Ngayon, ipagpalagay natin na ang ConfigMap ay nagbago (v.2). Gayunpaman, gagamitin ng mga pod ang nakaraang bersyon ng ConfigMap (v.1):

Pumunta ka? Bash! Kilalanin ang shell-operator (review at video report mula sa KubeCon EU'2020)

Paano ko sila mapapalipat sa bagong ConfigMap (v.2)? Ang sagot ay simple: gumamit ng isang template. Magdagdag tayo ng checksum annotation sa seksyon template Mga configuration ng deployment:

Pumunta ka? Bash! Kilalanin ang shell-operator (review at video report mula sa KubeCon EU'2020)

Bilang resulta, irerehistro ang checksum na ito sa lahat ng pod, at magiging pareho ito sa Deployment. Ngayon kailangan mo lang i-update ang anotasyon kapag nagbago ang ConfigMap. At ang shell-operator ay madaling gamitin sa kasong ito. Ang kailangan mo lang gawin ay program isang hook na magsu-subscribe sa ConfigMap at mag-a-update ng checksum.

Kung gumawa ang user ng mga pagbabago sa ConfigMap, mapapansin sila ng shell-operator at muling kalkulahin ang checksum. Pagkatapos nito ay gaganap ang mahika ng Kubernetes: papatayin ng orkestra ang pod, gagawa ng bago, hintayin itong maging Ready, at nagpapatuloy sa susunod. Bilang resulta, magsi-synchronize ang Deployment at lilipat sa bagong bersyon ng ConfigMap.

Pumunta ka? Bash! Kilalanin ang shell-operator (review at video report mula sa KubeCon EU'2020)

Halimbawa 2: Paggawa gamit ang Mga Kahulugan ng Custom na Resource

Tulad ng alam mo, pinapayagan ka ng Kubernetes na lumikha ng mga custom na uri ng mga bagay. Halimbawa, maaari kang lumikha ng uri MysqlDatabase. Sabihin nating may dalawang parameter ng metadata ang ganitong uri: name ΠΈ namespace.

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

Mayroon kaming Kubernetes cluster na may iba't ibang namespace kung saan makakagawa kami ng mga database ng MySQL. Sa kasong ito, ang shell-operator ay maaaring gamitin upang subaybayan ang mga mapagkukunan MysqlDatabase, pagkonekta sa kanila sa MySQL server at pag-synchronize ng ninanais at naobserbahang estado ng cluster.

Pumunta ka? Bash! Kilalanin ang shell-operator (review at video report mula sa KubeCon EU'2020)

Halimbawa 3: Cluster Network Monitoring

Tulad ng alam mo, ang paggamit ng ping ay ang pinakasimpleng paraan upang masubaybayan ang isang network. Sa halimbawang ito ipapakita namin kung paano ipatupad ang naturang pagsubaybay gamit ang shell-operator.

Una sa lahat, kakailanganin mong mag-subscribe sa mga node. Kailangan ng shell operator ang pangalan at IP address ng bawat node. Sa tulong nila, ipi-ping niya ang mga node na ito.

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: "* * * * *"

Parametro executeHookOnEvent: [] pinipigilan ang hook na tumakbo bilang tugon sa anumang kaganapan (iyon ay, bilang tugon sa pagbabago, pagdaragdag, pagtanggal ng mga node). Gayunpaman, siya tatakbo (at i-update ang listahan ng mga node) Naka-iskedyul - bawat minuto, gaya ng inireseta ng field schedule.

Ngayon ang tanong ay lumitaw, paano natin alam ang tungkol sa mga problema tulad ng packet loss? Tingnan natin ang code:

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
}

Ulitin namin ang listahan ng mga node, kunin ang kanilang mga pangalan at IP address, i-ping ang mga ito at ipadala ang mga resulta sa Prometheus. Ang Shell-operator ay maaaring mag-export ng mga sukatan sa Prometheus, i-save ang mga ito sa isang file na matatagpuan ayon sa landas na tinukoy sa variable ng kapaligiran $METRICS_PATH.

Narito ito maaari kang gumawa ng operator para sa simpleng network monitoring sa isang cluster.

Mekanismo ng pagpila

Ang artikulong ito ay hindi kumpleto nang hindi naglalarawan ng isa pang mahalagang mekanismo na binuo sa shell-operator. Isipin na nagsasagawa ito ng ilang uri ng hook bilang tugon sa isang kaganapan sa cluster.

  • Ano ang mangyayari kung, sa parehong oras, may nangyari sa cluster? isa pa kaganapan?
  • Magpapatakbo ba ang shell-operator ng isa pang instance ng hook?
  • Paano kung, sabihin nating, limang kaganapan ang mangyari sa cluster nang sabay-sabay?
  • Ipoproseso ba ng shell-operator ang mga ito nang magkatulad?
  • Paano ang tungkol sa mga natupok na mapagkukunan tulad ng memorya at CPU?

Sa kabutihang palad, ang shell-operator ay may built-in na mekanismo ng pagpila. Ang lahat ng mga kaganapan ay nakapila at pinoproseso nang sunud-sunod.

Ilarawan natin ito sa mga halimbawa. Sabihin nating mayroon tayong dalawang kawit. Ang unang kaganapan ay napupunta sa unang kawit. Kapag kumpleto na ang pagproseso nito, umuusad ang pila. Ang susunod na tatlong mga kaganapan ay na-redirect sa pangalawang kawit - sila ay tinanggal mula sa pila at ipinasok dito sa isang "bundle". Yan ay nakakatanggap ang hook ng hanay ng mga kaganapan β€” o, mas tiyak, isang hanay ng mga umiiral na konteksto.

Pati ang mga ito Ang mga kaganapan ay maaaring pagsamahin sa isang malaking. Ang parameter ay responsable para dito group sa pagsasaayos na nagbubuklod.

Pumunta ka? Bash! Kilalanin ang shell-operator (review at video report mula sa KubeCon EU'2020)

Maaari kang lumikha ng anumang bilang ng mga queues/hooks at ang kanilang iba't ibang mga kumbinasyon. Halimbawa, ang isang pila ay maaaring gumana sa dalawang kawit, o kabaliktaran.

Pumunta ka? Bash! Kilalanin ang shell-operator (review at video report mula sa KubeCon EU'2020)

Ang kailangan mo lang gawin ay i-configure ang field nang naaayon queue sa pagsasaayos na nagbubuklod. Kung hindi tinukoy ang pangalan ng queue, tatakbo ang hook sa default na queue (default). Ang mekanismo ng pagpila na ito ay nagpapahintulot sa iyo na ganap na malutas ang lahat ng mga problema sa pamamahala ng mapagkukunan kapag nagtatrabaho sa mga kawit.

Konklusyon

Ipinaliwanag namin kung ano ang shell-operator, ipinakita kung paano ito magagamit upang mabilis at walang kahirap-hirap na lumikha ng mga operator ng Kubernetes, at nagbigay ng ilang halimbawa ng paggamit nito.

Ang detalyadong impormasyon tungkol sa shell-operator, pati na rin ang isang mabilis na tutorial kung paano gamitin ito, ay makukuha sa kaukulang mga repositoryo sa GitHub. Huwag mag-atubiling makipag-ugnayan sa amin para sa mga tanong: maaari mong talakayin ang mga ito sa isang espesyal Grupo ng telegrama (sa Russian) o sa forum na ito (sa Ingles).

At kung nagustuhan mo ito, lagi kaming masaya na makakita ng mga bagong isyu/PR/star sa GitHub, kung saan, sa pamamagitan ng paraan, makakahanap ka ng iba mga kawili-wiling proyekto. Kabilang sa mga ito ay nagkakahalaga ng pag-highlight addon-operator, na siyang malaking kapatid ng shell-operator. Gumagamit ang utility na ito ng mga Helm chart upang mag-install ng mga add-on, makakapaghatid ng mga update at masubaybayan ang iba't ibang mga parameter/halaga ng chart, kinokontrol ang proseso ng pag-install ng mga chart, at maaari ding baguhin ang mga ito bilang tugon sa mga kaganapan sa cluster.

Pumunta ka? Bash! Kilalanin ang shell-operator (review at video report mula sa KubeCon EU'2020)

Mga video at slide

Video mula sa pagganap (~23 minuto):


Presentasyon ng ulat:

PS

Basahin din sa aming blog:

Pinagmulan: www.habr.com

Magdagdag ng komento