Vi lager en distribusjonsoppgave i GKE uten plugins, SMS eller registrering. La oss ta en titt under jakken til Jenkins

Det hele startet da teamlederen til et av utviklingsteamene våre ba oss om å teste den nye applikasjonen deres, som hadde blitt containerisert dagen før. Jeg la det ut. Etter ca 20 minutter kom det en forespørsel om å oppdatere applikasjonen, fordi det var lagt inn en helt nødvendig ting der. jeg fornyet. Etter et par timer til... vel, du kan gjette hva som begynte å skje neste gang...

Jeg må innrømme at jeg er ganske lat (innrømmet jeg ikke dette tidligere? Nei?), og gitt det faktum at teamledere har tilgang til Jenkins, der vi har alle CI/CD, tenkte jeg: la ham distribuere som mye som han vil! Jeg husket en vits: Gi en mann en fisk, og han vil spise for en dag; kall en person Fed og han vil bli Fed hele livet. Og gikk spille triks på jobben, som ville være i stand til å distribuere en beholder som inneholder applikasjonen av en hvilken som helst vellykket innebygd versjon i Kuber og overføre eventuelle verdier til den ENV (Min bestefar, en filolog, en engelsklærer i fortiden, ville nå snurre fingeren mot tinningen hans og så veldig uttrykksfullt på meg etter å ha lest denne setningen).

Så i dette notatet vil jeg fortelle deg hvordan jeg lærte:

  1. Dynamisk oppdater jobber i Jenkins fra selve jobben eller fra andre jobber;
  2. Koble til skykonsollen (Cloud shell) fra en node med Jenkins-agenten installert;
  3. Distribuer arbeidsmengde til Google Kubernetes Engine.


Faktisk er jeg selvfølgelig litt uoppriktig. Det antas at du har minst en del av infrastrukturen i Google-skyen, og derfor er du brukeren av den, og du har selvfølgelig en GCP-konto. Men det er ikke det dette notatet handler om.

Dette er mitt neste jukseark. Jeg vil bare skrive slike notater i ett tilfelle: Jeg sto overfor et problem, jeg visste i utgangspunktet ikke hvordan jeg skulle løse det, løsningen ble ikke googlet ferdig, så jeg googlet det i deler og løste problemet til slutt. Og for at jeg i fremtiden, når jeg glemmer hvordan jeg gjorde det, ikke trenger å google alt på nytt bit for bit og kompilere det sammen, skriver jeg selv slike jukseark.

Ansvarsfraskrivelse: 1. Notatet ble skrevet "for meg selv", for rollen beste praksis gjelder ikke. Jeg er glad for å lese "det hadde vært bedre å gjøre det på denne måten"-alternativene i kommentarene.
2. Hvis den påførte delen av seddelen regnes som salt, så er denne, som alle mine tidligere notater, en svak saltløsning.

Dynamisk oppdatering av jobbinnstillinger i Jenkins

Jeg ser for meg spørsmålet ditt: hva har dynamisk jobboppdatering med det å gjøre? Skriv inn verdien til strengparameteren manuelt, og så er du i gang!

Jeg svarer: Jeg er veldig lat, jeg liker ikke når de klager: Misha, utplasseringen krasjer, alt er borte! Du begynner å lete, og det er en skrivefeil i verdien av en oppgavestartparameter. Derfor foretrekker jeg å gjøre alt så effektivt som mulig. Hvis det er mulig å hindre brukeren i å legge inn data direkte ved å gi i stedet en liste med verdier å velge mellom, så organiserer jeg utvalget.

Planen er denne: vi oppretter en jobb i Jenkins, der vi før lansering kunne velge en versjon fra listen, spesifisere verdier for parametere sendt til containeren via ENV, så samler den opp beholderen og skyver den inn i beholderregisteret. Derfra lanseres containeren i cuber as arbeidsmengde med parametrene spesifisert i jobben.

Vi vil ikke vurdere prosessen med å opprette og sette opp en jobb i Jenkins, dette er off-topic. Vi vil anta at oppgaven er klar. For å implementere en oppdatert liste med versjoner, trenger vi to ting: en eksisterende kildeliste med a priori gyldige versjonsnumre og en variabel som Valgparameter i oppgaven. I vårt eksempel, la variabelen bli navngitt BUILD_VERSION, vi vil ikke dvele ved det i detalj. Men la oss se nærmere på kildelisten.

Det er ikke så mange alternativer. To ting kom umiddelbart til tankene:

  • Bruk Remote Access API som Jenkins tilbyr sine brukere;
  • Be om innholdet i den eksterne depotmappen (i vårt tilfelle er dette JFrog Artifactory, som ikke er viktig).

Jenkins Remote Access API

I følge den etablerte utmerkede tradisjonen vil jeg helst unngå lange forklaringer.
Jeg vil bare tillate meg en fri oversettelse av en del av første avsnitt første side med API-dokumentasjon:

Jenkins tilbyr en API for ekstern maskinlesbar tilgang til funksjonaliteten. <...> Fjerntilgang tilbys i en REST-lignende stil. Dette betyr at det ikke er et enkelt inngangspunkt til alle funksjoner, men i stedet en URL som ".../api/", Hvor "..." betyr objektet som API-funksjonene brukes på.

Med andre ord, hvis distribusjonsoppgaven vi snakker om er tilgjengelig på http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build, så er API-fløytene for denne oppgaven tilgjengelige på http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/

Deretter har vi et valg i hvilken form vi skal motta utdataene. La oss fokusere på XML, siden API bare tillater filtrering i dette tilfellet.

La oss bare prøve å få en liste over alle jobbkjøringer. Vi er kun interessert i samlingsnavnet (displaynavn) og resultatet (resultere):

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

Det viste seg?

La oss nå filtrere bare de kjøringene som ender opp med resultatet SUKSESS. La oss bruke argumentet &utelukke og som en parameter vil vi gi den veien til en verdi som ikke er lik SUKSESS. Ja Ja. En dobbel negativ er et utsagn. Vi utelukker alt som ikke interesserer oss:

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

Skjermbilde av listen over vellykkede
Vi lager en distribusjonsoppgave i GKE uten plugins, SMS eller registrering. La oss ta en titt under jakken til Jenkins

Vel, bare for moro skyld, la oss sørge for at filteret ikke lurte oss (filtre lyver aldri!) 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']

Skjermbilde av listen over ikke-vellykkede
Vi lager en distribusjonsoppgave i GKE uten plugins, SMS eller registrering. La oss ta en titt under jakken til Jenkins

Liste over versjoner fra en mappe på en ekstern server

Det er en annen måte å få en liste over versjoner på. Jeg liker det enda mer enn å få tilgang til Jenkins API. Vel, fordi hvis applikasjonen ble bygget, betyr det at den ble pakket og plassert i depotet i riktig mappe. Som et depot er standardlagringen for fungerende versjoner av applikasjoner. Som. Vel, la oss spørre ham hvilke versjoner som er lagret. Vi vil krølle, grep og awk den eksterne mappen. Hvis noen er interessert i oneliner, så er det under spoileren.

En linje kommando
Vær oppmerksom på to ting: Jeg sender tilkoblingsdetaljene i overskriften og jeg trenger ikke alle versjonene fra mappen, og jeg velger bare de som ble opprettet innen en måned. Rediger kommandoen for å passe 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[^/]+' )

Sette opp jobber og jobbkonfigurasjonsfil i Jenkins

Vi fant ut kilden til listen over versjoner. La oss nå inkludere den resulterende listen i oppgaven. For meg var den åpenbare løsningen å legge til et trinn i applikasjonsbyggingsoppgaven. trinnet som ville bli utført hvis resultatet var "suksess".

Åpne monteringsoppgaveinnstillingene og bla helt til bunnen. Klikk på knappene: Legg til byggetrinn -> Betinget trinn (enkelt). I trinninnstillingene velger du betingelsen Gjeldende byggestatus, angi verdien SUKSESS, handlingen som skal utføres hvis vellykket Kjør shell-kommando.

Og nå den morsomme delen. Jenkins lagrer jobbkonfigurasjoner i filer. I XML-format. Langs veien http://путь-до-задания/config.xml Følgelig kan du laste ned konfigurasjonsfilen, redigere den etter behov og sette den tilbake der du fikk den.

Husk at vi ble enige ovenfor at vi skal lage en parameter for listen over versjoner BUILD_VERSION?

La oss laste ned konfigurasjonsfilen og ta en titt i den. Bare for å være sikker på at parameteren er på plass og av ønsket type.

Skjermbilde under spoiler.

Config.xml-fragmentet ditt skal se likt ut. Bortsett fra at innholdet i valgelementet mangler ennå
Vi lager en distribusjonsoppgave i GKE uten plugins, SMS eller registrering. La oss ta en titt under jakken til Jenkins

Er du sikker? Det er det, la oss skrive et skript som vil bli utført hvis byggingen er vellykket.
Skriptet vil motta en liste over versjoner, laste ned konfigurasjonsfilen, skrive listen over versjoner inn i den på stedet vi trenger, og deretter sette den tilbake. Ja. Det er riktig. Skriv en liste over versjoner i XML på stedet der det allerede er en liste over versjoner (vil være i fremtiden, etter den første lanseringen av skriptet). Jeg vet at det fortsatt er heftige fans av regulære uttrykk i verden. Jeg tilhører ikke dem. Vennligst installer xmlstarler til maskinen der konfigurasjonen vil bli redigert. Det virker for meg at dette ikke er en så stor pris å betale for å unngå å redigere XML ved å bruke sed.

Under spoileren presenterer jeg koden som utfører sekvensen ovenfor i sin helhet.

Skriv en liste over versjoner fra en mappe på den eksterne serveren til konfigurasjonen

#!/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 foretrekker muligheten til å få versjoner fra Jenkins og du er like lat som meg, så er den samme koden under spoileren, men en liste fra Jenkins:

Skriv en liste over versjoner fra Jenkins til konfigurasjonen
Bare husk dette: samlingsnavnet mitt består av et sekvensnummer og et versjonsnummer, atskilt med et kolon. Følgelig kutter awk av den unødvendige delen. For deg selv, endre denne linjen for å passe 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 basert på eksemplene ovenfor, bør du allerede ha en rullegardinliste med versjoner i distribusjonsoppgaven. Det er som på skjermbildet under spoileren.

Korrekt utfylt liste over versjoner
Vi lager en distribusjonsoppgave i GKE uten plugins, SMS eller registrering. La oss ta en titt under jakken til Jenkins

Hvis alt fungerte, så kopier og lim inn skriptet Kjør shell-kommando og lagre endringer.

Kobler til Cloud shell

Vi har samlere i containere. Vi bruker Ansible som vårt applikasjonsleveringsverktøy og konfigurasjonsbehandling. Følgelig, når det kommer til å bygge containere, kommer tre alternativer til tankene: installer Docker i Docker, installer Docker på en maskin som kjører Ansible, eller bygg containere i en skykonsoll. Vi ble enige om å være stille om plugins for Jenkins i denne artikkelen. Huske?

Jeg bestemte meg: vel, siden containere "ut av esken" kan samles i skykonsollen, hvorfor bry seg? Hold det rent, ikke sant? Jeg vil samle Jenkins-beholdere i skykonsollen, og deretter starte dem inn i kuberen derfra. Dessuten har Google svært rike kanaler innenfor sin infrastruktur, noe som vil ha en gunstig effekt på utrullingshastigheten.

For å koble til skykonsollen trenger du to ting: gcloud og tilgangsrettigheter til Google API Cloud for VM-forekomsten som den samme forbindelsen skal opprettes med.

For de som planlegger å koble til ikke fra Google Cloud i det hele tatt
Google tillater muligheten for å deaktivere interaktiv autorisasjon i sine tjenester. Dette vil tillate deg å koble til konsollen selv fra en kaffemaskin, hvis den kjører *nix og har en konsoll selv.

Hvis det er behov for meg å dekke dette spørsmålet mer detaljert innenfor rammen av dette notatet, skriv i kommentarfeltet. Hvis vi får nok stemmer, vil jeg skrive en oppdatering om dette emnet.

Den enkleste måten å gi rettigheter på er gjennom nettgrensesnittet.

  1. Stopp VM-forekomsten som du senere vil koble til skykonsollen fra.
  2. Åpne Forekomstdetaljer og klikk endre.
  3. Helt nederst på siden velger du forekomsttilgangsomfanget Full tilgang til alle Cloud APIer.

    skjermbilde
    Vi lager en distribusjonsoppgave i GKE uten plugins, SMS eller registrering. La oss ta en titt under jakken til Jenkins

  4. Lagre endringene og start forekomsten.

Når VM er ferdig lastet, kobler du til den via SSH og kontrollerer at tilkoblingen skjer uten feil. Bruk kommandoen:

gcloud alpha cloud-shell ssh

En vellykket tilkobling ser omtrent slik ut
Vi lager en distribusjonsoppgave i GKE uten plugins, SMS eller registrering. La oss ta en titt under jakken til Jenkins

Distribuer til GKE

Siden vi på alle mulige måter streber etter å fullstendig bytte til IaC (Infrastucture as a Code), lagres docker-filene våre i Git. Dette er på den ene siden. Og distribusjon i kubernetes er beskrevet av en yaml-fil, som bare brukes av denne oppgaven, som i seg selv også er som kode. Dette er fra den andre siden. Generelt mener jeg, planen er denne:

  1. Vi tar verdiene til variablene BUILD_VERSION og, valgfritt, verdiene til variablene som vil bli sendt gjennom ENV.
  2. Last ned dockerfilen fra Git.
  3. Generer yaml for distribusjon.
  4. Vi laster opp begge disse filene via scp til skykonsollen.
  5. Vi bygger en container der og skyver den inn i Container-registeret
  6. Vi bruker lastdistribusjonsfilen til cuberen.

La oss være mer spesifikke. En gang begynte vi å snakke om ENV, anta at vi må sende verdiene til to parametere: PARAM1 и PARAM2. Vi legger til oppgaven deres for distribusjon, skriv - Strengeparameter.

skjermbilde
Vi lager en distribusjonsoppgave i GKE uten plugins, SMS eller registrering. La oss ta en titt under jakken til Jenkins

Vi vil generere yaml med en enkel omdirigering savner å lagre. Det forutsettes selvfølgelig at du har i dockerfilen din PARAM1 и PARAM2at lastnavnet blir fantastisk app, og den sammensatte beholderen med bruk av den spesifiserte versjonen ligger i Beholderregister er på vei gcr.io/awesomeapp/awesomeapp-$BUILD_VERSIONDer $BUILD_VERSION ble nettopp valgt fra rullegardinlisten.

Lagoppføring

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 etter tilkobling bruker gcloud alpha cloud-shell ssh interaktiv modus er ikke tilgjengelig, så vi sender kommandoer til skykonsollen ved å bruke parameteren --kommando.

Vi renser hjemmemappen i skykonsollen fra den gamle dockerfilen:

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

Plasser den nylig nedlastede dockerfilen i hjemmemappen til skykonsollen ved å bruke scp:

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

Vi samler inn, merker og skyver beholderen til beholderregisteret:

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 gjør det samme med distribusjonsfilen. Vær oppmerksom på at kommandoene nedenfor bruker fiktive navn på klyngen der distribusjonen skjer (awsm-klynge) og prosjektnavn (kjempebra prosjekt), hvor klyngen er plassert.

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 kjører oppgaven, åpner konsollutgangen og håper å se den vellykkede monteringen av beholderen.

skjermbilde
Vi lager en distribusjonsoppgave i GKE uten plugins, SMS eller registrering. La oss ta en titt under jakken til Jenkins

Og så den vellykkede utplasseringen av den sammensatte beholderen

skjermbilde
Vi lager en distribusjonsoppgave i GKE uten plugins, SMS eller registrering. La oss ta en titt under jakken til Jenkins

Jeg ignorerte innstillingen bevisst Ingress. Av en enkel grunn: når du har satt den opp arbeidsmengde med et gitt navn, vil den forbli operativ, uansett hvor mange distribusjoner med dette navnet du utfører. Vel, generelt sett er dette litt utenfor historiens rammer.

I stedet for konklusjoner

Alle trinnene ovenfor kunne sannsynligvis ikke blitt gjort, men installerte ganske enkelt en plugin for Jenkins, deres muuulion. Men av en eller annen grunn liker jeg ikke plugins. Vel, mer presist, jeg tyr til dem bare av desperasjon.

Og jeg liker bare å ta opp et nytt emne for meg. Teksten ovenfor er også en måte å dele funnene jeg gjorde mens jeg løste problemet som ble beskrevet helt i begynnelsen. Del med de som, som ham, slett ikke er en forferdelig ulv i devops. Hvis mine funn hjelper i det minste noen, vil jeg være glad.

Kilde: www.habr.com

Legg til en kommentar