Sel aastal toimus Euroopa peamine Kubernetes'i konverents KubeCon + CloudNativeCon Europe 2020 virtuaalselt. Siiski ei takistanud see formaadi muutus meid esitlemast juba ammu plaanitud ettekannet „Go? Bash! Tutvuge Shell-operatoriga”, mis on pühendatud meie Open Source projektile. .
Selles artiklis, mis põhineb ettekandel, esitatakse lähenemine Kubernetes'i operaatorite loomise protsessi lihtsustamiseks ja näidatakse, kuidas shell-operator'i abil saab minimaalse vaevaga luua omaenda operaatori.

Tutvustame (~23 minutit inglise keeles, selgelt informatiivsem kui artikkel) ja põhisisu sellest tekstivormis. Alustame!
Me „Flante'is” optimeerime ja automatiseerime pidevalt. Täna räägime veel ühest põnevast kontseptsioonist. Tere tulemast: cloud-native shell-skriptimine!
Kuid alustame kontekstist, milles see kõik aset leiab — Kubernetes’est.
Kubernetes API ja kontrollijad
Kubernetes'es võib API-d kujutada kui mingisugust failiserverit, kus igat tüüpi objektide jaoks on katalogid. Selle serveri objektid (ressursid) on esitatud YAML-failidena. Lisaks on serveril baas-API, mis võimaldab teha kolme asja:
- saama ressursi järgi selle kind'i ja nime;
- muutma ressurssi (samal ajal server salvestab ainult "õigeid" objekte — kõik valesti vormistatud või teiste kaustade jaoks määratud objektid jäetakse kõrvale);
- jälgida ressursi (sel juhul saab kasutaja kohe selle praeguse/uuendatud versiooni).
Seega toimib Kubernetes nagu failiserver (YAML-manifestide jaoks) kolme põhimeetodiga (jah, tegelikult on ka teisi, aga neid me praegu ei käsitle).

Probleem on selles, et server oskab ainult teavet salvestada. Selle töötamiseks on vajalik kontroller — teine olulisus ja fundamentaalsus Kubernetes'e maailmas.
Erinevad kaks peamist kontrollerite tüüpi. Esimene võtab teavet Kubernetes'est, töötleb seda vastavalt sisemise loogika järgi ja tagastab K8s-ile. Teine — võtab teavet Kubernetes'est, kuid erinevalt esimesest tüübist muudab välistes ressurssides olekut.
Vaatame lähemalt Kubernetes'st Deployment’i loomise protsessi:
- Deployment Controller (mis kuulub
kube-controller-manager) saab teavet Deployment’i kohta ja loob ReplicaSet’i. - ReplicaSet loob selle teabe põhjal kaks koopiat (kahte pod'i), kuid need pod'id veel ei planeeritud.
- Planeerija planeerib pod'e ja lisab nende YAML-idesse teavet sõlmede kohta.
- Kubelet'id teevad muudatusi välistes ressurssides (näiteks Dockeris).
Seejärel kordub kogu see järjestus vastupidises järjekorras: kubelet kontrollib konteinerite olekut, arvutab pod'i staatuse ja saadab selle tagasi. ReplicaSeti kontrollija saab staatuse ja värskendab replikate kogumi olekut. Sama toimub Deployment Controlleriga, ja kasutaja saab lõpuks värskendatud (praeguse) staatuse.

Shell-operator
Kubernetes'e aluseks on erinevate kontrollijate koostöö (Kubernetes'e operaatorid on samuti kontrollijad). Kuidas luua oma operaator minimaalse vaevaga? Siin tuleb appi meie välja töötatud . See võimaldab süsteemiadministraatoritel luua oma operaatorid, kasutades tuttavaid meetodeid.
Lihtne näide: saladuste kopeerimine
Vaatame lihtsat näidet.
Oletame, et meil on Kubernetes'e klaster. Seal on nimesüsteem default mingi Secret'iga mysecret. Lisaks on klastris ka teisi nimiruume. Mõnedele neist on kinnitatud teatud silt. Meie eesmärk on kopeerida Secret nimiruumidesse, kus on see silt.
Ülesanne muutub keerulisemaks, kuna klastris võivad tekkida uued nimiruumid, ja mõnel neist võib olla see silt. Teiselt poolt, sildi eemaldamisel peab Secret ka eemaldama. Lisaks võib ka ise Secret muutuda: sel juhul peab uus Secret olema kopeeritud kõigisse nimiruumidesse, kus on sildid. Kui Secret juhuslikult mõnes nimiruumis eemaldatakse, peab meie operaator selle kohe taastama.
Nüüd, kui ülesanne on sõnastatud, on aeg alustada selle elluviimist shell-operatori abil. Kuid esmalt tuleks öelda paar sõna shell-operatori kohta.
Shell-operatori tööpõhimõtted
Nagu teised töökoormused Kuberneteses, töötab shell-operator oma pod’is. Selles pod’is asub kataloog /hooks , kus hoitakse täidetavaid faile. Need võivad olla Bash, Python, Ruby jne skripte. Selliseid täidetavaid faile nimetame hook'ideks (hooks).

). Shell-operator subscribe'ib Kuberneteses toimuvatele sündmustele ja käivitab neid hook'e vastusena neile sündmustele, mis meid huvitavad.

Kuidas shell-operator teab, millal ja millist hook'i käivitada? Iga hook'il on kaks etappi. Shell-operator käivitab hook'id koos argumendiga alustatud etapis --config — see on seadistamise etapp. Pärast seda käivituvad hook'id tavaliselt — vastuseks sündmustele, millega nad on seotud. Viimases juhul saab hook sidumise konteksti (binding context) — andmed JSON-formaadis, millest räägime lähemalt allpool.
Teeme Bash'is operaatorit
Nüüd oleme valmis teostuseks. Selleks peame kirjutama kaks funktsiooni (märkusena soovitame raamatukogu , mis lihtsustab hook'ide kirjutamist Bash'is):
- esimene on vajalik seadistamise etapis — see kuvab sidumise konteksti;
- teine sisaldab hook'i põhiloogikat.
#!/bin/bash
source /shell_lib.sh
function __config__() {
cat << EOF
configVersion: v1
# BINDING CONFIGURATION
EOF
}
function __main__() {
# THE LOGIC
}
hook::run "$@"
J siguiente etapas — kindlaks teha, milliseid objekte vajame. Meie puhul on vajalik jälgida:
- salajase allika muudatused;
- kõiki namespace'e klastris, et teada, millistele neist on kleebis kinnitatud;
- salajased sihid, et veenduda, et kõik need on sünkroonitud salajase allikaga.
Liitume salajase allikaga
Binding configuration on sellele on piisavalt lihtne. Me näitame, et meid huvitab saladus nimega mysecret nimeserveris default:

function __config__() {
cat << EOF
configVersion: v1
kubernetes:
- name: src_secret
apiVersion: v1
kind: Secret
nameSelector:
matchNames:
- mysecret
namespace:
nameSelector:
matchNames: ["default"]
group: main
EOF
Kokkuvõttes käivitatakse hook, kui saladuse allikas muutub (src_secret) ja saab järgmise binding context'i:

Nagu näete, sisaldab see nime ja objekti tervikuna.
Jälgime nimesid
Nüüd peame registreeruma nimedele. Selleks näitame järgmist binding configuration'i:
- name: namespaces
group: main
apiVersion: v1
kind: Namespace
jqFilter: |
{
namespace: .metadata.name,
hasLabel: (
.metadata.labels // {} |
contains({"secret": "yes"})
)
}
group: main
keepFullObjectsInMemory: false
Nagu näete, on konfiguratsioonis uus väli nimega jqFilter. Kuidas tema nimi ütleb, jqFilter filtreerib kogu üleliigse teabe ja loob uue JSON objekti, mille väljad on meie jaoks huvitavad. Hook sellise konfiguratsiooniga saab järgmise binding context'i:

See sisaldab massiivi filterResults iga klastris asuva nime jaoks. Boolean muutuja hasLabel näitab, kas silt on antud nimede ruumiga seotud. Valija keepFullObjectsInMemory: false ütleb, et pole vaja hoida täisobjekte mälus.
Jälgime sihtsaladusi
Tellime kõiki salajasi, millel on määratud annotatsioon managed-secret: "yes" (need on meie sihttou 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
Sel juhul jqFilter filtreerib kogu teabe välja, välja arvatud nimede ruum ja parameeter resourceVersion. Viimane parameeter anti annotatsioonis, kui saladus loodi: see võimaldab võrrelda saladuste versioone ja hoida neid ajakohasena.
Hook, mis on selliselt seadistatud, saab tõhusalt kolme konteksti sidumist, nagu eespool kirjeldatud. Neid võib esitada kui teatud tüüpi hetkeseis (snapshot) klastri.

Kogu selle teabe põhjal saab välja töötada põhialgoritmi. See läbib kõik nimede ruumid ja:
- kui
hasLabelväärtustruepraeguse nimede ruumi jaoks:- võrdleb globaalset saladust kohaliku:
- kui nad on samad — ei tee midagi;
- kui nad erinevad — täidab
kubectl asendadavõicreate;
- võrdleb globaalset saladust kohaliku:
- kui
hasLabelväärtusfalsepraeguse nimede ruumi jaoks:- veendub, et Secret ei ole selles nimespetsiifis:
- kui kohalik Secret on olemas — kustutab selle
kubectl delete; - kui kohalik Secret ei leidu — ei tee midagi.
- kui kohalik Secret on olemas — kustutab selle
- veendub, et Secret ei ole selles nimespetsiifis:

saate alla laadida meie .
Nii suudame luua lihtsa Kubernetes'i juhtkontrolleri, kasutades 35 rida YAML-konfide ja umbes sama palju Bash'i koodi! Shell-operaatori ülesanne on need kokku siduda.
Kuid saladuste kopeerimine ei ole ainus rakendus, millega utiliit tegeleb. Siin on veel paar näidet, mis näitavad, milleks see võimeline on.
Näide 1: muudatuste tegemine ConfigMap'is
Vaadakem Deployment'i, mis koosneb kolmest pod'ist. Pod'id kasutavad ConfigMap'i teatud konfigureerimise talletamiseks. Pod'ide käivitamise ajal oli ConfigMap mingis seisundis (nimetame seda v.1). Seega kasutavad kõik pod'id just seda ConfigMap'i versiooni.
Nüüd eeldame, et ConfigMap on muutunud (v.2). Kuid pod'id jätkuvalt kasutavad vana ConfigMap'i versiooni (v.1):

Kuidas saavutada, et nad üleminek uuele ConfigMap'ile (v.2)? Vastus on lihtne: kasutada templati. Lisame annotatsiooni koos kontrollsummaga sektsiooni template Deployment'i konfiguratsioon:

Tulemusena on kõigis pod'ides selline kontrollsumma, nagu Deployment'il. Nüüd tuleb lihtsalt annotatsiooni uuendada, kui ConfigMap muutub. Ja shell-operator on selle puhul suurepärane lahendus. Kõik, mis on vajalik — programmeerida hook, mis registreerib end ConfigMap'i ning uuendab kontrollsummat.
Kui kasutaja teeb ConfigMap'is muudatusi, märkab shell-operator neid ning arvutab kontrollsummat uuesti. Seejärel astub mängu Kubernetes'i maagia: orkestreerija tapab pod'i, loob uue, ootab, kuni see muutub Valmis, ja liigub edasi. Tulemusena sünkroniseeritakse Deployment ja liigub uuele ConfigMap'i versioonile.

Näide 2: töö Custom Resource Definitions'iga
Nagu teada, võimaldab Kubernetes luua kohandatud tüüpe (kinds) objekte. Näiteks, võib luua tüübi MysqlDatabase.Oletame, et sellel tüübil on kaks metadata-parameetrit: name ja namespace.
apiVersion: example.com/v1alpha1
kind: MysqlDatabase
metadata:
name: foo
namespace: bar
Meil on Kubernetes klastri, millel on erinevad nimeruumi, kus saame luua MySQL andmebaase. Sel juhul saab shell-operatorit kasutada ressursside jälgimiseks MysqlDatabase., nende ühenduste jälgimiseks MySQL serveriga ja soovitud ning jälgitud klastriseisundite sünkroniseerimiseks.

Näide 3: klastrivõrgu jälgimine
Nagu teada, on ping'i kasutamine kõige lihtsam viis võrgu jälgimiseks. Selles näites näitame, kuidas sellist jälgimist rakendada shell-operatori abil.
Esiteks on vaja abonente registreerida sõlmedele. Shell-operator vajab iga sõlme nime ja IP-aadressi. Nende abil saavad nad neid sõlmi pingida.
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: "* * * * *"
Parameeter executeHookOnEvent: [] takistab hüüde käivitamist vastusena igasugustele sündmustele (st sõlmede muutmise, lisamise või kustutamise vastusena). Siiski käivitub ta (ja uuendab sõlmede loetelu) ajakava järgi — iga minut, nagu on ette nähtud väljas schedule.
Nüüd tekib küsimus, kuidas me saame teada probleemidest, nagu pakettide kaotus? Vaatame koodile:
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
}
Me käime läbi sõlmede loendi, saame nende nimed ja IP-aadressid, pingime ja edastame tulemused Prometheusse. Shell-operator oskab eksportida mõõdikud Prometheusesse, salvestades need faili, mis asub keskkonnamuutuja $METRICS_PATH.
abil saab teha operaatorit lihtsaks võrgu jälgimiseks klastris.
Queue mehhanism
See artikkel oleks puudulik, kui ei mainiks veel üht olulist mehhanismi, mis on integreeritud shell-operatorisse. Kujutage ette, et see käivitab mingi huki vastusena klastris toimunud sündmusele.
- Mis juhtub, kui samal ajal klastris toimub veel üks sündmus?
- Kas shell-operator käivitab veel ühe huki eksemplari?
- Ja mis siis, kui klastris toimub korraga, ütleme, viis sündmust?
- Kas shell-operator töötleb neid paralleelselt?
- Ent kuidas on lood ressursside, nagu mälu ja CPU, tarbimisega?
Õnneks on shell-operatoris olemas sisseehitatud järjekordade mehhanism. Kõik sündmused paigutatakse järjekorda ja töödeldakse järjestikku.
Illustreerime seda näidete abil. Oletame, et meil on kaks hooki. Esimene sündmus tõmmatakse esimeselt hookilt. Pärast töötlemise lõpetamist edeneb järjekord edasi. Järgmised kolm sündmust suunatakse teisele hookile — need tõmmatakse järjekorrast ja edastatakse sellele 'kogusena'. Seega hook saab sündmuste massiivi — või täpsemalt öeldes, sidumise konteksti massiivi.
Samuti saab neid sündmusi kombineerida ühte suurde. Selle eest vastutab parameeter grupp sidumise konfiguratsioonis.

Saate luua suvalise arvu järjekordi/hooke ja nende erinevaid kombinatsioone. Näiteks võib üks järjekord töötada kahe hookiga või vastupidi.

Kõik, mida tuleb teha, on vastavalt seadistada queue sidumise konfiguratsiooni väli. Kui järjekorra nime ei ole määratud, käivitatakse hook vaikimisi järjekorras (default). Selline järjekordade mehhanism lahendab täielikult kõik ressursihalduse probleemid hookidega töötamisel.
Kokkuvõte
Rääkisime, mis on shell-operator, näitasime, kuidas selle abil saab kiiresti ja vaevatasu luua Kubernetes'e operaattoreid, ning tõime esile mitmeid näiteid selle kasutamisest.
Üksikasjalik teave shell-operatori kohta ning lühike juhend selle kasutamiseks on saadaval vastavas . Ära kõhkle meiega küsimuste osas ühendust võtta: neid saab arutada eraldi (vene keeles) või (inglise keeles).
Ja kui see meeldis - oleme alati rõõmsad uutest issues/PR/tähtedest GitHubis, kus muide, leiad ka teisi . Nendest tasub esile tõsta , mis on shell-operatori vanem vend. See utiliit kasutab Helm'i chart'e laienduste installimiseks, suudab edastada uuendusi ja jälgida erinevaid parameetreid/väärtuseid chart'ides, kontrollib chart'ide installimise protsessi ja suudab neid modifitseerida vastusena klastris toimuvatele sündmustele.

Video ja slaidid
Esituse video (~23 minutit):

Ettekande esitlus:
P.S.
Lugege ka meie blogist:
- «»;
- «»;
- «»;
- «.
Allikas: habr.com
