Come Microsoft sviluppa con DevOps

Microsoft si impegna a usare One Engineering System per creare e distribuire tutti i prodotti Microsoft con un solido processo DevOps incentrato su un flusso di distribuzione e rilascio Git. Questo articolo illustra l'implementazione pratica, il modo in cui il sistema viene ridimensionato da piccoli servizi a esigenze di sviluppo di piattaforme di grandi dimensioni e lezioni apprese dall'uso del sistema in vari team Microsoft.

L'adozione di un processo di sviluppo standardizzato è un'impresa ambiziosa. I requisiti delle diverse organizzazioni Microsoft variano notevolmente e i requisiti dei diversi team all'interno delle organizzazioni vengono ridimensionati con dimensioni e complessità. Per soddisfare queste diverse esigenze, Microsoft usa una strategia di diramazione basata su trunk per aiutare a sviluppare rapidamente i prodotti, distribuirli regolarmente e distribuire le modifiche in modo sicuro nell'ambiente di produzione.

Microsoft usa anche i principi di progettazione della piattaforma come parte del sistema one engineering.

Flusso di rilascio Microsoft

Ogni organizzazione deve stabilire un processo di rilascio del codice standard per garantire la coerenza tra i team. Il flusso di versione Microsoft incorpora i processi DevOps dallo sviluppo al rilascio. I passaggi di base del flusso di rilascio sono costituiti da rami, push, richieste pull e merge.

Filiale

Per correggere un bug o implementare una funzionalità, uno sviluppatore crea un nuovo ramo fuori dal ramo di integrazione principale. Il modello di diramazione Git lightweight crea questi rami di argomenti di breve durata per ogni contributo del codice. Gli sviluppatori eseguono il commit anticipato ed evitano rami di funzionalità a esecuzione prolungata usando flag di funzionalità.

Push

Quando lo sviluppatore è pronto per l'integrazione e la distribuzione delle modifiche al resto del team, esegue il push del ramo locale in un ramo nel server e apre una richiesta pull. I repository con diverse centinaia di sviluppatori che lavorano in molti rami usano una convenzione di denominazione per i rami del server per alleviare confusione e proliferazione dei rami. Gli sviluppatori in genere creano rami denominati users/<username>/feature, dove <username> è il nome dell'account.

Richiesta pull

Le richieste pull controllano il ramo dell'argomento si uniscono nel ramo principale e assicurano che i criteri dei rami siano soddisfatti. Il processo di richiesta pull compila le modifiche proposte ed esegue un passaggio di test rapido. I gruppi di test di primo e secondo livello eseguono circa 60.000 test in meno di cinque minuti. Questa non è la matrice di test Microsoft completa, ma è sufficiente per fornire rapidamente fiducia nelle richieste pull.

Successivamente, altri membri del team esaminano il codice e approvano le modifiche. La revisione del codice rileva dove sono stati interrotti i test automatizzati ed è particolarmente utile per individuare i problemi di architettura. Le revisioni manuali del codice assicurano che altri tecnici del team abbiano visibilità sulle modifiche e che la qualità del codice rimanga elevata.

Unire

Una volta che la richiesta pull soddisfa tutti i criteri di compilazione e i revisori, il ramo dell'argomento viene unito al ramo di integrazione principale e la richiesta pull è stata completata.

Dopo l'unione, vengono eseguiti altri test di accettazione che richiedono più tempo per il completamento. Questi test tradizionali di post-checkin eseguono una convalida più approfondita. Questo processo di test offre un buon equilibrio tra avere test veloci durante la revisione delle richieste pull e avere una copertura di test completa prima del rilascio.

Differenze rispetto a GitHub Flow

GitHub Flow è un flusso di rilascio di sviluppo basato su trunk diffuso per le organizzazioni per implementare un approccio scalabile a Git. Tuttavia, alcune organizzazioni trovano che, man mano che le proprie esigenze aumentano, devono divergere da parti del flusso GitHub.

Ad esempio, una parte spesso trascurata di GitHub Flow è che le richieste pull devono essere distribuite nell'ambiente di produzione per i test prima di poter eseguire il merge nel ramo principale. Questo processo significa che tutte le richieste pull attendono nella coda di distribuzione per l'unione.

Alcuni team hanno diverse centinaia di sviluppatori che lavorano costantemente in un singolo repository, che possono completare più di 200 richieste pull nel ramo principale al giorno. Se ogni richiesta pull richiede una distribuzione in più data center di Azure in tutto il mondo per i test, gli sviluppatori dedicano tempo in attesa del merge dei rami, anziché scrivere software.

I team Microsoft continuano invece a sviluppare nel ramo principale e raggruppare le distribuzioni in versioni temporali, in genere allineate a una cadenza sprint di tre settimane.

Dettagli sull'implementazione

Ecco alcuni dettagli principali dell'implementazione del flusso di rilascio Microsoft:

Strategia del repository Git

Diversi team hanno strategie diverse per la gestione dei repository Git. Alcuni team mantengono la maggior parte del codice in un repository Git. Il codice viene suddiviso in componenti, ognuno nella propria cartella a livello radice. I componenti di grandi dimensioni, in particolare i componenti meno recenti, possono avere più sottocomponenti che dispongono di sottocartelle separate all'interno del componente padre.

Screenshot showing a Git repository structure.

Repository aggiuntivi

Alcuni team gestiscono anche repository aggiuntivi. Ad esempio, gli agenti e le attività di compilazione e rilascio, l'estensione VS Code e i progetti open source vengono sviluppati in GitHub. Le modifiche di configurazione vengono archiviate in un repository separato. Altri pacchetti da cui dipende il team provengono da altre posizioni e vengono utilizzati tramite NuGet.

Repository Mono o multi-repository

Mentre alcuni team scelgono di avere un singolo repository monolitico, il repository monolitico, altri prodotti Microsoft usano un approccio multi-repository. Skype, ad esempio, ha centinaia di piccoli repository che si combinano in varie combinazioni per creare molti client, servizi e strumenti diversi. Soprattutto per i team che adottano microservizi, il multi-repository può essere l'approccio corretto. In genere, i prodotti meno recenti che iniziano come monoliti trovano un approccio mono-repo per essere la transizione più semplice a Git e l'organizzazione del codice riflette questo aspetto.

Rami di versione

Il flusso di rilascio Microsoft mantiene sempre compilabile il ramo principale. Gli sviluppatori lavorano in rami di argomenti di breve durata che si uniscono a main. Quando un team è pronto per la spedizione, sia alla fine di uno sprint che per un aggiornamento principale, avvia un nuovo ramo di rilascio fuori dal ramo principale. I rami di rilascio non si uniscono mai al ramo principale, quindi potrebbero richiedere modifiche importanti per la selezione di ciliegie.

Il diagramma seguente mostra i rami di breve durata in rami blu e di rilascio in nero. Un ramo con un commit che richiede il cherry-pick viene visualizzato in rosso.

Diagram showing Git release branch structure.

Criteri e autorizzazioni dei rami

I criteri dei rami Git consentono di applicare la struttura del ramo di rilascio e mantenere pulito il ramo principale. Ad esempio, i criteri di ramo possono impedire il push diretto al ramo principale.

Per mantenere ordinata la gerarchia dei rami, i team usano le autorizzazioni per bloccare la creazione di rami a livello radice della gerarchia. Nell'esempio seguente tutti gli utenti possono creare rami in cartelle come utenti/, funzionalità/e team/. Solo i responsabili delle versioni hanno l'autorizzazione per creare rami in versioni/e alcuni strumenti di automazione dispongono dell'autorizzazione per le integrazioni/ cartella.

Screenshot that shows branches.

Flusso di lavoro del repository Git

All'interno del repository e della struttura dei rami, gli sviluppatori svolgono il proprio lavoro quotidiano. Gli ambienti di lavoro variano notevolmente in base al team e ai singoli utenti. Alcuni sviluppatori preferiscono la riga di comando, altri come Visual Studio e altri funzionano su piattaforme diverse. Le strutture e i criteri applicati nei repository Microsoft garantiscono una base solida e coerente.

Un flusso di lavoro tipico prevede le attività comuni seguenti:

Creare una nuova funzionalità

La creazione di una nuova funzionalità è il nucleo del lavoro di uno sviluppatore software. Le parti non Git del processo includono l'analisi dei dati di telemetria, la progettazione e una specifica e la scrittura del codice effettivo. Quindi, lo sviluppatore inizia a lavorare con il repository eseguendo la sincronizzazione con il commit più recente in main. Il ramo principale è sempre compilabile, quindi è garantito che sia un buon punto di partenza. Lo sviluppatore verifica un nuovo ramo di funzionalità, apporta modifiche al codice, commit, push al server e avvia una nuova richiesta pull.

Usare i criteri e i controlli dei rami

Al momento della creazione di una richiesta pull, i sistemi automatizzati verificano che il nuovo codice venga compilato, non interrompa nulla e non viola alcun criterio di sicurezza o conformità. Questo processo non impedisce l'esecuzione in parallelo di altri lavori.

I criteri e i controlli dei rami possono richiedere una compilazione corretta, inclusi i test superati, la disconnessione dai proprietari di qualsiasi codice toccato e diversi controlli esterni per verificare i criteri aziendali prima che una richiesta pull possa essere completata.

Screenshot showing the checks on a pull request.

Integrazione con Microsoft Teams

Molti team configurano l'integrazione con Microsoft Teams, che annuncia la nuova richiesta pull ai compagni di squadra degli sviluppatori. I proprietari di qualsiasi codice toccato vengono aggiunti automaticamente come revisori. I team Microsoft usano spesso revisori facoltativi per il codice che molti utenti toccano, ad esempio la generazione di client REST e i controlli condivisi, per ottenere occhi esperti su tali modifiche.

Screenshot showing Teams integration.

Screenshot showing Teams notification of a pull request.

Eseguire la distribuzione con i flag di funzionalità

Una volta soddisfatti i revisori, i proprietari del codice e l'automazione, lo sviluppatore può completare la richiesta pull. Se si verifica un conflitto di merge, lo sviluppatore riceve istruzioni su come eseguire la sincronizzazione con il conflitto, correggerlo ed eseguire nuovamente il push delle modifiche. L'automazione viene eseguita di nuovo nel codice fisso, ma gli esseri umani non devono disconnettersi di nuovo.

Il ramo viene unito a maine il nuovo codice viene distribuito nello sprint o nella versione principale successiva. Ciò non significa che la nuova funzionalità verrà visualizzata immediatamente. Microsoft separa la distribuzione e l'esposizione delle nuove funzionalità usando i flag di funzionalità.

Anche se la funzionalità richiede un po' di lavoro prima che sia pronta per essere mostrata, è sicuro passare a main se il prodotto compila e distribuisce. Una volta in main, il codice diventa parte di una build ufficiale, in cui viene nuovamente testato, confermato per soddisfare i criteri e firmato digitalmente.

Spostarsi a sinistra per rilevare i problemi in anticipo

Questo flusso di lavoro Git offre diversi vantaggi. Prima di tutto, lavorare fuori da un singolo ramo principale praticamente elimina il debito di merge. In secondo luogo, il flusso della richiesta pull fornisce un punto comune per applicare test, revisione del codice e rilevamento degli errori nelle prime fasi della pipeline. Questa strategia di spostamento a sinistra consente di abbreviare il ciclo di feedback agli sviluppatori perché può rilevare errori in minuti, non ore o giorni. Questa strategia offre anche la sicurezza per il refactoring, perché tutte le modifiche vengono testate costantemente.

Attualmente, un prodotto con più di 200 richieste pull potrebbe produrre 300 compilazioni di integrazione continua al giorno, pari a 500+ esecuzioni di test ogni 24 ore. Questo livello di test sarebbe impossibile senza il flusso di lavoro di diramazione e rilascio basato su trunk.

Rilascio in fase di attività cardine sprint

Alla fine di ogni sprint, il team crea un ramo di rilascio dal ramo principale. Ad esempio, alla fine dello sprint 129, il team crea un nuovo ramo releases/M129di versione. Il team inserisce quindi il ramo sprint 129 nell'ambiente di produzione.

Dopo il ramo del ramo di rilascio, il ramo principale rimane aperto per consentire agli sviluppatori di unire le modifiche. Queste modifiche verranno distribuite tre settimane dopo nella distribuzione sprint successiva.

Illustration of the release branch at sprint 129.

Hotfix di rilascio

A volte le modifiche devono passare rapidamente all'ambiente di produzione. Microsoft in genere non aggiungerà nuove funzionalità al centro di uno sprint, ma a volte vuole introdurre rapidamente una correzione di bug per sbloccare gli utenti. I problemi possono essere minori, ad esempio errori di digitazione o sufficientemente grandi da causare un problema di disponibilità o un evento imprevisto del sito attivo.

La correzione di questi problemi inizia con il normale flusso di lavoro. Uno sviluppatore crea un ramo da main, ottiene il codice esaminato e completa la richiesta pull per unirla. Il processo inizia sempre apportando la modifica in main primo luogo. In questo modo è possibile creare rapidamente la correzione e convalidarla in locale senza dover passare al ramo di rilascio.

L'esecuzione di questo processo garantisce anche che la modifica venga inserita in main, che è fondamentale. La correzione di un bug nel ramo di rilascio senza riportare la modifica in main significa che il bug si verifica durante la distribuzione successiva, quando lo sprint 130 rilascia rami da main. È facile dimenticare di aggiornare main durante la confusione e lo stress che possono verificarsi durante un'interruzione. L'introduzione delle modifiche al main primo significa avere sempre le modifiche nel ramo principale e nel ramo di rilascio.

La funzionalità Git abilita questo flusso di lavoro. Per introdurre immediatamente le modifiche nell'ambiente di produzione, una volta che uno sviluppatore unisce una richiesta pull in main, può usare la pagina della richiesta pull per selezionare le modifiche nel ramo di rilascio. Questo processo crea una nuova richiesta pull destinata al ramo di rilascio, con il backporting del contenuto appena unito in main.

Illustration of cherry-picking a hotfix commit into branch 129.

L'uso della funzionalità cherry-pick apre rapidamente una richiesta pull, fornendo la tracciabilità e l'affidabilità dei criteri di ramo. Cherry-pick può verificarsi nel server, senza dover scaricare il ramo di rilascio in un computer locale. Apportare modifiche, correggere i conflitti di merge o apportare modifiche minime a causa delle differenze tra i due rami possono verificarsi nel server. Teams può modificare le modifiche direttamente dall'editor di testo basato su browser o tramite l'estensione dei conflitti di merge delle richieste pull per un'esperienza più avanzata.

Quando una richiesta pull è destinata al ramo di rilascio, il codice del team lo rivede, valuta i criteri dei rami, testa la richiesta pull e lo unisce. Dopo l'unione, la correzione viene distribuita nel primo anello di server in pochi minuti. Da qui, il team distribuisce progressivamente la correzione a più account usando gli anelli di distribuzione. Man mano che le modifiche vengono distribuite a più utenti, il team monitora l'esito positivo e verifica che la modifica corregge il bug senza introdurre carenze o rallentamenti. La correzione viene infine distribuita in tutti i data center di Azure.

Passare allo sprint successivo

Durante le tre settimane successive, il team completa l'aggiunta di funzionalità allo sprint 130 e si prepara a distribuire tali modifiche. Creano il nuovo ramo di versione, releases/M130 da maine distribuiscono tale ramo.

A questo punto, ci sono in realtà due rami nell'ambiente di produzione. Con una distribuzione basata su anello per apportare modifiche all'ambiente di produzione in modo sicuro, l'anello veloce ottiene le modifiche dello sprint 130 e i server circolari lenti rimangono nello sprint 129 mentre le nuove modifiche vengono convalidate nell'ambiente di produzione.

L'aggiornamento rapido di una modifica al centro di una distribuzione potrebbe richiedere l'aggiornamento rapido di due versioni diverse, la versione sprint 129 e la versione sprint 130. Il team porta e distribuisce l'hotfix in entrambi i rami di rilascio. Il ramo 130 ridistribuisce con l'hotfix agli anelli che sono già stati aggiornati. Il ramo 129 ridistribuisce con l'hotfix agli anelli esterni che non sono ancora stati aggiornati alla versione dello sprint successivo.

Una volta distribuiti tutti gli anelli, il ramo sprint 129 precedente viene abbandonato, perché tutte le modifiche apportate al ramo sprint 129 come hotfix sono state apportate anche in main. Quindi, queste modifiche saranno anche nel releases/M130 ramo .

Illustration of a release branch at sprint 130.

Riepilogo

Il modello di flusso di rilascio è al centro del modo in cui Microsoft sviluppa con DevOps per offrire Servizi online. Questo modello usa una semplice strategia di diramazione basata su trunk. Ma invece di mantenere gli sviluppatori bloccati in una coda di distribuzione, in attesa di unire le modifiche, il flusso di versione Di Microsoft consente agli sviluppatori di continuare a lavorare.

Questo modello di versione consente anche di distribuire nuove funzionalità nei data center di Azure a cadenza regolare, nonostante le dimensioni delle codebase Microsoft e il numero di sviluppatori che lavorano in tali data center. Il modello consente anche di portare gli hotfix nell'ambiente di produzione in modo rapido ed efficiente.