In True Engineering abbiamo configurato il processo di consegna continua degli aggiornamenti sui server del cliente e desideriamo condividere questa esperienza.
Innanzitutto abbiamo sviluppato un sistema online per il cliente e lo abbiamo distribuito nel nostro cluster Kubernetes. Ora la nostra soluzione ad alta capacità è stata trasferita sulla piattaforma del cliente, per la quale abbiamo impostato un processo di Continuous Deployment completamente automatico. Grazie a questo, abbiamo ridotto il time-to-market – la consegna delle modifiche nell'ambiente di produzione.
In questo articolo parleremo di tutte le fasi del processo di Continuous Deployment (CD) o di consegna degli aggiornamenti sulla piattaforma del cliente:
- come inizia questo processo,
- sincronizzazione con il repository Git del cliente,
- compilazione del backend e del frontend,
- distribuzione automatica dell'applicazione nell'ambiente di test,
- distribuzione automatica in produzione.
Durante il processo condivideremo i dettagli della configurazione.

1. Avvio del CD
Il Continuous Deployment inizia quando lo sviluppatore pubblica le modifiche nel ramo di rilascio del nostro repository Git.
La nostra applicazione è basata su un'architettura a microservizi e tutti i suoi componenti sono memorizzati in un unico repository. Grazie a ciò, tutti i microservizi vengono assemblati e installati anche se uno di essi è stato modificato.
Abbiamo organizzato il lavoro tramite un unico repository per diverse ragioni:
- Comodità di sviluppo: l'applicazione è in continua evoluzione, quindi è possibile lavorare su tutto il codice contemporaneamente.
- Un'unica pipeline CI/CD che garantisce che l'applicazione, come un sistema unico, superi tutti i test e venga consegnata nell'ambiente di produzione del cliente.
- Escludiamo confusione nelle versioni: non dobbiamo conservare una mappa delle versioni dei microservizi né descrivere la propria configurazione in script Helm per ciascun microservizio.
2. Sincronizzazione con il repository Git del codice sorgente del cliente
Le modifiche apportate vengono automaticamente sincronizzate con il repository Git del cliente. Lì è configurata la build dell'applicazione, che viene avviata dopo l'aggiornamento del ramo, e il deployment in produzione. Entrambi i processi avvengono nel loro ambiente dal repository Git.
Non possiamo lavorare direttamente con il repository del cliente, poiché abbiamo bisogno di ambienti propri per lo sviluppo e il testing. Utilizziamo a tal fine il nostro repository Git, che è sincronizzato con il loro repository Git. Non appena uno sviluppatore pubblica modifiche nel ramo corrispondente del nostro repository, GitLab invia immediatamente queste modifiche al cliente.

Dopo questo, è necessario effettuare la costruzione. Questa consiste in diverse fasi: costruzione del backend e del frontend, testing e consegna in produzione.
3. Costruzione del backend e del frontend
La costruzione del backend e del frontend è composta da due compiti paralleli, realizzati nel sistema GitLab Runner. La configurazione della costruzione iniziale si trova nello stesso repository.
.
GitLab Runner preleva il codice dal repository corretto, costruisce l'applicazione Java con il comando di build e lo invia al Docker registry. Qui costruiamo il backend e il frontend, otteniamo le immagini Docker, che siamo in grado di caricare nel repository del cliente. Per gestire le immagini Docker, utilizziamo .
Sincronizziamo le versioni delle nostre immagini con la versione di rilascio che sarà pubblicata in Docker. Per un funzionamento fluido, abbiamo apportato alcune configurazioni:
1. Tra l'ambiente di test e quello di produzione, i container non vengono ricostruiti. Abbiamo implementato la parametrizzazione affinché lo stesso container possa funzionare senza ricostruzione con tutte le configurazioni, variabili ambientali e servizi sia nell'ambiente di test che in produzione.
2. Per aggiornare l'applicazione tramite Helm è necessario specificarne la versione. Abbiamo la build del backend, del frontend e l'aggiornamento dell'applicazione come tre compiti distinti, quindi è importante utilizzare ovunque la stessa versione dell'applicazione. Per questo utilizziamo i dati dalla cronologia Git, poiché la nostra configurazione del cluster K8S e dell'applicazione si trova nello stesso repository Git.
Otteniamo la versione dell'applicazione dai risultati dell'esecuzione del comando
git describe --tags --abbrev=7.
4. Distribuzione automatica di tutte le modifiche nell'ambiente di test (UAT)
Il prossimo passo in questo script di build è l'aggiornamento automatico del cluster K8S. Questo avviene a condizione che tutte le applicazioni siano state costruite e tutti gli artefatti pubblicati nel Docker Registry. Dopo di che, si avvia l'aggiornamento dell'ambiente di test.
L'aggiornamento del cluster viene avviato tramite . Se qualcosa va storto, Helm annullerà automaticamente tutte le modifiche apportate. Non è necessario monitorarne il funzionamento.
Forniamo insieme alla build la configurazione del cluster K8S. Pertanto, il prossimo passo è aggiornarla: configMaps, deployments, services, secrets e qualsiasi altra configurazione K8S che abbiamo modificato.
Successivamente, Helm avvia un aggiornamento RollOut dell'applicazione stessa nell'ambiente di test. Prima che l'applicazione venga distribuita in produzione. Questo è fatto affinché gli utenti possano verificare manualmente le funzionalità aziendali che abbiamo rilasciato nell'ambiente di test.
5. Distribuzione automatica di tutte le modifiche in Prod
Per distribuire l'aggiornamento nell'ambiente di produzione, è sufficiente premere un pulsante in GitLab — e i container vengono immediatamente inviati all'ambiente di produzione.
La stessa applicazione può funzionare senza ricompilazione in ambienti diversi — test e produzione. Utilizziamo gli stessi artefatti senza modificare nulla nell'applicazione, configurando i parametri esternamente.
La flessibile parametrizzazione delle impostazioni dell'applicazione dipende dall'ambiente in cui l'applicazione verrà eseguita. Abbiamo estratto tutte le impostazioni degli ambienti all'esterno: tutto viene parametrizzato attraverso la configurazione K8S e i parametri Helm. Quando Helm distribuisce la build nell'ambiente di test, vengono applicati parametri di test, mentre nell'ambiente di produzione si applicano parametri di prodotto.
La parte più difficile è stata parametrizzare tutti i servizi e le variabili utilizzati, che dipendono dall'ambiente, e trasformarli in variabili ambientali e nella descrizione della configurazione dei parametri per Helm.
Nei parametri dell'applicazione vengono utilizzate variabili ambientali. I loro valori vengono definiti nei contenitori tramite K8S configmap, che è templateizzato utilizzando i template Go. Ad esempio, per assegnare una variabile ambientale per il nome del dominio, è possibile fare così:
APP_EXTERNAL_DOMAIN: {{ (pluck .Values.global.env .Values.app.properties.app_external_domain | first) }}
.Values.global.env – in questa variabile viene memorizzato il nome dell'ambiente (prod, stage, UAT).
.Values.app.properties.app_external_domain – in questa variabile definiamo nel file .Values.yaml il dominio necessario
Durante l'aggiornamento dell'applicazione, Helm crea il file configmap.yaml dai modelli e riempie il valore di APP_EXTERNAL_DOMAIN con il valore corretto a seconda dell'ambiente in cui l'aggiornamento dell'applicazione viene avviato. Questa variabile viene impostata già nel contenitore. L'accesso è disponibile dall'applicazione, quindi in ogni ambiente dell'applicazione ci sarà un valore diverso per questa variabile.
Recentemente, Spring Cloud ha introdotto il supporto per K8S, inclusa la gestione dei configMaps: . Attualmente, il progetto è in fase di sviluppo attivo e sta subendo cambiamenti radicali, quindi non possiamo utilizzarlo in produzione. Ma monitoriamo attentamente il suo stato e lo utilizziamo nelle configurazioni DEV. Appena sarà stabilizzato, passeremo dall'uso delle variabili d'ambiente a questo.
Totale
Quindi, il Continuous Deployment è impostato e funziona. Tutti gli aggiornamenti avvengono con un solo clic. La consegna delle modifiche nell'ambiente di produzione è automatica. E, cosa importante, gli aggiornamenti non interrompono il funzionamento del sistema.

Piani futuri: migrazione automatica del database
Stiamo considerando di eseguire l'upgrade del database e la possibilità di ripristinare queste modifiche. Infatti, due versioni diverse dell'applicazione sono attive contemporaneamente: la versione precedente continua a funzionare, mentre la nuova viene implementata. Disattiveremo la versione precedente solo quando saremo certi che la nuova versione funzioni correttamente. La migrazione del database deve consentire il funzionamento di entrambe le versioni dell'applicazione.
Pertanto, non possiamo semplicemente cambiare il nome di una colonna o altri dati. Tuttavia, possiamo creare una nuova colonna, copiare i dati dalla colonna precedente e scrivere dei trigger che, all'aggiornamento dei dati, copieranno e aggiorneranno simultaneamente le informazioni nell'altra colonna. Dopo un deploy riuscito della nuova versione dell'applicazione e dopo il periodo di supporto post-lancio, saremo in grado di rimuovere la colonna precedente e il trigger diventato superfluo.
Se la nuova versione dell'applicazione non funziona correttamente, possiamo tornare alla versione precedente, inclusa la precedente versione del database. In altre parole, le nostre modifiche consentiranno di lavorare contemporaneamente con più versioni dell'applicazione.
Pianifichiamo l'automazione della migrazione del database tramite un job K8S, integrandola nel processo di CD. Condivideremo sicuramente questa esperienza su Habr.
Fonte: habr.com
