
Za početak, malo teorije. Što je ?
Jednostavnim rečima, ovo je dokument koji ima za cilj pojednostaviti razvoj SaaS aplikacija, pomaže tako što informira programere i DevOps inženjere o problemima i praksama koji su se najčešće susretali u razvoju modernih aplikacija.
Dokument je sastavljen od strane developera platforme Heroku.
Metodologija dvanaest faktora (The Twelve-Factor App) može se primeniti na aplikacije napisane na bilo kojem programskom jeziku i koristeći bilo koje kombinacije eksternih usluga (backing services) (baze podataka, redovi poruka, predmemorije itd.).
Ukratko o samim faktorima na kojima se bazira ova metodologija:
- Kôdna baza – Jedna kôdna baza koja se prati u sistemu kontrole verzija, – više implementacija
- Ovisnosti – Jasno deklarirajte i izolujte zavisnosti
- Konfiguracija – Čuvajte konfiguraciju u okruženju izvršavanja
- Eksterne usluge (Backing Services) – Smatrajte eksterne usluge (backing services) povezanim resursima
- Izgradnja, izdanje, izvršavanje – Strogo razdvojite faze izgradnje i izvršavanja
- Procesi – Pokrenite aplikaciju kao jedan ili više procesa koji ne zadržavaju unutrašnje stanje (stateless)
- Veza portova (Port binding) – Izvezite servise putem vezivanja portova
- Paralelizam – Skalirajte aplikaciju korišćenjem procesa
- Zbrinjavanje (Disposability) – Maksimizujte pouzdanost brzim pokretanjem i pravilnim završetkom rada
- Paritet razvoja/rada aplikacije – Održavajte okruženja za razvoj, staging i proizvodnju što sličnijim
- Evidentiranje (Logs) – Razmatrajte dnevnik kao tok događaja
- Administrativni zadaci – Izvršavajte administrativne/zadate zadatke putem jednokratnih procesa
Više informacija o 12 faktora možete dobiti iz sledećih resursa:
- — obavezno za čitanje
- — službeni prevod
- — svež pogled na 12 faktora s ciljem unapređenja.
Šta je Blue-Green deployment?
Blue-Green deployment – to je način isporuke aplikacije na production tako da krajnji klijent ne primjećuje nikakve promjene s njegove strane. Drugim riječima, implementacija aplikacije bez prekida rada.
Klasična shema BG Deploy izgleda kao što je prikazano na slici ispod.

- Na početku postoje 2 fizička servera s potpuno istim kodom, aplikacijom, projektom, i postoji usmjerivač (balansador).
- U prvom trenutku usmjerivač preusmjerava sve zahtjeve na jedan od servera (zelena).
- U trenutku kada je potrebno ponovno učiniti izdanja, cijeli projekt se ažurira na drugom serveru (plava), koji u ovom trenutku ne obrađuje nikakve zahtjeve.
- Nakon što je kod na plavom serveru potpuno ažuriran, usmjerivaču se daje komanda da se prebaciti s zelenog na plava server.
- Sada svi klijenti vide rezultate rada koda s plavog poslužitelja.
- Neko vrijeme, zelena server služi kao rezervna kopija u slučaju neuspjelog implementiranja na plava server, a u slučaju neuspjeha i grešaka, usmjerivač ponovno prebacuje tok korisnika nazad na zelena server sa starom stabilnom verzijom, a novi kod se šalje na doradu i testiranje.
- I na kraju procesa, na isti način se ažurira zelena server. I nakon njegovog ažuriranja, usmjerivač prebacuje tok zahtjeva nazad na zelena server.
Sve to izgleda vrlo dobro i na prvi pogled ne bi trebalo biti nikakvih problema.
Ali budući da živimo u modernom svijetu, opcija s fizičkim prebacivanjem kao što je prikazano u klasičnoj shemi nam ne odgovara. Informaciju za sada zadržite, vratit ćemo se njoj kasnije.
Loši i dobri savjeti
Odricanje: U primjerima ispod navedeni su alati / metodologije koje ja koristim, možete koristiti apsolutno bilo koje alternative sličnih funkcija.
Većina primjera će se na ovaj ili onaj način preklapati s web razvojem (iznenađenje), s PHP-om i Dockerom.
U točkama ispod ide jednostavan praktičan opis korištenja faktora na određenim primjerima, ako želite dobiti više teorije na ovu temu, obratite se putem linkova iznad na izvor.
1. Kodna baza
Koristite FTP i FileZilla za prenošenje datoteka na servere jedna po jedna, ne čuvajte kod nigdje osim na proizvodnom serveru.
U projektu uvijek mora postojati jedinstvena kodna baza, odnosno, sav kod dolazi iz jedne Git repo. Poslužitelji (production, staging, test1, test2 ...) koriste kod iz grana jednog zajedničkog repozitorija. Tako ostvarujemo konzistentnost koda.
2. Ovisnosti
Preuzmite sve biblioteke u mape direktno u korijen projekta. Ažuriranja radite jednostavnim premještanjem novog koda u mapu s trenutnom verzijom biblioteke. Instalirajte sve potrebne alate direktno na hosting poslužitelj gdje radi još 20 usluga.
Projekt uvijek treba imati jasno definiranu listu ovisnosti (pod ovisnostima također smatram i okruženje). Sve ovisnosti moraju biti jasno definirane i izolirane.
Kao primjer uzmimo Composer i Docker.
Composer — paketni menadžer koji omogućuje instaliranje PHP biblioteka. Composer omogućava strogo ili nestriktno određivanje verzija, i njihovo jasno definiranje. Na poslužitelju može biti 20 različitih projekata, a svaki ima svoju osobnu listu paketa i biblioteka neovisnu o drugima.
Docker — alat koji omogućuje definiranje i izoliranje okruženja u kojem će aplikacija raditi. Stoga, slično kao i s composerom, ali već detaljnije, možemo odrediti s čim aplikacija radi. Odabrati određenu verziju PHP-a, instalirati samo potrebne pakete za rad projekta, ne dodajući ništa suvišno. A najvažnije, ne preklapajući se s paketima i okruženjem host mašine i drugih projekata. Dakle, svi projekti na poslužitelju radni kroz Docker mogu koristiti apsolutno bilo kakav skup paketa i potpuno različito okruženje.
3. Konfiguracija
Čuvajte konfiguracije kao konstante direktno u kodu. Odvojene konstante za testni poslužitelj, odvojene za produkciju. Vežite rad aplikacije ovisno o okruženju direktno u poslovnoj logici projekta koristeći if else konstrukcije.
Konfiguracije — to je jedina stvar po kojoj se trebaju razlikovati implementacije projekta (deployment). U idealnom slučaju, konfiguracije bi trebale biti proslijeđene putem varijabli okruženja (env vars).
To znači da čak i ako ćete čuvati nekoliko konfiguracijskih datoteka .config.prod, .config.local i preimenovati ih u trenutku implementacije u .config (glavna konfiguracija iz koje aplikacija čita podatke) — to neće biti ispravan pristup, jer će u tom slučaju informacije iz konfiguracija biti javno dostupne svim programerima aplikacije, a podaci s produkcijskog servera će biti kompromitirani. Sve konfiguracije moraju se čuvati izravno u sustavu implementacije (CI/CD) i generirati za različita okruženja s različitim vrijednostima potrebnim za konkretno okruženje upravo u trenutku implementacije.
4. Vanjske usluge (Backing Services)
Čvrsto se oslanjajte na okruženje, koristite različite veze za iste usluge u određenim okruženjima.
Zapravo, ova točka se značajno preklapa s točkom o konfiguracijama, jer bez postojanja ove točke ne možete napraviti normalne konfiguracijske podatke i uopće mogućnost konfiguriranja bi pala.
Sve veze do vanjskih usluga, poput servera za redove, baza podataka, usluga za predmemoriju, trebaju biti jedinstvene za lokalno okruženje kao i za vanjsko / produkcijsko okruženje. Drugim riječima, u bilo kojem trenutku mogu promijeniti vezu zamjenom upita na bazu #1 s bazom #2 bez promjene koda aplikacije. Ili, ako unaprijed dam primjer, bit će korisno da prilikom skaliranja usluge, nećete morati posebno navoditi vezu za dodatni server predmemorije.
5. Sklapanje, objavljivanje, izvršavanje
Na serveru imajte samo konačnu verziju koda, bez mogućnosti vraćanja objave unatrag. Ne trebate zauzimati prostor na disku. Tko misli da može pustiti kod u produkciju s greškom, loš je programer!
Sve faze implementacije moraju biti međusobno razdvojene.
Imajte mogućnost da se vratite unatrag. Radite objave uz čuvanje starih kopija aplikacije (već sastavljenih i spremnih za rad) u brzom pristupu, kako biste u slučaju grešaka mogli obnoviti staru verziju. Dakle, uvjetno rečeno, postoji mapa releases i mapa current, a nakon uspješne implementacije i sastavljanja mapa current se povezuje simboličkom vezom s novom objavom koja se nalazi unutar releases s uvjetnim nazivom broja objave.
Ovdje se prisjećamo Blue-Green implementacije, koja omogućuje ne samo prebacivanje između koda, već i prebacivanje između svih resursa i čak okruženja uz mogućnost povratka na prethodno stanje.
6. Procesi
Držite podatke o stanjima aplikacije izravno unutar same aplikacije. Koristite sesije u RAM-u same aplikacije. Iskoristite što više dijeljenih između vanjskih usluga. Oslonite se na to da aplikacija može imati samo jedan proces i ne dopuštajte mogućnost skaliranja.
Što se tiče sesija, pohranjujte podatke samo u predmemoriji koju kontroliraju vanjske usluge (memcached, redis), na taj način, čak i ako imate 20 pokrenutih procesa aplikacije, bilo koji od njih, pristupajući predmemoriji, može nastaviti raditi s klijentom u istom stanju u kojem je korisnik radio s aplikacijom u drugom procesu. Ovim pristupom, bez obzira na broj kopija vanjskih usluga koje koristite, sve će raditi normalno i bez problema s pristupom podacima.
7. Veza portova (Port binding)
Samo web server treba znati kako raditi s vanjskim uslugama. Još bolje, podignite vanjske usluge unutar web servera. Na primjer, kao PHP modul u Apacheu.
Sve vaše usluge trebaju biti dostupne jedna drugoj putem pristupa nekoj adresi i portu (localgost:5432, localhost:3000, nginx:80, php-fpm:9000), što znači da iz nginx-a mogu pristupiti i php-fpm i postgres, a iz php-fpm do postgres-a i nginx-a, a zapravo iz svake usluge mogu pristupiti drugoj usluzi. Na taj način, održivost usluge nije povezana s održivošću druge usluge.
8. Paralelizam
Radite s jednim procesom, jer bi odjednom više procesa moglo imati problema u međusobnoj komunikaciji!
Ostavite mogućnost skaliranja. Docker swarm je za to odličan.
Docker Swarm je alat za izradu i upravljanje klasterima kontejnera, kako između različitih mašina tako i brojnim kontejnerima na jednoj mašini.
Korištenjem swarma mogu odrediti koliko resursa ću dodijeliti svakom procesu i koliko ću procesa iste usluge pokrenuti, a unutarnji balansirnik, primajući podatke na zadanoj luci, automatski će ih proslijediti procesima. Tako, kada primijetim da je opterećenje na poslužitelju poraslo, mogu dodati više procesa, čime ću smanjiti opterećenje na određenim procesima.
9. Odgojivost (Disposability)
Ne koristite redove za rad s procesima i podacima. Ubijanje jednog procesa treba utjecati na rad cijele aplikacije. Ako jedna usluga padne — sve pada.
Svaki proces i usluga mogu biti isključeni u bilo kojem trenutku, a to ne bi smjelo utjecati na druge usluge (naravno, to ne znači da usluga neće biti dostupna drugoj usluzi, već da druga usluga neće prestati raditi nakon tog). Svi procesi trebaju se završiti nježno, tako da pri njihovom završetku podaci ne budu oštećeni i da će sustav ispravno raditi prilikom sljedećeg uključivanja. To znači da čak i u slučaju neočekivanog završetka, podaci ne bi trebali biti oštećeni (ovdje se može primijeniti mehanizam transakcija, zahtjevi za bazom podataka rade samo u grupama, a ako nijedan zahtjev iz grupe nije uspio ili je postojao neki problem s tim, nijedan drugi zahtjev iz grupe u konačnici se ne izvršava).
10. Paritet razvoja/rada aplikacije
Producentska, staging i lokalna verzija aplikacije trebaju biti različite. Na produku imamo Yii Lite framework, a lokalno Yii, da bi na produku radilo brže!
U stvarnosti, sve implementacije i rad s kodom trebaju biti gotovo u identičnom okruženju (ne govorimo o fizičkom hardveru). Također, kod na produku treba moći pokrenuti bilo koji član tima za razvoj, a ne posebno obučeni devops odjel, koji samo zahvaljujući posebnoj snazi može pokrenuti aplikaciju u produkciji.
Ovdje nam također pomaže Docker. Usklađivanjem svih prethodnih točaka, korištenje dockera će dovesti do procesa implementacije okruženja i na produkciji i na lokalnom stroju do upisa jedne ili dvije komande.
11. Bilježenje (Logs)
Zapise pišemo u datoteke i bazu podataka! Datoteke i baza podataka od zapisa se ne čiste. Kupit ćemo jednostavno tvrdi disk od 9000 petabajta i to je to.
Sve logove treba smatrati tokom događaja. Aplikacija se ne bi trebala baviti obradom logova. Logovi bi trebali biti ispisani u stdout ili slani putem protokola kao što je udp, kako bi aplikaciji rad sa logovima ne stvarao nikakve probleme. Za to je Graylog dobar izbor. Graylog, koji prima sve logove putem udp-a (ovim protokolom nije potrebno čekati odgovor o uspješnom prijemu paketa), na bilo koji način ne smeta aplikaciji i bavi se samo strukturiranjem i obradom logova. Logika aplikacije ne mijenja se za rad s ovakvim pristupima.
12. Administrativni zadaci
Za ažuriranje podataka, baza podataka itd. koristite zasebno stvoreni endpoint u API-ju, čije izvođenje dva puta zaredom može rezultirati dupliciranjem podataka. Ali vi niste budale, nećete kliknuti dva puta, a migracije nam nisu potrebne.
Svi administrativni zadaci trebaju se izvoditi u istoj sredini kao i sav kod, na razini izdanja. Dakle, ako trebamo promijeniti strukturu baze podataka, nećemo to raditi ručno mijenjajući nazive kolona i dodajući nove putem nekih vizualnih alata za upravljanje bazom podataka. Za takve stvari stvaramo posebne skripte – migracije, koje se izvode svugdje i u svim okruženjima s istim i jasnim rezultatom. Za sve ostale zadatke, poput punjenja projekta podacima, trebaju se primijeniti slične metodologije.
Primjer implementacije u PHP-u, Laravelu, Laradocku, Docker-Composeu
P.S. Svi primjeri su rađeni na MacOS-u. Većina odgovara i za Linux. Korisnici Windowsa, izvinjavam se, ali ja već dugo nisam radio s Windowsom.
Zamislite situaciju da na našem PC-u nije instalirana nijedna verzija PHP-a i uopće ništa nema.
Instaliramo docker i docker-compose najnovije verzije. (to možete pronaći na internetu)
docker -v &&
docker-compose -v

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

Što se tiče Laradock-a, mogu reći da je to jako sjajna stvar, u kojoj je okupljeno mnogo kontejnera i pomoćnih alata. Međutim, koristiti Laradock bez modifikacija u produkciji — ne bih preporučio zbog njegove suvišnosti. Bolje je stvoriti vlastite kontejnere oslanjajući se na primjere u Laradock-u, tako da će biti mjesta za optimizaciju, jer nikome nije potrebno sve što je tamo odjednom.
2. Konfiguriramo Laradock za rad naše aplikacije.
cd laradock &&
cp env-example .env

2.1. Otvorite katalog habr (matična mapa u koju je kloniran laradock) u nekom editoru. (U mom slučaju PHPStorm)
U ovoj fazi postavite samo naziv projektu.

2.2. Pokrenite sliku workspace. (U vašem slučaju slike će se neko vrijeme graditi)
Workspace je posebno pripremljena slika za rad s okvirima u ime programera.
Uđite u kontejner pomoću
docker-compose up -d workspace &&
docker-compose exec workspace bash

2.3. Instalirajte Laravel
composer create-project --prefer-dist laravel/laravel application 
2.4. Nakon instalacije provjerite je li direktorij s projektom stvoren, i zatvorite compose.
ls
exit
docker-compose down

2.5. Vratite se u PHPStorm i postavite točnu putanju do našeg laravel aplikacije u datoteci .env.

3. Dodajte sav kod u Git.
Za to ćemo na Githubu (ili negdje drugdje) stvoriti repozitorij. Idite u terminalu u direktorij habr i izvršite sljedeći kod.
echo "# habr-12factor" >> README.md
git init
git add README.md
git commit -m "prvi commit"
git remote add origin git@github.com:nzulfigarov/habr-12factor.git # ovdje će biti link na vaš repozitorij
git push -u origin master
git status
Provjerite je li sve u redu.

Za praktičnost preporučujem korištenje nekog vizualnog sučelja za Git, u mom slučaju to je . (ovdje je referentna poveznica)
4. Pokrenite!
Prije pokretanja provjerite da na portovima 80 i 443 ništa ne visi.
docker-compose up -d nginx php-fpm 
Tako naš projekt se sastoji od 3 odvojena servisa:
- nginx — web server
- php-fpm — php za primanje zahtjeva s web servera
- workspace — php za programera
Trenutno smo postigli da smo stvorili aplikaciju koja odgovara već 4 točkama od 12, a to su:
1. Kôdna baza — sav kod se nalazi u jednom repozitoriju (mala napomena: možda bi bilo ispravno uvrstiti docker unutar laravel projekta, no to nije ključno).
2. Ovisnosti — Sve naše ovisnosti su jasno navedene u application/composer.json i u svakom Dockerfileu svakog kontejnera.
3. Eksterne usluge (Backing Services) — Svaka od službi (php-fpm, nginx, workspace) živi svojim životom i povezuje se izvana, pri radu s jednom službom, druga neće biti pogođena.
4. Procesi — svaka usluga je jedan proces. Svaka od službi ne čuva unutarnje stanje.
5. Veza portova (Port binding)
docker ps

Kao što vidimo, svaka služba je pokrenuta na svom vlastitom portu i dostupna je svim drugim službama.
6. Paralelizam
Docker nam omogućava pokretanje više procesa istih usluga s automatskom ravnotežom opterećenja među njima.
Zaustavite kontejnere i pokrenite ih s opcijom —scale
docker-compose down &&
docker-compose up -d --scale php-fpm=3 nginx php-fpm

Kao što vidimo, stvorene su kopije php-fpm kontejnera. U radu s ovim kontejnerom ne trebamo ništa mijenjati. I dalje se obraćamo njemu putem porta 9000, a Docker za nas regulira opterećenje među kontejnerima.
7. Zbrinjavanje (Disposability) — svaki kontejner se može ugasiti bez štete za druge. Zaustavljanje ili ponovo pokretanje kontejnera neće utjecati na rad aplikacije kod sljedećih pokretanja. Svaki kontejner se također može pokrenuti u bilo kojem trenutku.
8. Paritet razvoja/rada aplikacije — sva naša okruženja su identična. Pokretanjem sustava na serveru u produkciji nećete trebati mijenjati ništa u vašim komandama. Sve će se točno tako temeljiti na Dockeru.
9. Evidentiranje (Logs) — svi logovi u tim kontejnerima izlaze na tok i vidljivi su u Docker konzoli. (u ovom slučaju, zapravo, s drugim vlastitim kontejnerima, to možda neće biti tako ako se ne pobrinete za to)
docker-compose logs -f 
Ali, tu je problem u tome što Default vrijednosti u PHP-u i Nginxu također zapisuju logove u datoteku. Za usklađenost sa 12 faktora, potrebno je može se isključiti snimanje logova u datoteku u konfiguracijama svakog kontejnera zasebno.
Također, Docker omogućava usmjeravanje logova ne samo u stdout, već i u takve stvari kao što su graylog o kojem sam govorio ranije. A unutar grayloga, možemo obrađivati logove kako god želimo i naša aplikacija to neće primijetiti.
10. Administrativni zadaci — svi administrativni zadaci se rješavaju u Laravelu zahvaljujući alatu artisan na način na koji bi to željeli tvorci 12-faktorske aplikacije.
Kao primjer pokazat ću kako se izvršavaju neke komande.
Ušli smo u kontejner.
docker-compose exec workspace bash
php artisan list

Sada možemo iskoristiti bilo koju komandu. (primijetite da nismo konfigurirali bazu podataka i cache tako da će pola komandi neispravno izvršiti, jer su namijenjene za rad s cacheom i bdb).

11. Konfiguracije i 12. Izgradnja, izdanje, izvršavanje
Ovaj dio sam želio posvetiti Blue-Green Deployment-u, ali se to pokazalo previše razrađeno za ovaj članak. O tome ću napisati poseban članak.
U nekoliko rečenica, koncept se temelji na CI/CD sustavima poput Jenkins i Gitlab CI. I u jednom i u drugom možete postaviti varijable okruženja povezane s određenim okruženjem. Prema tome, u tom slučaju će se izvršavati stavka s Konfiguracijama.
A stavka o Izgradnja, izdanje, izvršavanje rješava se ugrađenim funkcijama u oba alata pod nazivom Cjevovod.
Cjevovod omogućuje dijeljenje procesa implementacije u više faza, ističući faze izgradnje, izdanja i izvršenja. Također u Pipelineu možete kreirati sigurnosne kopije, pa čak i sve drugo. Ovaj alat ima neograničen potencijal.
Kod aplikacije je na .
Ne zaboravite inicijalizirati submodule prilikom kloniranja ovog repozitorija.
P.S.: Sve ove pristupe možete koristiti s bilo kojim drugim alatima i programskim jezicima. Bitno je da suština ostane nepromijenjena.
Izvor: habr.com
