Loome GKE-s juurutusülesande ilma pistikprogrammide, SMS-ide või registreerimiseta. Piilume Jenkinsi jope alla

Kõik sai alguse sellest, et ühe meie arendusmeeskonna meeskonna juht palus meil testida nende uut rakendust, mis oli eelmisel päeval konteinerisse viidud. Postitasin selle. Umbes 20 minuti pärast saabus palve rakendust uuendada, kuna sinna oli lisatud väga vajalik asi. Uuendasin. Veel paari tunni pärast... noh, võite arvata, mis edasi juhtuma hakkas...

Pean tunnistama, et olen üsna laisk (kas ma ei tunnistanud seda varem? Ei?) ja arvestades tõsiasja, et meeskonnajuhtidel on juurdepääs Jenkinsile, kus meil on kõik CI/CD-d, mõtlesin: las ta võtab kasutusele nii palju kui ta tahab! Meenus nali: anna mehele kala ja ta sööb päeva; kutsuge inimene Fediks ja ta on kogu elu Söödetud. Ja läkski tööl trikke mängida, mis suudaks juurutada konteineri, mis sisaldab mis tahes edukalt ehitatud versiooni rakendust Kuberisse ja mis tahes väärtused sellele üle kanda ENV (mu vanaisa, filoloog, varem inglise keele õpetaja, keerutas nüüd pärast selle lause lugemist näpuga templi poole ja vaatas mulle väga ilmekalt otsa).

Niisiis, selles märkuses räägin teile, kuidas ma õppisin:

  1. Värskendage Jenkinsi töid dünaamiliselt töö enda või muude töökohtade põhjal;
  2. Ühendage pilvekonsooliga (Cloud shell) sõlmest, kuhu on installitud Jenkinsi agent;
  3. Rakendage Google Kubernetes Engine'i töökoormus.


Tegelikult olen ma muidugi mõnevõrra ebaviisakas. Eeldatakse, et teil on vähemalt osa infrastruktuurist Google'i pilves ja seetõttu olete selle kasutaja ja teil on loomulikult GCP konto. Kuid see ei ole see, millest see märkus räägib.

See on minu järgmine petuleht. Selliseid märkmeid tahan kirjutada ainult ühel juhul: seisin silmitsi probleemiga, ma ei teadnud alguses, kuidas seda lahendada, lahendust ei googeldatud valmis kujul, seega googeldasin seda osade kaupa ja lõpuks lahendasin probleemi. Ja et edaspidi, kui ma unustan, kuidas ma seda tegin, ei peaks ma kõike tükkhaaval uuesti googeldama ja kokku panema, kirjutan endale sellised petulehed.

Lahtiütlus: 1. Märkus oli kirjutatud “enda jaoks”, rolli jaoks parimate tavade ei kehti. Mul on hea meel lugeda kommentaarides valikuid "parem oleks olnud nii teha".
2. Kui noodi rakendatud osa lugeda soolaks, siis nagu kõik mu eelnevad noodid, on ka see nõrk soolalahus.

Tööseadete dünaamiline värskendamine Jenkinsis

Ma näen teie küsimust ette: mis seos on dünaamilisel töö värskendamisel sellega? Sisestage stringi parameetri väärtus käsitsi ja asuge minema!

Vastan: ma olen tõesti laisk, mulle ei meeldi, kui nad kurdavad: Miša, kasutuselevõtt jookseb kokku, kõik on kadunud! Hakkate otsima ja mõne ülesande käivitamise parameetri väärtuses on kirjaviga. Seetõttu eelistan teha kõike võimalikult tõhusalt. Kui on võimalik takistada kasutajal andmete otse sisestamist, andes selle asemel väärtuste loendi, mille vahel valida, siis korraldan valiku.

Plaan on järgmine: loome Jenkinsis töökoha, milles saime enne käivitamist valida loendist versiooni, määrata väärtusi konteinerisse edastatavatele parameetritele. ENV, siis kogub see konteineri kokku ja surub selle konteineriregistrisse. Seejärel lastakse sealt konteiner välja cuber as töökoormus töös määratud parameetritega.

Me ei käsitle Jenkinsis töökoha loomise ja loomise protsessi, see on teemaväline. Eeldame, et ülesanne on valmis. Versioonidega värskendatud loendi juurutamiseks vajame kahte asja: olemasolevat allikaloendit a priori kehtivate versiooninumbritega ja muutujaga nagu Valiku parameeter ülesandes. Meie näites olgu muutujale nimi BUILD_VERSION, me sellel üksikasjalikult ei peatu. Kuid vaatame allikate loendit lähemalt.

Ei ole nii palju valikuid. Kohe meenus kaks asja:

  • Kasutage kaugjuurdepääsu API-d, mida Jenkins oma kasutajatele pakub;
  • Küsige kaughoidla kausta sisu (meie puhul on see JFrog Artifactory, mis pole oluline).

Jenkinsi kaugjuurdepääsu API

Väljakujunenud suurepärase traditsiooni kohaselt eelistaksin vältida pikki selgitusi.
Luban endale vaid esimese lõigu tüki vaba tõlke API dokumentatsiooni esimene leht:

Jenkins pakub API-liidest, mis võimaldab kaugjuurdepääsu oma funktsioonidele masinloetavaks. <…> Kaugjuurdepääsu pakutakse REST-i stiilis. See tähendab, et kõikidele funktsioonidele pole ühtset sisenemispunkti, vaid URL nagu ".../api/", kus"..." tähendab objekti, millele API-võimalusi rakendatakse.

Teisisõnu, kui juurutusülesanne, millest praegu räägime, on saadaval aadressil http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build, siis on selle ülesande API viled saadaval aadressil http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/

Järgmiseks on meil valida, millisel kujul väljund kätte saada. Keskendume XML-ile, kuna API võimaldab ainult sel juhul filtreerida.

Proovime lihtsalt saada nimekirja kõigist töödest. Meid huvitab ainult koostu nimi (kuvanimi) ja selle tulemus (kaasa):

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

Selgus?

Nüüd filtreerime ainult need jooksud, mis lõpuks tulemusega lõppevad EDU. Kasutame argumenti &välista ja parameetrina edastame selle tee väärtuseni, mis ei ole võrdne EDU. Jah Jah. Topeltnegatiivne 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']

Ekraanipilt edukate loendist
Loome GKE-s juurutusülesande ilma pistikprogrammide, SMS-ide või registreerimiseta. Piilume Jenkinsi jope alla

Noh, nalja pärast veendume, et filter meid ei petnud (filtrid ei valeta kunagi!) ja kuvame "ebaõnnestunud" loendi:

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

Ekraanipilt mitteedukate loendist
Loome GKE-s juurutusülesande ilma pistikprogrammide, SMS-ide või registreerimiseta. Piilume Jenkinsi jope alla

Kaugserveri kausta versioonide loend

Versioonide loendi hankimiseks on teine ​​viis. Mulle meeldib see isegi rohkem kui Jenkinsi API-le juurdepääs. Noh, sest kui rakendus oli edukalt üles ehitatud, tähendab see, et see pakiti ja paigutati hoidlasse vastavasse kausta. Samamoodi on hoidla rakenduste tööversioonide vaikemälu. meeldib. Noh, küsime temalt, millised versioonid on laos. Me keerame kaugkausta lokki, grep ja awk. Kui kedagi huvitab oneliner, siis see on spoileri all.

Üherealine käsk
Pange tähele kahte asja: edastan ühenduse üksikasjad päises ja ma ei vaja kõiki kausta versioone ning valin ainult need, mis loodi kuu jooksul. Muutke käsku vastavalt oma tegelikkusele 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[^/]+' )

Tööde ja töö konfiguratsioonifaili seadistamine Jenkinsis

Selgitasime välja versioonide loendi allika. Lisame nüüd saadud loendi ülesandesse. Minu jaoks oli ilmselge lahendus lisada rakenduse koostamise ülesandesse etapp. Samm, mis täidetaks, kui tulemuseks oleks "edu".

Avage monteerimisülesande seaded ja kerige alla. Klõpsake nuppudel: Lisa ehitusetapp -> tingimuslik samm (üksik). Valige sammu seadetes tingimus Praegune ehituse olek, määrake väärtus EDU, toiming, mis tuleb sooritada, kui see õnnestub Käivitage shellikäsk.

Ja nüüd lõbus osa. Jenkins salvestab töö konfiguratsioonid failidesse. XML-vormingus. Tee peal http://путь-до-задания/config.xml Vastavalt sellele saate konfiguratsioonifaili alla laadida, vajadusel redigeerida ja asetada tagasi oma asukohta.

Pidage meeles, et eespool leppisime kokku, et loome versioonide loendi jaoks parameetri BUILD_VERSION?

Laadime alla konfiguratsioonifaili ja vaatame selle sisse. Lihtsalt veendumaks, et parameeter on paigas ja soovitud tüüpi.

Ekraanipilt spoileri all.

Teie config.xml fragment peaks välja nägema sama. Välja arvatud see, et valikute elemendi sisu on veel puudu
Loome GKE-s juurutusülesande ilma pistikprogrammide, SMS-ide või registreerimiseta. Piilume Jenkinsi jope alla

Oled sa kindel? See on kõik, kirjutame skripti, mis käivitatakse, kui ehitamine õnnestub.
Skript saab versioonide loendi, laadib alla konfiguratsioonifaili, kirjutab sellesse versioonide loendi vajalikku kohta ja asetab selle siis tagasi. Jah. See on õige. Kirjutage XML-i versioonide loend kohta, kus versioonide loend juba on (tulevikus, pärast skripti esimest käivitamist). Ma tean, et maailmas on ikka veel ägedaid regulaaravaldiste fänne. Ma ei kuulu nende hulka. Palun installige xmlstarler masinasse, kus konfiguratsiooni redigeeritakse. Mulle tundub, et see ei ole nii suur hind, et vältida XML-i redigeerimist sed-i abil.

Spoileri all esitan koodi, mis täidab ülaltoodud jada tervikuna.

Kirjutage versioonide loend kaugserveri kaustast konfiguratsiooni

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

Kui eelistate Jenkinsilt versioonide hankimist ja olete sama laisk kui mina, siis spoileri all on sama kood, kuid Jenkinsi nimekiri:

Kirjutage Jenkinsi versioonide loend konfiguratsioonini
Pidage lihtsalt meeles: minu koostenimi koosneb järjekorranumbrist ja versiooninumbrist, mis on eraldatud kooloniga. Vastavalt sellele lõikab awk mittevajaliku osa ära. Enda jaoks 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.xml

Teoreetiliselt, kui oled eelpool toodud näidete põhjal kirjutatud koodi testinud, siis juurutusülesandes peaks sul juba olema rippmenüü versioonidega. See on nagu spoileri all oleval ekraanipildil.

Õigesti täidetud versioonide loend
Loome GKE-s juurutusülesande ilma pistikprogrammide, SMS-ide või registreerimiseta. Piilume Jenkinsi jope alla

Kui kõik töötas, kopeerige ja kleepige skript sinna Käivitage shellikäsk ja salvestage muudatused.

Ühenduse loomine pilve kestaga

Meil on kogujad konteinerites. Kasutame Ansible'i rakenduste edastamise tööriista ja konfiguratsioonihaldurina. Sellest lähtuvalt tuleb konteinerite ehitamisel meelde kolm võimalust: installige Docker Dockeri, installige Docker Ansible'i töötavasse masinasse või ehitage konteinerid pilvekonsooli. Leppisime kokku, et selles artiklis Jenkinsi pistikprogrammidest vaikime. Mäletad?

Otsustasin: noh, kuna "kastist väljas" konteinereid saab pilvekonsooli koguda, siis milleks seda vaeva näha? Hoidke see puhtana, eks? Tahan Jenkinsi konteinerid pilvekonsooli koguda ja seejärel sealt kuubikusse käivitada. Lisaks on Google'i infrastruktuuris väga rikkalikud kanalid, millel on kasulik mõju juurutamise kiirusele.

Pilvekonsooliga ühenduse loomiseks vajate kahte asja. gpilv ja juurdepääsuõigused Google Cloud API VM-i eksemplari jaoks, millega sama ühendus luuakse.

Neile, kes plaanivad ühendust luua üldse mitte Google'i pilvest
Google lubab oma teenustes interaktiivse autoriseerimise keelata. See võimaldab teil konsooliga ühenduse luua isegi kohvimasinast, kui sellel töötab *nix ja sellel on konsool.

Kui on vajadus, et ma selle märkuse raames seda teemat põhjalikumalt käsitleksin, kirjutage kommentaaridesse. Kui saame piisavalt hääli, kirjutan sellel teemal värskenduse.

Lihtsaim viis õiguste andmiseks on veebiliidese kaudu.

  1. Peatage VM-i eksemplar, millest hiljem pilvekonsooliga ühenduse loote.
  2. Avage Eksemplari üksikasjad ja klõpsake nuppu muuta.
  3. Valige lehe allosas eksemplari juurdepääsu ulatus Täielik juurdepääs kõigile pilve API-dele.

    Screenshot
    Loome GKE-s juurutusülesande ilma pistikprogrammide, SMS-ide või registreerimiseta. Piilume Jenkinsi jope alla

  4. Salvestage muudatused ja käivitage eksemplar.

Kui virtuaalmasin on laadimise lõpetanud, looge sellega SSH kaudu ühendus ja veenduge, et ühendus toimub ilma veata. Kasutage käsku:

gcloud alpha cloud-shell ssh

Edukas ühendus näeb välja umbes selline
Loome GKE-s juurutusülesande ilma pistikprogrammide, SMS-ide või registreerimiseta. Piilume Jenkinsi jope alla

Juurutage GKE-sse

Kuna püüame igal võimalikul viisil IaC-le (infrastruktuur kui kood) täielikult üle minna, salvestatakse meie dokifailid Giti. See on ühelt poolt. Ja juurutamist kubernetes kirjeldab yaml-fail, mida kasutab ainult see ülesanne, mis ise on samuti nagu kood. See on teiselt poolt. Üldiselt on plaan järgmine:

  1. Võtame muutujate väärtused BUILD_VERSION ja soovi korral läbitavate muutujate väärtused ENV.
  2. Laadige dockerifail alla Gitist.
  3. Looge juurutamiseks yaml.
  4. Laadime mõlemad failid scp kaudu pilvekonsooli üles.
  5. Ehitame sinna konteineri ja lükkame selle Container registrisse
  6. Rakendame cuberile koormuse juurutusfaili.

Olgem täpsemad. Ükskord hakkasime rääkima ENV, siis oletame, et peame edastama kahe parameetri väärtused: PARAM1 и PARAM2. Lisame juurutamiseks nende ülesande, tüüp - Stringi parameeter.

Screenshot
Loome GKE-s juurutusülesande ilma pistikprogrammide, SMS-ide või registreerimiseta. Piilume Jenkinsi jope alla

Loome yamli lihtsa ümbersuunamisega miss viilima. Loomulikult eeldatakse, et see on teie dockeri failis PARAM1 и PARAM2et koormuse nimi saab olema fantastiline rakendusja kokkupandud konteiner koos määratud versiooni rakendusega asub selles Konteinerite register teel gcr.io/awesomeapp/awesomeapp-$BUILD_VERSIONKus $BUILD_VERSION valiti just ripploendist.

Meeskonna nimekiri

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

Jenkinsi agent pärast ühendamist gcloud alfa cloud-shell ssh interaktiivne režiim pole saadaval, seega saadame parameetri abil pilvekonsooli käsud -- käsk.

Puhastame pilvekonsooli kodukausta vanast dockerifailist:

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

Asetage värskelt allalaaditud dockerifail pilvekonsooli kodukausta, kasutades scp:

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

Kogume, märgistame ja lükkame konteineri konteineriregistrisse:

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"

Teeme sama ka juurutusfailiga. Pange tähele, et alltoodud käsud kasutavad selle klastri väljamõeldud nimesid, kus juurutus toimub (awsm-klaster) ja projekti nimi (vinge-projekt), 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 edukat kokkupanekut.

Screenshot
Loome GKE-s juurutusülesande ilma pistikprogrammide, SMS-ide või registreerimiseta. Piilume Jenkinsi jope alla

Ja siis kokkupandud konteineri edukas kasutuselevõtt

Screenshot
Loome GKE-s juurutusülesande ilma pistikprogrammide, SMS-ide või registreerimiseta. Piilume Jenkinsi jope alla

Ignoreerisin seadet meelega Ingress. Ühel lihtsal põhjusel: kui olete selle seadistanud töökoormus antud nimega jääb see tööle, olenemata sellest, kui palju selle nimega juurutusi teete. Noh, üldiselt jääb see ajaloost veidi kaugemale.

Järelduste asemel

Tõenäoliselt ei saanud kõiki ülaltoodud samme teha, vaid lihtsalt installida Jenkinsi, nende muuuli, pistikprogrammi. Aga millegipärast mulle pluginad ei meeldi. Noh, täpsemalt, ma kasutan neid ainult meeleheitest.

Ja mulle lihtsalt meeldib enda jaoks mõni uus teema üles võtta. Ülaltoodud tekst on ka viis jagada järeldusi, mille tegin alguses kirjeldatud probleemi lahendamisel. Jagage nendega, kes nagu temagi pole devopsis sugugi kohutav hunt. Kui mu leiud aitavad vähemalt kedagi, olen rahul.

Allikas: www.habr.com

Lisa kommentaar