
Per cominciare, un po' di teoria. Cos'è ?
In parole semplici, è un documento progettato per semplificare lo sviluppo di applicazioni SaaS, che aiuta informando sviluppatori e ingegneri DevOps sui problemi e le pratiche più comunemente riscontrati nello sviluppo di applicazioni moderne.
Questo documento è stato creato dagli sviluppatori della piattaforma Heroku.
La metodologia dei dodici fattori (The Twelve-Factor App) può essere applicata a applicazioni scritte in qualsiasi linguaggio di programmazione e che utilizzano qualsiasi combinazione di servizi esterni (backing services) (database, code di messaggi, cache, ecc.).
Un breve riepilogo dei fattori su cui si basa questa metodologia:
- Base di codice – Una base di codice, tracciata in un sistema di controllo versione, – molteplici distribuzioni
- Dipendenze – Dichiarare esplicitamente e isolare le dipendenze
- Configurazione – Conservare la configurazione nell'ambiente di esecuzione
- Servizi esterni (Backing Services) – Considerare i servizi esterni (backing services) come risorse collegate
- Costruzione, rilascio, esecuzione – Separare rigidamente le fasi di costruzione e esecuzione
- Processi – Eseguire l'applicazione come uno o più processi senza stato (stateless)
- Binding delle porte (Port binding) – Esportare i servizi tramite binding delle porte
- Parallelismo – Scalare l'applicazione utilizzando processi
- Eliminabilità (Disposability) – Massimizzare l'affidabilità tramite avvio rapido e chiusura corretta
- Parità tra sviluppo e produzione dell'applicazione – Mantenere gli ambienti di sviluppo, staging e produzione il più simili possibile
- Log (Logs) – Considerare il log come un flusso di eventi
- Attività di amministrazione – Eseguire attività di amministrazione/gestione tramite processi one-off
Ulteriori informazioni sui 12 fattori possono essere ottenute dalle seguenti risorse:
- – da leggere assolutamente
- – traduzione ufficiale
- – uno sguardo fresco sui 12 fattori con l'obiettivo di migliorarli.
Cos'è il Blue-Green deployment?
Il Blue-Green deployment è un modo per consegnare un'applicazione su production in modo che il cliente finale non veda alcuna modifica da parte sua. In altre parole, il deploy dell'applicazione avviene senza downtime.
Schema classico di BG Deploy è come illustrato nell'immagine sottostante.

- All'inizio ci sono 2 server fisici con codice, applicazione e progetto esattamente identici, e c'è un router (bilanciatore).
- Il router inizialmente indirizza tutte le richieste a uno dei server (verde).
- Nel momento in cui è necessario effettuare nuovamente un rilascio, l'intero progetto viene aggiornato sull'altro server (blu), che attualmente non gestisce alcuna richiesta.
- Dopo che il codice su blu server è completamente aggiornato, al router viene dato il comando di switchare da verde in blu il server.
- Ora tutti i clienti vedono il risultato del codice su blu Effettuano attacchi mobili.
- Per un certo periodo, verde il server funge da backup nel caso di un failed deploy su blu server e in caso di problemi, il router reindirizza il flusso degli utenti di nuovo su verde server con la versione stabile precedente, e il nuovo codice viene inviato per revisione e test.
- E alla fine del processo, anche verde il server viene aggiornato. E dopo il suo aggiornamento, il router reindirizza di nuovo il flusso delle richieste su verde il server.
Tutto ciò sembra molto bene e a prima vista non dovrebbe presentare problemi.
Ma poiché viviamo in un mondo moderno, l'opzione di commutazione fisica come indicato nello schema classico non ci si addice. Tieni queste informazioni a mente, torneremo su di esse più tardi.
Consigli utili e dannosi
Avviso: Negli esempi sottostanti sono indicate le utility / metodologie che utilizzo io, puoi usare qualsiasi alternativa con funzioni simili.
La maggior parte degli esempi avrà in qualche modo a che fare con lo sviluppo web (una grande sorpresa), con PHP e Docker.
Nei punti seguenti c’è una semplice descrizione pratica dell'uso dei fattori su esempi specifici. Se desideri ulteriori teorie su questo argomento, fai riferimento ai link sopra alla fonte originale.
1. Base di codice
Utilizza FTP e FileZilla per caricare i file sui server uno alla volta, non conservare il codice se non sul server di produzione.
Il progetto deve sempre avere un'unica base di codice, cioè tutto il codice proviene da uno Git repository. I server (production, staging, test1, test2…) utilizzano il codice da rami di un unico repository comune. In questo modo otteniamo coerenza nel codice.
2. Dipendenze
Scarica tutte le librerie in cartelle direttamente nella root del progetto. Gli aggiornamenti si effettuano semplicemente trasferendo il nuovo codice nella cartella con la versione attuale della libreria. Installa tutti gli strumenti necessari direttamente sul server host dove funzionano anche altre 20 servizi.
Il progetto deve sempre avere un elenco di dipendenze chiaramente definito (con dipendenze intendo anche l'ambiente). Tutte le dipendenze devono essere esplicitamente definite e isolate.
Prendiamo come esempio Composer e Docker.
Composer — un gestore di pacchetti che consente di installare librerie PHP. Composer permette di specificare versioni in modo rigoroso o non rigoroso e di definirle esplicitamente. Sul server possono esserci 20 progetti diversi e ciascuno avrà la propria lista personale di pacchetti e librerie indipendente dagli altri.
Docker — uno strumento che consente di definire e isolare l'ambiente in cui l'applicazione funzionerà. Pertanto, così come con composer, ma in modo più sostanziale, possiamo definire con cosa l'applicazione lavora. Scegliere una versione specifica di PHP, installare solo i pacchetti necessari per il funzionamento del progetto, senza aggiungere nulla di superfluo. E soprattutto, senza sovrapporsi ai pacchetti e all'ambiente della macchina host e degli altri progetti. Quindi tutti i progetti sul server che funzionano tramite Docker possono utilizzare qualsiasi set di pacchetti e qualsiasi ambiente completamente diverso.
3. Configurazione
Conserva le configurazioni come costanti direttamente nel codice. Costanti separate per il server di test, costanti separate per la produzione. Fai dipendere il funzionamento dell'applicazione dall'ambiente direttamente nella logica aziendale del progetto utilizzando costrutti if else.
Configurazioni — è l'unica cosa che dovrebbe differenziare i deployment del progetto. In ideale, le configurazioni dovrebbero essere passate attraverso variabili di ambiente (env vars).
Cioè, anche se si memorizzano più file di configurazione .config.prod .config.local e si rinominano al momento del deploy in .config (il config principale da cui l'applicazione legge i dati) — questo non è un approccio corretto, poiché in tal caso le informazioni delle configurazioni sarebbero accessibili a tutti gli sviluppatori dell'applicazione e i dati dal server di produzione sarebbero compromessi. Tutte le configurazioni devono essere memorizzate direttamente nel sistema di deploy (CI/CD) e generate per diversi ambienti con valori diversi necessari per ciascun ambiente proprio al momento del deploy.
4. Servizi di terze parti (Backing Services)
Legate rigidamente all'ambiente, utilizzate connessioni diverse per gli stessi servizi in determinati ambienti.
In realtà, questo punto si sovrappone fortemente a quello delle configurazioni, poiché senza questo punto non è possibile ottenere dati di configurazione normali e, in generale, la possibilità di configurazione svanirebbe.
Tutte le connessioni ai servizi esterni, come server di coda, database e servizi di caching, devono essere uniche sia per l'ambiente locale che per l'ambiente esterno / di produzione. In altre parole, posso in qualsiasi momento cambiare la stringa di connessione e sostituire le chiamate al database #1 con il database #2 senza modificare il codice dell'applicazione. O, per anticipare come esempio, è come se quando si scala il servizio, non è necessario specificare la connessione in un modo particolare per un ulteriore server di cache.
5. Build, rilascio, esecuzione
Mantieni sul server solo la versione finale del codice, senza possibilità di fare rollback sul rilascio. Non riempi spazio su disco. Chi pensa di poter lanciare codice in produzione con errori è un cattivo programmatore!
Tutte le fasi del deploy devono essere separate tra loro.
Assicurati di avere la possibilità di fare rollback. Fai rilasci mantenendo accessibili copie vecchie dell'applicazione (già buildate e pronte per l'uso), in modo che nel caso di errori tu possa ripristinare una versione precedente. In altre parole, c'è una cartella releases e una cartella current, e dopo un deploy e una build riusciti, la cartella current si collega tramite un link simbolico al nuovo rilascio che si trova all'interno releases con un nome per il numero del rilascio.
Qui ricordiamo il Blue-Green deployment, che non solo consente di effettuare il passaggio tra il codice, ma anche di scambiare tutte le risorse e persino gli ambienti, con la possibilità di ripristinare tutto.
6. Processi
Conserva i dati di stato dell'applicazione direttamente all'interno dell'app stessa. Utilizza sessioni nella memoria operativa dell'applicazione. Utilizza il maggior numero possibile di risorse condivise con i servizi esterni. Legati al fatto che l'app può avere solo un processo e non permettere la scalabilità.
Per quanto riguarda le sessioni, conserva i dati solo nella cache controllata da servizi esterni (memcached, redis), in modo che anche se hai 20 processi dell'applicazione in esecuzione, ognuno di essi, accedendo alla cache, possa continuare a lavorare con il cliente nello stesso stato in cui l'utente si trovava interagendo con l'applicazione in un altro processo. Con questo approccio, si ottiene così che, indipendentemente da quante copie di servizi esterni vengono utilizzate, tutto funzionerà regolarmente e senza problemi di accesso ai dati.
7. Binding delle porte
Solo il server web deve sapere come lavorare con i servizi esterni. È meglio sollevare i servizi esterni direttamente all'interno del server web. Ad esempio come modulo PHP in Apache.
Tutti i tuoi servizi devono essere accessibili l'uno all'altro tramite l'accesso a un indirizzo e una porta (localgost:5432, localhost:3000, nginx:80, php-fpm:9000), cioè da nginx posso accedere sia a php-fpm che a postgres, e da php-fpm a postgres e nginx, e fondamentalmente da ciascun servizio posso accedere a un altro servizio. In questo modo, la vitalità di un servizio non è legata alla vitalità di un altro servizio.
8. Parallelismo
Lavora con un solo processo, altrimenti potrebbe succedere che più processi non riescano a collaborare!
Lascia la possibilità di scalabilità. Docker Swarm è perfetto per questo.
Docker Swarm è uno strumento per creare e gestire cluster di container sia tra diverse macchine che tra molti container su una singola macchina.
Utilizzando swarm, posso determinare quanti risorse assegnare a ciascun processo e quanti processi della stessa servizio avviare. L'load balancer interno, ricevendo dati su una porta specifica, li inoltrerà automaticamente ai processi. In questo modo, notando che il carico sul server è aumentato, posso aggiungere più processi, riducendo così il carico su determinati processi.
9. Disposabilità
Non utilizzare code per lavorare con processi e dati. L'uccisione di un processo deve influire sul funzionamento dell'intera applicazione. Se un servizio si interrompe, cade tutto.
Ogni processo e servizio può essere spento in qualsiasi momento e questo non dovrebbe influenzare altri servizi (non si intende che un servizio diventi inaccessibile a un altro servizio, ma che un altro servizio non si spenga in seguito a questo). Tutti i processi devono terminare in modo ordinato, in modo tale che al termine non ci siano danni ai dati e alla successiva accensione il sistema funzioni correttamente. Cioè, anche in caso di arresto anomalo, i dati non devono subire danni (a tal proposito, va bene un meccanismo di transazioni; le query nel database funzionano solo in gruppi, e se almeno una query del gruppo non viene eseguita o si verifica un errore, nessun'altra query del gruppo verrà eseguita effettivamente).
10. Parità tra sviluppo e produzione
La versione di produzione, staging e locale dell'applicazione devono essere diverse. In produzione utilizziamo il framework Yii Lite, mentre localmente utilizziamo Yii, così funziona più velocemente in produzione!
In realtà, tutte le distribuzioni e il lavoro con il codice devono avvenire in ambienti quasi identici (non si parla di hardware fisico). Inoltre, chiunque faccia parte del team di sviluppo dovrebbe essere in grado di distribuire il codice in produzione se necessario, non solo un reparto devops appositamente addestrato, capace di lanciare l'applicazione in produzione solo grazie a poteri speciali.
In questo, ci aiuta anche Docker. Seguendo tutti i punti precedenti, l'uso di Docker renderà il processo di distribuzione dell'ambiente, sia in produzione che sulla macchina locale, una questione di una o due righe di comando.
11. Registrazione
Scriviamo i log in file e nel database! Non puliamo file e database dai log. Compreremo semplicemente un disco rigido da 9000 petabyte e va bene così.
Tutti i log devono essere considerati come un flusso di eventi. L'applicazione stessa non dovrebbe occuparsi dell'elaborazione dei log. I log devono essere inviati o a stdout, oppure trasmessi tramite un protocollo come udp, in modo che il lavoro dell'applicazione con i log non crei alcun problema. In questo caso, graylog è una buona opzione. Graylog, ricevendo tutti i log tramite udp (non è necessario attendere una risposta per l'accettazione del pacchetto), non interferisce in alcun modo con l'applicazione e si occupa solo della strutturazione e del trattamento dei log. La logica dell'applicazione non cambia per lavorare con approcci simili.
12. Compiti di amministrazione
Per aggiornare dati, database e così via, utilizzare un endpoint appositamente creato nell'API, il cui completamento due volte di seguito porterà a una duplicazione dei dati. Ma voi non siete fessi, non premerete due volte, e le migrazioni non ci servono.
Tutti i compiti di amministrazione devono essere eseguiti nell'ambiente in cui si trova tutto il codice, a livello di versioni. Quindi, se dobbiamo modificare la struttura del database, non lo faremo manualmente cambiando i nomi delle colonne e aggiungendo nuove colonne tramite strumenti di gestione del database. Per queste operazioni, creiamo script separati — migrazioni, che vengono eseguiti ovunque e in tutti gli ambienti con risultati comuni e chiari. Per tutti gli altri compiti, come popolare il progetto con dati, devono essere applicate metodologie simili.
Esempio di implementazione in PHP, Laravel, Laradock, Docker-Compose
P.S. Tutti gli esempi sono stati fatti su MacOS. La maggior parte si adatta anche a Linux. Gli utenti Windows, scusate, ma non lavoro con Windows da molto tempo.
Immaginiamo una situazione in cui non è installata alcuna versione di PHP sul PC e non c'è nulla.
Installiamo l'ultima versione di docker e docker-compose. (questo può essere trovato su Internet)
docker -v &&
docker-compose -v

1. Installiamo
git clone https://github.com/Laradock/laradock.git &&
ls

Per quanto riguarda Laradock, posso dire che è una cosa davvero fantastica, che raccoglie molti container e strumenti ausiliari. Tuttavia, non raccomanderei di utilizzare Laradock senza modifiche in produzione a causa della sua eccessività. È meglio creare i propri container basandosi sugli esempi di Laradock, così ci sarà spazio per ottimizzazioni, poiché non è necessario avere tutto ciò che offre contemporaneamente.
2. Configuriamo Laradock per il lavoro della nostra applicazione.
cd laradock &&
cp env-example .env

2.1. Apriamo il catalogo habr (la cartella principale in cui è stato clonato laradock) in un qualsiasi editor. (Nel mio caso PHPStorm)
In questa fase impostiamo solo il nome al progetto.

2.2. Avviamo l'immagine del workspace. (Nel vostro caso, le immagini impiegheranno un po' per essere costruite)
Il workspace è un'immagine appositamente preparata per lavorare con il framework a nome dello sviluppatore.
Entriamo all'interno del contenitore usando
docker-compose up -d workspace &&
docker-compose exec workspace bash

2.3. Installiamo Laravel
composer create-project --prefer-dist laravel/laravel application 
2.4. Dopo l'installazione, verifichiamo se è stata creata la directory con il progetto e chiudiamo il compose.
ls
exit
docker-compose down

2.5. Torniamo indietro in PHPStorm e impostiamo il percorso corretto per la nostra applicazione laravel nel file .env.

3. Aggiungiamo tutto il codice in Git.
Per fare ciò, creiamo un repository su Github (o altrove). Accediamo nel terminale alla directory habr ed eseguiamo il seguente codice.
echo "# habr-12factor" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin git@github.com:nzulfigarov/habr-12factor.git # qui ci sarà il link al vostro repo
git push -u origin master
git status
Controlliamo se va tutto bene.

Per comodità, consiglio di utilizzare un'interfaccia visiva per Git, nel mio caso si tratta di . (qui c'è il link di affiliazione)
4. Avviamo!
Prima di avviare, assicurati che non ci sia nulla in ascolto sulle porte 80 e 443.
docker-compose up -d nginx php-fpm 
Così, il nostro progetto è composto da 3 servizi separati:
- nginx — server web
- php-fpm — php per gestire le richieste dal server web
- workspace — php per lo sviluppatore
Finora siamo riusciti a creare un'applicazione corrispondente ai punti 4 su 12, nello specifico:
1. Base di codice — tutto il codice si trova in un unico repository (piccola nota: potrebbe essere corretto inserire docker all'interno del progetto laravel, ma non è fondamentale).
2. Dipendenze — Tutte le nostre dipendenze sono chiaramente elencate nel application/composer.json e in ogni Dockerfile di ciascun contenitore.
3. Servizi esterni (Backing Services) — Ogni servizio (php-fpm, nginx, workspace) vive la sua vita e viene connesso dall'esterno e, durante il lavoro con un servizio, l'altro non verrà toccato.
4. Processi — ogni servizio è un singolo processo. Ogni servizio non mantiene uno stato interno.
5. Binding delle porte (Port binding)
docker ps

Come vediamo, ogni servizio è avviato sulla propria porta e accessibile a tutti gli altri servizi.
6. Parallelismo
Docker ci consente di avviare più processi per gli stessi servizi con bilanciamento automatico del carico tra di essi.
Fermiamo i contenitori e li avviamo di nuovo tramite il flag —scale
docker-compose down &&
docker-compose up -d --scale php-fpm=3 nginx php-fpm

Come possiamo vedere, sono state create delle copie del container php-fpm. Non è necessario apportare modifiche a questo container. Continuiamo a collegarci a lui utilizzando la porta 9000, mentre Docker gestisce il carico tra i container.
7. Eliminabilità (Disposability) — ogni container può essere interrotto senza danneggiare gli altri. Fermare o riavviare un container non influenzerà il funzionamento dell'applicazione nei successivi avvii. Ogni container può essere avviato in qualsiasi momento.
8. Parità tra sviluppo e produzione dell'applicazione — tutti i nostri ambienti sono identici. Una volta avviata la sistemazione sul server in produzione, non sarà necessario modificare nulla nei tuoi comandi. Tutto si baserà esattamente su Docker.
9. Log (Logs) — tutti i log di questi container vengono inviati in stream e sono visibili nella console di Docker. (In questo caso, con altri container personalizzati, potrebbe non essere così se non ci si preoccupa di questo)
docker-compose logs -f 
Tuttavia, c'è una limitazione in quanto i valori default in PHP e Nginx registrano anch'essi i log in un file. Per soddisfare i 12 fattori, è necessario disabilitare registrare i log in un file nelle configurazioni di ciascun container separatamente.
Inoltre, Docker offre la possibilità di indirizzare i log non solo a stdout, ma anche a strumenti come graylog di cui ho parlato sopra. E all'interno di graylog, possiamo gestire i log come vogliamo e la nostra applicazione non se ne accorgerà affatto.
10. Attività di amministrazione — tutte le operazioni di amministrazione vengono gestite da Laravel grazie allo strumento artisan esattamente come avrebbero voluto i creatori di un'applicazione a 12 fattori.
Come esempio, mostrerò come vengono eseguite alcune comandi.
Entriamo nel container.
docker-compose exec workspace bash
php artisan list

Ora possiamo utilizzare qualsiasi comando. (Nota che non abbiamo configurato il database e la cache, quindi metà dei comandi non verrà eseguita correttamente, poiché sono destinati a funzionare con la cache e il db).

11. Configurazioni e 12. Costruzione, rilascio, esecuzione
Questa parte volevo dedicarla al Blue-Green Deployment, ma è risultata troppo estesa per questo articolo. Scriverò un articolo separato su questo tema.
In poche parole, il concetto si basa su sistemi CI/CD come Jenkins e Gitlab CI. In entrambi si possono definire variabili ambientali collegate a un ambiente specifico. Pertanto, in questo scenario si attuerà il punto con Le configurazioni.
E il punto su Costruzione, rilascio, esecuzione viene risolto dalle funzioni integrate in entrambe le utility chiamate Pipeline.
Pipeline consente di suddividere il processo di deployment in molte fasi, evidenziando le fasi di build, release e execution. Inoltre, nel Pipeline, puoi creare backup e fare praticamente qualsiasi cosa. Questo strumento ha un potenziale illimitato.
Il codice dell'applicazione si trova su .
Non dimenticare di inizializzare il submodule durante il cloning di questo repository.
P.S.: Tutti questi approcci possono essere utilizzati con qualsiasi altra utility e linguaggio di programmazione. L'importante è che l'essenza non cambi.
Fonte: habr.com
