
Tijdens RIT 2019 deed onze collega Alexander Korotkov verslag over de automatisering van ontwikkeling bij CIAN: om het leven en het werk te vereenvoudigen, gebruiken we ons eigen platform Integro. Dit platform volgt de levenscyclus van taken, haalt routinetaken weg bij ontwikkelaars en vermindert aanzienlijk het aantal bugs in productie. In dit bericht zullen we de presentatie van Alexander aanvullen en vertellen hoe we de overstap hebben gemaakt van eenvoudige scripts naar de integratie van open source-producten via ons eigen platform, en wat een apart automatiseringsteam bij ons doet.
Niveau nul
«Er is geen niveau nul, dat ken ik niet»
Master Shifu uit de animatiefilm «Kung Fu Panda»
Automatisering bij CIAN begon 14 jaar na de oprichting van het bedrijf. Toen bestond het ontwikkelteam uit 35 mensen. Moeilijk te geloven, nietwaar? Natuurlijk bestond automatisering in enige vorm al, maar een aparte richting voor continue integratie en codelevering begon zich pas in 2015 te vormen.
Op dat moment hadden we een enorme monolith uit Python, C# en PHP, geïmplementeerd op Linux/Windows-servers. Voor het uitrollen van dit monster hadden we een set scripts die we handmatig uitvoerden. Er was ook de assemblage van de monolith, wat pijn en lijden met zich meebracht door conflicten tijdens het samenvoegen van takken, het verhelpen van defecten en het opnieuw assembleren met 'een andere set taken in de build'. Vereenvoudigd zag het proces er als volgt uit:

Dit was niet naar onze zin, en we wilden een herhaalbaar, geautomatiseerd en beheerd proces voor de assemblage en uitrol opbouwen. Hiervoor hadden we een CI/CD-systeem nodig, en we kozen tussen de gratis versie van Teamcity en gratis Jenkins, aangezien we met beide hadden gewerkt en beiden voldeden aan onze functionele vereisten. We kozen Teamcity als het nieuwere product. Destijds gebruikten we nog geen microservices-architectuur en verwachtten we geen groot aantal taken en projecten.
We komen op het idee van een eigen systeem
De implementatie van Teamcity heeft slechts een deel van het handmatige werk weggenomen: er blijft nog steeds het aanmaken van Pull Requests, het verplaatsen van taken door status in Jira, en het selecteren van taken voor de release. Dit was iets waar het Teamcity-systeem niet mee om kon gaan. We moesten kiezen voor een verdere automatiseringsweg. We overwegen opties om met scripts in Teamcity te werken of over te stappen naar externe automatiseringssystemen. Uiteindelijk besloten we dat we de maximale flexibiliteit nodig hadden die alleen een eigen oplossing kon bieden. Zo ontstond de eerste versie van het interne automatiseringssysteem genaamd Integro.
Teamcity is verantwoordelijk voor de automatisering op het niveau van het starten van build- en deployprocessen, terwijl Integro zich richtte op de hogere niveaus van automatisering van ontwikkelingsprocessen. Het was noodzakelijk om het werk met taken in Jira te combineren met de verwerking van de bijbehorende broncode in Bitbucket. Op dat moment begonnen er binnen Integro eigen workflows voor het werken met taken van verschillende types te ontstaan.
Door de toename van automatisering in bedrijfsprocessen is het aantal projecten en runs in Teamcity gestegen. Dit leidde tot een nieuw probleem: één gratis instance van Teamcity voldeed niet meer (3 agents en 100 projecten), we hebben nog een instance toegevoegd (nog 3 agents en 100 projecten), en daarna nog een. Uiteindelijk hebben we een systeem gekregen dat uit meerdere clusters bestond, wat moeilijk te beheren was:

Toen de vraag over een 4e instance opkwam, realiseerden we ons dat we zo niet verder konden, aangezien de totale kosten voor het onderhouden van 4 instances al niet meer in de haak waren. De vraag kwam op om een betaalde Teamcity aan te schaffen of voor de gratis Jenkins te kiezen. We hebben berekeningen gemaakt voor de instances en de automatiseringsplannen en besloten dat we op Jenkins verder zouden gaan. Na een paar weken zijn we overgestapt op Jenkins en hebben we een deel van de hoofdpijn verholpen die verband hield met het onderhoud van verschillende Teamcity-instances. Daarom konden we ons concentreren op de ontwikkeling van Integro en het aanpassen van Jenkins naar onze wensen.
Met de groei van basisautomatisering (zoals het automatisch aanmaken van Pull Requests, verzamelen en publiceren van codecoverage en andere verificaties) ontstond de dringende wens om handmatige uitgaves tot een minimum te reduceren en deze taak aan robots over te laten. Daarnaast is binnen het bedrijf de overgang naar microservices begonnen, die frequente uitgaves vereisten, en dit allemaal apart van elkaar. Zo kwamen we geleidelijk aan bij automatische uitgaves van onze microservices (de monoliet blijven we voorlopig handmatig uitgeven vanwege de complexiteit van het proces). Maar zoals vaak het geval is, ontstond er een nieuwe complicatie.
We automatiseren testen

Door de automatisering van uitgaves zijn de ontwikkelingsprocessen versneld, deels door het overslaan van enkele fasen van testen. Dit leidde tot een tijdelijke kwaliteitsverliezen. Het klinkt misschien simpel, maar samen met de versnelling van de uitgaves moest ook de productontwikkelingsmethodologie worden aangepast. Er moest nagedacht worden over de automatisering van testen, het creëren van persoonlijke verantwoordelijkheid (hier gaat het om het 'internaliseren van het idee', niet om financiële boetes) van de ontwikkelaar voor de geproduceerde code en de bugs daarin, en ook over de beslissing om een taak al dan niet uit te geven via automatische deploy.
Bij het aanpakken van kwaliteitsproblemen kwamen we tot twee belangrijke oplossingen: we begonnen met canary testing en implementeerden automatische foutmonitoring met automatische reacties op overschrijdingen. De eerste oplossing maakte het mogelijk om voor de volledige inwerkingstelling van de code in productie voor de hand liggende fouten te ontdekken, de tweede verkortte de responstijd op problemen in productie. Fouten komen natuurlijk voor, maar we besteden het grootste deel van onze tijd en moeite niet aan het oplossen van ze, maar aan het minimaliseren.
Het automatiseringsteam
Momenteel hebben we een team van 130 ontwikkelaars, en we blijven . Het team voor continue integratie en levering van code (verder - het Deploy and Integration team of DI) bestaat uit 7 personen en werkt in 2 richtingen: ontwikkeling van het automatiseringsplatform Integro en DevOps.
DevOps is verantwoordelijk voor de Dev/Beta-omgevingen van de CIAN-website, de Integro-omgevingen, helpt ontwikkelaars met probleemoplossingen en ontwikkelt nieuwe benaderingen voor het schalen van omgevingen. De Integro-ontwikkelingsrichting houdt zich bezig met Integro zelf en aanverwante diensten, zoals plugins voor Jenkins, Jira, Confluence, en ontwikkelt ook hulpprogramma's en applicaties voor ontwikkelteams.
Het DI-team werkt samen met het platformteam, dat verantwoordelijk is voor de ontwikkeling van architecturen, bibliotheken en ontwikkelingsbenaderingen binnen het bedrijf. Daarnaast kan elke ontwikkelaar binnen CIAN bijdragen aan automatisering, bijvoorbeeld door micro-automatisering op te zetten voor de behoeften van het team of een geweldig idee te delen om automatisering nog beter te maken.
Gelaagde automatisering bij CIAN

Alle systemen die betrokken zijn bij automatisering kunnen in verschillende lagen worden onderverdeeld:
- Externe systemen (Jira, Bitbucket, enz.). Deze worden gebruikt door de ontwikkelingsteams.
- Integro-platform. Ontwikkelaars werken meestal niet direct hiermee, maar het ondersteunt alle automatiseringsprocessen.
- Leverings-, orkestratie- en detectiediensten (bijvoorbeeld Jenkins, Consul, Nomad). Hiermee implementeren we de code op servers en zorgen we ervoor dat de diensten samenwerken.
- Fysiek niveau (servers, besturingssystemen, aanverwante software). Op dit niveau draait onze code. Dit kan zowel een fysieke server als een virtuele server (LXC, KVM, Docker) zijn.
Op basis van dit concept delen we verantwoordelijkheden binnen het DI-team. De eerste twee lagen vallen onder de verantwoordelijkheid van de Integro-ontwikkelingsrichting, terwijl de laatste twee lagen onder de verantwoordelijkheid van DevOps vallen. Deze scheiding stelt ons in staat om ons op de taken te concentreren en belemmert de samenwerking niet, aangezien we dicht bij elkaar staan en voortdurend kennis en ervaring uitwisselen.
Integro
Laten we ons concentreren op Integro en beginnen met de technologische stack:
- CentOs 7
- Docker + Nomad + Consul + Vault
- Java 11 (de oude monoliet Integro blijft op Java 8)
- Spring Boot 2.X + Spring Cloud Config
- PostgreSql 11
- RabbitMQ
- Apache Ignite
- Camunda (embedded)
- Grafana + Graphite + Prometheus + Jaeger + ELK
- Web UI: React (CSR) + MobX
- SSO: Keycloak
Wij volgen het principe van microservices-ontwikkeling, hoewel we ook legacy hebben in de vorm van de oudere monolietversie van Integro. Elke microservice draait in zijn eigen Docker-container, en de services communiceren met elkaar via HTTP-verzoeken en RabbitMQ-berichten. Microservices vinden elkaar via Consul en voeren een verzoek erin uit, waarbij ze authenticatie doen via SSO (Keycloak, OAuth 2/OpenID Connect).

Laten we als een praktisch voorbeeld de interactie met Jenkins beschouwen, die uit de volgende stappen bestaat:
- De workflow-management microservice (verder Flow-microservice genoemd) wil een build in Jenkins starten. Hiervoor vindt hij via Consul het IP:PORT van de Jenkins-integratiemicroservice (verder Jenkins-microservice genoemd) en stuurt een asynchroon verzoek om de build in Jenkins te starten.
- De Jenkins-microservice vormt na ontvangst van het verzoek een Job ID en geeft deze terug, waarmee later de werkresultaten kunnen worden geïdentificeerd. Tegelijkertijd start hij de build in Jenkins via een REST API-aanroep.
- Jenkins voert de build uit en stuurt na afloop een webhook met de uitvoeringsresultaten naar de Jenkins-microservice.
- De Jenkins-microservice, na het ontvangen van de webhook, vormt een bericht over de afronding van de verwerking van het verzoek en voegt de uitvoeringsresultaten eraan toe. Het gevormde bericht wordt in de RabbitMQ-queue geplaatst.
- Via RabbitMQ komt het gepubliceerde bericht bij de Flow-microservice, die op de hoogte komt van het resultaat van de verwerking van zijn taak door de Job ID uit het verzoek te vergelijken met de ontvangen boodschap.
Momenteel hebben we ongeveer 30 microservices die we in verschillende groepen kunnen indelen:
- Configuratiemanagement.
- Informeren en communiceren met gebruikers (messenger, e-mail).
- Werking met de broncode.
- Integratie met deployment-tools (Jenkins, Nomad, Consul, etc.).
- Monitoring (van releases, fouten, etc.).
- Web-tools (UI voor het beheren van testomgevingen, verzamelen van statistieken, etc.).
- Integratie met taaktrackers en vergelijkbare systemen.
- Workflow management voor verschillende taken.
Taakworkflow
Integro automatiseert acties die verband houden met de levenscyclus van een taak. Vereenvoudigd stellen we onder de levenscyclus van een taak de taakworkflow in Jira voor. In onze ontwikkelprocessen zijn er verschillende variaties van workflows, afhankelijk van het project, het type taak en de opties die zijn gekozen voor de specifieke taak.
Laten we de workflow bekijken die we het vaakst gebruiken:

In het diagram geeft het tandwiel aan dat de overgang automatisch door Integro wordt aangeroepen, terwijl de figuur van de mens betekent dat de overgang handmatig door een persoon wordt aangeroepen. Laten we een paar paden bekijken die de taak in deze workflow kan volgen.
Volledig handmatige testing op DEV+BETA zonder canary tests (doorgaans zo brengen we de monolith uit):

Er kunnen ook andere combinaties van overgangen zijn. Soms kan het pad dat de taak volgt worden gekozen via opties in Jira.
Beweging van de taak
Laten we de belangrijkste stappen bekijken die worden uitgevoerd tijdens het bewegen van de taak door de workflow 'Testing op DEV + canary tests':
1. De ontwikkelaar of PM creëert de taak.
2. De ontwikkelaar neemt de taak aan. Na voltooiing verhuist hij naar de status IN REVIEW.
3. Jira stuurt een Webhook naar de Jira-microservice (verantwoordelijk voor de integratie met Jira).
4. De Jira-microservice stuurt een verzoek naar de Flow-service (verantwoordelijk voor interne workflows waarin het werk wordt uitgevoerd) om de workflow te starten.
5. Binnen de Flow-service:
- Worden reviewers voor de taak toegewezen (Users-microservice, die alles weet over gebruikers + Jira-microservice).
- Via de Source-microservice (weet van repositories en branches, maar werkt niet met de code zelf) wordt gezocht naar repositories waarin de branch van onze taak zich bevindt (ter vereenvoudiging komt de naam van de branch overeen met het nummer van de taak in Jira). Meestal heeft de taak slechts één branch in één repository, wat het beheer van de deployment queue vereenvoudigt en de koppeling tussen repositories vermindert.
- Voor elke gevonden branch wordt de volgende volgorde van acties uitgevoerd:
i) Merging met de master-branch (Git-microservice voor codewerk).
ii) De branch wordt geblokkeerd voor wijzigingen door de ontwikkelaar (Bitbucket-microservice).
iii) Er wordt een Pull Request gemaakt voor deze branch (Bitbucket-microservice).
iv) Er wordt een bericht over de nieuwe Pull Request naar de chats van ontwikkelaars gestuurd (Notify-microservice voor notificaties).
v) De build, testing en deployment van de taak op DEV worden gestart (Jenkins-microservice voor werken met Jenkins).
vi) Als alle voorgaande punten succesvol zijn afgerond, plaatst Integro zijn goedkeuring in de Pull Request (Bitbucket-microservice). - Integro wacht op goedkeuring in de Pull Request van de toegewezen reviewers.
- Zodra alle benodigde goedkeuringen zijn verkregen (incl. positief doorlopen geautomatiseerde tests), verplaatst Integro de taak naar de status Test on Dev (Jira-microservice).
6. Testers voeren de test van de taak uit. Als er geen problemen zijn, wordt de taak in de status Klaar voor Bouw gezet.
7. Integro ziet dat de taak klaar is voor release en start de uitrol in een canary modus (Jenkins-microservice). De gereedheid voor release wordt bepaald door een set regels. Bijvoorbeeld, de taak staat in de juiste status, er zijn geen blokkades op andere taken, er zijn op dat moment geen actieve uitrols van deze microservice, enzovoort.
8. De taak wordt in statuur Canary gezet (Jira-microservice).
9. Jenkins start via Nomad de uitrol van de taak in canary modus (meestal 1-3 instanties) en meldt de uitrol aan de release-monitoring service (DeployWatch-microservice).
10. DeployWatch-microservice verzamelt de foutbackground en reageert hierop indien nodig. Bij overschrijding van de foutbackground (de norm van de background wordt automatisch berekend) volgt een melding naar de ontwikkelaars via de Notify-microservice. Als de ontwikkelaar binnen 5 minuten niet reageert (op Revert of Stay drukt), wordt een automatische rollback van de canary-instanties gestart. Als de background niet is overschreden, moet de ontwikkelaar handmatig de uitrol van de taak naar Productie starten (door op de knop in de UI te klikken). Als de ontwikkelaar binnen 60 minuten geen uitrol naar Productie heeft gestart, worden de canary-instanties ook om veiligheidsredenen teruggedraaid.
11. Na het starten van de uitrol naar Productie:
- De taak wordt in de status Productie geplaatst (Jira-microservice).
- Jenkins-microservice start het uitrolproces en meldt de uitrol aan de DeployWatch-microservice.
- DeployWatch-microservice controleert of alle containers op Productie zijn bijgewerkt (er zijn gevallen geweest waarbij niet alles werd bijgewerkt).
- Via de Notify-microservice wordt een melding gestuurd over de resultaten van de uitrol naar Productie.
12. Ontwikkelaars hebben 30 minuten om een rollback van de taak uit Productie te starten in het geval van incorrect gedrag van de microservice. Na deze tijd wordt de taak automatisch in master geïntegreerd (Git-microservice).
13. Na een succesvolle merge in master wordt de status van de taak gewijzigd naar Gesloten (Jira-microservice).
Het schema pretendeert geen volledige detaillering te bevatten (in werkelijkheid zijn er nog meer stappen), maar biedt een beoordeling van de mate van integratie in de processen. We beschouwen dit schema niet als ideaal en verbeteren de processen voor automatische release- en uitrolondersteuning.
Hier was eigenlijk een praktische sectie gepland met demonstratie van drie projecten op STM32 en STM8, speciaal voor dit artikel gemaakt met behulp van datasheets, met lampen, SPI, timers, PWM en interrupts:
We have big plans for the development of automation, such as eliminating manual operations during monolith releases, improving monitoring during automatic deployments, and enhancing collaboration with developers.
But let's pause here for now. We've covered many topics in the automation overview superficially, and some we haven't touched at all, so we're happy to answer your questions. We look forward to suggestions on what to elaborate on in detail; please write in the comments.
Bron: habr.com
