Microservices & containers

Sinds het einde van 2014 wordt door Magnetic.io in Amsterdam hard gewerkt aan een open source oplossing voor het bouwen, uitrollen en beheren van (micro)services. Het project heet Vamp, wat (losjes) staat voor “The Very Awesome Microservices Platform”.

Vamp is redelijk omvangrijk en complex, gebouwd in Scala en een klein stukje Go. Vamp wordt (zelfs in zijn huidige alpha-status) al gebruikt in productie.

In dit artikel wil ik geen tutorial geven van Vamp. Ook wil ik geen beschrijving van alle features of van de API. Die informatie kan je immers allemaal vinden op http://vamp.io. Als je wilt weten hoe Vamp onder de motorkap werkt, dan staat alle source op https://github.com/magneticio/vamp.

Wat ik in dit artikel wel wil gaan doen, is de context toelichten waarin Vamp is ontstaan is. Ook wil ik misvattingen en aannames behandelen die in deze context spelen en een aantal van de ‘hogere doelen’ van Vamp toelichten.

Vamp service detail scherm

 

Een hele bak nieuwe speeltjes

De laatste twee á drie jaar zijn voor developers, system engineers en techies in het algemeen een interessante tijd geweest. Ik kan mij niet herinneren dat er ooit een periode was waarbij (de doorgaans wat suffe) backend wereld zo overspoeld werd met nieuwe talen, nieuwe tools, nieuwe werkwijzen etc. etc. Een kleine greep:

  • Reactive programming wordt serieus met Rx en Scala.
  • Google’s Go blijkt een écht alternatief voor alles tussen Python en Java.
  • Docker zet containers op de kaart (ook een beetje geholpen door de Google Borg paper: http://research.google.com/pubs/pub43438.html)
  • Streams zijn overal, reactive of niet.
  • Clusters en distribution zijn hot met Mesos, Akka, Hazelcast etc.
  • Alles is Dev/Ops en iedereen doet het.
  • En last but not least:  ”microservices” is het nieuwe elixer tegen alle kwaaltjes.

Natuurlijk zaten een hoop van deze zaken al wat langer in het vat. Bovendien zijn het vaak doorontwikkelingen van reeds bestaande technologie. Maar waar de gemiddelde frontend developer wel gewend is aan de introductie van een nieuw must have framework elke keer als hij/zij met de ogen knippert, zijn de backend developers en system engineers doorgaans wat conservatiever en voorzichtiger.

 

Groter, sneller, meer

Als je deze hoos aan technologie van een afstandje bekijkt, dan is het niet al te moeilijk om een patroon te ontdekken. De grote jongens uit (voornamelijk) Silicon Valley ontwikkelen dit soort tech, omdat ze moeten groeien en omdat ze sneller moeten zijn dan de concurrent. Ook willen ze hun klant nog betere diensten en waarde geven: dat klinkt logisch en zal behoren tot dag één van het gemiddelde bedrijfskunde curriculum.

Om al die bedrijfsdoelen te bereiken, heb je nodig wat IT-managers een ‘wendbare IT- organisatie’ noemen. Plat gezegd: een groep slimme developers en engineers die snel nieuwe services kunnen bouwen en uitrollen, deze kunnen opschalen als de service populair blijkt te zijn en dit allemaal terwijl de boel veilig, snel en stabiel is.

Dat is niet gemakkelijk. Sterker nog, dat is heel erg moeilijk en dat is dan ook precies het bestaansrecht van tools en frameworks, zoals Docker, RxJava en Mesos. Dit is ook het bestaansrecht van de architectuurstroming (paradigma?) die wij met zijn allen ‘microservices’ zijn gaan noemen.

 

Microservices als verzamelbak

De exacte definitie van de term ‘microservices’ is lastig en duiding ervan ontaardt vaak in semi-religieuze dan wel semantische discussies: “Wat is klein? Wat is groot? Wat is een service? Waarom zijn we op aarde?”.

Ik denk persoonlijk dat een exacte definitie niet persé iemand verder helpt. De microservice is meer een label voor een aantal ideeën op het gebied van software architectuur en hoe die architectuur help met het bereiken van bedrijfsdoelstellingen. Tegen beter weten in heb ik toch een kort lijstje van deze ideeën samengesteld. Let wel: dit zijn vrij geaccepteerde opvattingen en nergens echt revolutionair.

  • Services moeten los uitgerold kunnen worden en een upgrade kunnen krijgen met minimale impact op andere, afhankelijke services.
  • Teams moeten volledige verantwoording kunnen hebben over hun service; van code tot productieomgeving.
  • Services moeten niet afhankelijk zijn van een (zware) centrale enterprise bus of een te implementatie specifieke oplossing (denk TIBCO, Corba etc. vs “gewoon” HTTP).

Je ziet dat deze basisprincipes niet alleen gelden voor de Google’s en Netflix’s van deze wereld. Ze gaan ook prima op voor bedrijven van MKB-niveau en groter. Er zijn echter wel wat hardnekkige, vooral technische, aannames die je vaak tegenkomt bij de wat minder ingewijden:

  • microservices == kleine code base: een limiet op het aantal lines of code lijkt wat parmantig en arbitrair. Het gaat bij ‘micro’ meer om de hoeveelheid afhankelijkheden en functionaliteiten die je in één executable stopt. Dat niveau moet begrijpelijk en overzichtelijk zijn voor een klein team: niet zozeer jouw library of model moet klein blijven, de scope moet klein blijven. Ik zou hier de Linux-filosofie kunnen quoten, maar dat doet iedereen al te pas en te onpas.
  • microservices == Docker containers: Nee! Netflix deed al goede zaken met hun services die op EC2 met Tomcat draaien toen Dockers nog gewoon broeken waren voor mannen van middelbare leeftijd. Containers zijn wel een katalysator, maar totaal niet noodzakelijk. Sterker nog, ze vormen misschien voor veel mensen juist een obstakel, omdat ze nóg een nieuwe technologie moeten leren.
  • microservices == makkelijker dan monolitisch: Nee! Het is moeilijker. Echt waar. Je krijgt wel prachtige voordelen (snelheid, robuustheid, etc.) als je het goed doet, maar het is verdraaid lastig. Alleen al testen (unit, functioneel, end-to-end, regressie, etc.) is onpraktisch en lastig te behappen. Verder is de evolutie van meerdere afhankelijke API’s gewoon moeilijk te overzien. Dingen als losse boot-cycles en initialisatie-routines kunnen ook funest zijn. Service discovery is een heel hoofdstuk (en hoofdpijn) op zich.

 

Hoe tackelt Vamp deze problemen?

Met het risico om in herhaling te vallen: de wereld wordt niet simpeler als je volgens een microservice-architectuur je diensten gaat ontwikkelen. Orkestratie is lastig. Overzicht is lastig. Testen is lastig. Service discovery is lastig. En zo kan ik nog wel even doorgaan. Vamp heeft een doel en een visie die eigenlijk neerkomen op het zoveel mogelijk wegnemen van deze extra complexiteit van microservices, zodat organisaties de voordelen hebben van microservices en zo min mogelijk nadelen.

Vamp stelt een organisatie in staat om:

  • Met meerdere teams tegelijkertijd aan meerdere services te werken, waarbij Vamp alle afhankelijkheden bijhoudt en checkt.
  • Services snel naar productie te kunnen brengen met minimale risico’s.
  • Heel simpel te schalen: met de hand of automatisch op basis van load.

Vamp maakt gebruik van container-platformen, zoals Docker en Mesos om dit voor elkaar te krijgen. De container-platforms verzorgen het ‘water, gas en licht’ waarbij Vamp de complexere, team/service-gerichte functionaliteit voor zijn rekening neemt. Vamp heeft hiervoor een platform onafhankelijke DSL, een metrics engine en deployment en routing logica. Verder heeft Vamp een command line interface die voornamelijk dient voor integratie van Vamp met continuous integration platformen als Jenkins.

 

BHAG’s

Zoals ik in de introductie al aangaf, wilde ik hier geen opsomming geven van exact alle functies van Vamp. Artikelen zoals deze moeten immers meer teasers zijn dan naslagwerken, toch? Ik kies dus liever twee Big Hairy Audacious Goals uit die wij ons gesteld hebben met Vamp en ligt die toe met voorbeelden en details over de implementatie.

BHAG 1: Testen in productie

We zijn het er inmiddels met zijn allen over eens dat vaker kleine releases doen een betere gewoonte is dan eens in de zoveel tijd een joekel van een ‘big bang’. Dit is ook exact één van de beloftes van microservices: continuous deployment.

Vamp maakt het gemakkelijk om elke build, vanuit bijvoorbeeld Jenkins, als losse deployable naar een omgeving te duwen. In principe is het zes regels bash waarin de Vamp CLI wordt aangeroepen. Vamp doet hier echter geen naïeve aannames over hoe complex en gelaagd zo’n omgeving kan zijn. Je hebt volledige controle over welke versie van je service met welke andere praat en welke afhankelijkheden mee moeten komen.

Verder maakt Vamp dit soort releases zo risicovrij als mogelijk: elke release van elke service kan door middel van een A/B-test, canary release of blue/green-scenario langzaam en gecontroleerd worden blootgesteld aan productieverkeer. Dit is eigenlijk een keiharde randvoorwaarde voor continuous deployment: zonder test ben je namelijk nergens.

Een stukje Vamp-DSL geeft je al snel een idee. We splitsen hier simpelweg het verkeer in 90% en 10% tussen twee versies van onze frontend service. Verder zorgen we via een ‘filter’ dat iedereen met een Chrome browser sowieso bij versie 1.0 terecht komt.

Het gegeven voorbeeld is heel simpel:


 services:
      -
        breed: frontend:1.0
        routing:
          weight: 90
 filters:
           - condition: User-Agent = Chrome
      -
        breed: frontend:1.1
        routing:
          weight: 10

Je kunt het eigenlijk zo gek maken als je zelf wilt. Use cases zijn bijvoorbeeld:

  • Stel je nieuwe versie eerst ter beschikking aan interne gebruikers of target specifieke users op basis van applicatieve instellingen via cookies.
  • Test verschillende configuraties van dezelfde applicatie (denk aan JVM Heap size instellingen of connection pool setting).
  • Test totaal verschillende oplossingen (MongoDB vs CouchDB? Of Rails vs. NodeJS?).

Verder is het allemaal te automatiseren via de CLI of API. Je kunt bijvoorbeeld gedurende

een periode van een uur langzaam je traffic van service 1.0 naar 1.1 overzetten en daarna (geautomatiseerd) service 1.0 verwijderen. Wij vinden deze manier van werken zo belangrijk dat deze filters en routings binnen Vamp first class citizens zijn met hun eigen API endpoints en eigen datamodel.

De implementatie is een mix van een in Scala, Spray en Akka geschreven API, DSL parser en executie engine samen met een Go applicatie die routing structuren doorstuurt naar HAproxy. Vamp geeft je één taal waarin je de aansturing van deze subsystemen en deze complexe workflow heel simpel houdt.

BHAG 2: Automatische real time metrics & events op alles

Heel vaak deployen is prachtig en helemaal als je het gecontroleerd doet met Vamp’s A/B-testen. Maar hoe bepaal je of B net zo goed of beter is dan A? Vamp helpt je door vanaf het moment van deployment een set aan metrics af te tappen vanuit het routing component. Dit betekent dat je direct dingen als response-tijden, requests/seconde, HTTP 500 en 404 errors tot je beschikking hebt.

Vamp geeft je direct toegang tot deze informatie via Server Sent Event streams of gewoon door middel van API calls. Je kunt dus direct in je deployment logica stapjes bouwen die tijdens je deploy checken of er fouten zijn of dat de nieuwe versie van je service heel traag is in vergelijking met de oude. Deze oplossing stelt je in staat een feedback loop te maken van bouwen -> deployen -> meten -> bouwen.

De implementatie is een mix van een Go applicatie die metrics streamt naar Elasticsearch. Vanuit Elasticsearch streamt onze Scala/Akka engine deze (geaggregeerde) metrics weer door naar de API en naar de grafische UI. Omdat we Elasticsearch gebruiken kun je ook heel gemakkelijk een tool als Kibana opstarten en een paar mooie dashboards maken van alle Vamp metrics.

 

Tot slot

De microservices-markt is in Nederland nog maar net aan het ontstaan. De implementaties (waar wij van weten) zijn schaars, maar wel veelbelovend. Vamp’s raison d’être is om dit type implementaties aan te wakkeren en vooral de voordelen naar boven te halen. Want één ding is zeker: het wordt allemaal niet makkelijker.

 

Referenties

  • http://vamp.io/
  • http://vamp.io/blog
  • https://twitter.com/magneticio