Hoe we door asynchrone communicatie beter samenwerken: ontdek ons experiment

Lola: Eigen tooling voor AWS Cloudformation

Wieni werkt intensief met AWS Cloudformation voor het beheer van hun AWS Infrastructuur. Een van de -zelf ontwikkelde- tools die we hiervoor gebruiken is Lola.  Lees er hier meer over.

Een van de leiddraden bij Wieni is dat we dingen zélf willen kunnen doen. Dit geldt niet alleen voor design en development, maar dus ook voor hosting van websites en de onderliggende technologieen. Na wat omzwervingen bij lokale en internationale hosting service providers werd er gekozen voor de cloud services van Amazon (Amazon Web Services).

AWS Cloudformation voor Infastructure as Code

Waar we al langer gebruik maakten van Ansible voor configuration management op de servers zelf, bleek Ansible - voor ons - niet de meest voor de hand liggende tooling voor het provisionen van de infrastructuur zelf. Sowieso diende een gekozen oplossing gebruik te maken van Infrastructure as Code, want manuele provisioning van infrastructuur was voor ons geen valabele piste meer; er is geen versiebeheer, geen accountability (wie deed wat?), geen repeatability (had ik dat vinkje aangeklikt daar of niet?), etc. . (voor de nit-pickers:  je zou met AWS CloudTrail an AWS Config wel behoorlijk ver raken in monitoring en change management van je infrastructuur, maar dat zou eerder after-the-facts zijn en niet interessant voor onze use case).

AWS biedt -uiteraard- zelf tooling aan voor het beheer van infrastructuur: AWS Cloudformation. Deze is gratis voor het opzetten van infrastructuur binnen hun eigen namespaces en zijn template files die in json (nope!) of yaml (yep) worden aangeleverd. Op basis van deze templates (voorbeelden vind je op deze link) wordt dan de infrastructuur opgezet. Een stukje van zo'n template, die een S3 bucket die als website gaat dienen aanmaakt, ziet er zo uit:

Resources:
  S3BucketForWebsiteContent:
    Type: AWS::S3::Bucket
    Properties:
      AccessControl: PublicRead
      WebsiteConfiguration:
        IndexDocument: index.html
        ErrorDocument: error.html

Het lukte vrij vlot om het grootste deel van onze infrastructuur in zo'n templates neer te schrijven. Daarmee bedoelen we dat helaas niet alle AWS infrastructuur, en dan vooral nieuwe services, kunnen worden aangestuurd via Cloudformation. Hier bestaan wel oplossingen voor (Custom Resources), maar dat zou te ver leiden voor dit artikel. Er wordt wel continu op ontwikkeld bij AWS en we merken nog vaak dat we bepaalde stukken infrastructuur toch ineens mee in templating kunnen beheren.

Een template voor de templates

Wat snel opviel is dat we nu wel mooie geparameteriseerde templates hadden, maar geen even mooie manier om deze templates te laten interfacen met AWS (zgn. orchestration). De standaard opties die AWS hiervoor voorziet zijn:

  • Je browser(console). Je klikt wat rond en geeft template parameters manueel in. Geen optie.
  • CLI. De verder uitstekende command line interface van AWS heeft hetzelfde nadeel; waar steken we de parameters in version control? Moeten we die opeenvolgende commando's onthouden? (to be fair; dit is wat we eerst deden)
  • SDK. Libraries voor python, node, ...
  • CDK. Dit is redelijk nieuw en hebben we nog niet echt nader bekeken; kan heel goed een valabele optie zijn. Lijkt wel wat op Terraform.

Uit onze use case viel op dat we binnen projecten vaak dezelfde template gebruikten en deze parametriseerden om te kunnen gebruiken voor verschillende omgevingen (dev/stag/prod). Dus wat we nodig hadden was een éxtra template file die onze omgevingen definieerde en die voor elke combinatie omgeving/Cloudformation template een overeenkomende 'stack' (group van infrastructuur) zou creeren in AWS: Lola!

Lola

Lola orchestreert cloudformation templates en de daaraan gekoppelde stacks. Een minimale lola template ziet er zo uit (documentatie staat inline)

project: website #naam van project

stacks: # definieer welke stacks je gaat gebruiken, en waar lola ze kan vinden
  assets:
    template: assets.cloudformation.yml
  cdn:
     template: cdn.cloudformation.yml

environments: # welke environments zijn er, en welke stacks wil je in welke environment gebruiken
  dev: # er is een dev environment
    assets: # we willen een assets stack in dev
      region: eu-central-1 # in frankfurt
      profile: aws_profile_klant # we gebruiken deze profile uit ~/.aws/credentials
      params: # we geven de stack volgende parameters mee
        assetCdn: assets-dev.website.be
        createUser :false

  prod: # er is ook een prod environment
    assets: # die heeft ook een assets stack
      region: eu-west-1 # in een andere region
      profile: aws_profile_klant
      params:
        assetCdn: assets.website.be # met andere parameters
        createUser :true
    cdn: # prod krijgt ook een cdn mee
      region: eu-west-1
      profile: aws_profile_klant
      params:
        cdn: www.website.be
        enableWAF:true

Om bijvoorbeeld de assets stack naar dev te deployen doen we:

~ $ lola deploy
? Stack:  (Use arrow keys)
❯ assets
  cdn 
? Environment:  (Use arrow keys)
❯ dev
  prod
[09:47:31] DEPLOY: website - assets - dev
[09:47:31] Update assets: AWS::CloudFormation::Stack - website-assets-dev UPDATE_IN_PROGRESS User Initiated
[09:47:36] Update assets: AWS::CloudFront::Distribution - assetsCDN UPDATE_IN_PROGRESS 
[09:47:35] Update assets: AWS::S3::Bucket - assets UPDATE_IN_PROGRESS 
[09:47:55] Update assets: AWS::S3::Bucket - assets UPDATE_COMPLETE
[09:51:19] Update assets: AWS::CloudFront::Distribution - assetsCDN UPDATE_COMPLETE 
[09:51:23] Update assets: AWS::CloudFormation::Stack - website-assets-dev UPDATE_COMPLETE 
[09:51:22] Update assets: AWS::CloudFormation::Stack - website-assets-dev UPDATE_COMPLETE_CLEANUP_IN_PROGRESS 
[09:51:24] Deploy assets: Done
~ $ 

Dit zou ook korter kunnen:

~ $ lola d -s assets -e dev

Stel dat we daarna een parameter aanpassen aan onze lola file:

        createUser :true # stond eerst op false

Dan vragen we eerst een changeSet op om te kijken welke aanpassingen er aan de stack zouden gebeuren als we dit deployen

~ $ lola changeSet 
? Stack:  (Use arrow keys)
❯ assets
  cdn 
? Environment:  (Use arrow keys)
❯ dev
  prod
[11:27:43] CHANGESET: website - assets - dev 
[11:27:49] ChangeSet website-assets-dev : 
        - Type: Resource
          ResourceChange:
            Action: Create
            LogicalResourceId: AssetsFulluser
            ResourceType: 'AWS::IAM::User'
            Replacement: 'False'
            Scope:
              - Properties
            Details:
              - Target:
                  Attribute: Properties
                  Name: ResourceRecords
                  RequiresRecreation: Never
                Evaluation: Dynamic
                ChangeSource: ResourceAttribute
                CausingEntity: Username
~ $ 

Daaruit leren we dat bij deploy er een user gaat aangemaakt worden. Doen we daarna terug deploy d -e dev -s assets dan zouden de changes ook effectief gedeployed worden.

Meer weten?

Een heel groot deel van onze infrastructuur wordt onderhouden met Lola. We werken daarnaast ook nog met serverless framework voor componenten waar een nog nauwere integratie nodig is tussen infrastructuur en codebase, maar lola verzorgt toch 80% van onze infrastructuur.

De source van lola kan je vinden op github, de package zelf op npm

Alternatieven

Het warm water wordt overal wel uitgevonden (maar het wiel blijkbaar niet). Er zijn een hoop andere initiatieven die in the end hetzelfde doen (werken met Cloudformation templates) maar die niet aansluiten bij onze use-case: