Acest articol își propune să ofere un plan de nivel înalt pentru automatizarea unei mare părți a ciclului de viață al software-ului. De asemenea va arăta, pe baza unui exemplu concret, cum poate fi implementat un astfel de plan.
Ideile fundamentale
Mai întâi să menționăm ideile fundamentale pe care se bazează planul. Postulăm că următoarele idei sunt "bune". Pe acestea ar trebui să le implementăm sau să ne străduim să implementăm și care ne ajută să avem un ciclu de viață software lin:
revizuire cod (code review),
dezvoltare condusă de teste (test driven development),
sisteme versionare a surselor (version control systems),
analiză statică de cod (static code analysis / linting),
metodologii agile / lean,
definirea infrastructurii ca și cod (infrastructure as code).
Din lista de mai sus aș dori să subliniez în mod special importanța revizuirii de cod: conform [CC2nd] revizuirea codului de o altă persoană este de cel puțin de două ori mai eficient în găsirea defectelor comparativ cu testarea (unit-teste sau de altă natură). De asemenea, este un mod excelent de a răspândi cunoștințe despre sistemul aflat sub dezvoltare în interiorul echipei, reducând riscul de eșec în cazul în care cineva devine indisponibil (nu mai există problema că o anumită bucată de cod este cunoscută de o singură persoană).
Din păcate revizuirea codului poate fi foarte lentă (o estimare pune viteză optimă la aproximativ 150 de linii/oră) și consumatoare de timp. Acesta este un alt motiv bun pentru automatizarea proceselor: eliberează timpul dezvoltatorilor în favoarea revizuirii de cod.
Ce este livrarea continua?
Livrare continuă (continuous delivery) înseamnă că organizația are o modalitate relativ automată de a pune software-ul dezvoltat în producție. În termeni mai concreți, dacă avem în vedere procesul de dezvoltare software din figura de mai jos, se poate vorbi despre un proces continuu de livrare dacă domeniile evidențiate sunt automatizate.
Acest plan exemplificativ funcționează în următorul fel:
După definirea cerințelor (care ar trebui să fie cât mai mici posibil - conform mentalității Agile) este creat un "loc de muncă" (aceasta poate fi un "feature branch" în cazul în care folosim un DVCS ca și Git sau Mercurial sau o copie separată a codului sursă).
Se scriu testele și codul sursă necesare pentru a pune în aplicare cerințele (în această ordine dacă respectăm metodologia TDD).
După ce codul este "complet" se "publică" (din nou, în funcție de instrumentele specifice utilizate acest pas poate să ia mai multe forme - de exemplu cu Git acest pas se realizează prin "împingerea" (push) codului într-un repository).
Automat atunci când codul este publicat se rulează testele. În cazul oricărui eșec este notificat dezvoltatorul.
Automat se efectuează o analiză statică a codului. Dacă sunt detectate posibile probleme se notifică dezvoltatorul.
În cazul în care codul trece de teste și de analiza statică, sunt notificați oamenii care pot să revizuie codul. Având în vedere că codul a trecut deja de două controale la acest punct, revizuitorul uman nu este deranjat cu problemele triviale (cum ar fi codul nu este formatat corect) și se poate concentra pe aspectele importante, de nivel de business.
Automat, dacă revizuitorul dă undă verde, codul este integrat (aceasta înseamnă fuzionarea - merge - într-o anumită ramură dacă se utilizează un DVCS).
Automat după ce codul este integrat, acesta este instalat într-un mediu de pregătire (staging).
În mediul de pregătire se poate efectua un proces manual de asigurare a calității (QA).
Automat, după verficarea calității, codul poate fi instalat în mediul de producție.
Probabil sunteți deja familiari cu integrarea continuă și vă întrebați: care este diferența? Și, într-adevăr, există foarte puține - livrarea continuă este integrare continuă dusă la concluzia sa logică: automatizarea tuturor etapelor după integrare.
Dacă aveți ezitări în legătură cu răspunsul la întrebrea: "pot să am încredere într-o mașină să aibă aceeași grijă ca un inginer de instalare (deployment engineer) cu experiență?", următoarele idei vă pot oferi unele clarificări:
poți cu adevărat încredere că oamenii interacționează întotdeauna cu grijă maximă cu sistemele? Oamenii devin neglijenți în timp, au o zi proastă, pot fi distrași și așa mai departe sau chiar mai rău - ei pot deveni indisponibi, temporar sau permanent, fără avertisment.
oamenii nu se scalează - Ce se întâmplă dacă mâine vreți să instalați soft-ul în două medii? Va fi nevoie de două ori mai mult timp sau de două ori mai mulți oameni. Un program poate fi executat foarte simplu de mai multe ori sau chiar rula în paralel.
oamenii sunt lenți - sunt întârzieri între momentul în care un e-mail este trimis / un ticket este completat în momentul când inginerul de instalare îl vede. Ce se întâmplă dacă el/ea este în pauză? Un sistem automat va porni în câteva secunde după ce codul devine disponibil
oameni nu se scalează, partea a doua - în cazul în care mediul este format din cinci servere, inginerul de instalare trebuie să le modifice pe rând. Un proces automat le poate actualiza pe toate în paralel, terminând procesul într-o cincime de timp.
astfel de procese sunt implementate de multe companii care de zeci de ori pe zi pun cod în producție fără ca cineva să observe. Singurul efect este că oamenii văd îmbunătățiri și corecții mai rapid.
"Dacă te doare, fă-l mai des" - oameni și organizațiile nu devin mai bune exersând doar punctele lor forte. Ei trebuie să se uite mereu la cea mai slabă verigă a lanțului și să-l îmbunătățească. În cazul în care instalarea este o piedică, trebuie să dedicați efort îmbunătățirii lui.
De asemenea v-ați putea simț îngrijorați că procesul de implementare este atât de complicat încât nu se poate automatiza. Relaxați-vă, respirați adânc și faceți următorii pași:
creați o listă de pași urmat de persoana care face în mod curent instalarea. Această listă în sine este foarte valorosă: se poate folosi ca să ne asigurăm că pașii nu sunt omiși și poate servi ca material de instruire pentru alte persoane.
parcurgeți lista și transformați fiecare pas într-un proces de automatizat.
realizați că vor exista cazuri în care procesul automat nu funcționează impecabil (sau chiar eșuează în mod catastrofal)). Când vedeți aceste cazuri, amintiți-vă că și un proces uman poate să eșueze. Păstrați o statistică de genul "X zile de la ultimul eșec" dacă vă ajută. Întroduceți un pas de verificare efectuat de un om după instalare dacă simțiți nevoia - chiar și așa, cel puțin persoana este scutită de munca plictisitoare, automatizabilă. De asemenea, considerați modurile în care puteți detecta (sau chiar mai bine) de a evita data viitoare eșecurile de acel tip într-un mod automat.
Instrumente folosite
"Inima" unui proces de implementare continuă este un sistem care poate reacționa la evenimente externe (cum ar fi disponibilitatea unei noi bucăți de cod sursă) care execută pașii necesari. O soluție frecvent utilizată este Jenkins (cunoscut anterior sub numele de Hudson) care este foarte versatil și poate interacționa cu o mulțime de sisteme prin intermediul plugin-urilor. Câteva sfaturi legate de configurarea Jenkins-ului:
Jenkins pot folosi "sclavi" (slaves) pentru a executa procesul de build. Acest lucru înseamnă că Jenkins "maestru" (master) poate rula pe o mașină Linux, în timp ce sclavii pot rula pe diferite platforme (Windows, MacOS X - orice care poate rula Java), făcând posibil rularea procesului de build pe toate platformele suportate
Stabiliți limite pentru procesul de build. Specificați timpul maxim care poate să dureze pentru a preveni un build blocat care să consume toate resursele. Specificați numărul maxim de build-uri vechi pe care ar trebui să fie arhivate pentru a evita umplerea spațiului pe disc.
Profitați de job-urile parametrizate pentru a evita crearea job-urilor (aproape) identice.
Profitați de declanșarea de la distanță (remote triggering) a job-ului. În acest fel un build poate fi declanșat chiar în momentul în care o anumită schimbare a fost comisă să vă informați despre hooks / webhooks pentru VCS-ul vostru.
Rupeți build-ul în pași mai mici - de exemplu, un pas ar putea fi rularea unit test-elor, a doua ar fi rularea testelor de integrare și un al treilea pas ar putea să fie rularea analizei statice. Pașii mici oferă feedback mai rapid și fac posibilă rularea mai multor pași în paralel.
Când rupeți un proces în pași mici, asigurați-vă că fiecare pas operează pe exact aceleași fișiere sursă. Fiți cât mai specifici. Specificarea unui branch nu este suficient de specific, folosiți identificatorii de commit / changeset.
Jenkins te poate notifica în mai multe moduri cu privire la progresele job-ului (build-ul a început / a reusit / a eșuat) - în interfața web, e-mail, chat. Să-l configurați în așa fel încât să fie cât mai convenabil pentru membrii echipei dar să nu le spameze.
Alte variante în afară de Jenkins ar fi: TeamCity, CruiseControl și Travis-CI.
Cea de-a doua parte a unui astfel de configurări este un loc pentru a ține codul și comentariile legate de revizii. Acest lucru poate fi un sistem sau două sisteme distincte (în cazul în acesta aveți nevoie să le sincronizați - probabil folosind Jenkins). Câteva variante:
soluții complete (cod hosting și revizuire): Github, BitBucket, Google Code, hosted TFS
Cod hosting pe care le puteți instala pe serverul vostru: RhodeCode, Gitorious, Gitlab, gitweb, hgserv
Revizuire de cod: Gerrit, ReviewBoard, Rietveld
A treia piesă a puzzle-ului este un sistem care efectuează analiza statică pe cod pentru a oferi feedback despre problemele care pot fi detectate în mod automat. Pentru aceasta recomand SonarQube (cunoscut anterior sub numele Sonar). Ea nu face analiză statică pe cont propriu, ci consumă rapoartele create de alte instrumente (cum ar fi FindBugs, FxCop, Pylint, etc) și le prezintă într-o interfață web frumoasă, oferind o clasificare a problemelor, statistici, rapoarte cu schimbări și așa mai departe. Câteva sfaturi legate de folosirea SonarQube:
are o arhitectură ciudată: analiza se execută pe client care are nevoie de acces direct atât la interfața web cât și la baza de date.
analiza unui proiect mare poate să dureze o lungă perioadă de timp și să consume multe resurse (CPU/memorie - puteți găsi sfaturi specifice pentru proiecte mari de Java pe blog-ul Transylvania JUG). Dacă apar astfel de probleme, este un indicator bun că ar trebui să despărțiți proiectul în mai multe module mici.
SonarQube are nevoie de o bază de date performantă. Nu rulați cu baza de date încorporată (H2) cu care vine sau cu o instanță MySQL slabă. Recomand să folosiți un PostgreSQL configurat corect.
Concluzii
Existența unui "deployment pipeline" are multe beneficii. Se eliberează timpul pe echipe. Instalarea se face mai repede și garantează rezultate consistente. Asigură că procesul de instalare este definit cu precizie (suficient de precis ca să-l poate executa un calculator). De asemenea, ne învață despre instrumentele de bază și despre linia de comandă, un lucru indispensabil pentru a depana problemele de producție.
Puteți aplica livrarea continuă la toate tipurile de sisteme software, nu doar site-uri sau servicii găzduite (acolo unde este cel mai ușor). Pipeline-ul poate produce installkit-uri sau chiar mașini virtuale complete cu software-ul preinstalat. Livrarea continuă nu înseamnă neapărat că trebuie livrat noul software-ul la client de fiecare dată când se schimbă ceva. Înseamnă doar că aveți opțiunea de a face acest lucru la orice moment în timp.
O carte bună (deși ușor depășită) este "Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation" de Jez Humble și David Farley. De asemenea, puteți arunca o privire asupra unei prezentări cu acest subiect sau puteți să mă contactați pentru întrebări / sfaturi sau chiar să veniți la următoarea întâlnire Cluj.PM unde voi vorbi despre acest subiect.