Vi opretter en implementeringsopgave i GKE uden plugins, SMS eller registrering. Lad os tage et kig under Jenkins' jakke

Det hele startede, da teamlederen fra et af vores udviklingsteams bad os om at teste deres nye applikation, som var blevet containeriseret dagen før. Jeg postede det. Efter cirka 20 minutter blev der modtaget en anmodning om at opdatere applikationen, fordi der var tilføjet en meget nødvendig ting. jeg fornyede. Efter endnu et par timer... ja, du kan gætte, hvad der begyndte at ske næste gang...

Jeg må indrømme, at jeg er ret doven (indrømmede jeg ikke det tidligere? Nej?), og i betragtning af at teamledere har adgang til Jenkins, hvor vi alle har CI/CD, tænkte jeg: lad ham deployere som meget som han vil! Jeg huskede en vittighed: giv en mand en fisk, og han vil spise for en dag; kald en person Fed, og han vil blive Fed hele sit liv. Og gik spille et puds på jobbet, som ville være i stand til at implementere en container, der indeholder applikationen af ​​enhver vellykket indbygget version i Kuber og overføre eventuelle værdier til den ENV (min bedstefar, en filolog, en engelsk lærer i fortiden, ville nu dreje sin finger mod hans tinding og kigge meget udtryksfuldt på mig efter at have læst denne sætning).

Så i denne note vil jeg fortælle dig, hvordan jeg lærte:

  1. Opdater jobs i Jenkins dynamisk fra selve jobbet eller fra andre job;
  2. Opret forbindelse til skykonsollen (Cloud shell) fra en node med Jenkins-agenten installeret;
  3. Implementer arbejdsbelastning til Google Kubernetes Engine.


Faktisk er jeg selvfølgelig noget uærlig. Det antages, at du i det mindste har en del af infrastrukturen i Google-skyen, og derfor er du dens bruger, og du har selvfølgelig en GCP-konto. Men det er ikke det, denne note handler om.

Dette er mit næste snydeark. Jeg vil kun skrive sådanne noter i ét tilfælde: Jeg stod med et problem, jeg vidste i starten ikke hvordan jeg skulle løse det, løsningen var ikke googlet færdig, så jeg googlede det i dele og løste til sidst problemet. Og for at jeg i fremtiden, når jeg glemmer, hvordan jeg gjorde det, ikke skal google alt igen stykke for stykke og kompilere det sammen, skriver jeg mig selv sådanne snydeark.

Disclaimer: 1. Sedlen blev skrevet "til mig selv", til rollen bedste praksis gælder ikke. Jeg er glad for at læse mulighederne for "det ville have været bedre at gøre det på denne måde" i kommentarerne.
2. Hvis den påførte del af noten betragtes som salt, så er denne, ligesom alle mine tidligere noter, en svag saltopløsning.

Dynamisk opdatering af jobindstillinger i Jenkins

Jeg forudser dit spørgsmål: hvad har dynamisk jobopdatering med det at gøre? Indtast værdien af ​​strengparameteren manuelt, og så er du i gang!

Jeg svarer: Jeg er virkelig doven, jeg kan ikke lide, når de brokker sig: Misha, udsendelsen går ned, alt er væk! Du begynder at lede, og der er en tastefejl i værdien af ​​en opgavestartparameter. Derfor foretrækker jeg at gøre alting så effektivt som muligt. Hvis det er muligt at forhindre brugeren i at indtaste data direkte ved i stedet at give en liste med værdier at vælge imellem, så organiserer jeg valget.

Planen er denne: vi opretter et job i Jenkins, hvor vi før lancering kunne vælge en version fra listen, angive værdier for parametre, der sendes til containeren via ENV, så samler den containeren og skubber den ind i containerregistret. Derefter lanceres beholderen i cuber as arbejdsbyrde med de parametre, der er angivet i jobbet.

Vi vil ikke overveje processen med at skabe og oprette et job i Jenkins, dette er off-topic. Vi vil antage, at opgaven er klar. For at implementere en opdateret liste med versioner har vi brug for to ting: en eksisterende kildeliste med a priori gyldige versionsnumre og en variabel som f.eks. Valg parameter i opgaven. I vores eksempel, lad variablen navngives BUILD_VERSION, vil vi ikke dvæle ved det i detaljer. Men lad os se nærmere på kildelisten.

Der er ikke så mange muligheder. To ting kom straks til at tænke på:

  • Brug den Remote Access API, som Jenkins tilbyder sine brugere;
  • Anmod om indholdet af fjernlagermappen (i vores tilfælde er dette JFrog Artifactory, hvilket ikke er vigtigt).

Jenkins Remote Access API

Ifølge den etablerede udmærkede tradition vil jeg helst undgå lange forklaringer.
Jeg vil kun tillade mig en fri oversættelse af et stykke af første afsnit første side af API-dokumentation:

Jenkins leverer en API til fjernadgang til maskinlæsbar funktionalitet. <...> Fjernadgang tilbydes i en REST-lignende stil. Det betyder, at der ikke er et enkelt indgangspunkt til alle funktioner, men i stedet en URL som ".../api/", Hvor "..." betyder det objekt, som API-funktionerne anvendes på.

Med andre ord, hvis den udrulningsopgave, vi i øjeblikket taler om, er tilgængelig på http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build, så er API-fløjterne til denne opgave tilgængelige på http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/

Dernæst har vi et valg, i hvilken form vi skal modtage outputtet. Lad os fokusere på XML, da API'en kun tillader filtrering i dette tilfælde.

Lad os bare prøve at få en liste over alle jobkørsler. Vi er kun interesserede i samlingens navn (displaynavn) og dets resultat (resultere):

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

Det viste sig?

Lad os nu kun filtrere de kørsler, der ender med resultatet SUCCES. Lad os bruge argumentet &udelukke og som en parameter vil vi give den stien til en værdi, der ikke er lig med SUCCES. Ja Ja. En dobbelt negativ er et udsagn. Vi udelukker alt, der ikke interesserer os:

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

Skærmbillede af listen over succesrige
Vi opretter en implementeringsopgave i GKE uden plugins, SMS eller registrering. Lad os tage et kig under Jenkins' jakke

Nå, bare for sjov, lad os sørge for, at filteret ikke bedragede os (filtre lyver aldrig!) og vise en liste over "mislykkede":

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

Skærmbillede af listen over ikke-vellykkede
Vi opretter en implementeringsopgave i GKE uden plugins, SMS eller registrering. Lad os tage et kig under Jenkins' jakke

Liste over versioner fra en mappe på en fjernserver

Der er en anden måde at få en liste over versioner på. Jeg kan endnu bedre lide det end at få adgang til Jenkins API. Nå, for hvis applikationen blev bygget med succes, betyder det, at den blev pakket og placeret i depotet i den relevante mappe. Ligesom et lager er standardlageret for fungerende versioner af applikationer. Synes godt om. Nå, lad os spørge ham, hvilke versioner der er på lager. Vi krøller, grep og awk fjernmappen. Hvis nogen er interesseret i onelineren, så er den under spoileren.

En linje kommando
Bemærk venligst to ting: Jeg sender forbindelsesdetaljerne i overskriften, og jeg har ikke brug for alle versionerne fra mappen, og jeg vælger kun dem, der blev oprettet inden for en måned. Rediger kommandoen, så den passer til dine realiteter og 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[^/]+' )

Opsætning af job og jobkonfigurationsfil i Jenkins

Vi fandt ud af kilden til listen over versioner. Lad os nu inkorporere den resulterende liste i opgaven. For mig var den oplagte løsning at tilføje et trin i applikationsopbygningsopgaven. Det trin, der ville blive udført, hvis resultatet var "succes".

Åbn indstillingerne for monteringsopgaven, og rul helt ned til bunden. Klik på knapperne: Tilføj byggetrin -> Betinget trin (enkelt). Vælg betingelsen i trinindstillingerne Aktuel byggestatus, indstil værdien SUCCES, den handling, der skal udføres, hvis den lykkes Kør shell-kommando.

Og nu den sjove del. Jenkins gemmer jobkonfigurationer i filer. I XML-format. Langs vejen http://путь-до-задания/config.xml Derfor kan du downloade konfigurationsfilen, redigere den efter behov og lægge den tilbage, hvor du fik den.

Husk, vi aftalte ovenfor, at vi vil oprette en parameter til listen over versioner BUILD_VERSION?

Lad os downloade konfigurationsfilen og tage et kig i den. Bare for at sikre, at parameteren er på plads og af den ønskede type.

Skærmbillede under spoiler.

Dit config.xml-fragment skulle se ens ud. Bortset fra at indholdet af valgelementet mangler endnu
Vi opretter en implementeringsopgave i GKE uden plugins, SMS eller registrering. Lad os tage et kig under Jenkins' jakke

Er du sikker? Det er det, lad os skrive et script, der vil blive udført, hvis bygningen lykkes.
Scriptet vil modtage en liste over versioner, downloade konfigurationsfilen, skrive listen over versioner ind i det på det sted, vi har brug for, og derefter lægge det tilbage. Ja. Det er rigtigt. Skriv en liste over versioner i XML på det sted, hvor der allerede er en liste over versioner (vil være i fremtiden, efter den første lancering af scriptet). Jeg ved, at der stadig er voldsomme fans af regulære udtryk i verden. Jeg hører ikke til dem. Installer venligst xmlstarler til den maskine, hvor konfigurationen vil blive redigeret. Det forekommer mig, at dette ikke er så stor en pris at betale for at undgå at redigere XML ved hjælp af sed.

Under spoileren præsenterer jeg koden, der udfører ovenstående sekvens i sin helhed.

Skriv en liste over versioner fra en mappe på fjernserveren til 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

Hvis du foretrækker muligheden for at få versioner fra Jenkins, og du er lige så doven som mig, så er den samme kode under spoileren, men en liste fra Jenkins:

Skriv en liste over versioner fra Jenkins til konfigurationen
Bare husk på dette: mit samlingsnavn består af et sekvensnummer og et versionsnummer, adskilt af et kolon. Følgelig afskærer awk den unødvendige del. For dig selv, skift denne linje, så den passer til dine 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 teorien, hvis du har testet koden skrevet ud fra eksemplerne ovenfor, så burde du i implementeringsopgaven allerede have en rulleliste med versioner. Det er ligesom på skærmbilledet under spoileren.

Korrekt udfyldt liste over versioner
Vi opretter en implementeringsopgave i GKE uden plugins, SMS eller registrering. Lad os tage et kig under Jenkins' jakke

Hvis alt fungerede, så copy-paste scriptet ind Kør shell-kommando og gemme ændringer.

Opretter forbindelse til Cloud shell

Vi har samlere i containere. Vi bruger Ansible som vores applikationsleveringsværktøj og konfigurationsmanager. Når det kommer til at bygge containere, kommer tre muligheder til at tænke på: installer Docker i Docker, installer Docker på en maskine, der kører Ansible, eller byg containere i en cloud-konsol. Vi blev enige om at tie om plugins til Jenkins i denne artikel. Husk?

Jeg besluttede: Nå, da containere "ud af kassen" kan samles i skykonsollen, hvorfor så gider det? Hold det rent, ikke? Jeg vil samle Jenkins-beholdere i skykonsollen og derefter starte dem ind i kuberen derfra. Desuden har Google meget rige kanaler inden for sin infrastruktur, hvilket vil have en gavnlig effekt på implementeringshastigheden.

For at oprette forbindelse til cloud-konsollen skal du bruge to ting: gcloud og adgangsrettigheder til Google Cloud API for den VM-instans, hvorfra den samme forbindelse vil blive oprettet.

For dem, der planlægger at oprette forbindelse, slet ikke fra Google Cloud
Google tillader muligheden for at deaktivere interaktiv godkendelse i sine tjenester. Dette giver dig mulighed for at oprette forbindelse til konsollen selv fra en kaffemaskine, hvis den kører *nix og har en konsol i sig selv.

Hvis der er behov for, at jeg dækker dette spørgsmål mere detaljeret inden for rammerne af dette notat, så skriv i kommentarerne. Hvis vi får nok stemmer, skriver jeg en opdatering om dette emne.

Den nemmeste måde at tildele rettigheder på er via webgrænsefladen.

  1. Stop den VM-instans, hvorfra du efterfølgende vil oprette forbindelse til cloud-konsollen.
  2. Åbn Instance Details og klik ændre.
  3. Helt nederst på siden skal du vælge instansadgangsomfanget Fuld adgang til alle Cloud API'er.

    skærmbillede
    Vi opretter en implementeringsopgave i GKE uden plugins, SMS eller registrering. Lad os tage et kig under Jenkins' jakke

  4. Gem dine ændringer og start forekomsten.

Når VM'en er færdig med at indlæse, skal du oprette forbindelse til den via SSH og sikre dig, at forbindelsen sker uden fejl. Brug kommandoen:

gcloud alpha cloud-shell ssh

En vellykket forbindelse ser nogenlunde sådan ud
Vi opretter en implementeringsopgave i GKE uden plugins, SMS eller registrering. Lad os tage et kig under Jenkins' jakke

Implementer til GKE

Da vi på alle mulige måder stræber efter fuldstændigt at skifte til IaC (Infrastucture as a Code), er vores docker-filer gemt i Git. Dette er på den ene side. Og udrulning i kubernetes er beskrevet af en yaml-fil, som kun bruges af denne opgave, som i sig selv også er som kode. Dette er fra den anden side. Generelt mener jeg, planen er denne:

  1. Vi tager værdierne af variablerne BUILD_VERSION og, valgfrit, værdierne af de variable, der vil blive sendt igennem ENV.
  2. Download dockerfilen fra Git.
  3. Generer yaml til implementering.
  4. Vi uploader begge disse filer via scp til cloud-konsollen.
  5. Vi bygger en container der og skubber den ind i containerregistret
  6. Vi anvender load-implementeringsfilen til cuberen.

Lad os være mere specifikke. Engang begyndte vi at tale om ENV, antag så, at vi skal videregive værdierne af to parametre: PARAM1 и PARAM2. Vi tilføjer deres opgave til implementering, skriv - Strengparameter.

skærmbillede
Vi opretter en implementeringsopgave i GKE uden plugins, SMS eller registrering. Lad os tage et kig under Jenkins' jakke

Vi vil generere yaml med en simpel omdirigering ekko at arkivere. Det forudsættes selvfølgelig, at du har i din dockerfil PARAM1 и PARAM2at belastningsnavnet bliver fantastisk app, og den samlede beholder med anvendelsen af ​​den specificerede version ligger i Containerregistrering på vej gcr.io/awesomeapp/awesomeapp-$BUILD_VERSIONHvor $BUILD_VERSION blev netop valgt fra rullelisten.

Holdliste

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 tilslutning ved hjælp af gcloud alpha cloud-shell ssh interaktiv tilstand er ikke tilgængelig, så vi sender kommandoer til skykonsollen ved hjælp af parameteren --kommando.

Vi renser hjemmemappen i skykonsollen fra den gamle dockerfil:

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

Placer den nyligt downloadede dockerfil i hjemmemappen på skykonsollen ved hjælp af scp:

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

Vi indsamler, mærker og skubber containeren til 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 det samme med implementeringsfilen. Bemærk venligst, at kommandoerne nedenfor bruger fiktive navne på den klynge, hvor implementeringen finder sted (awsm-klynge) og projektnavn (fantastisk projekt), hvor klyngen er placeret.

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ører opgaven, åbner konsoludgangen og håber at se den vellykkede samling af containeren.

skærmbillede
Vi opretter en implementeringsopgave i GKE uden plugins, SMS eller registrering. Lad os tage et kig under Jenkins' jakke

Og så den vellykkede indsættelse af den samlede container

skærmbillede
Vi opretter en implementeringsopgave i GKE uden plugins, SMS eller registrering. Lad os tage et kig under Jenkins' jakke

Jeg ignorerede bevidst indstillingen Ingress. Af en simpel grund: Når du har konfigureret det arbejdsbyrde med et givet navn, vil det forblive operationelt, uanset hvor mange implementeringer med dette navn du udfører. Nå, generelt er dette lidt uden for historiens rammer.

I stedet for konklusioner

Alle ovenstående trin kunne sandsynligvis ikke have været udført, men installerede blot et eller andet plugin til Jenkins, deres muuulion. Men af ​​en eller anden grund kan jeg ikke lide plugins. Nå, mere præcist tyr jeg til dem kun af desperation.

Og jeg kan bare godt lide at tage et nyt emne op for mig. Ovenstående tekst er også en måde at dele de fund, jeg gjorde, mens jeg løste det problem, der blev beskrevet i begyndelsen. Del med dem, der ligesom ham slet ikke er en frygtelig ulv i devops. Hvis mine resultater hjælper i det mindste nogen, vil jeg blive glad.

Kilde: www.habr.com

Tilføj en kommentar