12-Factor apps

Hoe bouw ik applicaties

de 12-Factor app methodologie geeft een antwoord hoe de dag van vandaag, in een wereld van private en public cloud, applicaties dienen gebouwd te worden. Deze pagina geeft onze interpratatie van de 12-factor app op basis van het originele document (externe link).

De 12-Factor applicatie is een methodologie voor webapplicaties of software ontworpen als software-as-a-service applicaties. Applicaties die ontwikkeld zijn met deze methodologie hebben de volgende kenmerken:

  • De applicaties hebben geautomatiseerde setups door middel van declaratieve scripts
  • Ze zijn Besturingssysteem onafhankelijk, en bereiken zo maximum portabiliteit
  • Kunnen opgezet worden op moderne cloud platformen (public en private cloud)
  • De omgeving heeft een minimaal verschil tussen development en productie
  • De app moet continue uitgerold kunnen worden (Continuous Deployments)
  • Schaalbaar zonder significante aanpassingen aan de tools, architectuur of development methodes

De 12-Factor app methodologie kan gebruikt worden voor eender welke programmeertaal, gebundeld met externe technologieën zoals databases, caching software, enz.

Voordelen

Enkele voordelen van de 12-factor app methodologie:

  • Het voorkomen van software erosie. Applicaties die ontwikkeld worden, hebben vaak een hoge operationele kost. Het is meestal niet mogelijk de applicatie op nieuwere infrastructuur te plaatsen. Door de 12-factor methodologie te volgen wordt dit zoveel mogelijk gemitigeerd.
  • De app kan zowel op private, als publieke, als container uitgerold worden
  • De 12-factor app methodologie bestaat sinds 2012 en nog is steeds een goede methodologie voor applicatieontwikkeling

De 12 factors

Hieronder bespreken we de 12 factors. Wanneer deze factors toegepast worden tijdens het ontwikkelen van de applicatie, dan spreken we van een applicatie ontwikkeld met de 12-factor methodologie.

I. Codebase

Er is één enkele codebase voor de applicatie. Deze codebase zit in een versie controle systeem (bv git).

  • Worden verschillende codebases gebruikt, dan dient elke app onafhankelijk te zijn, zoals in het geval van microservices (zie Microservices)
  • Sharen verschillende apps een codebase om hergebruik van code te bevorderen, dan dient er met libraries en dependencies gewerkt te worden

II. Dependencies

Dependencies dienen expliciet gedefinieerd te zijn en dienen geïsoleerd te zijn van de applicatie. Enkele voorbeelden:

  • Java heeft Maven / Ant / gradle om dependencies te beheren
  • NodeJS heeft packages.json
  • PHP heeft composer
  • Ruby heeft Gems

III. Config

Een applicatie configuratie is alles wat kan verschillen tussen de verschillende omgevingen (dev, test/QA/accept, productie). Deze configuratie moet strikt gescheiden worden van de code. Dit zijn bijvoorbeeld credentials (logins en wachtwoorden), paden (paths), en dergelijke.

De twelve factor app raadt aan om deze in omgevingsvariabelen (environment variables) te zetten. Er zijn echter nog andere mogelijkheden:

  • Een gedistribueerde key/value store zoals consul, zookeeper of etcd kan gebruikt worden om configuratie instellingen op te slaan
  • Container Orchestration software (zoals Kubernetes) bieden vaak ook eigen oplossingen aan zoals een credential store en een config store. Vaak is dit toch ook wel een combinatie van een key value store en environment variables. Soms worden ook (tijdelijke) bestanden gebruikt op basis van de credential store / config store.
  • Soms worden aparte repositories gebruikt puur om configuratie op te slaan, dit kan echter snel omslachtig worden

IV. Backing Services

Backing Services, zoals een database, caching server, search software, dienen gezien te worden als een externe service. Het enige dat de applicatie hoeft te weten is de connectie informatie die in de configuratie ingesteld dient te worden. De externe services worden dan onafhankelijk van de applicatie beheert en onderhouden.

V. Build, release, run

Een strikte scheiding van build, release, en run stages:

  • De build stage gaat de software compilen, builden, testen en geeft als resultaat een build terug (artifact)
  • De release stage combineert de build met de configuratie variabelen specifiek voor een omgeving (bv. build + production config)
  • De run stage is de runtime, het is het opstarten van een specifieke release

Deze stappen zijn ook zeer duidelijk wanneer gebruik wordt gemaakt van containers. Een container wordt eerst gebouwd als een image (build). Daarna dient een specificatie geschreven te worden voor een release (build + config). Daarna kan de specificatie uitgevoerd worden en is de app live (run stage).

VI. Processes

De applicatie dient uitgevoerd te worden als 1 of meerdere stateless processen. Stateless betekent dat de applicatie geen “state” mag bewaren. Het kan geen bestanden lokaal wegschrijven en ook gebruikerssessie-informatie dient in een externe service opgeslagen te worden. Deze factor is zeer belangrijk, maar ook vaak een moeilijke om te implementeren als de applicatie niet stateless ontwikkeld geweest is.

Eenmaal de applicatie stateless is, kan deze gemakkelijk horizontaal geschaald worden. Dit schalen dient te gebeuren wanneer de applicatie meer requests af te handelen heeft. Omgekeerd ook, de applicatie kan horizontaal downscalen wanneer minder of geen eindgebruikers de applicatie nodig hebben, bijvoorbeeld buiten de kantooruren of ‘s nachts.

VII. Port binding

De applicatie dient volledig self-contained te zijn. Veel applicaties dienen nog vaak gebundeld te worden met een webserver (bv. Apache httpd / Tomcat). De applicatie dient er zelf voor te zorgen dat het aan poort binding doet en dat een vaste poort beschikbaar is om de applicatie aan te spreken. Enkele voorbeelden:

  • Java kan Jetty gebruiken
  • NodeJS kan een Express server opzetten

Dit neemt niet weg dat er uiteindelijk toch een webserver voor de applicatie geplaatst wordt (bv een load balancer of reverse proxy). Deze loadbalancer of proxy is dan volledig onafhankelijk en stuurt gewoon het dataverkeer door naar de onderliggende applicatielaag. Het voordeel van loadbalancers is dat de load gesplitst kan worden over de verschillende processen, en er geen dataverkeer gestuurd wordt naar processen die niet “healthy” zijn, bijvoorbeeld in het geval van software of hardware problemen.

VIII. Concurrency

Schalen dient te gebeuren via het process model. Er kunnen verschillende processen gestart worden voor verschillende workloads. Bijvoorbeeld voor long-running processen kan een apart worker proces gestart worden. Deze processen zijn volledig onafhankelijk van elkaar, ze mogen niets met elkaar delen, en moeten beiden onafhankelijk horizontaal kunnen schalen. Zo kan een worker bijvoorbeeld onafhankelijk meerdere processen starten om een piek op te vangen in het afhandelen van long-running processen.

IX. Disposability

De applicatie moet horizontaal kunnen schalen en zal dus verschillende instanties van elke applicatie draaien als afzonderlijke processen. Omdat onderliggende hardware altijd kan falen, en omdat een downscale event op elk moment kan voorvallen, dienen processen van applicaties gemakkelijk verwijderbaar (disposable) te zijn. Een applicatieproces moet snel zijn laatste acties kunnen afwerken en kunnen overgaan in een shutdown zonder gegevensverlies. Dit zorgt ervoor dat de applicatielaag robuust is en snel kan reageren in het geval van deployments.

Bij het vaak en snel uitvoeren van deployments moeten applicaties ook snel genoeg kunnen opstarten, anders dan duurt een deployment te lang, of kan er niet snel genoeg geschaald worden om de veranderingen (bvb meer gebruikers) op te vangen.

X. Dev/prod parity

Development, staging (test/QA/accept), en productie moeten zo gelijk mogelijk zijn.

XI. Logs

Logs zijn event streams en dienen niet per machine meer opgeslagen te worden in logfiles. Het is beter om de log file naar de standaard output te sturen, zodat deze dan opgevangen kan worden door andere software. Vaak wordt aggregatie software gebruikt om de output van de applicatie op te vangen en deze dan te aggregeren in een externe service. Deze service kan dan de logs weergeven in een dashboard.

XII. Admin Processes

Alle administratieve processen, zoals database migraties, of een terminal/console starten voor debugging, dienen altijd eenmalig te zijn en in dezelfde omgeving uitgevoerd te worden als de applicatie zelf. Deze code moet gebundeld worden met de applicatie zelf, zodat er geen versie mismatch kan zijn tussen de administratiecode en de applicatie

_images/contact-in4it.png