ProHoster > blogg > administration > Gå? Våldsamt slag! Möt skaloperatören (recension och videorapport från KubeCon EU'2020)
Gå? Våldsamt slag! Möt skaloperatören (recension och videorapport från KubeCon EU'2020)
I år var den största europeiska Kubernetes-konferensen - KubeCon + CloudNativeCon Europe 2020 - virtuell. En sådan förändring av formatet hindrade oss dock inte från att leverera vår sedan länge planerade rapport ”Gå? Våldsamt slag! Möt Shell-operatören” dedikerad till vårt Open Source-projekt skal-operatör.
Den här artikeln, inspirerad av föredraget, presenterar ett tillvägagångssätt för att förenkla processen att skapa operatörer för Kubernetes och visar hur du kan göra din egen med minimal ansträngning med hjälp av en skal-operator.
Introducerar video av rapporten (~23 minuter på engelska, märkbart mer informativ än artikeln) och huvudutdraget ur den i textform. Gå!
På Flant optimerar och automatiserar vi ständigt allt. Idag ska vi prata om ett annat spännande koncept. Träffa: molnbaserad skalskript!
Men låt oss börja med sammanhanget där allt detta händer: Kubernetes.
Kubernetes API och kontroller
API:et i Kubernetes kan representeras som en slags filserver med kataloger för varje typ av objekt. Objekt (resurser) på denna server representeras av YAML-filer. Dessutom har servern ett grundläggande API som låter dig göra tre saker:
motta resurs efter dess slag och namn;
förändra resurs (i det här fallet lagrar servern endast "korrekta" objekt - alla felaktigt utformade eller avsedda för andra kataloger kasseras);
Spår för resursen (i detta fall får användaren omedelbart sin nuvarande/uppdaterade version).
Sålunda fungerar Kubernetes som en slags filserver (för YAML-manifest) med tre grundläggande metoder (ja, det finns faktiskt andra, men vi kommer att utelämna dem tills vidare).
Problemet är att servern bara kan lagra information. För att få det att fungera behöver du styrenhet - det näst viktigaste och grundläggande konceptet i Kubernetes-världen.
Det finns två huvudtyper av styrenheter. Den första tar information från Kubernetes, bearbetar den enligt kapslad logik och returnerar den till K8s. Den andra tar information från Kubernetes, men, till skillnad från den första typen, ändrar tillståndet för vissa externa resurser.
Låt oss ta en närmare titt på processen att skapa en distribution i Kubernetes:
Implementeringskontroller (ingår i kube-controller-manager) tar emot information om distribution och skapar en replikuppsättning.
ReplicaSet skapar två repliker (två pods) baserat på denna information, men dessa pods är inte schemalagda än.
Schemaläggaren schemalägger poddar och lägger till nodinformation till deras YAML.
Kubelets gör ändringar i en extern resurs (säg Docker).
Sedan upprepas hela denna sekvens i omvänd ordning: kubelet kontrollerar behållarna, beräknar poddens status och skickar tillbaka den. ReplicaSet-styrenheten tar emot statusen och uppdaterar replikuppsättningens tillstånd. Samma sak händer med Deployment Controller och användaren får äntligen den uppdaterade (aktuella) statusen.
Shell-operatör
Det visar sig att Kubernetes bygger på olika kontrollanters gemensamma arbete (Kubernetes-operatörer är också kontrollanter). Frågan uppstår, hur skapar man en egen operatör med minimal ansträngning? Och här kommer den vi utvecklade till undsättning skal-operatör. Det låter systemadministratörer skapa sina egna uttalanden med bekanta metoder.
Enkelt exempel: kopiera hemligheter
Låt oss titta på ett enkelt exempel.
Låt oss säga att vi har ett Kubernetes-kluster. Den har ett namnutrymme default med någon hemlighet mysecret. Dessutom finns det andra namnutrymmen i klustret. Vissa av dem har en specifik etikett fäst vid dem. Vårt mål är att kopiera Secret till namnutrymmen med en etikett.
Uppgiften kompliceras av det faktum att nya namnutrymmen kan dyka upp i klustret, och vissa av dem kan ha denna etikett. Å andra sidan, när etiketten raderas, bör Secret också tas bort. Utöver detta kan själva hemligheten också ändras: i det här fallet måste den nya hemligheten kopieras till alla namnutrymmen med etiketter. Om Secret av misstag raderas i något namnområde, bör vår operatör återställa det omedelbart.
Nu när uppgiften har formulerats är det dags att börja implementera den med hjälp av skaloperatorn. Men först är det värt att säga några ord om själva skaloperatören.
Hur shell-operator fungerar
Liksom andra arbetsbelastningar i Kubernetes körs shell-operator i sin egen pod. I denna pod i katalogen /hooks körbara filer lagras. Dessa kan vara skript i Bash, Python, Ruby, etc. Vi kallar sådana körbara filer hooks (krokar).
Shell-operator prenumererar på Kubernetes-evenemang och kör dessa hooks som svar på de händelser som vi behöver.
Hur vet skaloperatören vilken krok som ska köras och när? Poängen är att varje krok har två steg. Under uppstart kör skal-operatören alla krokar med ett argument --config Detta är konfigurationsstadiet. Och efter det lanseras krokar på normalt sätt - som svar på de händelser som de är knutna till. I det senare fallet får kroken det bindande sammanhanget (bindande sammanhang) - data i JSON-format, som vi kommer att prata om mer i detalj nedan.
Att göra en operatör i Bash
Nu är vi redo för implementering. För att göra detta måste vi skriva två funktioner (vi rekommenderar förresten biblioteket shell_lib, vilket avsevärt förenklar skrivkrokar i Bash):
den första behövs för konfigurationssteget - den visar det bindande sammanhanget;
den andra innehåller krokens huvudlogik.
#!/bin/bash
source /shell_lib.sh
function __config__() {
cat << EOF
configVersion: v1
# BINDING CONFIGURATION
EOF
}
function __main__() {
# THE LOGIC
}
hook::run "$@"
Nästa steg är att bestämma vilka föremål vi behöver. I vårt fall måste vi spåra:
källhemlighet för ändringar;
alla namnutrymmen i klustret, så att du vet vilka som har en etikett kopplad till dem;
målhemligheter för att säkerställa att de alla är synkroniserade med källhemligheten.
Prenumerera på den hemliga källan
Bindande konfiguration för det är ganska enkel. Vi anger att vi är intresserade av Secret med namnet mysecret i namnutrymmet default:
Som du kan se har ett nytt fält dykt upp i konfigurationen med namnet jqFilter. Som namnet antyder, jqFilter filtrerar bort all onödig information och skapar ett nytt JSON-objekt med de fält som är av intresse för oss. En krok med en liknande konfiguration kommer att få följande bindande sammanhang:
Den innehåller en array filterResults för varje namnområde i klustret. Boolesk variabel hasLabel anger om en etikett är kopplad till ett givet namnområde. Väljare keepFullObjectsInMemory: false indikerar att det inte finns något behov av att behålla kompletta objekt i minnet.
Spåra målhemligheter
Vi prenumererar på alla hemligheter som har en anteckning specificerad managed-secret: "yes" (detta är vårt mål dst_secrets):
I det här fallet jqFilter filtrerar bort all information utom namnområdet och parametern resourceVersion. Den sista parametern skickades till anteckningen när hemligheten skapades: den låter dig jämföra versioner av hemligheter och hålla dem uppdaterade.
En hook konfigurerad på detta sätt kommer, när den exekveras, att ta emot de tre bindningskontexterna som beskrivs ovan. De kan ses som ett slags ögonblicksbild (ögonblicksbild) kluster.
Baserat på all denna information kan en grundläggande algoritm utvecklas. Det itererar över alla namnrymder och:
om hasLabel frågor true för det aktuella namnområdet:
jämför den globala hemligheten med den lokala:
om de är lika, gör det ingenting;
om de skiljer sig - exekverar kubectl replace eller create;
om hasLabel frågor false för det aktuella namnområdet:
ser till att Secret inte finns i det angivna namnutrymmet:
om den lokala hemligheten finns, radera den med hjälp av kubectl delete;
om den lokala hemligheten inte upptäcks gör den ingenting.
Det var så vi kunde skapa en enkel Kubernetes-kontroller med 35 rader YAML-konfiguration och ungefär samma mängd Bash-kod! Skaloperatörens uppgift är att länka ihop dem.
Att kopiera hemligheter är dock inte det enda användningsområdet för verktyget. Här är några fler exempel för att visa vad han kan.
Exempel 1: Göra ändringar i ConfigMap
Låt oss titta på en distribution som består av tre kapslar. Pods använder ConfigMap för att lagra viss konfiguration. När poddarna lanserades var ConfigMap i ett visst tillstånd (låt oss kalla det v.1). Följaktligen använder alla poddar just den här versionen av ConfigMap.
Låt oss nu anta att ConfigMap har ändrats (v.2). Poddarna kommer dock att använda den tidigare versionen av ConfigMap (v.1):
Hur kan jag få dem att byta till nya ConfigMap (v.2)? Svaret är enkelt: använd en mall. Låt oss lägga till en kontrollsummannotering till avsnittet template Implementeringskonfigurationer:
Som ett resultat kommer denna kontrollsumma att registreras i alla pods, och den kommer att vara densamma som för Deployment. Nu behöver du bara uppdatera anteckningen när ConfigMap ändras. Och skal-operatören kommer väl till pass i det här fallet. Allt du behöver göra är att programmera en hook som kommer att prenumerera på ConfigMap och uppdatera kontrollsumman.
Om användaren gör ändringar i ConfigMap kommer skal-operatören att lägga märke till dem och räkna om kontrollsumman. Därefter kommer Kubernetes magi att träda i kraft: orkestratorn kommer att döda poden, skapa en ny, vänta på att den blir Ready, och går vidare till nästa. Som ett resultat kommer Deployment att synkronisera och byta till den nya versionen av ConfigMap.
Exempel 2: Arbeta med anpassade resursdefinitioner
Som du vet låter Kubernetes dig skapa anpassade typer av objekt. Till exempel kan du skapa slag MysqlDatabase. Låt oss säga att den här typen har två metadataparametrar: name и namespace.
apiVersion: example.com/v1alpha1
kind: MysqlDatabase
metadata:
name: foo
namespace: bar
Vi har ett Kubernetes-kluster med olika namnutrymmen där vi kan skapa MySQL-databaser. I det här fallet kan skal-operatör användas för att spåra resurser MysqlDatabase, ansluta dem till MySQL-servern och synkronisera de önskade och observerade tillstånden i klustret.
Exempel 3: Klusternätverksövervakning
Som du vet är ping det enklaste sättet att övervaka ett nätverk. I det här exemplet kommer vi att visa hur man implementerar sådan övervakning med shell-operator.
Först och främst måste du prenumerera på noder. Skaloperatören behöver namnet och IP-adressen för varje nod. Med deras hjälp kommer han att pinga dessa noder.
Parameter executeHookOnEvent: [] förhindrar att kroken körs som svar på någon händelse (det vill säga som svar på att ändra, lägga till, ta bort noder). Men han kommer att köras (och uppdatera listan med noder) Schemalagt - varje minut, som föreskrivs av fältet schedule.
Nu uppstår frågan, hur exakt vet vi om problem som paketförlust? Låt oss ta en titt på koden:
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
}
Vi itererar genom listan med noder, får deras namn och IP-adresser, pingar dem och skickar resultaten till Prometheus. Shell-operatör kan exportera mätvärden till Prometheus, sparar dem i en fil som ligger enligt sökvägen som anges i miljövariabeln $METRICS_PATH.
Här så du kan skapa en operatör för enkel nätverksövervakning i ett kluster.
Kömekanism
Denna artikel skulle vara ofullständig utan att beskriva en annan viktig mekanism inbyggd i skal-operatören. Föreställ dig att den utför någon form av hook som svar på en händelse i klustret.
Vad händer om det samtidigt händer något i klustret? en till händelse?
Kommer skal-operatören att köra en annan instans av kroken?
Tänk om, säg, fem händelser händer i klustret samtidigt?
Kommer skaloperatören att behandla dem parallellt?
Hur är det med förbrukade resurser som minne och CPU?
Lyckligtvis har skal-operatören en inbyggd kömekanism. Alla händelser köas och bearbetas sekventiellt.
Låt oss illustrera detta med exempel. Låt oss säga att vi har två krokar. Den första händelsen går till den första kroken. När bearbetningen är klar flyttas kön framåt. De nästa tre händelserna omdirigeras till den andra kroken - de tas bort från kön och läggs in i den i ett "paket". Det är hook tar emot en rad händelser — eller, mer exakt, en rad bindande sammanhang.
Även dessa evenemang kan kombineras till en stor. Parametern är ansvarig för detta group i den bindande konfigurationen.
Du kan skapa valfritt antal köer/hooks och deras olika kombinationer. Till exempel kan en kö fungera med två krokar, eller vice versa.
Allt du behöver göra är att konfigurera fältet därefter queue i den bindande konfigurationen. Om ett könamn inte anges körs kroken på standardkön (default). Denna kömekanism låter dig helt lösa alla resurshanteringsproblem när du arbetar med krokar.
Slutsats
Vi förklarade vad en skal-operatör är, visade hur den kan användas för att snabbt och enkelt skapa Kubernetes-operatörer och gav flera exempel på dess användning.
Detaljerad information om skal-operatören, samt en snabb handledning om hur man använder den, finns i motsvarande repositories på GitHub. Tveka inte att kontakta oss med frågor: du kan diskutera dem i en speciell Telegram grupp (på ryska) eller på detta forum (på engelska).
Och om du gillade det är vi alltid glada att se nya nummer/PR/stjärnor på GitHub, där du förresten kan hitta andra intressanta projekt. Bland dem är det värt att lyfta fram addon-operatör, som är storebror till shell-operator. Det här verktyget använder Helm-diagram för att installera tillägg, kan leverera uppdateringar och övervaka olika diagramparametrar/värden, kontrollerar installationsprocessen för diagram och kan även modifiera dem som svar på händelser i klustret.