6 monitors met dashboards

Always Be Deploying

Jeroen Techniek

Ik heb een haat-liefde verhouding met CI/CD. Haat als ik eraan werk en liefde als het voor elkaar is. Het automatiseren van builds en deployments kost me altijd meer tijd dan ik van tevoren verwacht. Vooral omdat bij een niet-triviale setup het runnen van een build en/of deployment al snel een aantal minuten duurt. Doorgaans heb je een aantal iteraties nodig om het voor elkaar te krijgen en voor elke iteratie heb je dus de wachttijd van een testbuild. Toch is het bijna altijd de moeite waard om deze tijd erin te investeren omdat het tijd is die je terug verdient.

Tijd die je steekt in het automatiseren van taken is tijd die je terugverdiend. Stel het lukt je om een taak te automatiseren die een team van 5 personen elke werkdag 5 minuten ieder kost. Uitgaande van 40 werkbare weken met 5 dagen elk kun je dan dus 5*5*5*40=5000 minuten besteden om de tijd in een jaar terug te verdienen; dat is 83 uur, dus ruime 2 volle werkweken. Met andere woorden: het is de moeite waard om tijd te steken in iets wat een triviale tijdwinst lijkt.

De bron van wijsheid (en humor) die xkcd heet heeft een handige grafiek hiervoor (en voor degenen die het narekenen; hierboven gaat het over 5 personen, in de grafiek over 1 persoon):

XKCD met grid over tijd bespaard vs tijd nodig om te automatiseren, zie https://xkcd.com/1205/

Deployment voor elke merge request

Bij CI/CD gaat het om het automatisch kunnen deployen van je code naar de productieomgeving. In het ontwikkelproces komt er echter nog een stap voor het deployen naar productie en dat is deployen naar een test en/of acceptatieomgeving. In de meeste projecten die ik doe bij Infi werken we met kort-levende feature branches die door middel van een merge request in Gitlab gemerged worden naar de hoofdbranch. (Bij Infi doen we ook projecten op Github, al naar gelang de wens van de klant, maar om de een of andere reden worden de repositories waar ik in werk op gehost op private of public instanties van Gitlab).

Als onderdeel van ons proces wordt de code gereviewd als hij klaar staat in een merge request en de functionaliteit wordt getest door een collega developer, tester of een van de stakeholders. Je kunt mensen hiervoor toegang geven tot de repository en ze zelf de code laten uitchecken, builden en lokaal deployen, maar dat geeft in de praktijk altijd gedoe en kost veel tijd: door het switchen tussen verschillende branches raken databases in de knoop, mensen checken soms de verkeerde branch uit en testen iets anders dan ze denken te testen, etc, etc. Murphy’s Law gaat ook hier op: alles wat hierbij in theorie fout kan gaan, gaat ook een keer fout. En het vinden en corrigeren van de fout kost tijd van degene die iets wil testen, maar vaak ook van de developer die moet uitzoeken waarom die change die op zijn machine wel werkt nu niet werkt.

Om die reden vind ik het prettig dat voor elke merge request die aangemaakt wordt automatisch een omgeving wordt opgezet waar testers en stakeholders toegang toe hebben zodat ze snel en makkelijk de feature of fix kunnen testen. Ook de developer die de code review doet is hiermee geholpen omdat hij ook makkelijk een kijkje kan nemen naar de werkende code, zonder dat hij hiervoor op zijn machine de context switch moet maken.

Je spaart tijd uit van iedereen, reduceert de kans op fouten en daarmee verloren tijd, je bevordert de samenwerking en je test de wijzigingen op een andere omgeving dan een developer machine.

Deployen vanuit Gitlab CI

Automatisch deployen van een merge request of branch is in bijna elke omgeving te realiseren. In mijn eerste project bij Infi werd de deployment gedaan via ftp naar een on premise server.  Voor elke merge request werd een directory aangemaakt en werden de files via ftp geupload. Op die server draaide IIS en was de nieuw aangemaakte directory automatisch beschikbaar: bv: http://testserver.example.com/mr-137, voor de directory voor mr-137.

In mijn huidige project bouwen we docker containers die we via een docker stack deployen naar een test server. De gitlab CI taak daarvoor ziet er ongeveer zo uit:

deploy-test-environment: 
    services: 
        - docker:19-dind 
    image: docker:19 
    stage: deploy 
    needs: 
        - publish-test-api 
        - publish-test-backoffice 
        - publish-test-customer 
        - publish-test-webshop 
    rules: - if: $CI_MERGE_REQUEST_ID 
    environment: 
    name: mr-${CI_MERGE_REQUEST_IID} 
    url: https://mr-${CI_MERGE_REQUEST_IID}.${PUBLIC_DOMAIN} 
    on_stop: destroy-test-environment 
    auto_stop_in: 1 week 
    action: start 
    variables: 
    IMAGE_TAG: mr-${CI_MERGE_REQUEST_IID} 
    PUBLIC_DOMAIN: ${PUBLIC_DOMAIN} 
    script: 
        - eval $(ssh-agent -s) 
        - echo "${SSH_PRIVATE_KEY}" | tr -d '\r' | ssh-add - 
        - mkdir -p $HOME/.ssh 
        - chmod 700 $HOME/.ssh 
        - ssh-keyscan -H $HOSTNAME > $HOME/.ssh/known_hosts 
        - export DOCKER_HOST="ssh://$SSHUSER@$HOSTNAME" 
        - export PUBLIC_HOSTNAME=${CI_ENVIRONMENT_URL#"https://"} 
        - docker login -u "$DOCKER_REGISTRY_USERNAME" -p "$DOCKER_REGISTRY_PASSWORD" "$DOCKER_REGISTRY" 
        - docker stack deploy --with-registry-auth --resolve-image always --prune --compose-file ./infrastructure/testing/stack.testing.yml "${CI_ENVIRONMENT_SLUG}"

In de script stappen zie je dat de SSH key gezet wordt om docker op een remote machine aan te kunnen spreken via SSH. Door een DOCKER_HOST variabele te zetten gaat docker de commando’s uitvoeren op die host in plaats van op de eigen machine. Op die manier kun je op een remote machine inloggen in een private docker registry (in ons geval de Gitlab registry waar we de gebouwde containers naartoe pushen), en een stack deployen. De meeste variabelen zijn geconfigureerd in Gitlab of worden door Gitlab automatisch gezet voor een build.

Dynamische proxy met Traefik

Op de testserver staat een basis docker stack met als belangrijkste onderdeel Traefik. Traefik is een webserver, proxy en loadbalancer, maar hij heeft de voor ons prettige eigenschap dat zijn configuratie dynamisch is: Traefik kan zijn configuratie aanpassen en bijwerken zonder dat een herstart nodig is. Ook kan Traefik zijn configuratie informatie op verschillende manieren krijgen. De manier die wij hier gebruikt hebben is door gebruik te maken van Docker configuration discovery. Door aan je docker containers labels te hangen kun je Traefik informatie geven over of en hoe hij verkeer naar die docker container moet proxyen.

Dit is de definitie van een van de docker containers in de stack:

api:
    image: example.azurecr.io/api:${IMAGE_TAG}
    networks:
      - default
      - ssl-gateway
    depends_on:
      - example-db
    entrypoint: [ "/wait-for-it.sh", "example-db:1433", "-t", "120", "--", "dotnet", "Example.Api.dll"]
    healthcheck:
      test: curl --fail http://localhost:5001/health || exit
      interval: 10s
      timeout: 3s
      retries: 10
      start_period: 10s
    deploy:
      labels:
        - traefik.enable=true
        - traefik.http.routers.${CI_ENVIRONMENT_SLUG}-api.rule=Host(`api.${CI_ENVIRONMENT_SLUG}.${PUBLIC_DOMAIN}`)
        - traefik.http.routers.${CI_ENVIRONMENT_SLUG}-api.tls.certresolver=http
        - traefik.http.services.${CI_ENVIRONMENT_SLUG}-api.loadbalancer.server.port=5001

Traefik kijkt naar de labels die aan de container worden gehangen en bepaald hoe het verkeer geproxied moet worden. In deze definitie wordt bijvoorbeeld al het verkeer dat naar api.mr-12.example.com gaat naar deze container geleid. Je kunt op die manier meerdere docker stacks deployen: voor iedere merge request één. Traefik leidt het verkeer dan naar de juiste container. In ons project kunnen testers, stakeholders en collega developers eenvoudig browsen naar https://mr-12.example.com (ja, Traefik gebruikt letsencrypt om automatisch ssl certificaten te genereren, dat werk wordt je ook uit handen genomen) en op die manier toegang te krijgen tot de changes die klaar staan in merge request 12.

Veel bewegende delen

Een moderne applicatie heeft veel bewegende onderdelen: database, api, gebruikers front-end, beheer front-end, logging en monitoring. Op het moment dat het er allemaal staat ziet de configuratie er bedrieglijk eenvoudig uit, maar je moet ervoor zorgen dat alle onderdelen elkaar weten te vinden en dat hostnamen, gebruikersnamen, wachtwoorden en api-secrets allemaal op de juiste manier worden gezet in elke component. Het kost mij altijd een paar pogingen (en dus doorlooptijd) om dat goed te krijgen tot mijn eigen frustratie. Maar als het er eenmaal staat is het genieten. Dit is infrastructuur waar je daarna niet meer naar hoeft om te kijken. Daardoor kun jij je focussen op het bouwen van features en het blij maken van stakeholders in plaats van het debuggen van deployment problemen.

En code schrijven en stakeholders blij maken is wat we bij Infi graag doen.

Een afspraak maken bij ons op kantoor of wil je even iemand spreken? Stuur ons een mail of bel met Jolanda.