Microservices bij Malmberg

Malmberg behoort tot de top drie van educatieve uitgeverijen van Nederland in het basis-, voortgezet- en middelbaar onderwijs. Binnen Malmberg werken een aantal DevOps-teams aan digitale leerplatformen voor verschillende onderwijstypen. Deze case deelt de ervaringen vanuit het oogpunt van die teams.

Zo’n twee jaar geleden is Malmberg een aantal projecten gestart op een nieuwe technologie-stack: Vert.x, MongoDB en AngularJS, draaiend in de Amazon cloud. Vert.x is een toolkit voor het bouwen van reactive applicaties op de JVM [1]. De keuze voor Vert.x is gedreven door de reactive eigenschappen (responsive, resilient, elastic, message driven) [2] en het krachtige modulesysteem. Die laatste is belangrijk, omdat we wisten dat we een set herbruikbare services voor educatieve applicaties wilden gaan bouwen. Destijds was het concept van microservices nog niet wijdverspreid. Het feit dat we nu met een microservices-architectuur werken, zou je dan ook kunnen zien als ‘bijvangst’ van de keuze voor een modulair, reactive platform. Inmiddels draaien 8 projecten met deze technologiestack in productie.

 

Wat gaat er goed?

Een applicatie bestaat doorgaans uit een stuk of 10 tot 15 services. Een klein aantal hiervan zijn projectspecifiek en handelen zaken als http-requests en flows over meerdere services heen af. Het merendeel van de services is echter generiek voor alle projecten. Deze worden door een centraal team ontwikkeld en door de projecten als Maven dependencies naar binnen gehaald. Elk team heeft dus zijn eigen instanties en versies van de generieke services draaien. Dat kan, omdat de teams wél functionaliteit delen, maar geen data. De teams zijn in productie dus niet afhankelijk van andere teams. Doorgaans stappen de teams vrij snel over naar de nieuwste versies van de generieke services.

De services communiceren met elkaar via de gedistribueerde eventbus in Vert.x. Services hebben één duidelijk doel, bijvoorbeeld sessiebeheer. Als het handig is dat een service meerdere verantwoordelijkheden heeft (bijvoorbeeld omdat het als proxy dient voor een remote webservice met meerdere endpoints), dan doen we daar niet moeilijk over. Hetzelfde geldt voor het delen van code tussen services: als dat handig is, dan doen we dat.

We testen de services individueel en in combinatie met elkaar, onder andere via integratietests, waarin we tijdens een test een service en afhankelijkheden opbrengen.

We kunnen services los uitrollen en upgraden met minimale impact op andere services. Toch deployen we onze applicaties in de praktijk meestal nog wel als geheel. Onze deployment-tooling werkt dan overigens alleen de gewijzigde services bij. We tunen JVM-settings en Vert.x-configuratie op serviceniveau. Services die veel gebruikt worden of veel geheugen gebruiken (bijvoorbeeld voor caching), kunnen we dan meer resources toekennen. Draaien in losse JVM’s levert ook voordelen op bij probleemanalyse, want je ziet sneller welk proces (en dus in welke service) in de problemen zit.

Een applicatie-node bestaat uit een webserver met één stateless applicatie, opgebouwd uit een set services. Onze applicatie-nodes zijn geclusterd: doorgaans draaien we voor een productieomgeving met 2 tot 6 servers met een loadbalancer ervoor. Die voert een healthcheck op alle nodes uit, die intern per node weer alle services afgaat. Zodra er een service niet (tijdig) reageert, valt de node uit de loadbalancer en gaat er geen verkeer meer naartoe, totdat de healthcheck weer aangeeft dat alles in orde is.

Voor deployments van Vert.x modules gebruiken we open source tooling, die we zelf ontwikkeld hebben [3]. Op elke applicatie-instance draait een deployment agent, die we via REST benaderen vanuit Jenkins. De services die we deployen komen als binary uit Nexus. De deployment tooling interfacet met de Amazon API, zodat we seamless node-voor-node kunnen upgraden zonder impact op de eindgebruikers.

We hebben geen development teams, maar DevOps teams, die de volledige verantwoording over hun project hebben: van code tot productieomgeving. Alle infrastructuur wordt automatisch opgebouwd met Puppet en CloudFormation en we kunnen automatisch schalen op basis van load. De teams houden hun productieomgevingen zelf in de gaten via allerlei metrieken, dashboards en gecentraliseerde, gestandaardiseerde logging en sturen bij waar nodig.

We werken bewust niet met containeroplossingen, zoals Docker. Waarom niet? Dat is een artikel op zich waard 😉 Zie [4] voor een toelichting.

 

Wat kan er nog beter?

Alhoewel we het op DevOps-vlak al behoorlijk goed doen en er weinig misgaat, kan de adoptie binnen de organisatie nog wel beter. Dit is niet alleen een technisch proces. Het gaat echt om een andere manier van werken en dat soort verandering kost tijd en moeite.

Werken met microservices blijkt toch wel wat lastiger dan met een monoliet. We moeten ons her en der in wat bochten wringen om met afhankelijkheden tussen services om te gaan. We hebben er bewust voor gekozen om nog niets met service discovery te doen en om ook nog niet meerdere versies van een service tegelijkertijd te draaien. Dat brengt wat beperkingen met zich mee, maar daar is in de praktijk goed mee te leven.

Een typische applicatie bestaat uit 10 tot 15 services en dus 10 tot 15 JVM’s. Door de JVM- en OS-overhead moeten we af en toe wat kruidenieren met geheugen en is een machine met 2GB RAM hierdoor al aan de krappe kant. We proberen door het monitoren van productieomgevingen zo goed mogelijk inzicht te krijgen in de geheugendistributie JVM’s heen.

 

Conclusie

Voor ons werkt een microservices-architectuur behoorlijk goed, zeker gezien de behoefte om met generieke, herbruikbare bouwblokken te werken. We doen niet in alle gevallen microservices ‘volgens het boekje’, maar proberen er praktisch mee om te gaan. Werken met microservices is zonder meer lastiger dan met een monoliet, maar het levert je ook veel op.

 

Referenties

  1. http://vertx.io
  2. http://www.reactivemanifesto.org
  3. http://github.com/msoute/vertx-deploy-tools
  4. http://www.slideshare.net/BertJanSchrijver/geecon-microservices-2015-swimming-upstream-in-the-container-revolution