Il problema della pulizia “intelligente” delle immagini dei contenitori e la sua soluzione in werf

Il problema della pulizia “intelligente” delle immagini dei contenitori e la sua soluzione in werf

L'articolo discute i problemi di pulizia delle immagini che si accumulano nei registri dei contenitori (Docker Registry e suoi analoghi) nelle realtà delle moderne pipeline CI/CD per applicazioni native del cloud fornite a Kubernetes. Vengono forniti i criteri principali per la pertinenza delle immagini e le conseguenti difficoltà nell'automazione della pulizia, nel risparmio di spazio e nel soddisfare le esigenze dei team. Infine, utilizzando l'esempio di uno specifico progetto Open Source, ti racconteremo come è possibile superare queste difficoltà.

Introduzione

Il numero di immagini in un registro contenitori può aumentare rapidamente, occupando più spazio di archiviazione e aumentandone quindi significativamente i costi. Per controllare, limitare o mantenere una crescita accettabile dello spazio occupato nel registro, è accettato:

  1. utilizzare un numero fisso di tag per le immagini;
  2. pulire le immagini in qualche modo.


La prima limitazione è talvolta accettabile per i piccoli team. Se gli sviluppatori dispongono di un numero sufficiente di tag permanenti (latest, main, test, boris ecc.), il registro non aumenterà di dimensioni e per molto tempo non dovrai pensare a pulirlo. Dopotutto, tutte le immagini irrilevanti vengono cancellate e semplicemente non c'è più lavoro da pulire (tutto viene fatto da un normale netturbino).

Tuttavia, questo approccio limita notevolmente lo sviluppo ed è raramente applicabile ai moderni progetti CI/CD. Una parte integrante dello sviluppo era automazione, che consente di testare, distribuire e fornire nuove funzionalità agli utenti molto più velocemente. Ad esempio, in tutti i nostri progetti, una pipeline CI viene creata automaticamente con ogni commit. In esso, l'immagine viene assemblata, testata, distribuita su vari circuiti Kubernetes per il debug e i controlli rimanenti e, se tutto va bene, le modifiche raggiungono l'utente finale. E questa non è più scienza missilistica, ma un evento quotidiano per molti, molto probabilmente per te, dato che stai leggendo questo articolo.

Poiché la correzione dei bug e lo sviluppo di nuove funzionalità vengono eseguiti in parallelo e i rilasci possono essere eseguiti più volte al giorno, è ovvio che il processo di sviluppo è accompagnato da un numero significativo di commit, il che significa un gran numero di immagini nel registro. Di conseguenza, sorge la questione dell'organizzazione di una pulizia efficace del registro, ad es. rimozione di immagini non pertinenti.

Ma come si determina se un'immagine è rilevante?

Criteri per la rilevanza dell'immagine

Nella stragrande maggioranza dei casi, i criteri principali saranno:

1. Il primo (il più ovvio e il più critico di tutti) sono le immagini che attualmente utilizzato in Kubernetes. La rimozione di queste immagini può comportare notevoli costi di inattività della produzione (ad esempio, le immagini potrebbero essere necessarie per la replica) o annullare gli sforzi del team di debug su uno qualsiasi dei loop. (Per questo motivo abbiamo realizzato anche uno special Esportatori Prometeo, che tiene traccia dell'assenza di tali immagini in qualsiasi cluster Kubernetes.)

2. Secondo (meno ovvio, ma anche molto importante e ancora una volta correlato allo sfruttamento) - immagini che richiesto per il rollback in caso di rilevamento di problemi gravi nella versione attuale. Ad esempio, nel caso di Helm, si tratta di immagini utilizzate nelle versioni salvate della versione. (A proposito, per impostazione predefinita in Helm il limite è di 256 revisioni, ma è improbabile che qualcuno abbia davvero bisogno di salvare così un gran numero di versioni?..) Dopotutto, soprattutto noi memorizziamo le versioni per poterle utilizzare in seguito, ad es. "rollback" su di essi, se necessario.

3. Terzo - esigenze degli sviluppatori: tutte le immagini correlate al loro lavoro attuale. Ad esempio, se stiamo considerando un PR, allora ha senso lasciare un'immagine corrispondente all'ultimo commit e, diciamo, al commit precedente: in questo modo lo sviluppatore può tornare rapidamente a qualsiasi attività e lavorare con le ultime modifiche.

4. Quarto: immagini quello corrispondono alle versioni della nostra applicazione, cioè. sono il prodotto finale: v1.0.0, 20.04.01/XNUMX/XNUMX, sierra, ecc.

NB: I criteri qui definiti sono stati formulati sulla base dell'esperienza di interazione con decine di team di sviluppo di diverse aziende. Tuttavia, ovviamente, a seconda delle specificità dei processi di sviluppo e dell'infrastruttura utilizzata (ad esempio, non viene utilizzato Kubernetes), questi criteri possono differire.

Ammissibilità e soluzioni esistenti

I servizi più diffusi con registri contenitori, di norma, offrono le proprie politiche di pulizia delle immagini: in essi è possibile definire le condizioni in base alle quali un tag viene rimosso dal registro. Tuttavia, queste condizioni sono limitate da parametri quali nomi, ora di creazione e numero di tag*.

* Dipende dalle implementazioni specifiche del registro contenitori. Abbiamo considerato le possibilità delle seguenti soluzioni: Azure CR, Docker Hub, ECR, GCR, GitHub Packages, GitLab Container Registry, Harbour Registry, JFrog Artifactory, Quay.io - a partire da settembre 2020.

Questo insieme di parametri è abbastanza per soddisfare il quarto criterio, ovvero selezionare le immagini che corrispondono alle versioni. Tuttavia, per tutti gli altri criteri, si deve scegliere una sorta di soluzione di compromesso (una politica più dura o, al contrario, più indulgente), a seconda delle aspettative e delle capacità finanziarie.

Ad esempio, il terzo criterio - relativo alle esigenze degli sviluppatori - può essere risolto organizzando processi all'interno dei team: denominazione specifica delle immagini, mantenimento di elenchi di autorizzazioni speciali e accordi interni. Ma alla fine deve ancora essere automatizzato. E se le capacità delle soluzioni già pronte non bastano, devi fare qualcosa di tuo.

La situazione con i primi due criteri è simile: non possono essere soddisfatti senza ricevere dati da un sistema esterno, quello in cui vengono distribuite le applicazioni (nel nostro caso, Kubernetes).

Illustrazione del flusso di lavoro in Git

Diciamo che stai lavorando in qualcosa del genere in Git:

Il problema della pulizia “intelligente” delle immagini dei contenitori e la sua soluzione in werf

L'icona con una testa nel diagramma indica le immagini del contenitore attualmente distribuite in Kubernetes per qualsiasi utente (utenti finali, tester, manager, ecc.) o utilizzate dagli sviluppatori per il debug e scopi simili.

Cosa succede se le politiche di pulizia consentono solo la conservazione delle immagini (non l'eliminazione) in base ai nomi dei tag specificati?

Il problema della pulizia “intelligente” delle immagini dei contenitori e la sua soluzione in werf

Ovviamente, uno scenario del genere non renderà felice nessuno.

Cosa cambierà se le politiche consentiranno di non eliminare le immagini? in base a un dato intervallo di tempo/numero di ultimi commit?

Il problema della pulizia “intelligente” delle immagini dei contenitori e la sua soluzione in werf

Il risultato è migliorato molto, ma è ancora lontano dall'ideale. Dopotutto, abbiamo ancora sviluppatori che hanno bisogno di immagini nel registro (o addirittura distribuite in K8) per eseguire il debug dei bug...

Per riassumere l'attuale situazione del mercato: le funzioni disponibili nei registri dei contenitori non offrono sufficiente flessibilità durante la pulizia, e il motivo principale di ciò è nessun modo di interagire con il mondo esterno. Si scopre che i team che richiedono tale flessibilità sono costretti a implementare autonomamente la cancellazione delle immagini “dall’esterno”, utilizzando l’API Docker Registry (o l’API nativa dell’implementazione corrispondente).

Tuttavia, stavamo cercando una soluzione universale che automatizzasse la pulizia delle immagini per team diversi che utilizzano registri diversi...

Il nostro percorso verso la pulizia universale delle immagini

Da dove nasce questa esigenza? Il fatto è che non siamo un gruppo separato di sviluppatori, ma un team che serve molti di loro contemporaneamente, aiutando a risolvere in modo completo i problemi CI/CD. E lo strumento tecnico principale per questo è l'utilità Open Source werf. La sua particolarità è che non svolge una singola funzione, ma accompagna i processi di consegna continua in tutte le fasi: dall'assemblaggio alla distribuzione.

La pubblicazione di immagini nel registro* (immediatamente dopo la loro creazione) è una funzione ovvia di tale utilità. E poiché le immagini vengono archiviate lì, se il tuo spazio di archiviazione non è illimitato, devi essere responsabile della loro successiva pulizia. Il modo in cui abbiamo raggiunto il successo in questo, soddisfacendo tutti i criteri specificati, verrà discusso ulteriormente.

* Sebbene i registri stessi possano essere diversi (Docker Registry, GitLab Container Registry, Harbour, ecc.), i loro utenti devono affrontare gli stessi problemi. La soluzione universale nel nostro caso non dipende dall'implementazione del registro, perché funziona al di fuori dei registri stessi e offre lo stesso comportamento per tutti.

Anche se utilizziamo werf come esempio di implementazione, speriamo che gli approcci utilizzati possano essere utili ad altri team che affrontano difficoltà simili.

Quindi ci siamo dati da fare esterno implementazione di un meccanismo per la pulizia delle immagini, invece di quelle funzionalità già integrate nei registri per i contenitori. Il primo passo è stato utilizzare l'API Docker Registry per creare le stesse policy primitive per il numero di tag e l'ora della loro creazione (menzionate sopra). Aggiunto a loro elenco di consenti basato sulle immagini utilizzate nell'infrastruttura distribuita, cioè. Kubernetes. Per quest'ultimo è stato sufficiente utilizzare l'API Kubernetes per scorrere tutte le risorse distribuite e ottenere un elenco di valori image.

Questa banale soluzione ha risolto il problema più critico (criterio n. 1), ma è stato solo l’inizio del nostro viaggio per migliorare il meccanismo di pulizia. Il passo successivo, e molto più interessante, è stata la decisione associare le immagini pubblicate alla cronologia Git.

Schemi di etichettatura

Per cominciare, abbiamo scelto un approccio in cui l'immagine finale dovrebbe memorizzare le informazioni necessarie per la pulizia e abbiamo costruito il processo su schemi di tagging. Durante la pubblicazione di un'immagine, l'utente ha selezionato un'opzione di tag specifica (git-branch, git-commit o git-tag) e utilizzato il valore corrispondente. Nei sistemi CI, questi valori venivano impostati automaticamente in base alle variabili di ambiente. Infatti l'immagine finale era associata ad una specifica primitiva Git, memorizzando i dati necessari per la pulizia nelle etichette.

Questo approccio ha prodotto una serie di policy che hanno consentito di utilizzare Git come unica fonte di verità:

  • Quando si eliminava un ramo/tag in Git, le immagini associate nel registro venivano automaticamente cancellate.
  • Il numero di immagini associate ai tag e ai commit Git può essere controllato dal numero di tag utilizzati nello schema selezionato e dall'ora in cui è stato creato il commit associato.

Nel complesso, l’implementazione risultante ha soddisfatto le nostre esigenze, ma presto ci attendeva una nuova sfida. Il fatto è che durante l'utilizzo degli schemi di tagging basati sulle primitive Git, abbiamo riscontrato una serie di carenze. (Poiché la loro descrizione va oltre lo scopo di questo articolo, tutti possono familiarizzarsi con i dettagli qui.) Pertanto, avendo deciso di passare a un approccio più efficiente al tagging (tagging basato sul contenuto), abbiamo dovuto riconsiderare l'implementazione della pulizia delle immagini.

Nuovo algoritmo

Perché? Con il tagging basato sul contenuto, ogni tag può soddisfare più commit in Git. Quando si puliscono le immagini, non si può più presumere solo dal commit in cui il nuovo tag è stato aggiunto al registro.

Per il nuovo algoritmo di pulizia, si è deciso di abbandonare gli schemi di tagging e di costruire processo di metaimmagine, ognuno dei quali memorizza un sacco di:

  • il commit su cui è stata eseguita la pubblicazione (non importa se l'immagine è stata aggiunta, modificata o è rimasta uguale nel registro dei contenitori);
  • e il nostro identificatore interno corrispondente all'immagine assemblata.

In altre parole, è stato fornito collegare i tag pubblicati con i commit in Git.

Configurazione finale e algoritmo generale

Quando si configura la pulizia, gli utenti ora hanno accesso ai criteri che selezionano le immagini correnti. Ciascuna di queste politiche è definita:

  • molti riferimenti, ad es. Tag Git o rami Git utilizzati durante la scansione;
  • e il limite delle immagini cercate per ciascun riferimento dal set.

Per illustrare, questo è l'aspetto della configurazione della policy predefinita:

cleanup:
  keepPolicies:
  - references:
      tag: /.*/
      limit:
        last: 10
  - references:
      branch: /.*/
      limit:
        last: 10
        in: 168h
        operator: And
    imagesPerReference:
      last: 2
      in: 168h
      operator: And
  - references:  
      branch: /^(main|staging|production)$/
    imagesPerReference:
      last: 10

Questa configurazione contiene tre policy conformi alle seguenti regole:

  1. Salva l'immagine per gli ultimi 10 tag Git (per data di creazione del tag).
  2. Salva non più di 2 immagini pubblicate nell'ultima settimana per non più di 10 thread con attività nell'ultima settimana.
  3. Salva 10 immagini per i rami main, staging и production.

L’algoritmo finale si riduce ai seguenti passaggi:

  • Recupero dei manifesti dal registro contenitori.
  • Escluse le immagini utilizzate in Kubernetes, perché Li abbiamo già preselezionati interrogando l'API K8s.
  • Scansione della cronologia Git ed esclusione delle immagini in base alle policy specificate.
  • Rimozione delle immagini rimanenti.

Tornando alla nostra illustrazione, questo è ciò che accade con werf:

Il problema della pulizia “intelligente” delle immagini dei contenitori e la sua soluzione in werf

Tuttavia, anche se non si utilizza werf, un approccio simile alla pulizia avanzata delle immagini - in un'implementazione o nell'altra (a seconda dell'approccio preferito al tagging delle immagini) - può essere applicato ad altri sistemi/utilità. Per fare ciò, è sufficiente ricordare i problemi che si presentano e trovare quelle opportunità nel tuo stack che ti consentono di integrare la loro soluzione nel modo più fluido possibile. Ci auguriamo che il percorso che abbiamo percorso ti aiuti a guardare al tuo caso particolare con nuovi dettagli e pensieri.

conclusione

  • Prima o poi, la maggior parte dei team incontra il problema dell'overflow del registro.
  • Quando si cercano soluzioni, è prima necessario determinare i criteri per la pertinenza dell'immagine.
  • Gli strumenti offerti dai popolari servizi di registro dei contenitori consentono di organizzare una pulizia molto semplice che non tiene conto del “mondo esterno”: delle immagini utilizzate in Kubernetes e delle peculiarità dei flussi di lavoro del team.
  • Un algoritmo flessibile ed efficiente deve comprendere i processi CI/CD e funzionare non solo con i dati immagine Docker.

PS

Leggi anche sul nostro blog:

Fonte: habr.com

Aggiungi un commento