Abbiamo sviluppato DevOps come meglio potevamo. Eravamo in 8 persone, e Vasya era il migliore per Windows. Improvvisamente, Vasya se n'è andato e mi è stata affidata la responsabilità di lanciare un nuovo progetto per lo sviluppo su Windows. Quando ho riversato sul tavolo l'intero stack per lo sviluppo su Windows, ho capito che la situazione era critica...
Così inizia la storia Aleksandr Sinchinov con . Quando il nostro principale esperto di Windows ha lasciato l'azienda, Aleksandr si è chiesto cosa fare ora. Passare a Linux, naturalmente! Aleksandr racconterà come è riuscito a creare un precedente e a trasferire parte dello sviluppo Windows su Linux con l'esempio di un progetto implementato per 100.000 utenti finali.

Come è facile e informale distribuire un progetto in RPM utilizzando TFS, Puppet, Linux .NET core? Come mantenere la versioning del database del progetto, se gli sviluppatori sentono per la prima volta le parole Postgres e Flyway, mentre la scadenza è dopodomani? Come integrarsi con Docker? Come motivare gli sviluppatori .NET a rinunciare a Windows e smoothie a favore di Puppet e Linux? Come risolvere i conflitti ideologici se non ci sono né forze né voglia né risorse per gestire Windows in produzione? Di questo, così come di Web Deploy, testing, CI, delle pratiche di utilizzo di TFS in progetti esistenti, e naturalmente delle stampelle rotte e delle soluzioni funzionanti, ne parlerà la trascrizione della presentazione di Alexander.

Quindi, Vasya se n'è andato, il compito è su di me, gli sviluppatori aspettano con i forconi con impazienza. Quando ho finalmente realizzato che non potevo riportare Vasya, mi sono messo al lavoro. Per prima cosa, ho valutato la percentuale di VM Windows nel nostro parco. Il conteggio non era a favore di Windows.

Poiché stiamo attivamente sviluppando DevOps, ho capito che dovevo cambiare qualcosa nell'approccio alla distribuzione di una nuova applicazione. La soluzione era una sola: trasferire tutto su Linux, se possibile. Google mi ha aiutato: a quel tempo .Net era già stato portato su Linux, e ho capito che questa era la soluzione!
Perché .NET core in combinazione con Linux?
Ci sono diverse ragioni. Tra "pagare" e "non pagare", la maggior parte sceglierà la seconda opzione, proprio come ho fatto io. Una licenza per MSDB costa circa 1.000 $, e la manutenzione di un parco di macchine virtuali Windows si traduce in centinaia di dollari. Per una grande azienda, sono costi significativi. Perciò risparmio — prima ragione. Non è la più importante, ma è una delle più rilevanti.
Le macchine virtuali Windows consumano più risorse rispetto ai loro equivalenti Linux - sono pesanti. Considerando le dimensioni di una grande azienda, abbiamo scelto Linux.
Il sistema si integra facilmente con il CI esistente. Ci consideriamo DevOps all'avanguardia, utilizziamo Bamboo, Jenkins e GitLab CI, quindi gran parte delle nostre operazioni si svolgono su Linux.
L'ultima ragione - facile manutenzione. Avevamo bisogno di abbassare la soglia d'ingresso per i "manutentori" - le persone che comprendono la parte tecnica, garantiscono la continuità e gestiscono i servizi di secondo livello. Loro conoscevano già lo stack Linux, quindi per loro è molto più facile capire il nuovo prodotto, supportarlo e mantenerlo, piuttosto che spendere risorse aggiuntive per orientarsi in funzionalità analoghe di software per la piattaforma Windows.
Requisiti
Prima di tutto - la comodità della nuova soluzione per gli sviluppatori. Non tutti erano pronti per i cambiamenti, specialmente dopo aver sentito la parola Linux. Gli sviluppatori vogliono Visual Studio, TFS con autotest sui build e smoothie. Non importa come avviene la consegna in produzione. Pertanto, abbiamo deciso di non cambiare il processo abituale e di mantenere tutto invariato per lo sviluppo su Windows.
Un nuovo progetto deve essere integrato nel CI esistente. I binari erano già presenti e tutto il lavoro doveva essere svolto tenendo conto delle impostazioni del sistema di gestione della configurazione, degli standard di consegna adottati e dei sistemi di monitoraggio.
Semplicità nella manutenzione e operatività, come condizione per una soglia minima di ingresso per tutti i nuovi partecipanti provenienti da diversi reparti e dal settore di supporto.
La scadenza è ieri.
Gruppo di sviluppo Win
Con cosa ha lavorato quindi il team Windows?

Ora posso dire con certezza che IdentityServer4 è un'ottima alternativa gratuita a ADFS con funzionalità simili, o che Entity Framework Core — un paradiso per gli sviluppatori, dove non bisogna preoccuparsi di scrivere script SQL, ma piuttosto di descrivere le query nel database in termini di OOP. Tuttavia, durante la discussione del piano d'azione, guardavo a questo stack come se fosse un cuneo sumero, riconoscendo solo PostgreSQL e Git.
A quel tempo, usavamo attivamente Puppet come sistema di gestione della configurazione. In gran parte dei nostri progetti, utilizzavamo GitLab CI, Elastic, bilanciando i servizi ad alta richiesta con HAProxy, monitorando tutto con Zabbix, una combinazione di Grafana e Prometheus, Jaeger, e tutto ciò girava su hardware HP c ESXi con VMware. È noto a tutti — un classico del settore.

Diamo un'occhiata e cerchiamo di capire cosa stesse succedendo prima che iniziassimo tutti questi interventi.
Cosa era
TFS è un sistema piuttosto potente, che non solo consegna il codice dallo sviluppatore alla macchina di produzione finale, ma ha anche un insieme di strumenti per un'integrazione molto flessibile con diversi servizi — per garantire CI a livello di cross-platform.

In passato, si trattava di semplici finestre. TFS utilizzava diversi agenti di build su cui venivano raccolti numerosi progetti. Ogni agente aveva 3-4 worker per parallelizzare i compiti e ottimizzare il processo. Successivamente, secondo i piani di rilascio, TFS inviava il build freschissimo su un server Windows per applicazioni.
A cosa volevamo arrivare
Per la distribuzione e lo sviluppo utilizziamo TFS, mentre l'applicazione viene eseguita su un server Linux Application, e tra di loro c'è una sorta di magia. Questa Magic Box è il cuore del lavoro a venire. Prima di smontarlo pezzo per pezzo, farò un passo indietro e dirò due parole sull'applicazione.
Progetto
L'applicazione fornisce funzionalità per gestire carte prepagate.

Client
C'erano due tipi di utenti. Primo accedeva autenticandosi tramite certificato SSL SHA-2. Un secondo aveva accesso tramite nome utente e password.
HAProxy
Successivamente, la richiesta del cliente arrivava a HAProxy, che gestiva le seguenti operazioni:
- autenticazione primaria;
- terminazione SSL;
- ottimizzazione delle richieste HTTP;
- traslazione delle richieste.
La verifica del certificato del cliente avveniva attraverso la catena. Noi siamo authority e possiamo permetterci di farlo, poiché noi stessi rilasciamo certificati ai nostri clienti.
Fai attenzione al terzo punto, ne riparleremo più tardi.
Backend
Il backend era previsto su Linux. Il backend interagisce con il DB, carica l'elenco necessario delle autorizzazioni e poi, a seconda delle autorizzazioni del utente autenticato, fornisce l'accesso per la firma di documenti finanziari e il loro invio per l'esecuzione, oppure per la generazione di un rapporto.
Risparmi con HAProxy
Oltre ai due contesti in cui ciascuno dei clienti era attivo, esisteva anche il contesto identity. IdentityServer4 proprio permette l'autenticazione, è un'alternativa gratuita e potente per ADFS — Active Directory Federation Services.
La richiesta a identity veniva elaborata in diversi passaggi. Il primo passaggio è client andava al backend, che scambiava dati con questo server e verificava la presenza di un token per il cliente. Se non lo trovava, la richiesta veniva restituita al contesto da cui era arrivata, ma con un redirect, e col redirect andava a identity.
Il secondo passaggio è che la richiesta arrivava alla pagina di autenticazione in IdentityServer, dove il cliente si registrava, e nel database di IdentityServer appariva il tanto atteso token.
Il terzo passaggio è che il cliente veniva reindirizzato di nuovo. nel contesto da cui è arrivato.

IdentityServer4 ha una peculiarità: la risposta alla richiesta di callback viene restituita tramite HTTP. Non importa quanto ci siamo sforzati nel configurare il server, né quanto abbiamo studiato la documentazione, ogni volta ricevevamo la richiesta originale del client con un URL che arrivava tramite HTTPS, mentre IdentityServer restituiva lo stesso contesto, ma tramite HTTP. Siamo rimasti scioccati! E abbiamo reindirizzato tutto questo attraverso il contesto identity su HAProxy, e nelle intestazioni abbiamo dovuto modificare il protocollo da HTTP a HTTPS.
Qual è stato il miglioramento e dove abbiamo risparmiato?
Abbiamo risparmiato denaro utilizzando una soluzione gratuita per l'autorizzazione di un gruppo di utenti, risorse, poiché non abbiamo separato IdentityServer4 come nodo autonomo in un segmento separato, ma lo abbiamo utilizzato insieme al backend sullo stesso server dove gira il backend dell'applicazione.
Come dovrebbe funzionare
Quindi, come promesso — Magic Box. Ora capiamo che ci stiamo muovendo in direzione di Linux. Formuliamo le specifiche esigenze che necessitavano di una soluzione.

Manifesti Puppet. Per fornire e gestire la configurazione del servizio e dell'applicazione, era necessario scrivere ricette eccellenti. Un rotolo con una matita mostra eloquentemente quanto rapidamente e qualitativamente sia stato fatto.
Metodo di consegna. Lo standard è RPM. Tutti capiscono che in Linux non si può fare a meno di lui, ma il progetto stesso, dopo la compilation, era un insieme di file DLL eseguibili. Ce n'erano circa 150, il progetto era piuttosto pesante. L'unica soluzione armoniosa era imballare questo insieme di binari in RPM e poi lanciare l'applicazione da lì.
Versioning. Avevamo in programma di rilasciare molto frequentemente e dovevamo decidere come generare il nome del pacchetto. Questa è una questione di integrazione con TFS. Il nostro agent di build era su Linux. Quando TFS invia un compito a un worker sul Build-agent, gli passa anche un insieme di variabili che finiscono nell'ambiente del processo. In queste variabili d'ambiente vengono passati il nome della Build, il nome della versione e altre variabili. Maggiori dettagli su questo nella sezione "Costruzione del pacchetto RPM."
Configurazione TFS si riassume nella configurazione di Pipeline. In precedenza, raccoglievamo tutti i progetti Windows sugli agenti Windows, mentre ora introduciamo un agente Linux — l'agente Build, che deve essere incluso nel gruppo di build, arricchito con alcuni artefatti, specificare che tipo di progetti verranno costruiti su questo agente Build e modificare in qualche modo il Pipeline.
IdentityServer. ADFS non è la nostra strada, sosteniamo l'open source.
Passiamo ai componenti.
Magic Box
È composto da quattro parti.

Agente Build Linux. Linux, perché stiamo costruendo per esso — è logico. Questa parte è stata eseguita in tre fasi.
- Configurare i worker e non uno solo, poiché si prevedeva una collaborazione distribuita sul progetto.
- Installare .NET Core 1.x. Perché proprio 1.x, quando è già disponibile la 2.0 nel repository standard? Perché, quando abbiamo iniziato lo sviluppo, la versione stabile era 1.09, e si è deciso di realizzare il progetto per essa.
- Git 2.x.
RPM-repository. I pacchetti RPM dovevano essere archiviati da qualche parte. Si prevedeva che avremmo utilizzato lo stesso repository RPM aziendale, accessibile a tutti gli host Linux. E così è stato. Sul server del repository è configurato webhook che scaricava il pacchetto RPM richiesto dal luogo specificato. La versione del pacchetto veniva comunicata all'agente Build.
GitLab. Attenzione! GitLab qui è utilizzato non dai programmatori, ma dal reparto operativo per il controllo delle versioni dell'applicazione, delle versioni dei pacchetti, del monitoraggio dello stato di tutte le macchine Linux e contiene la ricetta— tutti i manifesti Puppet.
Puppet — risolve tutte le questioni controverse e fornisce esattamente quella configurazione che vogliamo, da GitLab.
Iniziamo a immergerci. Come avviene la consegna di DLL in RPM?
Consegna di DLL in RPM
Supponiamo di avere una rockstar dello sviluppo su .NET. Utilizza Visual Studio e crea un ramo di rilascio. Dopo di che lo carica in Git, e Git qui è un'entità TFS, cioè è il repository dell'applicazione con cui lavora lo sviluppatore.

Dopo di che TFS vede che è arrivato un nuovo commit. Quale applicazione? Nelle impostazioni di TFS c'è un'etichetta su quali risorse dispone ciascun agente di Build. In questo caso vede che stiamo costruendo un progetto .NET Core e seleziona un agente di build Linux dal pool.
L'agente di build riceve i sorgenti, scarica i necessari dependencies dal repository .NET, npm, ecc. e dopo la compilazione dell'applicazione e l'imballaggio successivo invia il pacchetto RPM nel repository RPM.
Dall'altra parte accade quanto segue. L'ingegnere del reparto operativo si occupa direttamente del rilascio del progetto: cambia le versioni dei pacchetti in Hiera nel repository dove è conservata la ricetta dell'applicazione, dopo di che Puppet attiva Yum, recupera il nuovo pacchetto dal repository e la nuova versione dell'applicazione è pronta per l'uso.

A parole sembra tutto semplice, ma cosa succede realmente all'interno del Build-agent?
Imballaggio DLL RPM
I sorgenti del progetto sono stati ricevuti e c'è un compito di build da TFS. Il Build-agent avvia la build del progetto stesso dai sorgenti. Il progetto costruito è disponibile sotto forma di numerosi file DLL, che vengono imballati in un archivio zip per ridurre il carico sul sistema file.
L'archivio ZIP viene posizionato nella directory di build del pacchetto RPM. Successivamente, uno script Bash inizializza le variabili d'ambiente, trova la versione di Build, la versione del progetto, il percorso della directory di build e avvia RPM-build. Al termine della compilazione, il pacchetto viene pubblicato in repository locale, che si trova sul Build-agent.
Successivamente, dal Build-agent al server nel repository RPM viene inviato un richiesta JSON con specifica del nome della versione e del build. Il webhook di cui parlavo prima scarica proprio questo pacchetto dal repository locale sul build agent e rende il nuovo build disponibile per l'installazione.

Perché proprio questo schema di consegna del pacchetto nel repository RPM? Perché non inviare direttamente il pacchetto costruito nel repository? La questione è che questa è una condizione per garantire la sicurezza. Tale scenario limita la possibilità di caricamento non autorizzato di pacchetti RPM da parte di estranei sul server, accessibile a tutte le macchine Linux.
Versioning del DB
Nel consiglio di sviluppo si è scoperto che ai ragazzi piace di più MS SQL, ma nella maggior parte dei progetti non Windows avevamo già utilizzato PostgreSQL. Poiché avevamo già deciso di rinunciare a tutto ciò che era a pagamento, abbiamo iniziato a utilizzare PostgreSQL anche qui.

In questa parte voglio raccontare come abbiamo effettuato il versioning del DB e come abbiamo scelto tra Flyway e Entity Framework Core. Consideriamo i loro pro e contro.
Svantaggi
Flyway funziona solo in una direzione, noi non possiamo tornare indietro. — è un grosso svantaggio. È possibile confrontarlo con Entity Framework Core sotto altri aspetti — dal punto di vista della comodità per lo sviluppatore. Dovete ricordare che abbiamo posto questo al centro della nostra attenzione, e il criterio principale era non modificare nulla per lo sviluppo su Windows.
Per Flyway, ci voleva qualche tipo di wrapper, in modo che i ragazzi non scrivessero query SQL. È molto più vicino a operare in termini di OOP. Abbiamo scritto istruzioni su come lavorare con gli oggetti del DB, si è formata la query SQL ed è stata eseguita. La nuova versione del DB è pronta, ha funzionato — tutto bene, tutto funziona.
Entity Framework Core ha un difetto — sotto carichi elevati genera query SQL non ottimali, e il downgrade del DB può essere significativo. Ma poiché non abbiamo un servizio ad alta intensità di carico, non misuriamo il carico in centinaia di RPS, abbiamo accettato questi rischi e delegato il problema a noi stessi in futuro.
Pro
Entity Framework Core funziona out-of-the-box ed è comodo per lo sviluppo, mentre Flyway si integra facilmente nella CI esistente. Ma stiamo rendendo tutto più comodo per i developer :)
Procedura di rollout
Puppet osserva che c'è un cambiamento nella versione dei pacchetti, tra cui quello responsabile della migrazione. Inizia installando il pacchetto che contiene gli script di migrazione e le funzionalità legate al database. Successivamente, riavvia l'applicazione che interagisce con il database. Poi si procede all'installazione dei componenti restanti. L'ordine di installazione dei pacchetti e di avvio delle applicazioni è descritto nel manifesto di Puppet.
Le applicazioni utilizzano dati sensibili come token e password del database, tutti questi vengono recuperati nella configurazione dal Puppet master, dove sono memorizzati in forma cifrata.
Problemi TFS
Dopo aver stabilito che tutto funziona correttamente, ho deciso di controllare cosa stava succedendo con le build in TFS in generale per il dipartimento sviluppo Win in altri progetti - se ci stavamo compilando/rilasciando rapidamente o meno, e ho scoperto problemi significativi con la velocità.
Uno dei principali progetti si compila in 12-15 minuti - è troppo lungo, non si può vivere così. Un'analisi rapida ha mostrato una grave flessione delle prestazioni I/O, e questo sui dischi.
Analizzando i componenti, ho identificato tre aree critiche. La prima - «Kaspersky antivirus», che scansiona i sorgenti su tutti gli agenti di build Windows. Secondo — Windows Indexer. Non è stato disattivato e durante il deployment tutto veniva indicizzato in tempo reale sugli agenti di build.
Terzo — Npm install. Si è scoperto che in gran parte dei pipeline abbiamo utilizzato proprio questo script. Qual è il problema? La procedura Npm install viene eseguita durante la creazione dell'albero delle dipendenze in package-lock.json, dove vengono registrate le versioni dei pacchetti che verranno utilizzate per la costruzione del progetto. Il problema è che Npm install recupera ogni volta le versioni attuali dei pacchetti da Internet, e questo richiede tempo in caso di progetti di grandi dimensioni.
Gli sviluppatori a volte fanno esperimenti sulla macchina locale per testare una parte separata o l'intero progetto. A volte si scopre che localmente funziona tutto bene, ma quando si compila e si distribuisce, non funziona niente. Iniziamo a capire dove sia il problema — ecco, versioni diverse dei pacchetti con dipendenze.
Soluzione
- Sorgenti nelle esclusioni AV.
- Disattivazione dell'indicizzazione.
- Passaggio a npm ci.
I vantaggi di npm ci sono che creiamo l'albero delle dipendenze una sola volta, e otteniamo la possibilità di fornire allo sviluppatore un elenco aggiornato dei pacchetti, con cui può sperimentare localmente quanto vuole. Questo risparmia tempo agli sviluppatori che scrivono codice.
Configurazione
Ora parliamo un po' della configurazione del repository. Storicamente, usiamo Nexus per la gestione dei repository, incluso Internal REPO. Questo repository interno contiene tutti i componenti che utilizziamo per scopi interni, come ad esempio i monitoraggi personalizzati.

Utilizziamo anche NuGet, poiché gestisce meglio la cache rispetto ad altri gestori di pacchetti.
Risultato
Dopo aver ottimizzato gli agenti di build, il tempo medio di compilazione è stato ridotto da 12 minuti a 7.
Se si considerano tutte le macchine che avremmo potuto usare per Windows, ma che abbiamo migrato su Linux in questo progetto, abbiamo risparmiato circa $10.000. E questo solo per le licenze; se consideriamo il costo operativo, è di più.
Piani
Per il prossimo trimestre, abbiamo pianificato di lavorare sull'ottimizzazione della consegna del codice.
Transizione a un'immagine Docker pre-costruita. TFS è fantastico, con molti plugin che consentono di integrare nel pipeline, incluso il build basato su trigger, ad esempio, l'immagine Docker. Questo trigger vogliamo implementarlo per quello specifico package-lock.json. Se in qualche modo cambia la composizione dei componenti utilizzati per assemblare il progetto, viene creato una nuova immagine Docker. Questa viene poi utilizzata per distribuire un contenitore con l'applicazione assemblata. Al momento non è tutto ciò che abbiamo, ma prevediamo di passare a un'architettura a microservizi in Kubernetes, che si sta sviluppando attivamente nella nostra azienda e gestisce da tempo soluzioni di produzione.
Riepilogo
Invito tutti a abbandonare Windows, ma non perché non sappia prepararlo. La ragione è che la maggior parte delle soluzioni open source è un stack Linux. Risparmierete bene sulle risorse. A mio parere, il futuro appartiene alle soluzioni Open Source su Linux con una comunità forte.
Profilo del relatore Aleksandr Sinchinov .
è una conferenza sull'integrazione dei processi di sviluppo, test e operazioni per professionisti da professionisti. È proprio per questo che il progetto di cui parlava Aleksandr è stato realizzato e funziona, e nel giorno della presentazione sono stati effettuati due rilascio di successo. Alla il 27 e 28 maggio ci saranno ancora più casi simili da parte di praticanti. C'è ancora spazio per salire sull'ultimo vagone e o farlo con calma biglietto. Ci vediamo a Skolkovo!
Fonte: habr.com
