Creiamo un task di deploy in GKE senza plugin, SMS e registrazione. Diamo un’occhiata a Jenkins sotto la giacca.

Tutto è iniziato quando il team leader di uno dei nostri gruppi di sviluppatori ha chiesto di esporre in modo temporaneo la loro nuova applicazione, che era stata recentemente containerizzata. L'ho fatto. Circa 20 minuti dopo, ho ricevuto una richiesta per aggiornare l'applicazione, perché avevano aggiunto una funzionalità molto utile. L'ho aggiornata. Dopo un paio d'ore… beh, immaginate cosa sia successo dopo…

Confesso di essere piuttosto pigro (l'avevo già detto, giusto? no?), e considerando il fatto che i team leader hanno accesso a Jenkins, dove gestiamo tutto il nostro CI/CD, ho pensato: lasci che sia lui a fare il deploy quanto gli pare! Mi è tornata in mente una barzelletta: dì a una persona di pescare e mangerà per un giorno; chiama una persona Sazioso e mangerà per tutta la vita. E sono andato a creare un job, capace di effettuare il deploy di un container Kubernetes con un'applicazione di qualsiasi versione compilata con successo e di passarvi qualsiasi valore. ENV (mio nonno, un filologo e insegnante di inglese in passato, adesso girerebbe il dito alla tempia e mi guarderebbe con espressione scettica leggendo questa frase).

Quindi, in questo appunto parlerò di come ho imparato:

  1. Aggiornare dinamicamente i compiti in Jenkins direttamente dal compito o da altri compiti;
  2. Collegarmi alla console cloud (Cloud shell) da un nodo con l'agente Jenkins installato;
  3. Distribuire un carico di lavoro (workload) in Google Kubernetes Engine.


In realtà, sto un po' esagerando. Si presume che almeno parte dell'infrastruttura sia nel cloud di Google, e quindi sei un suo utente e, naturalmente, hai un account GCP. Ma non è di questo che parla l'appunto.

Questa è un'altra mia breve nota. Scrivo appunti del genere solo in un caso: quando mi viene assegnato un compito, inizialmente non so come risolverlo, la soluzione non si trova facilmente su Google, quindi la cerco a pezzi e alla fine risolvo il compito. E per assicurarci che in futuro, quando dimenticherò come l'ho fatto, non debba cercare tutto di nuovo a pezzi e compilarlo in un insieme, scrivo queste brevi note per me stesso.

Disclaimer: 1. L'appunto è stato scritto "per me stesso", nel ruolo di best practice non pretendo. Sarà un piacere leggere le alternative 'è meglio farlo così' nei commenti.
2. Se consideriamo la parte applicativa della nota come il sale, allora, come tutte le mie precedenti note, questa è una soluzione a bassa salinità.

Aggiornamento dinamico delle impostazioni dei job in Jenkins

Prevedo la vostra domanda: che c'entra l'aggiornamento dinamico del job? Ho inserito manualmente il valore del parametro stringa e avanti!

Rispondo: sono davvero pigro e non mi piace quando si lamentano: Misha, il deploy è andato in crash, è tutto finito! Cominci a controllare e trovi un errore di battitura nel valore di qualche parametro di avvio del job. Per questo motivo, preferisco fare tutto il più a prova di errore possibile. Se c'è la possibilità di privare l'utente della possibilità di inserire dati manualmente, fornendo invece un elenco di valori tra cui scegliere, allora organizzo la selezione.

Il piano è il seguente: creiamo un job in Jenkins, in cui prima dell'avvio si possa scegliere una versione da un elenco, specificare valori per i parametri da passare al contenitore tramite ENV, poi costruisce il contenitore e lo carica nel Container Registry. Successivamente, il contenitore viene avviato nel Kubernetes come workload con i parametri stabiliti nel job.

Non tratteremo il processo di creazione e configurazione di un'attività in Jenkins, poiché non è pertinente. Partiremo dal presupposto che l'attività sia pronta. Per implementare un elenco aggiornabile di versioni, abbiamo bisogno di due cose: un elenco sorgente già esistente con numeri di versione validi e una variabile di tipo parametro di scelta nell'attività. Nel nostro esempio, supponiamo che la variabile si chiami BUILD_VERSION, non ci soffermeremo su di essa. Focalizziamoci piuttosto sull'elenco sorgente.

Le opzioni non sono molte. Mi vengono in mente due soluzioni:

  • Utilizzare l'API di accesso remoto che Jenkins offre ai propri utenti;
  • Richiedere il contenuto di una cartella remota del repository (nel nostro caso si tratta di JFrog Artifactory, ma non è fondamentale).

API di accesso remoto di Jenkins

Seguendo la bella tradizione, preferisco evitare spiegazioni lunghe.
Mi permetto solo una libera traduzione di un estratto del primo paragrafo della prima pagina della documentazione dell'API:

Jenkins offre un'API per l'accesso remoto compatibile con le macchine alle sue funzionalità. L'accesso remoto è progettato in uno stile simile a REST. Questo significa che non esiste un'unica entry point per tutte le funzionalità, ma piuttosto si utilizza un URL del tipo ".../api/", dove "" rappresenta l'oggetto a cui si applicano le funzionalità dell'API.

In altre parole, se il job di deploy di cui stiamo parlando è accessibile all'indirizzo http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build, le API per quel job sono accessibili all'indirizzo http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/

Dopo di che abbiamo la scelta su come ricevere l'output. Concentrandoci su XML, poiché l'API consente la filtrazione solo in questo caso.

Proviamo a ottenere semplicemente l'elenco di tutte le esecuzioni del job. Ci interessa solo il nome della build (displayName) e il suo risultato (result):

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

È andato a buon fine?

Ora filtriamo solo le esecuzioni che alla fine hanno il risultato SUCCESS. Utilizziamo l'argomento &exclude e come parametro passiamo il percorso al valore che non è uguale a SUCCESS. Sì-sì. La doppia negazione è un'affermazione. Escludiamo tutto ciò che non ci interessa:

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

Screenshot dell'elenco dei successi
Creiamo un'attività di deploy in GKE senza plugin, SMS e registrazione. Diamo un'occhiata a Jenkins sotto la giacca

E semplicemente per divertimento, verifichiamo che il filtro non ci abbia ingannato (i filtri non mentono mai!) e mostriamo l'elenco delle "non-riuscite":

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

Screenshot dell'elenco delle non-riuscite
Creiamo un'attività di deploy in GKE senza plugin, SMS e registrazione. Diamo un'occhiata a Jenkins sotto la giacca

Elenco delle versioni dalla cartella sul server remoto

C'è anche un secondo modo per ottenere l'elenco delle versioni. Mi piace persino di più rispetto alla chiamata all'API di Jenkins. Beh, perché se l'applicazione è stata compilata con successo, significa che è stata impacchettata e messa nella cartella corretta nel repository. Tipo, il repository è di default uno storage delle versioni lavorative delle applicazioni. Beh, quindi chiediamo quali versioni sono in deposito. Accederemo alla cartella remota tramite curl, filtreremo con grep e awk. Se a qualcuno interessa un one-liner, lo troverà sotto spoiler.

Comando in un'unica riga
Fate attenzione a due cose: sto passando nel header le credenziali per la connessione e non ho bisogno di tutte le versioni dalla cartella, ma seleziono solo quelle create nell'ultimo mese. Modificate il comando secondo le vostre necessità:

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[^/]+' )

Impostazione dei task e file di configurazione del task in Jenkins

Abbiamo sistemato la sorgente dell'elenco versioni. Ora integrare l'elenco ottenuto nel task. Per me, la soluzione ovvia è stata aggiungere un passo nel task per la compilazione dell'applicazione. Un passo che venga eseguito in caso di risultato "successo".

Apriamo le impostazioni del task di compilazione e scorriamo fino in fondo. Facciamo clic sui pulsanti: Aggiungi passo di compilazione -> Passo condizionale (singolo). Nelle impostazioni del passo scegliamo la condizione Stato attuale della compilazione, impostiamo il valore SUCCESS, azione da eseguire in caso di successo Esegui comando shell.

E ora la parte più interessante. Jenkins memorizza le configurazioni dei task in file. In formato XML. Al percorso http://percorso-del-task/config.xml Pertanto, è possibile scaricare il file di configurazione, modificarlo come necessario e riposizionarlo nel luogo da cui è stato prelevato.

Ricordate, sopra abbiamo concordato di creare un parametro per l'elenco delle versioni BUILD_VERSION?

Scarichiamo il file di configurazione e diamo un'occhiata al suo interno. Solo per assicurarci che il parametro sia presente e realmente della giusta forma.

Screenshot sotto il spoiler.

Il frammento di config.xml deve apparire esattamente così. Con l'eccezione che il contenuto dell'elemento choices è attualmente assente.
Creiamo un'attività di deploy in GKE senza plugin, SMS e registrazione. Diamo un'occhiata a Jenkins sotto la giacca

Fatto? Bene, scriviamo lo script che verrà eseguito in caso di successo della build.
Lo script recupererà l'elenco delle versioni, scaricherà il file di configurazione, scriverà nell'apposito spazio l'elenco delle versioni e poi lo riposizionerà. Giusto. Esattamente. Scriveremo l'elenco delle versioni in XML dove c'è già un elenco di versioni (che verrà creato in futuro, dopo la prima esecuzione dello script). So che ci sono ancora appassionati di espressioni regolari nel mondo. Non mi annovero tra loro. Per favore, installa xmlstarlet sulla macchina dove verrà modificato il config. Mi sembra un piccolo prezzo da pagare per evitare di modificare l'XML con sed.

Sotto spoiler, riporto il codice che esegue l'intera sequenza sopra descritta.

Scriviamo nell'config l'elenco delle versioni dalla cartella sul server remoto.

#!/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

Se ti piace di più l'opzione di ottenere le versioni da Jenkins e sei altrettanto pigro come me, sotto spoiler trovi lo stesso codice, ma l'elenco è da Jenkins:

Scriviamo nell'config l'elenco delle versioni da Jenkins.
Tieni presente che il nome della mia build è composto da un numero di sequenza e da un numero di versione, separati da due punti. Pertanto, awk rimuove la parte non necessaria. Modifica questa stringa in base alle tue esigenze.

#!/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

In teoria, se hai testato il codice scritto sulla base degli esempi sopra, dovresti già avere un menu a tendina con le versioni nell'assegnazione di deployment. Ecco come apparirebbe, più o meno, nella schermata sotto il spoiler.

Elenco delle versioni compilato correttamente
Creiamo un'attività di deploy in GKE senza plugin, SMS e registrazione. Diamo un'occhiata a Jenkins sotto la giacca

Se tutto funziona, copia e incolla lo script in Esegui comando shell e salva le modifiche.

Connessione a Cloud shell

I builder sono nei nostri container. Come strumento di distribuzione delle applicazioni e gestore delle configurazioni utilizziamo Ansible. Di conseguenza, quando si tratta di costruire container, ci vengono in mente tre opzioni: installare Docker in un Docker, installare Docker su una macchina con Ansible, oppure costruire container nella console cloud. Abbiamo concordato di non parlare dei plugin per Jenkins in questa nota. Ricordi?

Ho deciso: visto che i container possono essere assemblati direttamente nella console cloud, perché complicarsi la vita? Mantenere tutto pulito, giusto? Voglio assemblare container con Jenkins nella console cloud e poi spedirli direttamente su Kubernetes. Soprattutto considerando che all'interno dell'infrastruttura di Google ci sono canali molto veloci, il che favorirà la velocità di deployment.

Per connettersi alla console cloud sono necessarie due cose: gcloud e i diritti di accesso a Google Cloud API per l'istanza VM da cui verrà effettuata questa connessione.

Per chi non intende connettersi dal cloud di Google
Google consente la disattivazione dell'autenticazione interattiva nei suoi servizi. Questo permetterà di accedere alla console anche da una macchina da caffè, se è sotto *nix e ha una console propria.

Se c'è bisogno che approfondisca questo argomento in questo articolo, scrivetelo nei commenti. Se si raccoglie un numero sufficiente di voti, scriverò un aggiornamento su questo tema.

Il modo più semplice per concedere diritti è tramite l'interfaccia web.

  1. Arresta l'istanza VM da cui verrà effettuata la connessione alla console cloud.
  2. Apri le Informazioni dell'istanza e fai clic su Modifica.
  3. In fondo alla pagina, seleziona l'area di accesso dell'istanza Accesso completo a tutte le API Cloud.

    Screenshot
    Creiamo un'attività di deploy in GKE senza plugin, SMS e registrazione. Diamo un'occhiata a Jenkins sotto la giacca

  4. Salva le modifiche e avvia l'istanza.

Una volta terminato il caricamento della VM, collegati via SSH e assicurati che la connessione sia riuscita senza errori. Usa il comando:

gcloud alpha cloud-shell ssh

Una connessione riuscita appare circa così
Creiamo un'attività di deploy in GKE senza plugin, SMS e registrazione. Diamo un'occhiata a Jenkins sotto la giacca

Deploy su GKE

Poiché ci sforziamo di passare completamente a IaC (Infrastructure as Code), i Dockerfile sono conservati nel git. Da un lato. Dall'altro, il deploy in Kubernetes è descritto da un file yaml, utilizzato solo da questo compito, che è anch'esso considerabile come codice. In generale, il piano è il seguente:

  1. Prendiamo i valori delle variabili BUILD_VERSION e, facoltativamente, i valori delle variabili che saranno passati tramite ENV.
  2. Scarichiamo il Dockerfile dal git.
  3. Generiamo il yaml per il deploy.
  4. Carichiamo entrambi questi file via scp nella console cloud.
  5. Costruiamo il contenitore e lo pubblichiamo nel Container registry
  6. Applichiamo il file di deploy del carico in Kubernetes.

Facciamo più concretamente. Dato che stiamo parlando di ENV, supponiamo di dover passare i valori di due parametri: PARAM1 e PARAM2. Aggiungiamo il loro incarico per il deploy, tipo — Parametro Stringa.

Screenshot
Creiamo un'attività di deploy in GKE senza plugin, SMS e registrazione. Diamo un'occhiata a Jenkins sotto la giacca

Genereremo il yaml semplicemente reindirizzando echo in un file. Si presume, naturalmente, che nel Dockerfile siano presenti PARAM1 e PARAM2, ovvero che il nome del carico sarà awesomeapp, e il contenitore assemblato con la versione specificata si trova in Container registry al percorso gcr.io/awesomeapp/awesomeapp-$BUILD_VERSION, dove $BUILD_VERSION è stato scelto dal menu a discesa.

Elenco comandi

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

All'agente di Jenkins, dopo la connessione tramite gcloud alpha cloud-shell ssh la modalità interattiva non è disponibile, quindi passiamo i comandi alla console cloud utilizzando il parametro —command.

Puliamo la cartella home nella console cloud dal vecchio Dockerfile:

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

Carichiamo il nuovo Dockerfile nella cartella home della console cloud usando scp:

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

Costruiamo, tagghiamo e pubblichiamo il contenitore nel 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"

Analogamente, procediamo con il file di deploy. Si noti che nei comandi seguenti vengono utilizzati nomi di cluster fittizi, dove avviene il deploy (awsm-cluster) e il nome del progetto (awesome-project), dove si trova il cluster.

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"

Avviamo il job, apriamo l'output della console e speriamo di vedere un build del contenitore riuscita.

Screenshot
Creiamo un'attività di deploy in GKE senza plugin, SMS e registrazione. Diamo un'occhiata a Jenkins sotto la giacca

E poi anche un deploy riuscito del contenitore costruito.

Screenshot
Creiamo un'attività di deploy in GKE senza plugin, SMS e registrazione. Diamo un'occhiata a Jenkins sotto la giacca

Ho intenzionalmente trascurato la configurazione Ingress. Per un semplice motivo: una volta configurata. workload con il nome specificato, rimarrà operativo, non importa quante implementazioni vengano effettuate con quel nome. E in generale, è un po' al di fuori della storia.

Invece delle conclusioni

Tutti i passaggi sopra descritti probabilmente non erano necessari e si sarebbe potuto semplicemente installare un qualche plugin per Jenkins, ce ne sono mille. Ma per qualche motivo non mi piacciono i plugin. O meglio, li uso solo in caso di estrema necessità.

Inoltre, mi piace semplicemente esplorare un argomento nuovo per me. Il testo sopra è anche un modo per condividere le scoperte che ho fatto nel risolvere il compito descritto all'inizio. Condividerlo con chi, come me, non è affatto un lupo feroce nel DevOps. Se anche solo a qualcuno le mie scoperte saranno utili, ne sarò felice.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster