A 12-Factor App módszertan első alapelve szerint egy alkalmazásnak egy kódbázisa legyen, amelyet verziókezelő rendszerben tárolunk. Ez a kódbázis lehet például egy Git repository. Ugyanebből az egyetlen kódbázisból több különböző környezetbe is történhet telepítés, például fejlesztői, teszt és éles környezetbe.
A legfontosabb gondolat tehát az, hogy egy alkalmazás = egy kódbázis, de ebből a kódbázisból lehet több deploy. A deploy itt azt jelenti, hogy ugyanazt az alkalmazást különböző környezetekben vagy konfigurációval futtatjuk.
Ha van egy webalkalmazásunk, akkor annak teljes forráskódja egyetlen repositoryban található. Ebből készülhet például:
A három környezet nem három külön projekt, hanem ugyanannak az alkalmazásnak három külön futtatási példánya.
Az első ábrán ugyanabból a kódbázisból indul a három környezet. A második ábrán már három külön másolat létezik, ami hosszú távon hibákhoz és eltérésekhez vezethet.
Példa:
my-webapp/ src/ tests/ package.json Dockerfile README.md
Ebben az esetben a my-webapp egyetlen alkalmazás, és egyetlen kódbázissal rendelkezik.
Nem jó, ha ugyanannak az alkalmazásnak több különálló, kézzel szinkronizált másolata van, például:
my-webapp-dev/ my-webapp-test/ my-webapp-prod/
Ez azért problémás, mert idővel a három változat eltér egymástól, és nem lehet biztosan tudni, hogy melyik az aktuális vagy helyes verzió.
Szintén rossz megoldás, ha egyetlen repositoryban több, egymástól független alkalmazás található úgy, hogy azok valójában külön életet élnek.
Ez az elv azért fontos, mert csökkenti a káoszt a fejlesztés és az üzemeltetés során. Ha egy alkalmazásnak több “félig azonos” kódbázisa van, akkor nagyon könnyen előfordulhat, hogy:
Az egyetlen kódbázis biztosítja, hogy mindenki ugyanarra az alapra építsen.
Tegyük fel, hogy készítünk egy Python FastAPI alapú rendszert. A projekt repositoryja például ez:
invoice-service/
app/
main.py
routes.py
requirements.txt
Dockerfile
Ebből ugyanabból a kódbázisból indulhat el:
A különbség nem a forráskódban van, hanem a konfigurációban és a futtatási környezetben.
Egy Node.js backend esetén is ugyanez az elv:
student-api/ src/ package.json package-lock.json .env.example
A fejlesztői, staging és production rendszer mind ugyanebből a repositoryból épül fel. Nem készítünk külön student-api-prod vagy student-api-final mappát.
Kezdő projektekben gyakran előfordul, hogy valaki ezt csinálja:
projekt/ projekt_uj/ projekt_vegso/ projekt_vegso_jav/ projekt_vegleges_tenyleg/
Ez nem verziókezelés, hanem kézi másolgatás. A 12-Factor szemlélet szerint ezt Git repositoryval kell kiváltani, ahol a verziók commitokkal, branchekkel és tagekkel követhetők.
A kódbázis elve szorosan kapcsolódik a verziókezeléshez. A legtipikusabb eszköz erre a Git. A fejlesztők ugyanazt a repositoryt használják, és ott külön ágakon dolgozhatnak. Ettől még maga az alkalmazás továbbra is egyetlen kódbázissal rendelkezik.
Sokan azt hiszik, hogy ha több microservice van, akkor az sérti ezt az elvet. Valójában nem, mert ilyenkor minden microservice külön alkalmazásnak számít, ezért mindegyiknek lehet saját kódbázisa.
Például:
user-service → külön repositoryorder-service → külön repositorypayment-service → külön repositoryEz teljesen helyes, mert ezek külön alkalmazások vagy komponensek.
A 12-Factor App módszertan második alapelve szerint egy alkalmazásnak minden külső függőségét explicit módon deklarálnia kell. Ez azt jelenti, hogy az alkalmazás nem támaszkodhat arra, hogy bizonyos könyvtárak vagy eszközök már telepítve vannak a rendszerben.
A függőségek deklarálása általában egy függőségkezelő rendszer segítségével történik. Így az alkalmazás pontosan meghatározza, hogy milyen külső csomagokra, könyvtárakra vagy frameworkökre van szüksége.
A cél az, hogy az alkalmazás bármilyen környezetben ugyanúgy felépíthető és futtatható legyen.
A modern alkalmazások gyakran több külső könyvtárat használnak, például:
A 12-Factor elv szerint ezeknek a függőségeknek mind szerepelniük kell a projekt konfigurációjában.
Ha valaki letölti a projektet, akkor egyetlen paranccsal telepíthetővé kell válniuk a szükséges csomagoknak.
Python példa:
requirements.txt
fastapi==0.110 uvicorn==0.29 pydantic==2.6
A projekt struktúrája például:
invoice-service/ app/ requirements.txt
Telepítés:
pip install -r requirements.txt
Ez biztosítja, hogy minden fejlesztő ugyanazokat a csomagokat használja.
Node.js esetén a függőségek a package.json fájlban szerepelnek.
{
"name": "student-api",
"dependencies": {
"express": "^4.18.2",
"jsonwebtoken": "^9.0.0"
}
}
Telepítés:
npm install
Ez automatikusan letölti az összes szükséges csomagot.
Java projektekben gyakori a Maven vagy Gradle használata.
Maven példa:
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
A függőségek automatikusan letöltődnek a Maven repositoryból.
Hibás gyakorlat, ha a projekt implicit módon feltételezi, hogy bizonyos könyvtárak már telepítve vannak.
Példa:
# kódban használjuk import fastapi import pandas
de nincs:
requirements.txt
Ebben az esetben egy másik fejlesztő gépén az alkalmazás nem fog elindulni.
Sok fejlesztő találkozott már a következő hibával:
ModuleNotFoundError: No module named 'fastapi'
Ez tipikusan azért történik, mert a projekt függőségei nincsenek deklarálva.
A 12-Factor elv gyakran együtt jár virtuális környezetek használatával.
Python példa:
python -m venv venv source venv/bin/activate pip install -r requirements.txt
Ez biztosítja, hogy a projekt saját csomagkészlettel rendelkezzen.
Docker használatakor a függőségek a Dockerfile-ban jelennek meg.
FROM python:3.11 WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . .
Ebben az esetben a konténer minden szükséges függőséget tartalmaz.
Az explicit függőségkezelés biztosítja, hogy:
Ez különösen fontos nagy csapatoknál.
A függőségek deklarálása nemcsak programkönyvtárakat jelent.
Ide tartozhatnak például:
A 12-Factor App módszertan harmadik alapelve szerint az alkalmazás konfigurációját el kell választani a forráskódtól. A konfigurációt nem a programkódban kell tárolni, hanem környezeti változókban (environment variables).
A konfiguráció olyan értékeket jelent, amelyek környezetenként változhatnak, például:
A 12-Factor elv szerint ezek nem lehetnek hardcode-olva a kódban.
Ha egy alkalmazás több környezetben fut (például fejlesztői, teszt, éles), akkor a konfiguráció különböző lehet.
Például:
| Környezet | Adatbázis |
|---|---|
| fejlesztői | localhost |
| teszt | test-db |
| production | prod-db |
Ha az adatbázis címe a kódban van, akkor minden deploy előtt módosítani kellene a programot. A 12-Factor megközelítés szerint a konfiguráció külső paraméterként kerül a programba.
Python példa:
DATABASE_URL = "postgres://user:password@prod-db:5432/app"
Ebben az esetben:
Ez biztonsági és üzemeltetési problémákat okoz.
Python példa:
import os
DATABASE_URL = os.getenv("DATABASE_URL")
A környezetben:
export DATABASE_URL=postgres://user:password@prod-db:5432/app
Így a kód minden környezetben azonos marad.
const dbUrl = process.env.DATABASE_URL;
Indítás:
DATABASE_URL=postgres://localhost/mydb node server.js
Docker konténer esetén a konfiguráció szintén environment változóként jelenik meg.
docker run -e DATABASE_URL=postgres://db/app myapp
Kubernetesben a konfiguráció gyakran ConfigMap vagy Secret formájában jelenik meg.
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: database-secret
key: url
Tipikus konfigurációk:
Ami nem konfiguráció, az a program működési logikája.
Sok projektben található például ilyen fájl:
config.py
DATABASE_URL = "postgres://localhost/app" SECRET_KEY = "123456"
Ha ezt commitolják, akkor:
Az environment változók használata több előnyt ad:
A 12-Factor App módszertan negyedik alapelve szerint az alkalmazás által használt infrastruktúra komponenseket külső szolgáltatásként (backing service) kell kezelni. Ezek olyan erőforrások, amelyek nem az alkalmazás részei, hanem külön rendszerek, amelyekhez az alkalmazás hálózaton keresztül kapcsolódik.
Tipikus backing service példák:
A fontos elv az, hogy ezek a szolgáltatások cserélhető erőforrásként jelenjenek meg az alkalmazás számára.
Az alkalmazás logikája nem tartalmazza magát az infrastruktúrát. Az adatbázis, cache vagy más komponensek külön szolgáltatásként futnak, és az alkalmazás csak kapcsolódik hozzájuk.
Például egy webalkalmazás használhat egy adatbázist és egy cache rendszert:
Ebben a modellben az adatbázis és a cache külön szolgáltatások.
A backing service elv egyik fontos következménye, hogy a szolgáltatás könnyen lecserélhető.
Például egy alkalmazás használhat:
Az alkalmazás működése ettől nem változik, mert az adatbázis külső szolgáltatásként jelenik meg.
Az alkalmazás minden külső erőforrást külön szolgáltatásként kezel.
Az ötödik faktor azt írja elő, hogy az alkalmazás életciklusát három jól elkülönülő fázisra kell bontani:
A három fázis szétválasztása azért fontos, mert így a telepítés reprodukálható, automatizálható és biztonságos lesz.
A build fázis során a forráskódból egy futtatható csomag (artifact) készül.
Ebben a lépésben történik például:
Példák:
A release fázis a build eredményének és a konfigurációnak az összekapcsolása.
Ekkor jön létre egy konkrét alkalmazásverzió, amely telepíthető.
A release tehát:
build + konfiguráció
A run fázis során az alkalmazás futtatásra kerül a kiválasztott környezetben.
Ez lehet például:
A run fázis már nem módosítja a buildet, csak elindítja az alkalmazást.
Ha a három fázis nem válik el egymástól, akkor nehéz lesz:
A Build–Release–Run modell biztosítja, hogy ugyanaz a build több környezetben is futtatható legyen.
Build:
docker build -t myapp:1.0 .
Release:
docker tag myapp:1.0 registry/myapp:1.0
Run:
docker run myapp:1.0
Egy modern pipeline gyakran pontosan ezt a három lépést követi.
A hatodik faktor szerint az alkalmazás stateless folyamatokból (processes) álljon. Ez azt jelenti, hogy az alkalmazás futó példányai nem tárolhatnak tartós állapotot a saját memóriájukban vagy lokális fájlrendszerükben.
A tartós adatokat mindig külső szolgáltatásban kell tárolni, például adatbázisban vagy cache-rendszerben.
A stateless azt jelenti, hogy bármelyik futó példány képes kiszolgálni egy kérést anélkül, hogy előző kérések állapotát ismerné.
Ha az alkalmazás több példányban fut, akkor a rendszer bármelyik példányhoz irányíthatja a kéréseket.
Ebben a modellben több alkalmazás példány fut egyszerre. Mindegyik ugyanahhoz az adatbázishoz kapcsolódik, és egyik sem tárol tartós adatot saját magában.
Hibás megoldás, ha az alkalmazás a felhasználói állapotot a saját memóriájában tárolja.
Példa:
sessions = {}
def login(user):
sessions[user.id] = "active"
Ha az alkalmazás több példányban fut, akkor az egyik példány memóriájában lévő állapot a másik példány számára nem lesz elérhető.
A felhasználói állapotot külső rendszerben tároljuk.
Például Redis-ben:
Így minden alkalmazás példány ugyanazt az állapotot látja.
A stateless elv a fájlokra is vonatkozik.
Hibás megoldás:
/tmp/uploads/file1.jpg
Ha az alkalmazás új példányba kerül, a fájl eltűnhet.
Jó megoldás:
A stateless folyamatok lehetővé teszik:
A cloud rendszerek gyakran automatikusan indítanak és állítanak le alkalmazás példányokat.
Ha az alkalmazás stateless, akkor ez nem okoz problémát.
A hetedik faktor azt mondja ki, hogy az alkalmazás önálló szolgáltatásként publikálja magát egy porton keresztül. Az alkalmazás saját HTTP vagy hálózati szervert indít, és ezen keresztül válik elérhetővé.
Ez azt jelenti, hogy az alkalmazás nem függ külső web szervertől, hanem maga biztosítja a szolgáltatás elérését.
Sok régebbi rendszerben az alkalmazás egy külső webszerverhez kapcsolódik.
Példa:
Ebben a modellben a webszerver tölti be az alkalmazást, például egy plugin vagy modul segítségével. A 12-Factor megközelítés ezzel szemben azt javasolja, hogy az alkalmazás saját szervert indítson, és egy porton keresztül legyen elérhető.
Egy Node.js vagy Python webalkalmazás gyakran így indul:
app.listen(8080)
vagy
uvicorn main:app --port 8000
Az alkalmazás ekkor közvetlenül a megadott porton érhető el.
Az alkalmazás saját szervert futtat, és közvetlenül fogadja a kéréseket.
Konténeres rendszerekben ez különösen fontos.
Egy Docker konténer tipikusan egy porton szolgáltat.
Példa:
docker run -p 8000:8000 myapp
Az alkalmazás a konténeren belül a 8000-es porton fut.
Microservice rendszerekben minden szolgáltatás saját porton publikálja magát.
Minden szolgáltatás külön porton érhető el.
A port binding lehetővé teszi:
A platform (például Kubernetes vagy egy cloud szolgáltató) képes a szolgáltatásokat automatikusan összekapcsolni.
A nyolcadik faktor azt írja le, hogyan lehet az alkalmazást skálázni. A 12-Factor App módszertan szerint az alkalmazás több folyamat indításával skálázható, nem pedig egyetlen nagyobb folyamat erősítésével. Ez azt jelenti, hogy ha nő a terhelés, akkor több azonos alkalmazás példányt indítunk el párhuzamosan.
A skálázás két alapvető módja létezik:
A 12-Factor szemlélet a horizontális skálázást támogatja.
A terheléselosztó (load balancer) a kéréseket több futó példány között osztja el.
Az alkalmazások gyakran több különböző típusú folyamatot tartalmaznak.
Például:
Ezek külön folyamatként futtathatók.
A webfolyamat a kéréseket fogadja, a worker pedig háttérfeladatokat végez.
Ha sok háttérfeladat érkezik, több worker indítható.
Modern cloud rendszerekben a skálázás gyakran automatikus.
Példa Kubernetesben:
A rendszer automatikusan indíthat új példányokat.
A több folyamat használata lehetővé teszi:
A kilencedik faktor szerint az alkalmazás folyamatai gyorsan induljanak el és gyorsan álljanak le. Az ilyen folyamatokat könnyen lehet létrehozni vagy megszüntetni anélkül, hogy a rendszer működése megszakadna.
Ez a tulajdonság különösen fontos modern cloud- és konténeres környezetekben, ahol az alkalmazás példányai gyakran automatikusan indulnak és állnak le.
Ha egy alkalmazás új példányát kell indítani (például terhelésnövekedése miatt), akkor az néhány másodperc alatt elinduljon.
Ha pedig egy példányt le kell állítani (például frissítés vagy skálázás miatt), akkor az alkalmazás biztonságosan befejezze a futó műveleteket, majd leálljon.
A gyors indulás lehetővé teszi, hogy a rendszer új példányokat indítson el, amikor nő a terhelés.
A folyamat leállításakor az alkalmazásnak lehetőséget kell adni a futó műveletek befejezésére.
Példa folyamat:
1. a rendszer jelzi a leállítást, 2. az alkalmazás befejezi az aktuális kéréseket, 3. a folyamat kilép.
Egy háttérfeldolgozó (worker) a leállítás előtt befejezheti az aktuális feladatot.
A gyors indulás és leállás lehetővé teszi:
Modern cloud-rendszerek gyakran indítanak új példányokat rövid idő alatt.
Egy Kubernetes-rendszer például új konténert indíthat, ha nő a terhelés. Ha egy példány hibás, a rendszer leállítja és újat indít.
A tizedik faktor azt mondja ki, hogy a fejlesztői (development), teszt (staging) és éles (production) környezetek között a különbség legyen minél kisebb.
Minél jobban hasonlítanak egymásra a környezetek, annál kisebb az esélye annak, hogy az alkalmazás a fejlesztés során működik, de az éles rendszerben hibát okoz.
Sok rendszerben a fejlesztői és az éles környezet nagyon különbözik.
Például:
| Fejlesztés | Production |
|---|---|
| SQLite adatbázis | PostgreSQL |
| lokális fájlrendszer | cloud storage |
| egyszerű szerver | több szerverből álló rendszer |
Ilyenkor gyakran előfordul, hogy a program fejlesztés közben működik, de production környezetben hibát okoz.
A cél az, hogy a környezetek közötti különbség minimális legyen.
Ideális esetben:
A tizenegyedik faktor szerint az alkalmazás nem kezeli közvetlenül a log fájlokat, hanem a logokat folyamként (stream) írja ki a standard kimenetre.
A logok gyűjtését, tárolását és feldolgozását a futtatási környezet végzi, nem maga az alkalmazás.
Az alkalmazás egyszerűen kiírja a naplóüzeneteket a standard outputra.
Például:
print("User logged in")
vagy
console.log("Server started")
Az alkalmazás nem hoz létre saját logfájlokat és nem foglalkozik a logok archiválásával.
Régebbi rendszerek gyakran közvetlenül fájlba írják a logokat.
Példa:
/var/log/myapp.log
Ez több problémát okozhat:
Az alkalmazás csak kiírja az eseményeket, a platform pedig összegyűjti a logokat.
A log gyűjtő rendszer lehet például:
Docker esetén a logok automatikusan a standard outputra kerülnek.
A Docker rendszer gyűjti és tárolja őket.
Cloud-rendszerekben a logok gyakran központi rendszerbe kerülnek.
Például:
A tizenkettedik faktor szerint az alkalmazáshoz tartozó adminisztratív vagy karbantartási feladatokat külön folyamatként kell futtatni, ugyanabban a környezetben, mint maga az alkalmazás. Az adminisztratív feladatok nem a normál alkalmazásfolyamat részei, hanem egyszeri vagy időszakos műveletek, amelyeket külön kell elindítani.
Tipikus adminisztratív műveletek:
Ezek a feladatok nem a webalkalmazás részeként futnak, hanem külön indított folyamatként.
Egy alkalmazás frissítése előtt gyakran szükséges az adatbázis szerkezetének módosítása.
python manage.py migrate
Ez egy adminisztratív folyamat, amelyet külön kell elindítani.
Az adminfolyamat ugyanazt az adatbázist használja, mint az alkalmazás, de külön fut.
Az adminfolyamatoknak ugyanazt a környezetet kell használniuk, mint az alkalmazásnak.
Ez azt jelenti, hogy:
Így biztosítható, hogy az admin műveletek ugyanúgy működnek, mint az alkalmazás.
Konténeres rendszerben egy admin-feladat külön konténerként is futtatható.
A migrációs konténer csak egyszer fut le.