Da script a piattaforma proprietaria: come abbiamo automatizzato lo sviluppo in CIAN

Da script a piattaforma proprietaria: come abbiamo automatizzato lo sviluppo in CIAN

Durante il RIT 2019, il nostro collega Aleksandr Korotkov ha parlato rapporto dell'automazione dello sviluppo in TCIAN: per semplificare la vita e il lavoro, utilizziamo la nostra piattaforma Integro. Essa tiene traccia del ciclo di vita delle attività, solleva gli sviluppatori dalle operazioni routinarie e riduce notevolmente il numero di bug in produzione. In questo post, completeremo la presentazione di Aleksandr e racconteremo come abbiamo passato dalla semplice scrittura di script all'integrazione di prodotti open source attraverso la nostra piattaforma e quali sono le attività della nostra squadra di automazione.
 

Livello zero

«Non esiste un livello zero, non ne so nulla»
Master Shifu dal film «Kung Fu Panda»

L'automazione in TCIAN è iniziata 14 anni dopo la fondazione dell'azienda. Allora, nel team di sviluppo c'erano 35 persone. Difficile da credere, vero? Certo, in qualche modo l'automazione esisteva, ma un'area separata per l'integrazione continua e la distribuzione del codice ha iniziato a formarsi nel 2015. 

A quel tempo avevamo un enorme monolite composto da Python, C# e PHP, distribuito su server Linux/Windows. Per il deployment di questo mostro avevamo un set di script che eseguivamo manualmente. C'era anche la costruzione del monolite, che portava dolore e sofferenza a causa dei conflitti durante la fusione dei branch, delle correzioni di difetti e della ricostruzione con ‘un diverso set di attività nel build’. In modo semplificato, il processo appariva così:

Da script a piattaforma proprietaria: come abbiamo automatizzato lo sviluppo in CIAN

Questo non ci soddisfava e volevamo costruire un processo di build e deployment ripetibile, automatizzato e gestito. Per fare ciò, avevamo bisogno di un sistema CI/CD e sceglievamo tra la versione gratuita di Teamcity e Jenkins gratuito, poiché avevamo già lavorato con entrambi e ci soddisfacevano per il set di funzionalità. Abbiamo scelto Teamcity come prodotto più recente. All'epoca non utilizzavamo ancora un'architettura a microservizi e non prevedevamo un numero elevato di attività e progetti.

Arriviamo all'idea di un nostro sistema

L'implementazione di Teamcity ha eliminato solo una parte del lavoro manuale: sono rimasti ancora la creazione di Pull Request, il passaggio delle attività tra gli stati in Jira, la selezione delle attività per il rilascio. Su questo aspetto, il sistema Teamcity non riusciva già a gestire. Era necessario scegliere una strada per un'ulteriore automazione. Abbiamo valutato opzioni per lavorare con script in Teamcity o passare a sistemi di automazione esterni. Alla fine, abbiamo deciso che era necessaria la massima flessibilità, che solo una soluzione interna poteva fornire. Così è nata la prima versione del sistema di automazione interno chiamato Integro.

Teamcity si occupa dell'automazione a livello di avvio dei processi di build e deployment, mentre Integro si è concentrata sull'automazione di alto livello dei processi di sviluppo. Era necessario unire il lavoro con le attività in Jira al trattamento del codice sorgente correlato in Bitbucket. In questo stadio, all'interno di Integro hanno cominciato a apparire i propri workflow per lavorare con diversi tipi di attività. 

A causa dell'aumento dell'automazione nei processi aziendali, è aumentato anche il numero di progetti e run in Teamcity. Così è emerso un nuovo problema: un'istanza gratuita di Teamcity non era più sufficiente (3 agenti e 100 progetti), abbiamo aggiunto un'altra istanza (altri 3 agenti e 100 progetti), poi ancora un'altra. Alla fine abbiamo ottenuto un sistema composto da più cluster, difficile da gestire:

Da script a piattaforma proprietaria: come abbiamo automatizzato lo sviluppo in CIAN

Quando è emersa la questione di una quarta istanza, abbiamo capito che non potevamo continuare così, poiché i costi totali per il supporto di 4 istanze superavano ogni limite. È emersa la questione dell'acquisto di Teamcity a pagamento o della scelta di Jenkins gratuito. Abbiamo effettuato calcoli sulle istanze e i piani di automazione e abbiamo deciso che avremmo usato Jenkins. Dopo un paio di settimane, siamo passati a Jenkins, liberandoci di parte del mal di testa associato al supporto di più istanze di Teamcity. Così abbiamo potuto concentrarci sullo sviluppo di Integro e sull'ottimizzazione di Jenkins per le nostre esigenze.

Con l'aumento dell'automazione di base (nella forma di creazione automatica di Pull Request, raccolta e pubblicazione della Code coverage e di altre verifiche), si è consolidato il desiderio di abbandonare completamente i rilasci manuali e di delegare questo lavoro ai robot. In aggiunta, all'interno dell'azienda è iniziato il passaggio ai microservizi, che richiedevano rilasci frequenti, ciascuno separatamente. Così, gradualmente siamo arrivati ai rilasci automatici dei nostri microservizi (per il monolite, al momento, continuiamo a rilasciarlo manualmente a causa della complessità del processo). Ma, come spesso accade, è emersa una nuova difficoltà. 

Automatizziamo i test

Da script a piattaforma proprietaria: come abbiamo automatizzato lo sviluppo in CIAN

Grazie all'automazione dei rilasci, i processi di sviluppo si sono velocizzati, parzialmente a scapito di alcune fasi di test. Questo ha portato a una temporanea perdita di qualità. Può sembrare banale, ma con la velocizzazione dei rilasci era necessario cambiare anche la metodologia di sviluppo del prodotto. È stato essenziale considerare l'automazione dei test, promuovere la responsabilità personale (qui si parla di «accettare l'idea nella propria mente», non di penali monetarie) per gli sviluppatori riguardo al codice che producono e ai bug presenti, oltre a decidere sul rilascio o meno delle task attraverso il deploy automatico. 

Affrontando i problemi di qualità, siamo arrivati a due decisioni importanti: abbiamo iniziato a effettuare test canary e implementato un monitoraggio automatico per gli errori con reazione automatica al superamento della soglia. La prima decisione ha permesso di trovare errori evidenti prima che il codice arrivasse in produzione, mentre la seconda ha ridotto il tempo di risposta ai problemi in produzione. Certo, gli errori ci sono, ma spendiamo la maggior parte del tempo e delle energie non per correggerli, ma per minimizzarli. 

Team di automazione

Attualmente abbiamo un team di 130 sviluppatori e continuiamo a crescere. Il team di integrazione e consegna continua del codice (da ora in poi, team Deploy and Integration o DI) è composto da 7 persone e lavora in due direzioni: sviluppo della piattaforma di automazione Integro e DevOps. 

DevOps si occupa degli ambienti Dev/Beta del sito CIAN, dell'ambiente Integro, supporta gli sviluppatori nella risoluzione dei problemi e sviluppa nuovi approcci per scalare gli ambienti. La direzione dello sviluppo di Integro si occupa sia di Integro sia dei servizi correlati, ad esempio, plugin per Jenkins, Jira, Confluence, e sviluppa anche utility e applicazioni di supporto per i team di sviluppo. 

Il team DI collabora con il team Piattaforma, che si occupa dello sviluppo dell'architettura, delle librerie e degli approcci allo sviluppo all'interno dell'azienda. Inoltre, qualsiasi sviluppatore all'interno di CIAN può contribuire all'automazione, ad esempio, realizzando micro-automazioni secondo le esigenze del team o condividendo idee interessanti per migliorare ulteriormente l'automazione.

La torta stratificata dell'automazione in CIAN

Da script a piattaforma proprietaria: come abbiamo automatizzato lo sviluppo in CIAN

Tutti i sistemi coinvolti nell'automazione possono essere suddivisi in diversi strati:

  1. Sistemi esterni (Jira, Bitbucket e altri). Questi sono gestiti dai team di sviluppo.
  2. Piattaforma Integro. Gli sviluppatori non lavorano direttamente su di essa, ma è fondamentale per il funzionamento di tutta l'automazione.
  3. Servizi di consegna, orchestrazione e scoperta (ad esempio, Jenkins, Consul, Nomad). Questi aiutano a deployare il codice sui server e a garantire che i servizi funzionino tra loro.
  4. Livello fisico (server, OS, software correlato). Su questo livello il nostro codice opera. Può essere un server fisico o virtuale (LXC, KVM, Docker).

In base a questo concetto, dividiamo le aree di responsabilità all'interno del team DI. I primi due livelli appartengono alla direzione dello sviluppo di Integro, mentre gli ultimi due livelli sono sotto la responsabilità di DevOps. Questa divisione consente di concentrarsi sui compiti e non ostacola l'interazione, poiché siamo vicini l'uno all'altro e ci scambiamo costantemente conoscenze ed esperienze.

Integro

Concentriamoci su Integro e iniziamo con lo stack tecnologico:

  • CentOs 7
  • Docker + Nomad + Consul + Vault
  • Java 11 (il vecchio monolite di Integro rimarrà su Java 8)
  • Spring Boot 2.X + Spring Cloud Config
  • PostgreSql 11
  • RabbitMQ 
  • Apache Ignite
  • Camunda (embedded)
  • Grafana + Graphite + Prometheus + Jaeger + ELK
  • Web UI: React (CSR) + MobX
  • SSO: Keycloak

Seguiamo il principio dello sviluppo a microservizi, anche se abbiamo un'eredità sotto forma di un monolite di una versione precedente di Integro. Ogni microservizio gira nel proprio container Docker, i servizi comunicano tra loro tramite richieste HTTP e messaggi RabbitMQ. I microservizi si trovano tramite Consul e fanno una richiesta a quest'ultimo, passando attraverso l'autenticazione SSO (Keycloak, OAuth 2/OpenID Connect).

Da script a piattaforma proprietaria: come abbiamo automatizzato lo sviluppo in CIAN

Come esempio concreto, consideriamo l'interazione con Jenkins, che consiste nei seguenti passaggi:

  1. Il microservizio di gestione del workflow (da ora in poi, microservizio Flow) desidera avviare una build in Jenkins. A tal fine, trova tramite Consul l'IP:PORT del microservizio di integrazione con Jenkins (da ora in poi, microservizio Jenkins) e invia una richiesta asincrona per avviare la build in Jenkins.
  2. Il microservizio Jenkins, dopo aver ricevuto la richiesta, genera e restituisce in risposta un Job ID, che potrà poi essere utilizzato per identificare il risultato dell'esecuzione. Allo stesso tempo, avvia la build in Jenkins attraverso una chiamata all'API REST.
  3. Jenkins esegue la build e, al termine, invia un webhook con i risultati dell'esecuzione al microservizio Jenkins.
  4. Il microservizio Jenkins riceve un webhook, genera un messaggio riguardo alla conclusione dell'elaborazione della richiesta e allega i risultati dell'esecuzione. Il messaggio generato viene inviato in coda a RabbitMQ.
  5. Attraverso RabbitMQ, il messaggio pubblicato arriva al microservizio Flow, che scopre il risultato dell'elaborazione del proprio compito confrontando il Job ID della richiesta con quello ricevuto nel messaggio.

Attualmente abbiamo circa 30 microservizi, che possono essere suddivisi in diversi gruppi:

  1. Gestione delle configurazioni.
  2. Informativa e interazione con gli utenti (messaggeri, email).
  3. Gestione del codice sorgente.
  4. Integrazione con strumenti di deployment (jenkins, nomad, consul, ecc.).
  5. Monitoraggio (rilascio, errori, ecc.).
  6. Web utility (UI per la gestione degli ambienti di test, raccolta di statistiche, ecc.).
  7. Integrazione con task tracker e sistemi simili.
  8. Gestione del workflow per compiti diversi.

Workflow del compito

Integro automatizza le attività legate al ciclo di vita di un compito. In senso semplificato, per ciclo di vita di un compito intendiamo il workflow di un compito in Jira. Nei nostri processi di sviluppo ci sono diverse varianti di workflow a seconda del progetto, del tipo di compito e delle opzioni selezionate in un compito specifico. 

Consideriamo il workflow che utilizziamo più frequentemente:

Da script a piattaforma proprietaria: come abbiamo automatizzato lo sviluppo in CIAN

Nello schema, l'ingranaggio indica che la transition è attivata automaticamente da Integro, mentre la figura umana indica che la transition è attivata manualmente da una persona. Esploriamo alcuni percorsi che un compito può seguire in questo workflow.

Completamente manuale test su DEV+BETA senza test canary (solitamente rilasciamo monoliti):

Da script a piattaforma proprietaria: come abbiamo automatizzato lo sviluppo in CIAN

Possono esserci anche altre combinazioni di transition. A volte il percorso che seguirà un compito può essere scelto tramite opzioni in Jira.

Movimento del compito

Esaminiamo i principali passaggi che vengono eseguiti quando il compito si muove nel workflow "Test su DEV + test canary":

1. Lo sviluppatore o il PM crea un compito.

2. Lo sviluppatore prende in carico il compito. Dopo averlo completato, lo sposta nello stato IN REVIEW.

3. Jira invia un Webhook al microservizio Jira (responsabile dell'integrazione con Jira).

4. Il microservizio Jira invia una richiesta al servizio Flow (responsabile per i workflow interni in cui viene svolto il lavoro) per avviare il workflow.

5. All'interno del servizio Flow:

  • Vengono designati i revisori per il compito (Users-microservizio, che conosce tutto sugli utenti + microservizio Jira).
  • Attraverso il microservizio Source (che conosce i repository e i rami, ma non lavora direttamente con il codice), viene effettuata la ricerca dei repository contenenti il ramo del nostro compito (per semplificare la ricerca, il nome del ramo corrisponde al numero del compito in Jira). Di solito, il compito ha solo un ramo in un repository, il che semplifica la gestione della coda per il deployment e riduce la dipendenza tra i repository.
  • Per ciascun ramo trovato, viene eseguita la seguente sequenza di azioni:

    i) Integrazione del ramo master (Git-microservizio per lavorare con il codice).
    ii) Il ramo viene bloccato dalle modifiche dello sviluppatore (Bitbucket-microservizio).
    iii) Viene creato un Pull Request per quel ramo (Bitbucket-microservizio).
    iv) Viene inviata una notifica su un nuovo Pull Request nelle chat degli sviluppatori (Notify-microservizio per le notifiche).
    v) Viene avviata la creazione, il test e il deployment del compito su DEV (Jenkins-microservizio per l'uso di Jenkins).
    vi) Se tutti i punti precedenti sono stati completati con successo, Integro aggiunge la propria approvazione al Pull Request (Bitbucket-microservizio).

  • Integro attende l'approvazione nel Pull Request da parte dei revisori designati.
  • Non appena vengono ricevute tutte le approvazioni necessarie (compresi i test automatizzati effettuati con successo), Integro sposta il compito nello stato Test su Dev (microservizio Jira).

6. I tester eseguono i test sul compito. Se non ci sono problemi, spostano il compito nello stato Pronto per il Build.

7. Integro "vede" che il compito è pronto per il rilascio e avvia il suo deployment in modalità canary (Jenkins-microservizio). La prontezza per il rilascio è determinata da un insieme di regole. Ad esempio, il compito è nello stato corretto, non ci sono blocchi su altri compiti, non ci sono deployment attivi di questo microservizio, ecc.

8. Il compito viene spostato nello stato Canary (microservizio Jira).

9. Jenkins avvia tramite Nomad il deployment del compito in modalità canary (solitamente 1-3 istanze) e notifica al servizio di monitoraggio dei rilasci (DeployWatch-microservizio) riguardo al rilascio.

10. DeployWatch è un microservizio che raccoglie il fondo degli errori e risponde se necessario. Qualora il fondo degli errori superi la soglia (calcolata automaticamente), viene inviata una notifica agli sviluppatori tramite il microservizio Notify. Se dopo 5 minuti lo sviluppatore non interviene (non preme Revert o Stay), si avvia un rollback automatico delle istanze canary. Se il fondo non è superato, lo sviluppatore deve avviare manualmente il deployment in produzione (premendo il pulsante nell'interfaccia). Se entro 60 minuti lo sviluppatore non ha avviato il deployment in produzione, anche le istanze canary verranno rollbackate per ragioni di sicurezza.

11. Dopo l'avvio del deployment in produzione:

  • Il compito viene trasferito allo stato Production (microservizio Jira).
  • Il microservizio Jenkins avvia il processo di deployment e informa il microservizio DeployWatch riguardo al rilascio.
  • Il microservizio DeployWatch verifica che tutti i contenitori in produzione siano stati aggiornati (si sono verificati casi in cui non tutti venivano aggiornati).
  • Una notifica riguardante i risultati del deployment in produzione viene inviata tramite il microservizio Notify.

12. Gli sviluppatori avranno 30 minuti per avviare il rollback del compito in produzione nel caso venga riscontrato un comportamento errato del microservizio. Trascorso questo tempo, il compito sarà automaticamente integrato nel master (microservizio Git).

13. Dopo un merge riuscito nel master, lo stato del compito sarà cambiato in Closed (microservizio Jira).

Lo schema non pretende di essere completamente dettagliato (in realtà ci sono ancora più passi), ma consente di valutare il grado di integrazione nei processi. Non consideriamo questo schema ideale e stiamo migliorando i processi di supporto automatico ai rilasci e al deployment.

Cosa succede dopo

Abbiamo grandi piani per lo sviluppo dell'automazione, ad esempio, eliminare le operazioni manuali nei rilasci del monolito, migliorare il monitoraggio del deployment automatico e migliorare l'interazione con gli sviluppatori.

Ma ci fermiamo qui per ora. Abbiamo coperto superficialmente molti argomenti nell'overview dell'automazione e alcuni non sono stati trattati affatto, quindi siamo felici di rispondere a domande. Aspettiamo suggerimenti su cosa approfondire, scrivete nei commenti.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS 🔥 Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS | ProHoster