Het probleem en de wens
Het doel van CI/CD is om zoveel mogelijk werk uit de handen van het team te nemen: het stukje CI test en valideert de code, terwijl het stukje CD de code automatisch bouwt en uitrolt zodra deze in de correcte branch geïntegreerd is. Een dergelijk systeem biedt veel voordelen: het zorgt voor een consistente deploymentstrategie, bespaart veel tijd (op lange termijn), bewaakt codekwaliteit en vereenvoudigt het gehele leveringsproces voor het team.
Bouwblokken
Wij wilden een opzet waarmee we voor elk smaakje product gemakkelijk een geschikte pipeline in elkaar kunnen klikken. Ook moet elke pipeline alleen taken uitvoeren die relevant zijn voor het betreffende product en zo min mogelijk configuratie vereisen. Daarnaast is het belangrijk dat de pipeline makkelijk toe te voegen is aan onze repositories. Een echte plug-en-play oplossing dus.
Deze tool wilden we gaan bouwen met GitHub Actions. Dit is een CI/CD-platform van GitHub waarmee pipelines gebouwd kunnen worden op basis van actions: herbruikbare stukjes code die één specifieke taak uitvoeren. Een action kan bijvoorbeeld een Python environment opzetten, unit tests uitvoeren, een Docker-image bouwen, een zelfgeschreven bash-script uitvoeren, en nog veel meer. Deze actions worden vervolgens in een workflow bestand aangeroepen, waar de configuratie en volgorde van uitvoering gedefinieerd worden. Deze bestanden zijn in YAML-formaat geschreven en vormen samen de CI/CD-pipelines (hierna workflows te noemen).
Niet scripten, maar software bouwen
We gingen aan de slag – de eerste stap was het aanmaken van een nieuwe repository op GitHub: basic-workflows. In deze repository kunnen we alle bouwblokjes en bijbehorende workflows ontwikkelen. De structuur is eenvoudig opgezet met twee mapjes:
- actions – waar we alle losse stappen van onze workflows opslaan.
- workflows – waar we de actions samenkomen tot volwaardige workflows die in onze repositories gebruikt kunnen worden.
Een SOLID aanpak
Om ons te helpen een toereikende tool te bouwen, beschouwen we het niet als setje scripts, maar meer als softwareproduct. We houden ons aan de algemene ontwerpprincipes zoals KISS (“keep it simple, stupid”), DRY (“don’t repeat yourself”) en SOLID; de vijf principes van softwareontwikkeling. Onderstaand de drie die voor ons relevant zijn:
- Single responsibility: dit houdt in dat een object maar één taak moet hebben – dus een object dat A én B doet, moet gesplitst worden in twee klassen. Dit geldt voor alle workflowstappen die we bouwen. Die moeten allemaal maar één taak hebben.
- Open-closed principe: dit houdt in dat objecten open moeten zijn voor uitbreiding, maar gesloten voor wijzigingen. Oftewel, een object moet uitgebreid kunnen worden zonder dat het verder aangepast hoeft te worden. Dit is belangrijk voor alle workflowbestanden, zodat we later nog stappen toe kunnen voegen zonder dat de hele workflow kapot gaat.
- Interface segregation principe: een object zou nooit methodes moeten implementeren die het niet gebruikt. Dit is eigenlijk niet helemaal van toepassing gezien we geen interfaces gebruiken, maar de filosofie erachter is relevant. Wanneer we een workflow in een van onze repositories toepassen, is het niet de bedoeling dat deze workflow allerlei stappen bevat die niet gebruikt worden. Dit is de reden dat we verschillende workflows voor verschillende typen producten gebruiken. De Java API workflow hoeft immers geen Python linting uit te voeren.
Al deze principes zorgen ervoor dat een softwareproduct modulair, herbruikbaar, onderhoudbaar en uitbreidbaar is. Niet te vergeten dat de workflows hierdoor sneller en efficiënter zijn, wat op zijn beurt weer tijd scheelt.
Implementatie
Na de initiële opzet was het simpelweg een kwestie van functionaliteit inbouwen. Omdat dit van alles omvat – tests draaien, code scans, images bouwen, lambda’s zippen, alles uitrollen en meer – kostte dit natuurlijk wat tijd. Maar de moeite loont: het resultaat is een breed inzetbare tool die precies doet wat we voor ogen hadden.
Wanneer nieuwe code op een van de permanente branches (main, release, develop) komt, start deze workflow. Dan wordt automatisch een nieuwe versie gemaakt en uitgerold. Op dezelfde manier is een workflow voor de continuous delivery/deployment stap toegevoegd in elke repository waar nodig.
Onderstaand diagram toont hoe de implementatie van basic-workflows nieuwe code integreert in onze codebase en hoe features, bugs en hotfixes naar onze live omgevingen uitgerold worden:
Het stukje CI wordt door de python-integration workflow uitgevoerd, en het stukje CD door de python-delivery workflow. Twee soortgelijke workflows bestaan voor alle talen die we gebruiken en voor de Lambda-functies.
Vanuit een feature- of hotfix branch wordt de code middels een pull request naar develop gebracht. Vanuit deze pull request gaat de CI workflow draaien om de code te testen, scannen en te valideren. Wanneer dit geslaagd is wordt de merge handmatig uitgevoerd en wordt automatisch de C- workflow afgetrapt. Deze workflow bouwt een artifact of Docker image (afhankelijk van het type product) en deployt deze naar de relevante omgeving. Ook wordt er gelijk een corresponderende GitHub release gemaakt met behulp van GitVersion.
Vervolgens wordt de code (ook via pull requests) handmatig doorgevoerd naar release en main en wordt wederom automatisch de CD workflow afgetrapt. Dit zorgt ervoor dat we controle hebben over welke versies in welke omgeving terechtkomen, wat erg belangrijk is als je met test/acceptatie/productieomgevingen te maken hebt.
Conclusie
Het resultaat is een tool die ons enorm helpt: we kunnen in een handomdraai nieuwe code integreren en uitrollen naar de gewenste omgeving, het zorgt ervoor dat elke versie blijft bestaan in GitHub, en biedt daarmee de mogelijkheid om zeer snel en gemakkelijk een deployment terug te draaien naar een vorige versie. Ook zijn we vliegensvlug geworden in het opleveren van onze producten, waardoor we betere kwaliteit sneller kunnen leveren. En niet te vergeten – als er een keer iets in productie kapot gaat – hebben we het zo gefixt middels een hotfix. Al met al is het de moeite waard, een aanrader voor elk ontwikkelteam!