Implementare Laravel Zero Downtime

Publicat: 2022-03-11

Când vine vorba de actualizarea unei aplicații live, există două moduri fundamental diferite de a face acest lucru.

În prima abordare, facem modificări incrementale în starea sistemului nostru. De exemplu, actualizăm fișierele, modificăm proprietățile mediului, instalăm elemente de necesitate suplimentare și așa mai departe. În a doua abordare, dărâmăm mașini întregi și reconstruim sistemul cu imagini noi și configurații declarative (de exemplu, folosind Kubernetes).

Implementarea Laravel este ușoară

Acest articol acoperă în principal aplicații relativ mici, care este posibil să nu fie găzduite în cloud, deși voi menționa cum Kubernetes ne poate ajuta foarte mult cu implementări dincolo de scenariul „fără cloud”. Vom discuta, de asemenea, câteva probleme generale și sfaturi pentru efectuarea de actualizări cu succes care ar putea fi aplicabile într-o serie de situații diferite, nu doar cu implementarea Laravel.

În scopul acestei demonstrații, voi folosi un exemplu Laravel, dar rețineți că orice aplicație PHP ar putea folosi o abordare similară.

Versiune

Pentru început, este esențial să cunoaștem versiunea de cod implementată în prezent în producție. Poate fi inclus într-un fișier sau cel puțin în numele unui folder sau fișier. În ceea ce privește denumirea, dacă respectăm practica standard a versiunilor semantice, putem include mai multe informații în ea decât un singur număr.

Privind la două versiuni diferite, aceste informații adăugate ne-ar putea ajuta să înțelegem cu ușurință natura modificărilor introduse între ele.

Imagine care arată explicația versiunii semantice.

Versiunea versiunii începe cu un sistem de control al versiunilor, cum ar fi Git. Să presupunem că am pregătit o versiune pentru implementare, de exemplu, versiunea 1.0.3. Când vine vorba de organizarea acestor versiuni și fluxuri de cod, există diferite stiluri de dezvoltare, cum ar fi dezvoltarea bazată pe trunk și fluxul Git, pe care le puteți alege sau amesteca în funcție de preferințele echipei și de specificul proiectului. În cele din urmă, cel mai probabil vom ajunge cu lansările noastre etichetate corespunzător pe ramura noastră principală.

După commit, putem crea o etichetă simplă ca aceasta:

git tag v1.0.3

Și apoi includem etichete în timp ce executăm comanda push:

git push <origin> <branch> --tags

De asemenea, putem adăuga etichete la vechile comit-uri folosind hashurile lor.

Obținerea fișierelor de lansare la destinația lor

Implementarea Laravel necesită timp, chiar dacă este pur și simplu copierea fișierelor. Cu toate acestea, chiar dacă nu durează prea mult timp, obiectivul nostru este să obținem un timp de nefuncționare zero .

Prin urmare, ar trebui să evităm instalarea actualizării în loc și să nu modificăm fișierele care sunt difuzate live. În schimb, ar trebui să implementăm într-un alt director și să facem schimbarea doar după ce instalarea este complet gata.

De fapt, există diverse instrumente și servicii care ne pot ajuta cu implementări, cum ar fi Envoyer.io (de la designerul Laravel.com Jack McDade), Capistrano, Deployer etc. Nu le-am folosit încă pe toate în producție, așa că nu pot faceți recomandări sau scrieți o comparație cuprinzătoare, dar permiteți-mi să vă prezint ideea din spatele acestor produse. Dacă unele (sau toate) dintre ele nu vă pot îndeplini cerințele, puteți oricând să vă creați scripturi personalizate pentru a automatiza procesul în modul cel mai potrivit.

În scopul acestei demonstrații, să presupunem că aplicația noastră Laravel este deservită de un server Nginx din următoarea cale:

/var/www/demo/public

În primul rând, avem nevoie de un director pentru a plasa fișierele de lansare de fiecare dată când facem o implementare. De asemenea, avem nevoie de un link simbolic care va indica versiunea curentă de lucru. În acest caz, /var/www/demo va servi drept link simbolic. Reatribuirea indicatorului ne va permite să schimbăm rapid versiunile.

Gestionarea fișierelor Laravel Deployment

În cazul în care avem de-a face cu un server Apache, ar putea fi necesar să permitem următoarele legături simbolice în configurație:

Options +FollowSymLinks

Structura noastră poate fi cam așa:

 /opt/demo/release/v0.1.0 /opt/demo/release/v0.1.1 /opt/demo/release/v0.1.2

S-ar putea să existe unele fișiere pe care trebuie să le persistem prin diferite implementări, de exemplu, fișiere jurnal (dacă nu folosim Logstash, evident). În cazul implementării Laravel, s-ar putea să dorim să păstrăm directorul de stocare și fișierul de configurare .env. Le putem păstra separate de alte fișiere și le putem folosi în schimb legăturile simbolice.

Pentru a prelua fișierele noastre de lansare din depozitul Git, putem folosi fie comenzi de clonare, fie de arhivare. Unii oameni folosesc git clone, dar nu puteți clona un anumit commit sau etichetă. Aceasta înseamnă că întregul depozit este preluat și apoi eticheta specifică este selectată. Când un depozit conține multe ramuri sau un istoric mare, dimensiunea sa este considerabil mai mare decât arhiva de lansare. Deci, dacă nu aveți nevoie în mod specific de git repo în producție, puteți utiliza git archive . Acest lucru ne permite să preluăm doar o arhivă de fișiere printr-o anumită etichetă. Un alt avantaj al folosirii acestuia din urmă este că putem ignora unele fișiere și foldere care nu ar trebui să fie prezente în mediul de producție, de exemplu, teste. Pentru aceasta, trebuie doar să setăm proprietatea export-ignore în .gitattributes file . În Lista de verificare a practicilor de codare sigură OWASP puteți găsi următoarea recomandare: „Eliminați codul de testare sau orice funcționalitate care nu este destinată producției, înainte de implementare.”

Dacă preluăm ediția din sistemul de control al versiunii sursă, git archive și export-ignore ne pot ajuta cu această cerință.

Să aruncăm o privire la un script simplificat (ar avea nevoie de o mai bună gestionare a erorilor în producție):

deploy.sh

 #!/bin/bash # Terminate execution if any command fails set -e # Get tag from a script argument TAG=$1 GIT_REMOTE_URL='here should be a remote url of the repo' BASE_DIR=/opt/demo # Create folder structure for releases if necessary RELEASE_DIR=$BASE_DIR/releases/$TAG mkdir -p $RELEASE_DIR mkdir -p $BASE_DIR/storage cd $RELEASE_DIR # Fetch the release files from git as a tar archive and unzip git archive \ --remote=$GIT_REMOTE_URL \ --format=tar \ $TAG \ | tar xf - # Install laravel dependencies with composer composer install -o --no-interaction --no-dev # Create symlinks to `storage` and `.env` ln -sf $BASE_DIR/.env ./ rm -rf storage && ln -sf $BASE_DIR/storage ./ # Run database migrations php artisan migrate --no-interaction --force # Run optimization commands for laravel php artisan optimize php artisan cache:clear php artisan route:cache php artisan view:clear php artisan config:cache # Remove existing directory or symlink for the release and create a new one. NGINX_DIR=/var/www/public mkdir -p $NGINX_DIR rm -f $NGINX_DIR/demo ln -sf $RELEASE_DIR $NGINX_DIR/demo

Pentru implementarea versiunii noastre, am putea doar să executăm următoarele:

deploy.sh v1.0.3

Notă: În acest exemplu, v1.0.3 este eticheta git a lansării noastre.

Compozitor în producție?

S-ar putea să fi observat că scriptul invocă Composer pentru a instala dependențe. Deși vedeți acest lucru în multe articole, pot exista unele probleme cu această abordare. În general, este cea mai bună practică să creați o versiune completă a unei aplicații și să avansați această versiune prin diferite medii de testare ale infrastructurii dvs. În cele din urmă, veți avea o versiune complet testată, care poate fi implementată în siguranță în producție. Chiar dacă fiecare construcție ar trebui să fie reproductibilă de la zero, aceasta nu înseamnă că ar trebui să reconstruim aplicația în diferite etape. Când facem instalarea compozitorului în producție, aceasta nu este cu adevărat aceeași versiune ca cea testată și iată ce poate merge prost:

  • Eroarea de rețea poate întrerupe descărcarea dependențelor.
  • Este posibil ca furnizorul bibliotecii să nu urmeze întotdeauna SemVer.

O eroare de rețea poate fi observată cu ușurință. Scriptul nostru ar înceta chiar să se execute cu o eroare. Dar o schimbare radicală într-o bibliotecă ar putea fi foarte dificil de stabilit fără a rula teste, ceea ce nu le puteți face în producție. În timpul instalării dependențelor, Composer, npm și alte instrumente similare se bazează pe versiunea semantică – major.minor.patch. Dacă vedeți ~1.0.2 în composer.json, înseamnă că instalați versiunea 1.0.2 sau cea mai recentă versiune a corecțiilor, cum ar fi 1.0.4. Dacă vedeți ^1.0.2, înseamnă că instalați versiunea 1.0.2 sau cea mai recentă versiune minoră sau de corecție, cum ar fi 1.1.0. Avem încredere că furnizorul de biblioteci va ridica numărul major atunci când este introdusă orice modificare, dar uneori această cerință este ratată sau nu este respectată. Au existat astfel de cazuri în trecut. Chiar dacă puneți versiuni fixe în composer.json, dependențele dvs. ar putea avea ~ și ^ în composer.json lor.

Dacă este accesibil, în opinia mea, o modalitate mai bună ar fi să folosiți un depozit de artefacte (Nexus, JFrog etc.). Versiunea de lansare, care conține toate dependențele necesare, va fi creată o singură dată, inițial. Acest artefact ar fi stocat într-un depozit și preluat pentru diferite etape de testare de acolo. De asemenea, aceasta ar fi versiunea care urmează să fie implementată în producție, în loc să reconstruiască aplicația din Git.

Păstrarea codului și a bazei de date compatibile

Motivul pentru care m-am îndrăgostit de Laravel la prima vedere a fost modul în care autorul său a acordat o atenție deosebită detaliilor, s-a gândit la comoditatea dezvoltatorilor și a încorporat, de asemenea, o mulțime de bune practici în cadru, cum ar fi migrarea bazelor de date.

Migrațiile bazelor de date ne permit să avem baza de date și codul în sincronizare. Ambele modificări pot fi incluse într-un singur commit, deci o singură lansare. Cu toate acestea, acest lucru nu înseamnă că orice modificare poate fi implementată fără timp de nefuncționare. La un moment dat în timpul implementării, vor rula diferite versiuni ale aplicației și ale bazei de date. În caz de probleme, acest punct se poate transforma chiar într-o perioadă. Ar trebui să încercăm întotdeauna să le facem pe ambele compatibile cu versiunile anterioare ale însoțitorilor lor: veche bază de date – aplicație nouă, bază de date nouă – aplicație veche.

De exemplu, să presupunem că avem o coloană de address și trebuie să o împărțim în address1 și address2 . Pentru a menține totul compatibil, s-ar putea să avem nevoie de mai multe versiuni.

  1. Adăugați două coloane noi în baza de date.
  2. Modificați aplicația pentru a utiliza câmpuri noi ori de câte ori este posibil.
  3. Migrați datele de address în coloane noi și plasați-le.

Acest caz este, de asemenea, un bun exemplu al modului în care modificările mici sunt mult mai bune pentru implementare. De asemenea, derularea lor este mai ușoară. Dacă schimbăm baza de cod și baza de date pentru câteva săptămâni sau luni, ar putea fi imposibil să actualizam sistemul de producție fără timp de nefuncționare.

Unele minunatii ale Kubernetes

Chiar dacă amploarea aplicației noastre ar putea să nu aibă nevoie de nori, noduri și Kubernetes, aș dori totuși să menționez cum arată implementările în K8s. În acest caz, nu facem modificări sistemului, ci mai degrabă declarăm ce ne-am dori să realizăm și ce ar trebui să ruleze pe câte replici. Apoi, Kubernetes se asigură că starea reală se potrivește cu cea dorită.

Ori de câte ori avem pregătită o nouă versiune, construim o imagine cu fișiere noi în ea, etichetăm imaginea cu noua versiune și o transmitem lui K8s. Acesta din urmă va învârti rapid imaginea noastră în interiorul unui cluster. Va aștepta înainte ca aplicația să fie gata, pe baza verificării de pregătire pe care o oferim, apoi va redirecționa inobservabil traficul către noua aplicație și o va ucide pe cea veche. Putem avea foarte ușor să rulăm mai multe versiuni ale aplicației noastre, ceea ce ne-ar permite să realizăm implementări albastru/verde sau canar cu doar câteva comenzi.

Dacă sunteți interesat, există câteva demonstrații impresionante în discursul „9 pași pentru a fi minunat cu Kubernetes de Burr Sutter”.

Înrudit : Autentificare completă a utilizatorului și control al accesului – Un tutorial pentru pașaport Laravel, Pt. 1