Kõik hakkas pihta sellest, et ühe meie arendustiimi timlead palus testrežiimil välja panna nende uus rakendus, mis eelmisel päeval oli konteinerisse pandud. Ma panin selle välja. Umbes 20 minuti pärast tuli palve rakendust uuendada, sest sinna oli lisatud vajalik funktsioon. Ma uuendasin. Veel paar tundi hiljem... noh, te juba arvatavasti tead, mis edasi juhtus...
Ma pean tunnistama, et olen üsna laisk (kas ma pole seda varem juba öelnud? ei?). Arvestades, et timlead'il on juurdepääs Jenkinsile, kus meil on kogu CI/CD, mõtlesin: las ta ise deployib, kui soovib! Meenus ütlus: anna inimesele kala ja ta on päev läbi rahul; nimeta inimene Rahul ja ta on rahul kogu elu. Ja ma läksin töötama ülesande kallal, mis oskas deployida konteinerit rakendusega mistahes edukalt loodud versioonist ja edastada sinna mistahes väärtused ENV (minu vanaisa — filoloog, inglise keele õpetaja minevikus — keeraks praegu näppu otsaesise ümber ja vaataks mulle väga väljendusrikkalt otsa, kui ta seda lauset loeks).
Nii et selles märkmes räägin ma, kuidas ma õppisin:
- Dünaamiliselt uuendama ülesandeid Jenkins'is kas ülesandest endast või teistest ülesannetest;
- Ühenduma pilvekonsoliga (Cloud shell) nodest, kus on paigaldatud Jenkins'i agent;
- Kandma töökoormust (workload) Google Kubernetes Engine'i.
Tegelikult ma, loomulikult, veidi liialdan. Eeldatakse, et vähemalt osa teie infrastruktuurist on Google'i pilves, seega olete te selle kasutaja ja loomulikult on teil GCP konto. Kuid see märkme ei ole sellest.
See on minu järgmine mälestusmärk. Selliseid märkmeid tahan ma kirjutada vaid ühes olukorras: kui mul oli ülesanne, mille lahendamisest ma algselt ei teadnud, lahendust ei leidnud Google'ist valmis kujul, seega uurisin seda tükkhaaval ja lõpuks lahendasin ülesande. Ja et tulevikus, kui ma unustan, kuidas ma selle tegin, ei peaks ma uuesti kõike tükkideks googeldama ja kokku panema, kirjutan ma endale selliseid mälestusmärke.
Käesolev: 1. Märkus kirjutati "enda jaoks", ei pretendeeri parima praktika tiitlile. Lugesin meeleldi kommentaarides läbi variandid "aga oleks parem teha nii".
2. Kui märkme praktilist osa pidada soolaks, siis, nagu kõik mu varasemad märkmed, on see - nõrk soolalahus.
Jenkins'i ülesannete seadete dünaamiline uuendamine
Ma ootan teie küsimust: mis otseselt seondub dünaamilise ülesande uuendamisega? Sisestasin käsitsi stringi parameetri väärtuse ja edasi!
Vastan: ma olen tõeliselt laisk, ei armasta, kui kaevatakse, et Misha, deploy kukub kokku, kõik on kadunud! Hakkad vaatama ja seal on mõne ülesande käivitamise parameetri väärtuses trükiviga. Seetõttu eelistan teha kõik maksimaalselt veatuks. Kui on võimalus kasutajat lahti hoida otse andmete sisestamisest, andes selle asemel valikute nimekirja, siis korraldan valiku.
Plaani on selline: loome Jenkins'is ülesande, kus enne käivitamist on võimalik nimekirjast valida versioon, määrata väärtused parameetritele, mis edastatakse konteinerisse läbi ENV, seejärel kogub see konteineri ja tõukab selle Container Registry'sse. Sealt käivitatakse see konteiner kubereisina nagu töökoormus parameetritega, mis on määratud ülesandes.
Jenkins'i ülesande loomise ja seadistamise protsessi ei aruta, see on off-topic. Läheneme sellele, et ülesanne on valmis. Uuendatava versioonide nimekirja rakendamiseks vajame kahte asja: juba olemasolevat allika nimekirja, kus on alustuseks kehtivad versiooninumbrid ja muutuja tüüpi Valikute parameeter ülesandes. Meie näites olgu muutuja nimeks BUILD_VERSION, sellega ei peatuda. Kuid allika nimekirjal peatume lähemalt.
Valikuid pole sugugi palju. Kaks variant tuleb kohe meelde:
- Kasutada Jenkins'i kaugjuhtimise API-d, mida Jenkins oma kasutajatele pakub;
- Küsimise kaudu kaug-repositooriumi kausta sisu (meie puhul on see JFrog Artifactory, mis pole oluline).
Jenkins'i kaugjuhtimise API
Hea traditsiooni kohaselt eelistan vältida pikemaid seletusi.
Luban endale vaid vabalt tõlgitud lõiku esimesest lõigus :
Jenkins pakub API-d, et kaugjuhtmiste kooskõla oma funktsionaalsusega. Kaugjuhtimine on saadaval REST-ala vastavuses. See tähendab, et puudub ühtne sisenemiskoht kõigile võimalustele, selle asemel kasutatakse URL-i formaadis ".../api/", kus "…" tähistab objekt, millele API võimalusi rakendatakse.
Teisisõnu, kui antud käivitamisel, millest me praegu räägime, on ligipääsetav aadressil http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build, siis API-kiibid selle ülesande jaoks on saadaval aadressil http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/
Järgmisena on meil valida, kuidas väljundit saada. Peatume XML-il, kuna API ainult sel juhul võimaldab filtreerimist.
Proovime lihtsalt saada nimekirja kõigist ülesande käivitustest. Meid huvitab ainult kogumise nimi (displayName) ja selle tulemus (result):
http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]Kas see õnnestus?
Nüüd filtreerime välja ainult need käivitused, mille tulemus on SUCCESS. Kasutame argumenti &exclude ja edastame selle väärtuse, mis ei ole võrreldav SUCCESS. Jah, jah. Kahekordne eitamine on väide. Jätame välja kõik, mis meid ei huvita:
http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]&exclude=freeStyleProject/allBuild[result!='SUCCESS'] Edukate käivituste nimekirja ekraanipilt

Ja lihtsalt lõbu pärast veendume, et filtrime ei petnud (filtrid ei valeta kunagi!) ja kuvame nimekirja «mitte-edukatest»:
http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]&exclude=freeStyleProject/allBuild[result='SUCCESS'] Mitte-edukate nimekirja ekraanipilt

Versioonide nimekiri kaugserveri kaustast
On ka teine viis versioonide nimekirja saamiseks. Mulle meeldib see isegi rohkem kui Jenkins API-le pöördumine. No, sest kui rakendus on edukalt koondatud, siis see pakendatakse ja pannakse vastavasse kausta hoidlast. Nagu, hoidla on vaikimisi rakenduste tööversioonide hoidla. Nagu. Niisiis, küsime temalt, millised versioonid on säilitatud. Kaugkausta kraabime, grepime ja awkime. Kui kellegile huvitab, siis one-liner on peidikus.
Ühe rea käsk
Pange tähele kahte asja: edastan ühenduse jaoks pealkirjas tõendid ja ma ei vaja tõesti kõiki versioone kaustast, vaid filtreerin ainult need, mis on loodud kuu jooksul. Kohandage käsku vastavalt oma oludele ja vajadustele:
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[^/]+')Ülesannete seadistus ja Jenkins'i ülesande konfigureerimise fail
Oleme versioonide nimekirjaga tutvunud. Nüüd viime saadud nimekirja ülesande sisse. Minu jaoks oli ilmne lahendus lisada rakenduse koostamise ülesandele samm. Samm, mis täidetakse juhul, kui tulemus on «edukus».
Avame koostamisülesande seaded ja kerime kõige alla. Klõpsame nuppudel: Add build step -> Conditional step (single). Sammude seadetes valime tingimuse Current build status, seadistame väärtuse SUCCESS, tegevuse täitmine edu korral Run shell command.
Ja nüüd kõige huvitavam. Jenkins'i ülesannete konfiguratsioonid salvestatakse failides. XML-formaadis. Tee kaudu http://tee-ülesandeni/config.xml Seetõttu on võimalik alla laadida konfiguratsioonifail, muuta seda vastavalt vajadusele ja panna see tagasi, kust see võeti.
Pidage meeles, et varem leppisime kokku, et loome versioonide loendiks parameetri BUILD_VERSION?
Laadime alla konfiguratsioonifaili ja vaatame selle sisse. Lihtsalt, et veenduda, et parameeter on olemas ja tõeliselt soovitud kujul.
Screenshot peidetud.
Teie esitatud fragment config.xml peaks välja nägema täpselt sama. Ainult et choices elemendi sisu on hetkel puuduv

Kas olete veendunud? Noh, kirjutame skripti, mis käivitatakse eduka koostamise korral.
Skript saab versioonide loendi, allalaadib konfiguratsioonifaili, kirjutab sinna soovitud kohta versioonide loendi ja paneb selle siis tagasi. Jah. Kõik on õige. Kirjutame versioonide loendi XML'i sinna kohta, kus juba on versioonide loend (see on tulevikus, pärast skripti esmakordset käivitamist). Ma tean, et maailmas on endiselt hirmuäratavad regulaarsete väljendite armastajad. Mina nende hulka ei kuulu. Palun installige sellele masinale, kus konfiguratsioon redigeeritakse. Ma arvan, et see ei ole nii suur hind, et vältida XML-i redigeerimist sed'iga.
Peidetud on kood, mis täidab ülaltoodud järkjärgulist protsessi tervikuna.
Kirjutame konfiguratsiooni versioonide loendi kaugserveri kaustast
#!/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.xmlKui teile meeldib rohkem versioonide hankimise variant Jenkins'ist ja olete samuti nii laisk nagu mina, siis peidetud on sama kood, kuid loend Jenkins'ist:
Kirjutame konfiguratsiooni versioonide loendi Jenkins'ist
Aga pidage meeles: minu koostise nimeks on järjestusnumber ja versiooninumber, mida eraldab kolon. Vastavalt awk lõikab ebavajaliku osa. Muutke seda rida vastavalt oma vajadustele.
#!/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.xmlTeoreetiliselt, kui olete testinud koodi, mis on kirjutatud ülaltoodud näidiste põhjal, peaks teie juurutamise ülesandes juba olema rippmenüü versioonidega. Niimoodi, nagu on peidetud screenshot.
Korrektselt täidetud versioonide loend

Kui kõik töötas, siis kopeerige skript Run shell command ja salvestage muudatused.
Ühendamine Cloud shell'iga
Kogujad on meil konteinerites. Rakenduste ja konfiguratsioonihalduri tarnimiseks kasutame Ansible't. Seega, kui rääkida konteinerite koostamisest, tuleb meelde kolm varianti: paigaldada Docker Dockerisse, paigaldada Docker masinale, kus on Ansible, või koostada konteinerid pilvekonsoolis. Jenkins'i pistikprogrammide osas leppisime selle märkme kontekstis vaikuses kokku. Kas meenutad?
Otsustasin: no, kui konteinerid "kastist välja" saab koostada pilvekonsoolis, siis miks vaeva näha? Hoidke see puhtana, eks? Tahan koostada konteinerid Jenkins'iga pilvekonsoolis ja seejärel sealt oma kubernetes'sse saata. Eriti kuna Google'i infrastruktuuris on väga head ühendused, mis peaksid positiivselt mõjutama rakenduse juurutamise kiirus.
Pilvekonsooliga ühendamiseks on vajalikud kaks asja: gcloud ja juurdepääsuõigused Google Cloud API-le selle VM'i eksemplari jaoks, millest see ühendus tehakse.
Need, kes plaanivad ühenduda mitte Google'i pilvest
Google lubab võimalust keelata interaktiivne autentimine oma teenustes. See võimaldab ühenduda konsooliga isegi kohvimasinast, kui see töötab *nix'idel ja tal endal on konsool.
Kui on soov, et ma räägiksin sellest küsimusest rohkem enda märkmete kontekstis — kirjutage kommentaaridesse. Kui piisavalt hääli koguneb — kirjutan selle teema ülevaate.
Lihtsaim viis õiguste andmiseks on läbi veebiliidese.
- Peatage VM'i eksemplar, millest edaspidi ühendus pilvekonsooliga luuakse.
- Avage Eksemplari andmed ja klõpsake Muuta.
- Lehe allosas valige eksemplari juurdepääsu ulatus Täielik juurdepääs kõigile Cloud API-dele.
Screenshoot

- Salvestage muudatused ja käivitage eksemplar.
Pärast VM'i käivitamist ühendage selle kaudu SSH-ga ja veenduge, et ühendus toimub ilma tõrgeteta. Kasutage käsku:
gcloud alpha cloud-shell ssh Edukad ühendused näevad välja umbes nii

Juurutamine GKE-s
Kuna me püüame igati täielikult üle minna IaC-le (infrastruktuur koodina), hoitakse meie Dockerfile'sid git'is. See on ühelt poolt. Ja kubernetes'i juurutamine kirjeldatakse yaml-failiga, mis kasutatakse ainult selle ülesande jaoks, mis omakorda on justkui ka kood. See on teiselt poolt. Ühesõnaga, plaan on järgmine:
- Võtame muutujate väärtused BUILD_VERSION ja, ja valikuliselt muutuja väärtused, mis edastatakse läbi ENV.
- Laadime dockerifaili gitist.
- Genereerime yaml-deployimiseks.
- Laadime need kaks faili scp-ga pilvekonsooli.
- Ehitusime seal konteineri ja paneme selle Container registry-sse.
- Kasutame koormuse deploy faili kuberes.
Olgu, räägime konkreetselt. Kui oleme juba rääkinud ENV, siis eeldame, et peame edastama kahe parameetri väärtused: PARAM1 ja PARAM2. Lisame nende määramise deploy jaoks, tüüp — String Parameter.
Screenshoot

Genereerime yaml-i lihtsa ümbersuunamisega echo faili. Eeldame, et teie dockerifailis on olemas PARAM1 ja PARAM2, et koormuse nimi on awesomeapp, ja kokku pandud konteiner antud versiooniga asub Container registry-s tee kaudu gcr.io/awesomeapp/awesomeapp-$BUILD_VERSION, kus $BUILD_VERSION valiti just rippmenüüst.
Käskude loetelu
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.yamlJenkins’i agendi puhul, pärast ühendust gcloud alpha cloud-shell ssh interaktiivne režiim ei ole saadaval, seega edastame käsud pilvekonsooli parametri abil —command.
Kustutame pilvekonsoolis kodukaustast vana dockerifaili:
gcloud alpha cloud-shell ssh --command="rm -f Dockerfile"Paneme just allalaaditud dockerifaili pilvekonsooli kodukausta scp abil:
gcloud alpha cloud-shell scp localhost:./Dockerfile cloudshell:~Kogume, märgistame ja pushime konteineri Container registry-sse:
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"
Sama teeme ka deploy failiga. Pange tähele, et alltoodud käskudes kasutatakse väljamõeldud klastrinimesid, kuhu toimub deploy (awsm-cluster) ja projekti nime (awesome-project), kus klaster asub.
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"Käivitame ülesande, avame konsooli väljundi ja loodame näha konteineri eduka koostamise.
Screenshoot

Ja seejärel ka kokku pandud konteineri edukat juurutamist.
Screenshoot

Ma teadlikult jätsin seadistamise tähelepanuta. IngressÜhel lihtsal põhjusel: kui oled selle seadistanud ülesande nimega, jääb see tööle sõltumata sellest, kui palju juurutamisi sama nimega teostatakse. Ja üldiselt on see pisut väljaspool lugu. töökoormus Kõiki ülaltoodud samme oleks ilmselt saanud mitte teha ja lihtsalt installida mõni Jenkins'i plugin, neid on ju tuhandeid. Kuid mingil põhjusel ma ei armasta pluginaid. Täpsemalt öeldes, kasutan neid vaid häda korral.
Järeldused
Ja mulle lihtsalt meeldib uurida mingit uut teemat, mis on minu jaoks uus. Ülalolev tekst on ka viis jagada leidusid, mida ma tegin, lahendades alguses kirjeldatud probleemi. Jagada neid, kes pole üldse devopsis kogenud. Kui mu leidud aitavad vähemalt kedagi — olen rahul.
Loome ülesande juurutamiseks GKE-s ilma pluginate, SMS-ide ja registreerimiseta. Ühe silmaga piilume Jenkins'ile vahelduseks.
Allikas: habr.com

