Det hela började med att teamledaren för ett av våra utvecklingsteam bad mig att i ett testläge publicera deras nya applikation, som dagen innan hade genomgått containerisering. Jag publicerade den. Ungefär 20 minuter senare kom begäran om att uppdatera applikationen eftersom de hade lagt till en mycket viktig funktion. Jag uppdaterade. Några timmar senare… ja, ni kan ana vad som hände härnäst…
Jag är, ärligt talat, ganska lat (erkände jag inte det tidigare? Nej?), och med tanke på att teamledarna har tillgång till Jenkins, där vi har hela vår CI/CD, tänkte jag: låt dem själv utföra deployment, hur mycket de vill! Jag kom att tänka på ett skämt: ge en människa en fisk så får han mat för en dag; kalla människan Mätt och han kommer att vara mätt hela livet. Och så gick jag för att skapa ett jobb, som skulle kunna deploya en container med applikationen av vilken framgångsrikt byggd version som helst och överföra valfria värden till den ENV (min morfar, — filosof, tidigare lärare i engelska, — skulle nu snurra sitt finger vid tinningen och titta mycket uttrycksfullt på mig efter att ha läst denna mening).
Så i denna anteckning kommer jag att berätta om hur jag lärde mig:
- Dynamiskt uppdatera uppgifter i Jenkins från själva uppgiften eller från andra uppgifter;
- Ansluta till den molnbaserade konsolen (Cloud shell) från en nod med en installerad Jenkins-agent;
- Deplaera arbetsbelastning (workload) i Google Kubernetes Engine.
I verkligheten överdriver jag givetvis lite. Det förutsätts att åtminstone en del av infrastrukturen finns i Googles moln, vilket innebär att ni är användare och givetvis har ett GCP-konto. Men anteckningen handlar inte om det.
Detta är min nya kortfattade guide. Jag vill skriva sådana anteckningar endast i en situation: jag stod inför en uppgift, jag visste från början inte hur jag skulle lösa den, lösningen fanns inte klart och tydligt på nätet, så jag sökte efter delar och klurade ut hur man löser uppgiften. Och för att jag i framtiden, när jag glömt hur jag gjorde, inte ska behöva googla allt igen i delar och kompilera det till en helhet, skriver jag sådana kortfattade guider.
Disclaimer: 1. Anteckningen skrevs "för mig själv", och har inget anspråk på att vara best practice. Jag ser fram emot att läsa förslag som "eller så skulle man ha gjort så" i kommentarerna.
2. Om den praktiska delen av anteckningen betraktas som salt, så är den, liksom mina tidigare anteckningar, en svag salthalt.
Dynamisk uppdatering av inställningar för jobb i Jenkins
Jag förutser din fråga: vad har egentligen dynamisk uppdatering av jobb med detta att göra? Jag skrev manuellt in värdet för den strängparameter och körde på!
Jag svarar: jag är faktiskt lat, jag gillar inte när folk klagar: Misha, deployment kraschar, allt är förlorat! Man börjar kolla, och där finns ett stavfel i värdet för någon startparameter. Så jag föredrar att göra allt så felfritt som möjligt. Om det finns möjlighet att beröva användaren möjligheten att ange data direkt, genom att istället ge en lista över värden att välja från, då organiserar jag valet.
Planen är följande: vi skapar ett jobb i Jenkins, där man innan körning kan välja version från en lista, ange värden för parametrar som skickas till containern via ENV, och därefter bygger det containern och pushar den till Container Registry. Därefter startas containern därifrån i k8s som workload med parametrar angivna i jobbet.
Vi kommer inte att diskutera processen för att skapa och konfigurera jobb i Jenkins, det är sidospår. Vi utgår från att jobbet är färdigt. För att implementera en uppdaterad lista med versioner behöver vi två saker: en redan existerande källista med på förhand giltiga versionsnummer och en variabel av typen Choice parameter i jobbet. I vårt exempel låt variabeln kallas BUILD_VERSION, vi ska inte gå in på detaljer om den. Men låt oss titta närmare på källistan.
Alternativen är inte så många. Jag kommer omedelbart att tänka på två:
- Använda Remote access API som Jenkins erbjuder sina användare;
- Begära innehållet i en fjärrmapp i ett repositorium (i vårt fall JFrog Artifactory, vilket inte är principiellt).
Jenkins Remote access API
Enligt den fina traditionen föredrar jag att undvika långa förklaringar.
Jag tillåter mig att göra en fri översättning av stycket i första paragrafen :
Jenkins tillhandahåller ett API för fjärrmaskinläsbar åtkomst till sin funktionalitet. Fjärråtkomst erbjuds i en REST-liknande stil. Detta innebär att det inte finns en enda ingångspunkt till alla funktioner, utan istället används en URL av typen "…/api/", där "…" representerar objektet som API-funktionerna tillämpas på.
Med andra ord, om jobbet för deployment som vi pratar om just nu är tillgängligt på adressen http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build, så API-urlerna för denna uppgift finns på adressen http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/
Sedan har vi valet av hur vi vill få utdata. Låt oss fokusera på XML, eftersom API:et bara tillåter filtrering i detta fall.
Låt oss helt enkelt försöka hämta en lista över alla körningar av uppgiften. Vi är intresserade av byggnamnet (displayName) och dess resultat (result):
http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]Lyckades det?
Nu filtrerar vi bara de körningar som i slutändan gav resultatet SUCCESS. Vi använder argumentet &exclude och som parameter får vi ange vägen till värdet som inte är lika med SUCCESS. Ja, dubbelnegation innebär ett påstående. Vi utesluter allt som vi inte är intresserade av:
http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]&exclude=freeStyleProject/allBuild[result!='SUCCESS'] Skärmbild av listan med framgångar

Och för skojs skull ska vi försäkra oss om att filtret inte har lurat oss (filter ljuger ju aldrig!) och vi visar listan med "icke-framgångar":
http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]&exclude=freeStyleProject/allBuild[result='SUCCESS'] Skärmbild av listan med icke-framgångar

Lista över versioner från mappen på den fjärrservern
Det finns även ett andra sätt att få lista över versioner. Jag gillar det faktiskt ännu mer än att anropa Jenkins API. Eftersom om applikationen har byggts framgångsrikt, då har den packats och lagts i repositoryn i motsvarande mapp. Typ, repositoryn är som standard lagringsplats för arbetsversioner av applikationer. Typ. Så vi frågar den vad som finns lagrat. Vi kommer att köra curl, grep och awk. Om någon är intresserad av en enradig lösning, så finns den under spoiler.
En rad kommando
Observera två saker: jag skickar in anslutningsuppgifterna i rubriken och jag behöver inte alla versioner från mappen, och jag väljer bara sådana som skapades under den senaste månaden. Redigera kommandot enligt dina omständigheter 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[^/]+')Inställningar för uppgifter och konfigurationsfil för uppgiften i Jenkins
Vi har klurat ut källan för listan över versioner. Låt oss nu integrera den erhållna listan i uppgiften. En uppenbar lösning för mig var att lägga till ett steg i uppgiften för byggande av applikationen. Ett steg som ska utföras om resultatet är "framgång".
Öppna inställningarna för bygguppgiften och rulla ner till botten. Klicka på knapparna: Lägg till byggsteg -> Villkorligt steg (enligt). I inställningarna för steget väljer vi villkoret Aktuell byggstatus, anger värdet SUCCESS, åtgärden som ska utföras vid framgång Kör skalkommando.
Och nu kommer det mest intressanta. Jenkins-uppgiftens konfiguration lagras i filer. I XML-format. På sökvägen http://sökväg-till-uppgift/config.xml Således kan du ladda ner konfigurationsfilen, redigera den efter behov och lägga tillbaka den på platsen där du tog den.
Kom ihåg, ovan kom vi överens om att vi skulle skapa en parameter för versionslistan. BUILD_VERSION?
Låt oss ladda ner konfigurationsfilen och titta inuti. Bara för att se till att parametern är på plats och verkligen har rätt format.
Skärmdump under spoiler.
Din angivna del av config.xml bör se likadan ut. Med det undantaget att innehållet i elementet choices för närvarande saknas.

Visste ni? Bra, vi skriver ett skript som kommer att köras vid en lyckad byggnation.
Skriptet kommer att hämta versionslistan, ladda ner konfigurationsfilen, skriva versionslistan på rätt ställe i den, och sedan lägga tillbaka den. Ja. Precis. Skriva versionslistan i XML:en på den plats där det redan finns en versionslista (som kommer att finnas i framtiden, efter första körningen av skriptet). Jag vet att det fortfarande finns hängivna älskare av reguljära uttryck i världen. Jag tillhör inte dem. Vänligen installera på den maskin där konfigurationsfilen kommer att redigeras. Jag tror att det inte är en alltför stor kostnad för att undvika att redigera XML med hjälp av sed.
Under spoiler ger jag koden som utför hela den ovan beskrivna sekvensen.
Vi skriver in versionslistan i konfig från en mapp på en fjärrserver.
#!/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.xmlOm du föredrog alternativet med att hämta versionerna från Jenkins och är lika lat som jag, så finns här samma kod, men lista från Jenkins:
Vi skriver in versionslistan från Jenkins.
Men notera detta: för mig består byggnamnet av ett ordningsnummer och versionsnummer, åtskilt av ett kolon. Således skär awk bort den oönskade delen. Anpassa denna rad efter 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.xmlTeoretiskt sett, om du har testat koden som är skriven baserat på exemplen ovan, ska du redan ha en rullgardinslista med versioner i ditt deploymentsjobb. Så här skulle det se ut enligt skärmdumpen under spoilern.
Korrekt ifylld versionslista.

Om allt fungerade, kopiera skriptet till Kör skalkommando och spara ändringarna.
Anslutning till Cloud Shell.
Vi har byggare i våra containrar. Som leveransmedel för applikationer och konfigurationshanterare använder vi Ansible. När det kommer till att bygga containrar tänker jag på tre alternativ: installera Docker inuti Docker, installera Docker på en maskin med Ansible, eller bygga containrar i den molnbaserade konsolen. Vi har kommit överens om att inte prata om plugins för Jenkins i denna anteckning. Kommer ni ihåg?
Jag beslutade: tja, eftersom containrar "ur lådan" kan byggas i den molnbaserade konsolen, varför krångla till det? Håll det rent, eller hur? Jag vill bygga containrar med Jenkins i den molnbaserade konsolen, och sedan slänga dem till Kubernetes därifrån. Särskilt med tanke på att Googles infrastruktur har otroligt bra bandbredd, vilket kommer att gynna hastigheten på deployment.
För att ansluta till den molnbaserade konsolen behövs två saker: gcloud och behörigheter till Google Cloud API för den VM-instans som anslutningen kommer att göras ifrån.
För dem som planerar att ansluta från något annat än Google moln
Google tillåter att interaktiv autentisering stängs av i sina tjänster. Detta gör att man kan ansluta till konsolen även från en kaffemaskin, om den kör *nix och har en egen konsol.
Om det finns ett behov av att jag utför en mer detaljerad genomgång av denna fråga i den här anteckningen — skriv gärna i kommentarerna. Om det samlas tillräckligt med röster — kommer jag att skriva en uppdatering om detta ämne.
Det enklaste sättet att ge behörigheter är via webgränssnittet.
- Stäng av VM-instansen som kommer att användas för anslutningen till den molnbaserade konsolen.
- Öppna instansens information och klicka på Ändra.
- Längst ner på sidan väljer du åtkomsträckvidden för instansen Full åtkomst till alla Cloud API.
Skärmdump

- Spara ändringarna och starta instansen.
När VM:en har startat, anslut till den via SSH och kontrollera att anslutningen sker utan fel. Använd kommandot:
gcloud alpha cloud-shell ssh En lyckad anslutning ser ut ungefär så här

Deployment till GKE
Eftersom vi strävar efter att helt övergå till IaC (Infrastructure as Code), lagras våra Docker-filer i Git. Det är den ena sidan. Deployment till Kubernetes beskrivs med en yaml-fil, som endast används för denna uppgift, vilket i sig också är som en kod. Så, min plan är:
- Vi tar värdena för variablerna BUILD_VERSION och, valfritt, värden på variabler som kommer att överföras via ENV.
- Laddar ner Dockerfile från Git.
- Genererar yaml för deployment.
- Överför båda dessa filer via SCP till molnkonsolen.
- Bygger där containern och pushar den till Container registry
- Tillämpa belastningsdeploymentfilen i Kubernetes.
Låt oss bli mer specifika. Eftersom vi talar om ENV, låt oss anta att vi behöver överföra värden för två parametrar: PARAM1 och PARAM2. Lägger till deras uppgifter för deployment, typ — String Parameter.
Skärmdump

Vi kommer att generera yaml genom enkel omdirigering echo till en fil. Antas självklart att ni har PARAM1 och PARAM2, vilket innebär att belastningens namn kommer att vara awesomeapp, och den byggda containern med den angivna versionen ligger i Container registry på vägen gcr.io/awesomeapp/awesomeapp-$BUILD_VERSION, där $BUILD_VERSION valdes precis från rullgardinslistan.
Lista över kommandon
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.yamlFör Jenkins-agenten, efter att ha anslutit med hjälp av gcloud alpha cloud-shell ssh interaktivt läge är inte tillgängligt, så vi överför kommandon till molnkonsolen med hjälp av parametern --command.
Rensar hemmappen i molnkonsolen från den gamla Dockerfilen:
gcloud alpha cloud-shell ssh --command="rm -f Dockerfile"Lägger den nyligen nedladdade Dockerfilen i hemmappen i molnkonsolen med SCP:
gcloud alpha cloud-shell scp localhost:./Dockerfile cloudshell:~Vi bygger, taggar och pushar containern till Container registry:
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"
På liknande sätt går vi tillväga med deploymentfilen. Observera att i kommandona nedan används fiktiva namn för klustret dit deployment sker (awsm-cluster) och projektets namn (awesome-project), 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 konsolens utdata och hoppas att vi får se en framgångsrik byggen av containern.
Skärmdump

Och sedan en framgångsrik distribution av den byggda containern.
Skärmdump

Jag har avsiktligt undvikit att diskutera inställningen. IngressAv en enkel anledning: en gång konfigurerad med ett specificerat namn kommer den att förbli funktionell, oavsett hur många distributioner med det namnet som görs. Och överlag är det lite utanför berättelsen. workload Alla ovanstående steg hade troligen kunnat undvikas genom att bara installera något plugin för Jenkins, det finns miljoner av dem. Men av någon anledning gillar jag inte plugins. Eller, snarare, jag använder dem bara i nödfall.
Istället för slutsatser
Och jag gillar bara att gräva i något nytt för mig. Texten ovan — är också ett sätt att dela med mig av de insikter jag har gjort, när jag löste den uppgift som beskrevs i början. Att dela med dem som inte är så avskräckande i DevOps. Om mina insikter kan hjälpa åtminstone någon — så kommer jag att vara nöjd.
Vi skapar en distributionsuppgift i GKE utan plugins, sms och registrering. Vi kikar lite under Jenkins' jacka.
Källa: habr.com

