Mennä? Lyödä! Tapaa shell-operaattori (arvostelu ja videoraportti KubeCon EU'2020:sta)

Tänä vuonna Euroopan pääkonferenssi Kubernetes - KubeCon + CloudNativeCon Europe 2020 - oli virtuaalinen. Tällainen muotomuutos ei kuitenkaan estänyt meitä toimittamasta pitkään suunniteltua raporttiamme "Go? Lyödä! Tapaa Shell-operaattori”, joka on omistettu Open Source -projektillemme kuorioperaattori.

Tämä keskustelun inspiroima artikkeli esittelee lähestymistavan operaattoreiden luomisprosessin yksinkertaistamiseen Kubernetesille ja näyttää, kuinka voit tehdä omasi mahdollisimman pienellä vaivalla shell-operaattorilla.

Mennä? Lyödä! Tapaa shell-operaattori (arvostelu ja videoraportti KubeCon EU'2020:sta)

Esittelyssä video raportista (~23 minuuttia englanniksi, huomattavasti informatiivisempi kuin artikkeli) ja pääote siitä tekstimuodossa. Mennä!

Flantilla optimoimme ja automatisoimme kaiken jatkuvasti. Tänään puhumme toisesta jännittävästä konseptista. Tavata: pilvi-natiivi komentosarjakirjoitus!

Aloitetaan kuitenkin kontekstista, jossa tämä kaikki tapahtuu: Kubernetes.

Kubernetes API ja ohjaimet

Kubernetesin API voidaan esittää eräänlaisena tiedostopalvelimena, jossa on hakemistoja jokaiselle objektityypille. Tämän palvelimen objektit (resurssit) esitetään YAML-tiedostoina. Lisäksi palvelimessa on perussovellusliittymä, jonka avulla voit tehdä kolme asiaa:

  • saada resurssi lajinsa ja nimensä mukaan;
  • muuttaa resurssi (tässä tapauksessa palvelin tallentaa vain "oikeat" objektit - kaikki väärin muodostetut tai muihin hakemistoihin tarkoitetut hylätään);
  • seurata resurssille (tässä tapauksessa käyttäjä saa välittömästi sen nykyisen/päivitetyn version).

Siten Kubernetes toimii eräänlaisena tiedostopalvelimena (YAML-luetteloille) kolmella perusmenetelmällä (kyllä, itse asiassa on muitakin, mutta jätämme ne pois toistaiseksi).

Mennä? Lyödä! Tapaa shell-operaattori (arvostelu ja videoraportti KubeCon EU'2020:sta)

Ongelmana on, että palvelin voi tallentaa vain tietoja. Jotta se toimisi, tarvitset ohjain - Kubernetesin maailman toiseksi tärkein ja perustavin konsepti.

Säätimiä on kahta päätyyppiä. Ensimmäinen ottaa tiedot Kubernetesista, käsittelee ne sisäkkäisen logiikan mukaisesti ja palauttaa ne K8s:lle. Toinen ottaa tietoja Kubernetesista, mutta toisin kuin ensimmäinen tyyppi muuttaa joidenkin ulkoisten resurssien tilaa.

Katsotaanpa lähemmin käyttöönoton luomisprosessia Kubernetesissa:

  • Käyttöönoton ohjain (sisältyy kube-controller-manager) vastaanottaa tietoja käyttöönotosta ja luo ReplicaSetin.
  • ReplicaSet luo kaksi kopiota (kaksi podia) näiden tietojen perusteella, mutta näitä podeja ei ole vielä ajoitettu.
  • Ajastin ajoittaa podeja ja lisää solmutiedot niiden YAML:eihin.
  • Kubeletit tekevät muutoksia ulkoiseen resurssiin (kuten Docker).

Sitten tämä koko sarja toistetaan käänteisessä järjestyksessä: kubelet tarkistaa säiliöt, laskee podin tilan ja lähettää sen takaisin. ReplicaSet-ohjain vastaanottaa replikajoukon tilan ja päivittää sen tilan. Sama tapahtuu Deployment Controllerin kanssa ja käyttäjä saa vihdoin päivitetyn (nykyisen) tilan.

Mennä? Lyödä! Tapaa shell-operaattori (arvostelu ja videoraportti KubeCon EU'2020:sta)

Shell-operaattori

Osoittautuu, että Kubernetes perustuu eri valvojien yhteistyöhön (Kubernetes-operaattorit ovat myös valvojia). Herää kysymys, kuinka luoda oma operaattori minimaalisella vaivalla? Ja tässä kehittämämme tulee apuun kuorioperaattori. Sen avulla järjestelmänvalvojat voivat luoda omia lausuntojaan tutuilla menetelmillä.

Mennä? Lyödä! Tapaa shell-operaattori (arvostelu ja videoraportti KubeCon EU'2020:sta)

Yksinkertainen esimerkki: salaisuuksien kopioiminen

Katsotaanpa yksinkertaista esimerkkiä.

Oletetaan, että meillä on Kubernetes-klusteri. Siinä on nimiavaruus default jollain salaisuudella mysecret. Lisäksi klusterissa on muita nimiavaruuksia. Joihinkin niistä on kiinnitetty erityinen etiketti. Tavoitteenamme on kopioida Secret nimiavaruuksiin, joissa on tunniste.

Tehtävää vaikeuttaa se, että klusteriin saattaa ilmestyä uusia nimiavaruuksia, joista joissakin voi olla tämä nimike. Toisaalta, kun tarra poistetaan, myös Secret tulee poistaa. Tämän lisäksi itse salaisuus voi myös muuttua: tässä tapauksessa uusi Secret on kopioitava kaikkiin nimiavaroihin, joissa on tunnisteet. Jos Secret poistetaan vahingossa jostain nimiavaruudesta, operaattorimme tulee palauttaa se välittömästi.

Nyt kun tehtävä on muotoiltu, on aika aloittaa sen toteuttaminen shell-operaattorilla. Mutta ensin on syytä sanoa muutama sana itse shell-operaattorista.

Kuinka shell-operaattori toimii

Kuten muutkin Kubernetes-työkuormat, komentotulkkioperaattori toimii omassa kotelossaan. Tässä hakemistossa olevassa podissa /hooks suoritettavat tiedostot tallennetaan. Nämä voivat olla skriptejä Bashissa, Pythonissa, Rubyssa jne. Kutsumme tällaisia ​​suoritettavia tiedostoja koukkuiksi (koukut).

Mennä? Lyödä! Tapaa shell-operaattori (arvostelu ja videoraportti KubeCon EU'2020:sta)

Shell-operaattori tilaa Kubernetes-tapahtumat ja suorittaa nämä koukut vastauksena tarvitsemiimme tapahtumiin.

Mennä? Lyödä! Tapaa shell-operaattori (arvostelu ja videoraportti KubeCon EU'2020:sta)

Mistä shell-operaattori tietää, mikä koukku ajaa ja milloin? Pointti on, että jokaisessa koukussa on kaksi vaihetta. Käynnistyksen aikana komentotulkkioperaattori suorittaa kaikki koukut argumentin avulla --config Tämä on konfigurointivaihe. Ja sen jälkeen koukut laukeavat normaalisti - vastauksena tapahtumiin, joihin ne on kiinnitetty. Jälkimmäisessä tapauksessa koukku vastaanottaa sitovan kontekstin (sitova konteksti) - JSON-muodossa olevat tiedot, joista puhumme yksityiskohtaisemmin alla.

Operaattorin tekeminen Bashissa

Nyt olemme valmiita toteutukseen. Tätä varten meidän on kirjoitettava kaksi funktiota (muuten suosittelemme kirjasto shell_lib, mikä yksinkertaistaa suuresti koukkujen kirjoittamista Bashissa):

  • ensimmäinen tarvitaan konfigurointivaiheessa - se näyttää sidoskontekstin;
  • toinen sisältää koukun päälogiikan.

#!/bin/bash

source /shell_lib.sh

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

function __main__() {
  # THE LOGIC
}

hook::run "$@"

Seuraava askel on päättää, mitä esineitä tarvitsemme. Meidän tapauksessamme meidän on seurattava:

  • lähdesalaisuus muutoksia varten;
  • kaikki klusterin nimitilat, jotta tiedät, mihin niistä on liitetty tunniste;
  • kohdesalaisuuksia varmistaaksesi, että ne ovat synkronoitu lähdesalaisuuden kanssa.

Tilaa salainen lähde

Sen sidontakokoonpano on melko yksinkertainen. Ilmoitamme nimellä, että olemme kiinnostuneita Secretistä mysecret nimiavaruudessa default:

Mennä? Lyödä! Tapaa shell-operaattori (arvostelu ja videoraportti KubeCon EU'2020:sta)

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

Tämän seurauksena koukku laukeaa, kun lähteen salaisuus muuttuu (src_secret) ja vastaanottaa seuraavan sitovan kontekstin:

Mennä? Lyödä! Tapaa shell-operaattori (arvostelu ja videoraportti KubeCon EU'2020:sta)

Kuten näet, se sisältää nimen ja koko objektin.

Nimitilojen kirjaaminen

Nyt sinun on tilattava nimitilat. Tätä varten määritämme seuraavan sidontamäärityksen:

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

Kuten näet, kokoonpanoon on ilmestynyt uusi kenttä nimellä jqFilter. Kuten nimikin kertoo, jqFilter suodattaa pois kaikki tarpeettomat tiedot ja luo uuden JSON-objektin meitä kiinnostavilla kentillä. Koukku, jolla on samanlainen kokoonpano, saa seuraavan sidoskontekstin:

Mennä? Lyödä! Tapaa shell-operaattori (arvostelu ja videoraportti KubeCon EU'2020:sta)

Se sisältää taulukon filterResults jokaiselle klusterin nimiavaruudelle. Boolen muuttuja hasLabel ilmaisee, onko tiettyyn nimiavaruuteen liitetty tarra. Valitsin keepFullObjectsInMemory: false ilmaisee, että täydellisiä objekteja ei tarvitse säilyttää muistissa.

Kohteen salaisuudet

Tilaamme kaikki salaisuudet, joihin on määritetty huomautus managed-secret: "yes" (nämä ovat tavoitteemme 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

Tässä tapauksessa jqFilter suodattaa pois kaikki tiedot paitsi nimitilan ja parametrin resourceVersion. Viimeinen parametri välitettiin annotaatiolle salaisuutta luotaessa: sen avulla voit verrata salaisuuksien versioita ja pitää ne ajan tasalla.

Tällä tavalla määritetty koukku vastaanottaa suoritettuaan kolme yllä kuvattua sidoskontekstia. Niitä voidaan pitää eräänlaisena tilannekuvana (kuva) -klusteri.

Mennä? Lyödä! Tapaa shell-operaattori (arvostelu ja videoraportti KubeCon EU'2020:sta)

Kaiken tämän tiedon perusteella voidaan kehittää perusalgoritmi. Se toistuu kaikissa nimiavaruuksissa ja:

  • jos hasLabel asioissa true nykyiselle nimiavaruudelle:
    • vertaa globaalia salaisuutta paikalliseen:
      • jos ne ovat samat, se ei tee mitään;
      • jos ne eroavat - suorittaa kubectl replace tai create;
  • jos hasLabel asioissa false nykyiselle nimiavaruudelle:
    • varmistaa, että Secret ei ole annetussa nimiavaruudessa:
      • Jos paikallinen Secret on olemassa, poista se käyttämällä kubectl delete;
      • jos paikallista salaisuutta ei havaita, se ei tee mitään.

Mennä? Lyödä! Tapaa shell-operaattori (arvostelu ja videoraportti KubeCon EU'2020:sta)

Algoritmin toteutus Bashissa voit ladata meidän arkistot esimerkeineen.

Näin pystyimme luomaan yksinkertaisen Kubernetes-ohjaimen käyttämällä 35 riviä YAML-konfiguraatioita ja suunnilleen yhtä paljon Bash-koodia! Shell-operaattorin tehtävänä on yhdistää ne toisiinsa.

Salaisuuksien kopioiminen ei kuitenkaan ole apuohjelman ainoa sovellusalue. Tässä on vielä muutama esimerkki osoittamaan, mihin hän pystyy.

Esimerkki 1: Muutosten tekeminen ConfigMapiin

Katsotaanpa käyttöönottoa, joka koostuu kolmesta kotelosta. Podit käyttävät ConfigMap-ohjelmaa joidenkin asetusten tallentamiseen. Kun podit käynnistettiin, ConfigMap oli tietyssä tilassa (kutsutaanko sitä v.1:ksi). Näin ollen kaikki podit käyttävät tätä tiettyä ConfigMap-versiota.

Oletetaan nyt, että ConfigMap on muuttunut (v.2). Podit käyttävät kuitenkin ConfigMapin aiempaa versiota (v.1):

Mennä? Lyödä! Tapaa shell-operaattori (arvostelu ja videoraportti KubeCon EU'2020:sta)

Kuinka saan ne siirtymään uuteen ConfigMapiin (v.2)? Vastaus on yksinkertainen: käytä mallia. Lisätään osioon tarkistussummamerkintä template Käyttöönottomääritykset:

Mennä? Lyödä! Tapaa shell-operaattori (arvostelu ja videoraportti KubeCon EU'2020:sta)

Tämän seurauksena tämä tarkistussumma rekisteröidään kaikkiin koteloihin, ja se on sama kuin käyttöönoton tarkistussumma. Nyt sinun tarvitsee vain päivittää huomautus, kun ConfigMap muuttuu. Ja shell-operaattori on hyödyllinen tässä tapauksessa. Sinun tarvitsee vain ohjelmoida koukku, joka tilaa ConfigMapin ja päivittää tarkistussumman.

Jos käyttäjä tekee muutoksia ConfigMapiin, shell-operaattori huomaa ne ja laskee tarkistussumman uudelleen. Sen jälkeen Kubernetesin taika astuu peliin: orkesteri tappaa podin, luo uuden, odottaa, että siitä tulee Ready, ja siirtyy seuraavaan. Tämän seurauksena käyttöönotto synkronoituu ja siirtyy ConfigMapin uuteen versioon.

Mennä? Lyödä! Tapaa shell-operaattori (arvostelu ja videoraportti KubeCon EU'2020:sta)

Esimerkki 2: Mukautettujen resurssimääritelmien käyttäminen

Kuten tiedät, Kubernetes antaa sinun luoda mukautettuja objektityyppejä. Voit esimerkiksi luoda ystävällisiä MysqlDatabase. Oletetaan, että tällä tyypillä on kaksi metatietoparametria: name и namespace.

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

Meillä on eri nimiavaruilla varustettu Kubernetes-klusteri, johon voimme luoda MySQL-tietokantoja. Tässä tapauksessa shell-operaattoria voidaan käyttää resurssien seurantaan MysqlDatabase, yhdistämällä ne MySQL-palvelimeen ja synkronoimalla klusterin halutut ja havaitut tilat.

Mennä? Lyödä! Tapaa shell-operaattori (arvostelu ja videoraportti KubeCon EU'2020:sta)

Esimerkki 3: Cluster Network Monitoring

Kuten tiedät, pingin käyttäminen on yksinkertaisin tapa valvoa verkkoa. Tässä esimerkissä näytämme kuinka tällainen valvonta toteutetaan shell-operatorilla.

Ensinnäkin sinun on tilattava solmut. Shell-operaattori tarvitsee kunkin solmun nimen ja IP-osoitteen. Heidän avullaan hän pingoittaa nämä solmut.

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: [] estää koukun suorittamisen vastauksena mihin tahansa tapahtumaan (eli vastauksena solmujen muuttamiseen, lisäämiseen tai poistamiseen). Kuitenkin hän juoksee (ja päivitä solmuluettelo) Aikataulutettu - joka minuutti kentän ohjeiden mukaan schedule.

Nyt herää kysymys, kuinka tarkalleen tiedämme pakettien katoamisen kaltaisista ongelmista? Katsotaanpa koodia:

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
}

Selaamme solmuluettelon läpi, hankimme niiden nimet ja IP-osoitteet, pingimme ne ja lähetämme tulokset Prometheukselle. Shell-operaattori voi viedä mittareita Prometheukseen, tallentaa ne tiedostoon, joka sijaitsee ympäristömuuttujassa määritetyn polun mukaisesti $METRICS_PATH.

näin voit tehdä operaattorin yksinkertaista verkon valvontaa varten klusterissa.

Jonotusmekanismi

Tämä artikkeli olisi epätäydellinen kuvaamatta toista tärkeää mekanismia, joka on sisäänrakennettu shell-operaattoriin. Kuvittele, että se suorittaa jonkinlaisen koukun vastauksena klusterin tapahtumaan.

  • Mitä tapahtuu, jos samaan aikaan klusterissa tapahtuu jotain? yksi vielä tapahtuma?
  • Suorittaako shell-operaattori koukun toisen esiintymän?
  • Entä jos vaikkapa viisi tapahtumaa tapahtuu klusterissa kerralla?
  • Käsitteleekö shell-operaattori niitä rinnakkain?
  • Entä kulutetut resurssit, kuten muisti ja prosessori?

Onneksi shell-operaattorissa on sisäänrakennettu jonotusmekanismi. Kaikki tapahtumat ovat jonossa ja käsitellään peräkkäin.

Havainnollistetaan tätä esimerkein. Oletetaan, että meillä on kaksi koukkua. Ensimmäinen tapahtuma menee ensimmäiseen koukkuun. Kun sen käsittely on valmis, jono siirtyy eteenpäin. Seuraavat kolme tapahtumaa ohjataan toiseen koukkuun - ne poistetaan jonosta ja sisällytetään siihen "nippuna". Tuo on koukku vastaanottaa joukon tapahtumia - tai tarkemmin sanottuna joukko sitovia konteksteja.

Myös nämä tapahtumat voidaan yhdistää yhdeksi suureksi. Parametri on vastuussa tästä group sitovassa kokoonpanossa.

Mennä? Lyödä! Tapaa shell-operaattori (arvostelu ja videoraportti KubeCon EU'2020:sta)

Voit luoda minkä tahansa määrän jonoja/koukkuja ja niiden erilaisia ​​yhdistelmiä. Esimerkiksi yksi jono voi toimia kahdella koukulla tai päinvastoin.

Mennä? Lyödä! Tapaa shell-operaattori (arvostelu ja videoraportti KubeCon EU'2020:sta)

Sinun tarvitsee vain määrittää kenttä vastaavasti queue sitovassa kokoonpanossa. Jos jonon nimeä ei ole määritetty, koukku toimii oletusjonossa (default). Tämän jonotusmekanismin avulla voit ratkaista täysin kaikki resurssienhallintaongelmat koukkujen kanssa työskennellessäsi.

Johtopäätös

Selitimme mitä shell-operaattori on, osoitimme kuinka sen avulla voidaan luoda nopeasti ja vaivattomasti Kubernetes-operaattoreita ja annoimme useita esimerkkejä sen käytöstä.

Yksityiskohtaiset tiedot shell-operaattorista sekä nopea opetusohjelma sen käyttöön ovat saatavilla vastaavassa arkistot GitHubissa. Älä epäröi ottaa meihin yhteyttä, jos sinulla on kysyttävää: voit keskustella niistä erityisessä Telegram ryhmä (venäjäksi) tai kielellä tämä foorumi (englanniksi).

Ja jos pidit siitä, olemme aina iloisia nähdessämme uusia numeroita/PR/tähtiä GitHubissa, josta muuten löydät muita mielenkiintoisia projekteja. Niistä kannattaa korostaa addon-operaattori, joka on shell-operaattorin isoveli. Tämä apuohjelma käyttää Helm-kaavioita lisäosien asentamiseen, voi toimittaa päivityksiä ja valvoa erilaisia ​​kaavioparametreja/arvoja, ohjaa kaavioiden asennusprosessia ja voi myös muokata niitä vastauksena klusterin tapahtumiin.

Mennä? Lyödä! Tapaa shell-operaattori (arvostelu ja videoraportti KubeCon EU'2020:sta)

Videot ja diat

Video esityksestä (~23 minuuttia):


Raportin esittely:

PS.

Lue myös blogistamme:

Lähde: will.com

Lisää kommentti