Vi skapar en distributionsuppgift i GKE utan plugins, SMS eller registrering. Låt oss ta en titt under Jenkins jacka

Allt började när teamledaren för ett av våra utvecklingsteam bad oss ​​att testa deras nya applikation, som hade blivit containeriserad dagen innan. Jag postade den. Efter cirka 20 minuter inkom en begäran om att uppdatera applikationen, eftersom en mycket nödvändig sak hade lagts till där. jag förnyade. Efter ytterligare ett par timmar... ja, ni kan gissa vad som började hända härnäst...

Jag måste erkänna att jag är ganska lat (erkände jag inte detta tidigare? Nej?), och med tanke på att teamledarna har tillgång till Jenkins, där vi har alla CI/CD, tänkte jag: låt honom distribuera som mycket som han vill! Jag kom ihåg ett skämt: ge en man en fisk så äter han för en dag; kalla en person Fed och han kommer att vara Fed hela sitt liv. Och gick spela ett spratt på jobbet, som skulle kunna distribuera en behållare som innehåller applikationen av vilken framgångsrikt inbyggd version som helst i Kuber och överföra alla värden till den ENV (min farfar, en filolog, en engelsk lärare i det förflutna, skulle nu snurra fingret mot hans tinning och titta på mig mycket uttrycksfullt efter att ha läst den här meningen).

Så i den här anteckningen kommer jag att berätta hur jag lärde mig:

  1. Uppdatera jobb i Jenkins dynamiskt från själva jobbet eller från andra jobb;
  2. Anslut till molnkonsolen (molnskal) från en nod med Jenkins-agenten installerad;
  3. Distribuera arbetsbelastning till Google Kubernetes Engine.


I själva verket är jag, naturligtvis, något oprigtig. Det antas att du har åtminstone en del av infrastrukturen i Googles moln, och därför är du dess användare och, naturligtvis, har du ett GCP-konto. Men det är inte vad den här anteckningen handlar om.

Det här är min nästa cheat sheet. Jag vill bara skriva sådana anteckningar i ett fall: jag ställdes inför ett problem, jag visste från början inte hur jag skulle lösa det, lösningen googlades inte färdig, så jag googlade det i delar och löste så småningom problemet. Och så att jag i framtiden, när jag glömmer hur jag gjorde det, inte behöver googla på allt igen bit för bit och sammanställa det, skriver jag själv sådana fuskblad.

Varning: 1. Anteckningen skrevs "för mig själv", för rollen bästa praxis gäller inte. Jag är glad att läsa alternativen "det hade varit bättre att göra på det här sättet" i kommentarerna.
2. Om den applicerade delen av sedeln anses vara salt, så är den här, liksom alla mina tidigare anteckningar, en svag saltlösning.

Dynamisk uppdatering av jobbinställningar i Jenkins

Jag förutser din fråga: vad har dynamisk jobbuppdatering att göra med det? Ange värdet på strängparametern manuellt och så är du igång!

Jag svarar: Jag är verkligen lat, jag gillar inte när de klagar: Misha, utplaceringen kraschar, allt är borta! Du börjar leta, och det finns ett stavfel i värdet på någon uppgiftsstartparameter. Därför föredrar jag att göra allt så effektivt som möjligt. Om det är möjligt att hindra användaren från att mata in data direkt genom att istället ge en lista med värden att välja mellan, så organiserar jag urvalet.

Planen är denna: vi skapar ett jobb i Jenkins, där vi innan lansering kunde välja en version från listan, ange värden för parametrar som skickas till behållaren via ENV, sedan samlar den in behållaren och skjuter in den i behållarregistret. Därefter lanseras behållaren i cuber as arbetsbelastning med de parametrar som anges i jobbet.

Vi kommer inte att överväga processen att skapa och skapa ett jobb i Jenkins, detta är off-topic. Vi kommer att anta att uppgiften är klar. För att implementera en uppdaterad lista med versioner behöver vi två saker: en befintlig källlista med a priori giltiga versionsnummer och en variabel som Valparameter i uppgiften. Låt variabeln namnges i vårt exempel BUILD_VERSION, vi kommer inte att uppehålla oss i detalj. Men låt oss ta en närmare titt på källlistan.

Det finns inte så många alternativ. Två saker kom genast att tänka på:

  • Använd API:et för fjärråtkomst som Jenkins erbjuder sina användare;
  • Begär innehållet i fjärrlagringsmappen (i vårt fall är detta JFrog Artifactory, vilket inte är viktigt).

Jenkins fjärråtkomst API

Enligt den etablerade utmärkta traditionen vill jag helst undvika långa förklaringar.
Jag tillåter mig endast en fri översättning av en del av första stycket första sidan i API-dokumentation:

Jenkins tillhandahåller ett API för fjärrmaskinläsbar åtkomst till dess funktionalitet. <...> Fjärråtkomst erbjuds i en REST-liknande stil. Det betyder att det inte finns någon enskild ingång till alla funktioner, utan istället en URL som ".../api/", Var "." betyder objektet som API-funktionerna tillämpas på.

Med andra ord, om den utplaceringsuppgift vi för närvarande talar om är tillgänglig på http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build, så finns API-visslarna för denna uppgift tillgängliga på http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/

Därefter har vi ett val i vilken form vi ska ta emot utdata. Låt oss fokusera på XML, eftersom API endast tillåter filtrering i det här fallet.

Låt oss bara försöka få en lista över alla jobbkörningar. Vi är bara intresserade av samlingsnamnet (visningsnamn) och dess resultat (resultera):

http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]

Det visade sig?

Låt oss nu filtrera bara de körningar som slutar med resultatet FRAMGÅNG. Låt oss använda argumentet &utesluta och som en parameter kommer vi att skicka den vägen till ett värde som inte är lika med FRAMGÅNG. Jaja. Ett dubbelnegativ är ett påstående. Vi utesluter allt som inte intresserar oss:

http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]&exclude=freeStyleProject/allBuild[result!='SUCCESS']

Skärmdump av listan över framgångsrika
Vi skapar en distributionsuppgift i GKE utan plugins, SMS eller registrering. Låt oss ta en titt under Jenkins jacka

Tja, bara för skojs skull, låt oss se till att filtret inte lurade oss (filter ljuger aldrig!) och visa en lista över "misslyckade":

http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]&exclude=freeStyleProject/allBuild[result='SUCCESS']

Skärmdump av listan över misslyckade
Vi skapar en distributionsuppgift i GKE utan plugins, SMS eller registrering. Låt oss ta en titt under Jenkins jacka

Lista över versioner från en mapp på en fjärrserver

Det finns ett andra sätt att få en lista över versioner. Jag gillar det ännu mer än att komma åt Jenkins API. Jo, för om applikationen byggdes framgångsrikt betyder det att den paketerades och placerades i förvaret i lämplig mapp. Som ett arkiv är standardlagringen för fungerande versioner av applikationer. Tycka om. Tja, låt oss fråga honom vilka versioner som finns i lagring. Vi kommer att curl, grep och awk fjärrmappen. Om någon är intresserad av onelinern så ligger den under spoilern.

En rad kommando
Observera två saker: Jag skickar anslutningsdetaljerna i rubriken och jag behöver inte alla versioner från mappen, och jag väljer bara de som skapades inom en månad. Redigera kommandot så att det passar dina realiteter och behov:

curl -H "X-JFrog-Art-Api:VeryLongAPIKey" -s http://arts.myre.po/artifactory/awesomeapp/ | sed 's/a href=//' | grep "$(date +%b)-$(date +%Y)|$(date +%b --date='-1 month')-$(date +%Y)" | awk '{print $1}' | grep -oP '>K[^/]+' )

Konfigurera jobb och jobbkonfigurationsfil i Jenkins

Vi har tagit reda på källan till listan med versioner. Låt oss nu införliva den resulterande listan i uppgiften. För mig var den självklara lösningen att lägga till ett steg i applikationsbyggandet. Steget som skulle utföras om resultatet var "framgång".

Öppna inställningarna för monteringsuppgiften och scrolla till botten. Klicka på knapparna: Lägg till byggsteg -> Villkorssteg (enkel). I steginställningarna väljer du villkoret Aktuell byggstatus, ställ in värdet FRAMGÅNG, åtgärden som ska utföras om den lyckas Kör skalkommando.

Och nu den roliga delen. Jenkins lagrar jobbkonfigurationer i filer. I XML-format. Längs vägen http://путь-до-задания/config.xml Följaktligen kan du ladda ner konfigurationsfilen, redigera den vid behov och lägga tillbaka den där du fick den.

Kom ihåg att vi kom överens ovan att vi kommer att skapa en parameter för listan med versioner BUILD_VERSION?

Låt oss ladda ner konfigurationsfilen och ta en titt inuti den. Bara för att se till att parametern är på plats och av önskad typ.

Skärmdump under spoiler.

Ditt config.xml-fragment bör se likadant ut. Förutom att innehållet i elementet choices saknas ännu
Vi skapar en distributionsuppgift i GKE utan plugins, SMS eller registrering. Låt oss ta en titt under Jenkins jacka

Är du säker? Det är det, låt oss skriva ett skript som kommer att köras om bygget lyckas.
Skriptet kommer att få en lista över versioner, ladda ner konfigurationsfilen, skriva listan över versioner i den på den plats vi behöver och sedan lägga tillbaka den. Ja. Det är rätt. Skriv en lista över versioner i XML på den plats där det redan finns en lista över versioner (kommer att finnas i framtiden, efter den första lanseringen av skriptet). Jag vet att det fortfarande finns hårda fans av reguljära uttryck i världen. Jag tillhör inte dem. Vänligen installera xmlstarler till maskinen där konfigurationen kommer att redigeras. Det verkar för mig att detta inte är ett så stort pris att betala för att slippa redigera XML med sed.

Under spoilern presenterar jag koden som utför ovanstående sekvens i sin helhet.

Skriv en lista över versioner från en mapp på fjärrservern till konfigurationen

#!/bin/bash
############## Скачиваем конфиг
curl -X GET -u username:apiKey http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_k8s/config.xml -o appConfig.xml

############## Удаляем и заново создаем xml-элемент для списка версий
xmlstarlet ed --inplace -d '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a[@class="string-array"]' appConfig.xml

xmlstarlet ed --inplace --subnode '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]' --type elem -n a appConfig.xml

xmlstarlet ed --inplace --insert '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a' --type attr -n class -v string-array appConfig.xml

############## Читаем в массив список версий из репозитория
readarray -t vers < <( curl -H "X-JFrog-Art-Api:Api:VeryLongAPIKey" -s http://arts.myre.po/artifactory/awesomeapp/ | sed 's/a href=//' | grep "$(date +%b)-$(date +%Y)|$(date +%b --date='-1 month')-$(date +%Y)" | awk '{print $1}' | grep -oP '>K[^/]+' )

############## Пишем массив элемент за элементом в конфиг
printf '%sn' "${vers[@]}" | sort -r | 
                while IFS= read -r line
                do
                    xmlstarlet ed --inplace --subnode '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a[@class="string-array"]' --type elem -n string -v "$line" appConfig.xml
                done

############## Кладем конфиг взад
curl -X POST -u username:apiKey http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_k8s/config.xml --data-binary @appConfig.xml

############## Приводим рабочее место в порядок
rm -f appConfig.xml

Om du föredrar alternativet att få versioner från Jenkins och du är lika lat som jag, så finns samma kod under spoilern, men en lista från Jenkins:

Skriv en lista över versioner från Jenkins till konfigurationen
Tänk bara på detta: mitt sammanställningsnamn består av ett sekvensnummer och ett versionsnummer, separerade med ett kolon. Följaktligen skär awk bort den onödiga delen. För dig själv, ändra denna linje för att passa dina behov.

#!/bin/bash
############## Скачиваем конфиг
curl -X GET -u username:apiKey http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_k8s/config.xml -o appConfig.xml

############## Удаляем и заново создаем xml-элемент для списка версий
xmlstarlet ed --inplace -d '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a[@class="string-array"]' appConfig.xml

xmlstarlet ed --inplace --subnode '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]' --type elem -n a appConfig.xml

xmlstarlet ed --inplace --insert '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a' --type attr -n class -v string-array appConfig.xml

############## Пишем в файл список версий из Jenkins
curl -g -X GET -u username:apiKey 'http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]&exclude=freeStyleProject/allBuild[result!=%22SUCCESS%22]&pretty=true' -o builds.xml

############## Читаем в массив список версий из XML
readarray vers < <(xmlstarlet sel -t -v "freeStyleProject/allBuild/displayName" builds.xml | awk -F":" '{print $2}')

############## Пишем массив элемент за элементом в конфиг
printf '%sn' "${vers[@]}" | sort -r | 
                while IFS= read -r line
                do
                    xmlstarlet ed --inplace --subnode '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a[@class="string-array"]' --type elem -n string -v "$line" appConfig.xml
                done

############## Кладем конфиг взад
curl -X POST -u username:apiKey http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_k8s/config.xml --data-binary @appConfig.xml

############## Приводим рабочее место в порядок
rm -f appConfig.xml

I teorin, om du har testat koden skriven baserat på exemplen ovan, bör du redan i implementeringsuppgiften ha en rullgardinslista med versioner. Det är som på skärmdumpen under spoilern.

Korrekt ifylld versionslista
Vi skapar en distributionsuppgift i GKE utan plugins, SMS eller registrering. Låt oss ta en titt under Jenkins jacka

Om allt fungerade, kopiera och klistra in skriptet Kör skalkommando och spara ändringar.

Ansluter till Cloud Shell

Vi har samlare i containrar. Vi använder Ansible som vårt applikationsleveransverktyg och konfigurationshanterare. Följaktligen, när det kommer till att bygga containrar, kommer tre alternativ att tänka på: installera Docker i Docker, installera Docker på en maskin som kör Ansible eller bygg containrar i en molnkonsol. Vi kom överens om att vara tysta om plugins för Jenkins i den här artikeln. Kom ihåg?

Jag bestämde mig: ja, eftersom behållare "out of the box" kan samlas in i molnkonsolen, varför bry sig då? Håll det rent, eller hur? Jag vill samla Jenkins-behållare i molnkonsolen och sedan starta dem i kubern därifrån. Dessutom har Google mycket rika kanaler inom sin infrastruktur, vilket kommer att ha en gynnsam effekt på implementeringshastigheten.

För att ansluta till molnkonsolen behöver du två saker: gmoln och åtkomsträttigheter till Google Cloud API för VM-instansen som samma anslutning kommer att göras med.

För dem som planerar att ansluta inte från Google moln alls
Google tillåter möjligheten att inaktivera interaktiv auktorisering i sina tjänster. Detta gör att du kan ansluta till konsolen även från en kaffemaskin, om den körs *nix och har en konsol i sig.

Om det finns ett behov för mig att täcka denna fråga mer i detalj inom ramen för denna not, skriv i kommentarerna. Om vi ​​får tillräckligt med röster kommer jag att skriva en uppdatering om detta ämne.

Det enklaste sättet att bevilja rättigheter är via webbgränssnittet.

  1. Stoppa VM-instansen från vilken du sedan kommer att ansluta till molnkonsolen.
  2. Öppna instansdetaljer och klicka Изменить.
  3. Längst ned på sidan väljer du instansens åtkomstomfång Full tillgång till alla Cloud API:er.

    skärmdump
    Vi skapar en distributionsuppgift i GKE utan plugins, SMS eller registrering. Låt oss ta en titt under Jenkins jacka

  4. Spara dina ändringar och starta instansen.

När den virtuella datorn har laddats klart, anslut till den via SSH och se till att anslutningen sker utan fel. Använd kommandot:

gcloud alpha cloud-shell ssh

En framgångsrik anslutning ser ut ungefär så här
Vi skapar en distributionsuppgift i GKE utan plugins, SMS eller registrering. Låt oss ta en titt under Jenkins jacka

Distribuera till GKE

Eftersom vi på alla möjliga sätt strävar efter att helt byta till IaC (Infrastucture as a Code), lagras våra docker-filer i Git. Detta är å ena sidan. Och distribution i kubernetes beskrivs av en yaml-fil, som endast används av denna uppgift, som i sig också är som kod. Det här är från andra sidan. I allmänhet menar jag att planen är denna:

  1. Vi tar variablernas värden BUILD_VERSION och, valfritt, värdena för de variabler som kommer att passeras igenom ENV.
  2. Ladda ner dockerfilen från Git.
  3. Generera yaml för distribution.
  4. Vi laddar upp båda dessa filer via scp till molnkonsolen.
  5. Vi bygger en container där och skjuter in den i containerregistret
  6. Vi tillämpar laddningsdistributionsfilen på cubern.

Låt oss vara mer specifika. En gång började vi prata om ENV, anta att vi måste skicka värdena för två parametrar: PARAM1 и PARAM2. Vi lägger till deras uppgift för distribution, skriv - Strängparameter.

skärmdump
Vi skapar en distributionsuppgift i GKE utan plugins, SMS eller registrering. Låt oss ta en titt under Jenkins jacka

Vi kommer att generera yaml med en enkel omdirigering missar att arkivera. Det förutsätts naturligtvis att du har i din dockerfil PARAM1 и PARAM2att lastnamnet blir grym app, och den sammansatta behållaren med tillämpning av den specificerade versionen ligger i Behållarregistret längs vägen gcr.io/awesomeapp/awesomeapp-$BUILD_VERSIONvar $BUILD_VERSION valdes precis från rullgardinsmenyn.

Lagförteckning

touch deploy.yaml
echo "apiVersion: apps/v1" >> deploy.yaml
echo "kind: Deployment" >> deploy.yaml
echo "metadata:" >> deploy.yaml
echo "  name: awesomeapp" >> deploy.yaml
echo "spec:" >> deploy.yaml
echo "  replicas: 1" >> deploy.yaml
echo "  selector:" >> deploy.yaml
echo "    matchLabels:" >> deploy.yaml
echo "      run: awesomeapp" >> deploy.yaml
echo "  template:" >> deploy.yaml
echo "    metadata:" >> deploy.yaml
echo "      labels:" >> deploy.yaml
echo "        run: awesomeapp" >> deploy.yaml
echo "    spec:" >> deploy.yaml
echo "      containers:" >> deploy.yaml
echo "      - name: awesomeapp" >> deploy.yaml
echo "        image: gcr.io/awesomeapp/awesomeapp-$BUILD_VERSION:latest" >> deploy.yaml
echo "        env:" >> deploy.yaml
echo "        - name: PARAM1" >> deploy.yaml
echo "          value: $PARAM1" >> deploy.yaml
echo "        - name: PARAM2" >> deploy.yaml
echo "          value: $PARAM2" >> deploy.yaml

Jenkins agent efter anslutning använder gcloud alfa moln-skal ssh interaktivt läge är inte tillgängligt, så vi skickar kommandon till molnkonsolen med parametern --kommando.

Vi rensar hemmappen i molnkonsolen från den gamla dockerfilen:

gcloud alpha cloud-shell ssh --command="rm -f Dockerfile"

Placera den nyligen nedladdade dockerfilen i hemmappen på molnkonsolen med hjälp av scp:

gcloud alpha cloud-shell scp localhost:./Dockerfile cloudshell:~

Vi samlar in, taggar och skickar behållaren till Containerregistret:

gcloud alpha cloud-shell ssh --command="docker build -t awesomeapp-$BUILD_VERSION ./ --build-arg BUILD_VERSION=$BUILD_VERSION --no-cache"
gcloud alpha cloud-shell ssh --command="docker tag awesomeapp-$BUILD_VERSION gcr.io/awesomeapp/awesomeapp-$BUILD_VERSION"
gcloud alpha cloud-shell ssh --command="docker push gcr.io/awesomeapp/awesomeapp-$BUILD_VERSION"

Vi gör samma sak med distributionsfilen. Observera att kommandona nedan använder fiktiva namn på klustret där distributionen sker (awsm-kluster) och projektnamn (grymt projekt), där klustret finns.

gcloud alpha cloud-shell ssh --command="rm -f deploy.yaml"
gcloud alpha cloud-shell scp localhost:./deploy.yaml cloudshell:~
gcloud alpha cloud-shell ssh --command="gcloud container clusters get-credentials awsm-cluster --zone us-central1-c --project awesome-project && 
kubectl apply -f deploy.yaml"

Vi kör uppgiften, öppnar konsolutgången och hoppas kunna se den framgångsrika monteringen av behållaren.

skärmdump
Vi skapar en distributionsuppgift i GKE utan plugins, SMS eller registrering. Låt oss ta en titt under Jenkins jacka

Och sedan den framgångsrika utplaceringen av den sammansatta behållaren

skärmdump
Vi skapar en distributionsuppgift i GKE utan plugins, SMS eller registrering. Låt oss ta en titt under Jenkins jacka

Jag ignorerade medvetet inställningen Ingress. Av en enkel anledning: när du har ställt in den arbetsbelastning med ett givet namn kommer det att förbli i drift, oavsett hur många distributioner med detta namn du utför. Tja, i allmänhet är detta lite utanför historiens räckvidd.

I stället för slutsatser

Alla ovanstående steg kunde förmodligen inte ha gjorts, utan installerade helt enkelt något plugin för Jenkins, deras muuulion. Men av någon anledning gillar jag inte plugins. Tja, mer exakt, jag tar till dem bara av desperation.

Och jag gillar bara att ta upp något nytt ämne för mig. Texten ovan är också ett sätt att dela de upptäckter jag gjorde när jag löste problemet som beskrevs i början. Dela med dem som, precis som han, inte alls är en hemsk varg i devops. Om mina fynd hjälper åtminstone någon så blir jag glad.

Källa: will.com

Lägg en kommentar