Cum să construiți o conductă de implementare inițială eficientă

Publicat: 2022-03-11

Îmi place să construiesc lucruri – ce nu face dezvoltatorul? Îmi place să găsesc soluții la probleme interesante, să scriu implementări și să creez cod frumos. Totuși, ceea ce nu îmi place sunt operațiile . Operațiunile reprezintă tot ceea ce nu este implicat în construirea unui software grozav — totul, de la configurarea serverelor până la livrarea codului în producție.

Acest lucru este interesant, deoarece, în calitate de dezvoltator independent Ruby on Rails, trebuie frecvent să creez noi aplicații web și să repet procesul de a descoperi partea DevOps a lucrurilor. Din fericire, după ce am creat zeci de aplicații, m-am hotărât în ​​sfârșit pe o conductă de implementare inițială perfectă. Din păcate, nu toată lumea le-a dat seama ca mine – în cele din urmă, aceste cunoștințe m-au determinat să fac pasul și să-mi documentez procesul.

În acest articol, vă voi prezenta canalul meu perfect pe care să îl utilizați la începutul proiectului dumneavoastră. Cu pipeline-ul meu, fiecare împingere este testată, ramura principală este implementată în staging cu un dump proaspăt al bazei de date din producție, iar etichetele cu versiuni sunt implementate în producție cu backup-uri și migrări care au loc automat.

Rețineți, deoarece este pipeline-ul meu , este, de asemenea, obișnuit și potrivit nevoilor mele; cu toate acestea, vă puteți simți liber să schimbați orice nu vă place și să-l înlocuiți cu ceea ce vă convine. Pentru conducta mea, vom folosi:

  • GitLab pentru a găzdui codul.
    • De ce: clienții mei preferă ca codul lor să rămână secret, iar nivelul gratuit al GitLab este minunat. De asemenea, CI gratuit integrat este minunat. Mulțumesc GitLab!
    • Alternative: GitHub, BitBucket, AWS CodeCommit și multe altele.
  • GitLab CI pentru a construi, testa și implementa codul nostru.
    • De ce: se integrează cu GitLab și este gratuit!
    • Alternative: TravisCI, Codeship, CircleCI, DIY cu Fabric8 și multe altele.
  • Heroku pentru a găzdui aplicația noastră.
    • De ce: funcționează de la cutie și este platforma perfectă pentru a începe. Puteți schimba acest lucru în viitor, dar nu orice aplicație nouă trebuie să ruleze pe un cluster Kubernetes special creat. Chiar și Coinbase a început pe Heroku.
    • Alternative: AWS, DigitalOcean, Vultr, DIY cu Kubernetes și multe altele.

Vechea școală: creați o aplicație de bază și implementați-o în Heroku

În primul rând, să recreăm o aplicație tipică pentru cineva care nu folosește nicio conductă CI/CD luxoasă și dorește doar să-și implementeze aplicația.

Diagrama acțiunilor tradiționale de găzduire și implementare a codului

Nu contează ce fel de aplicație creați, dar veți avea nevoie de Yarn sau npm. Pentru exemplul meu, creez o aplicație Ruby on Rails deoarece vine cu migrații și un CLI și am deja configurația scrisă pentru ea. Sunteți binevenit să utilizați orice cadru sau limba pe care o preferați, dar veți avea nevoie de Yarn pentru a face versiunea pe care o fac mai târziu. Creez o aplicație CRUD simplă folosind doar câteva comenzi și fără autentificare.

Și haideți să testăm dacă aplicația noastră rulează conform așteptărilor. Am continuat și am creat câteva postări, doar pentru a fi sigur.

Aplicația rulează în dezvoltare

Și haideți să-l implementăm în Heroku împingând codul nostru și rulând migrații

 $ heroku create toptal-pipeline Creating ⬢ toptal-pipeline... done https://toptal-pipeline.herokuapp.com/ | https://git.heroku.com/toptal-pipeline.git $ git push heroku master Counting objects: 132, done. ... To https://git.heroku.com/toptal-pipeline.git * [new branch] master -> master $ heroku run rails db:migrate Running rails db:migrate on ⬢ toptal-pipeline... up, run.9653 (Free) ...

În sfârșit, să-l testăm în producție

Aplicația rulează în producție

Si asta e! De obicei, aici își părăsesc operațiunile majoritatea dezvoltatorilor. În viitor, dacă faceți modificări, va trebui să repetați pașii de implementare și migrare de mai sus. Puteți chiar să efectuați teste dacă nu întârziați la cină. Acest lucru este grozav ca punct de plecare, dar să ne gândim puțin la această metodă.

Pro

  • Rapid de configurat.
  • Implementările sunt ușoare.

Contra

  • Nu USCAT: necesită repetarea acelorași pași la fiecare modificare.
  • Fără versiune: „Refac implementarea de ieri la cea de săptămâna trecută” nu este foarte specific peste trei săptămâni.
  • Nu e prost-cod-proof: știi că ar trebui să rulezi teste, dar nimeni nu caută, așa că s-ar putea să-l împingi în ciuda testului întrerupt ocazional.
  • Nu este dovada unui actor rău: ce se întâmplă dacă un dezvoltator nemulțumit decide să-ți spargă aplicația prin împingerea unui cod cu un mesaj despre faptul că nu comanzi suficiente pizza pentru echipa ta?
  • Nu se extinde: permiterea fiecărui dezvoltator de abilitatea de a implementa le-ar oferi acces la aplicație la nivel de producție, încălcând principiul privilegiului minim.
  • Fără mediu de pregătire: erorile specifice mediului de producție nu vor apărea până la producție.

Conducta de implementare inițială perfectă

O să încerc ceva diferit astăzi: să avem o conversație ipotetică. Îți voi oferi o voce „voi” și vom vorbi despre cum putem îmbunătăți acest flux de curent. Haide, spune ceva.

Spune ce? Stai, pot vorbi?

Da, asta am vrut să spun despre a-ți oferi o voce. Ce mai faci?

Sunt bine. Se simte ciudat

Înțeleg, dar rulează-te cu el. Acum, să vorbim despre conducta noastră. Care este cea mai enervantă parte despre rularea implementărilor?

Oh, e ușor. Cantitatea de timp pe care o pierd. Ai încercat vreodată să împingi la Heroku?

Da, este oribil să vă uitați la descărcarea dependențelor și la construirea aplicației ca parte a git push !

Nu-i așa? Este nebun. Mi-aș dori să nu fi trebuit să fac asta. Există, de asemenea, faptul că trebuie să rulez migrații *după* implementare, așa că trebuie să urmăresc emisiunea și să mă asigur că implementarea mea se execută.

Bine, ați putea de fapt rezolva această ultimă problemă prin înlănțuirea celor două comenzi cu && , cum ar fi git push heroku master && heroku run rails db:migrate , sau pur și simplu creând un script bash și introducându-l în codul dvs., dar totuși, un răspuns excelent, timpul și repetarea este o adevărată durere.

Da, chiar e nasol

Ce se întâmplă dacă ți-aș spune că poți remedia acel bit imediat cu o conductă CI/CD?

A ce acum? Ce este asta?

CI/CD înseamnă integrare continuă (CI) și livrare/implementare continuă (CD). Mi-a fost destul de greu să înțeleg exact ce a fost când am început, deoarece toată lumea folosea termeni vagi precum „amalgamarea dezvoltării și operațiunilor”, dar simplu:

  • Integrare continuă: Asigurați-vă că tot codul dvs. este îmbinat într-un singur loc. Faceți-vă echipa să folosească Git și veți folosi CI.
  • Livrare continuă: asigurându-vă că codul dvs. este gata în permanență pentru a fi expediat. Înseamnă să produci rapid versiunea de citire pentru a distribui a produsului tău.
  • Implementare continuă: Preluați fără probleme produsul de la livrarea continuă și doar implementați-l pe serverele dvs.

Oh, am înțeles acum. Este vorba despre a-mi face aplicația să se implementeze magic în lume!

Articolul meu preferat care explică CI/CD este de la Atlassian aici. Acest lucru ar trebui să clarifice orice întrebări pe care le aveți. Oricum, revenim la problema.

Da, înapoi la asta. Cum evit implementările manuale?

Configurarea unei conducte CI/CD pentru a fi implementată în Push to master

Ce se întâmplă dacă ți-aș spune că poți repara imediat acest bit cu un CI/CD? Puteți împinge la telecomanda GitLab ( origin ) și un computer va fi generat pentru a împinge pur și simplu codul dvs. în Heroku.

În nici un caz!

Da așa! Să revenim din nou la cod.

Diagrama unei conducte CI/CD de implementare simplă

Creați un .gitlab-ci.yml cu următorul conținut, schimbând toptal-pipeline cu numele aplicației Heroku:

 image: ruby:2.4 before_script: - > : "${HEROKU_EMAIL:?Please set HEROKU_EMAIL in your CI/CD config vars}" - > : "${HEROKU_AUTH_TOKEN:?Please set HEROKU_AUTH_TOKEN in your CI/CD config vars}" - curl https://cli-assets.heroku.com/install-standalone.sh | sh - | cat >~/.netrc <<EOF machine api.heroku.com login $HEROKU_EMAIL password $HEROKU_AUTH_TOKEN machine git.heroku.com login $HEROKU_EMAIL password $HEROKU_AUTH_TOKEN EOF - chmod 600 ~/.netrc - git config --global user.email "[email protected]" - git config --global user.name "CI/CD" variables: APPNAME_PRODUCTION: toptal-pipeline deploy_to_production: stage: deploy environment: name: production url: https://$APPNAME_PRODUCTION.herokuapp.com/ script: - git remote add heroku https://git.heroku.com/$APPNAME_PRODUCTION.git - git push heroku master - heroku pg:backups:capture --app $APPNAME_PRODUCTION - heroku run rails db:migrate --app $APPNAME_PRODUCTION only: - master

Împingeți-l în sus și urmăriți cum eșuează în pagina Pipelines a proiectului dvs. Asta pentru că îi lipsesc cheile de autentificare pentru contul tău Heroku. Remedierea acestui lucru este, totuși, destul de simplă. Mai întâi veți avea nevoie de cheia dvs. API Heroku . Obțineți-l din pagina Gestionați contul și apoi adăugați următoarele variabile secrete în setările CI/CD ale depozitului GitLab:

  • HEROKU_EMAIL : adresa de e-mail pe care o utilizați pentru a vă conecta la Heroku
  • HEROKU_AUTH_KEY : Cheia pe care ai primit-o de la Heroku

Imaginea variabilelor secrete din pagina de setări GitLab CI/CD

Acest lucru ar trebui să aibă ca rezultat implementarea unui GitLab funcțional către Heroku la fiecare apăsare. Cat despre ce se intampla:

  • La împingerea la stăpânire
    • Heroku CLI este instalat și autentificat într-un container.
    • Codul tău este trimis către Heroku.
    • O copie de rezervă a bazei de date este capturată în Heroku.
    • Migrațiile sunt efectuate.

Deja, puteți vedea că nu numai că economisiți timp prin automatizarea totul într-un git push , ci și creați o copie de rezervă a bazei de date la fiecare implementare! Dacă vreodată ceva nu merge bine, veți avea o copie a bazei de date la care să reveniți.

Crearea unui mediu de organizare

Dar stai, întrebare rapidă, ce se întâmplă cu problemele tale specifice producției? Ce se întâmplă dacă întâlnești o eroare ciudată, deoarece mediul tău de dezvoltare este prea diferit de cel de producție? Am întâlnit odată câteva probleme ciudate cu SQLite 3 și PostgreSQL când am executat o migrare. Specificul mă scapă, dar este foarte posibil.

Folosesc strict PostgreSQL în dezvoltare, nu potrivesc niciodată motoarele de baze de date în acest fel și îmi monitorizez cu atenție stack-ul pentru potențiale incompatibilități.

Ei bine, asta e o muncă obositoare și aplaud disciplina ta. Personal, îmi este mult prea lene să fac asta. Cu toate acestea, puteți garanta acel nivel de diligență tuturor potențialilor viitori dezvoltatori, colaboratori sau colaboratori?

Errrr— Da, nu. M-ai prins acolo. Alți oameni o vor încurca. Care este ideea ta, totuși?

Ideea mea este că ai nevoie de un mediu de scenă. Este ca producția, dar nu este. Un mediu de pregătire este în care repetați implementarea în producție și vă detectați toate erorile devreme. Mediile mele de pregătire oglindesc, de obicei, producția și arunc o copie a bazei de date de producție la implementarea în staging pentru a mă asigura că nu există cazuri de colț deranjante să-mi încurce migrațiile. Cu un mediu de punere în scenă, puteți înceta să vă tratați utilizatorii ca niște cobai.

Are sens! Deci, cum fac asta?

Aici devine interesant. Îmi place să implementez master direct la montaj.

Stai, nu este acolo unde implementăm producția chiar acum?

Da, este, dar acum ne vom implementa în scenă.

Dar dacă master se implementează în staging, cum vom implementa în producție?

Folosind ceva ce ar fi trebuit să faci cu ani în urmă: versiunea codului nostru și împingerea etichetelor Git.

Etichete Git? Cine folosește etichetele Git?! Acest lucru începe să sune ca o mulțime de muncă.

Sigur a fost, dar, din fericire, am făcut deja toată această muncă și puteți pur și simplu să aruncați codul meu și va funcționa.

Prezentare generală a modului în care vor funcționa implementările de punere în scenă și producție

Mai întâi, adăugați un bloc despre implementarea staging-ului în fișierul dvs. .gitlab-ci.yml , am creat o nouă aplicație Heroku numită toptal-pipeline-staging :

 … variables: APPNAME_PRODUCTION: toptal-pipeline APPNAME_STAGING: toptal-pipeline-staging deploy_to_staging: stage: deploy environment: name: staging url: https://$APPNAME_STAGING.herokuapp.com/ script: - git remote add heroku https://git.heroku.com/$APPNAME_STAGING.git - git push heroku master - heroku pg:backups:capture --app $APPNAME_PRODUCTION - heroku pg:backups:restore `heroku pg:backups:url --app $APPNAME_PRODUCTION` --app $APPNAME_STAGING --confirm $APPNAME_STAGING - heroku run rails db:migrate --app $APPNAME_STAGING only: - master - tags ...

Apoi modificați ultima linie a blocului dvs. de producție pentru a rula pe etichete Git cu versiuni semantice în loc de ramura principală:

 deploy_to_production: ... only: - /^v(?'MAJOR'(?:0|(?:[1-9]\d*)))\.(?'MINOR'(?:0|(?:[1-9]\d*)))\.(?'PATCH'(?:0|(?:[1-9]\d*)))(?:-(?'prerelease'[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?(?:\+(?'build'[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$/ # semver pattern above is adapted from https://github.com/semver/semver.org/issues/59#issuecomment-57884619

Rularea acestui lucru chiar acum va eșua, deoarece GitLab este suficient de inteligent pentru a permite numai ramurilor „protejate” accesul la variabilele noastre secrete. Pentru a adăuga etichete de versiune, accesați pagina de setări a depozitului a proiectului GitLab și adăugați v* la etichetele protejate.

Imaginea etichetei de versiune care este adăugată la etichetele protejate în pagina de setări a depozitului

Să recapitulăm ce se întâmplă acum:

  • La împingerea la master sau la împingerea unui commit etichetat
    • Heroku CLI este instalat și autentificat într-un container.
    • Codul tău este trimis către Heroku.
    • O copie de rezervă a producției bazei de date este capturată în Heroku.
    • Backup-ul este aruncat în mediul dumneavoastră de pregătire.
    • Migrațiile sunt executate pe baza de date de transfer.
  • La împingerea unei versiuni etichetate semantic commit
    • Heroku CLI este instalat și autentificat într-un container.
    • Codul tău este trimis către Heroku.
    • O copie de rezervă a producției bazei de date este capturată în Heroku.
    • Migrațiile sunt executate în baza de date de producție.

Te simți puternic acum? Mă simt puternic. Îmi amintesc, prima dată când am ajuns până aici, mi-am sunat soția și i-am explicat toată această conductă în detaliu chinuitor. Și nici măcar nu este tehnică. Am fost super impresionat de mine și ar trebui să fii și tu! O treabă grozavă ajungând până aici!

Testarea fiecărei împingeri

Dar există mai mult, deoarece un computer oricum face lucruri pentru tine, ar putea rula și toate lucrurile pe care ești prea lene să le faci: teste, erori, aproape orice vrei să faci și, dacă oricare dintre acestea eșuează, au câștigat. nu trece la desfășurare.

Îmi place să am asta în pipeline, face ca recenziile mele de cod să fie distractive. Dacă o solicitare de îmbinare trece prin toate verificările mele de cod, merită să fie revizuită.

Imagine de testare la fiecare apăsare

Adăugați un bloc de test :

 test: stage: test variables: POSTGRES_USER: test POSTGRES_PASSSWORD: test-password POSTGRES_DB: test DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSSWORD}@postgres/${POSTGRES_DB} RAILS_ENV: test services: - postgres:alpine before_script: - curl -sL https://deb.nodesource.com/setup_8.x | bash - apt-get update -qq && apt-get install -yqq nodejs libpq-dev - curl -o- -L https://yarnpkg.com/install.sh | bash - source ~/.bashrc - yarn - gem install bundler --no-ri --no-rdoc - bundle install -j $(nproc) --path vendor - bundle exec rake db:setup RAILS_ENV=test script: - bundle exec rake spec - bundle exec rubocop

Să recapitulăm ce se întâmplă acum:

  • La fiecare împingere sau cerere de îmbinare
    • Ruby și Node sunt instalate într-un container.
    • Dependențele sunt instalate.
    • Aplicația este testată.
  • La împingerea la master sau la împingerea unui commit etichetat și numai dacă toate testele trec
    • Heroku CLI este instalat și autentificat într-un container.
    • Codul tău este trimis către Heroku.
    • O copie de rezervă a producției bazei de date este capturată în Heroku.
    • Backup-ul este aruncat în mediul dumneavoastră de pregătire.
    • Migrațiile sunt executate pe baza de date de transfer.
  • La împingerea unei versiuni etichetate semantic commit și numai dacă toate testele trec
    • Heroku CLI este instalat și autentificat într-un container.
    • Codul tău este trimis către Heroku.
    • O copie de rezervă a producției bazei de date este capturată în Heroku.
    • Migrațiile sunt executate în baza de date de producție.

Faceți un pas înapoi și minunați-vă de nivelul de automatizare pe care l-ați realizat. De acum înainte, tot ce trebuie să faci este să scrii cod și să împingi. Testați-vă manual aplicația în punere în scenă, dacă aveți chef, iar când vă simțiți suficient de încrezător încât să o expuneți în lume, etichetați-o cu versiuni semantice!

Versiune semantică automată

Da, este perfect, dar lipsește ceva. Nu-mi place să caut ultima versiune a aplicației și să o etichetez în mod explicit. Asta necesită mai multe comenzi și îmi distrage atenția pentru câteva secunde.

Bine, omule, oprește-te! Destul. Pur și simplu îl suprainginerești acum. Funcționează, este genial, nu stricați nimic bun trecând peste vârf.

Bine, am un motiv întemeiat să fac ceea ce sunt pe cale să fac.

Roagă-te, luminează-mă.

Am fost ca tine. Am fost mulțumit de această configurație, dar apoi m-am încurcat. git tag afișează etichetele în ordine alfabetică, v0.0.11 este peste v0.0.2 . Odată am etichetat accidental o versiune și am continuat să o fac timp de aproximativ o jumătate de duzină de lansări până mi-am văzut greșeala. Atunci am decis să automatizez și asta.

Iar începem

Bine, deci, din fericire, avem puterea npm la dispoziție, așa că am găsit un pachet potrivit: Rulați yarn add --dev standard-version și adăugați următoarele în fișierul package.json :

 "scripts": { "release": "standard-version", "major": "yarn release --release-as major", "minor": "yarn release --release-as minor", "patch": "yarn release --release-as patch" },

Acum trebuie să faceți un ultim lucru, configurați Git să împingă etichete în mod implicit. Momentan, trebuie să rulați git push --tags pentru a împinge o etichetă în sus, dar să faceți acest lucru automat la git push obișnuit este la fel de simplu ca să rulați git config --global push.followTags true .

Pentru a utiliza noua conductă, oricând doriți să creați o rulare de lansare:

  • yarn patch pentru eliberarea de plasture
  • yarn minor pentru lansări minore
  • yarn major pentru lansări majore

Dacă nu sunteți sigur ce înseamnă cuvintele „major”, „minor” și „patch”, citiți mai multe despre acest lucru pe site-ul de versiune semantică.

Acum că v-ați finalizat în sfârșit pipeline, să recapitulăm cum să o folosiți!

  • Scrie cod.
  • Acordați-l și împingeți-l pentru a testa și implementa-l în staging.
  • Folosiți un yarn patch pentru a eticheta o eliberare de plasture.
  • git push pentru a-l împinge în producție.

Rezumat și pași ulterioare

Tocmai am zgâriat suprafața a ceea ce este posibil cu conductele CI/CD. Acesta este un exemplu destul de simplist. Puteți face mult mai mult schimbând Heroku cu Kubernetes. Dacă decideți să utilizați GitLab CI, citiți documentele yaml, deoarece puteți face mult mai multe prin memorarea în cache a fișierelor între implementări sau salvarea artefactelor!

O altă schimbare uriașă pe care ați putea să o faceți acestei conducte este introducerea declanșatoarelor externe pentru a rula versiunile și eliberarea semantică. În prezent, ChatOps face parte din planul lor plătit și sper că îl lansează în planuri gratuite. Dar imaginați-vă că puteți declanșa următoarea imagine printr-o singură comandă Slack!

Diagrama unei conducte de implementare CI/CD în care implementările de producție sunt declanșate extern, eventual prin chat sau webhook-uri

În cele din urmă, pe măsură ce aplicația dvs. începe să devină complexă și necesită dependențe la nivel de sistem, poate fi necesar să utilizați un container. Când se întâmplă acest lucru, consultați ghidul nostru: Noțiuni introductive cu Docker: Simplificarea Devops-urilor .

Acest exemplu de aplicație este într-adevăr live și puteți găsi codul sursă pentru el aici.