Rifletti attentamente prima di utilizzare Docker-in-Docker per CI o ambiente di test

Rifletti attentamente prima di utilizzare Docker-in-Docker per CI o ambiente di test

Docker-in-Docker è un ambiente daemon Docker virtualizzato in esecuzione all'interno del contenitore stesso per creare immagini del contenitore. Lo scopo principale della creazione di Docker-in-Docker era quello di aiutare a sviluppare Docker stesso. Molte persone lo usano per eseguire Jenkins CI. All'inizio sembra normale, ma poi sorgono problemi che possono essere evitati installando Docker in un contenitore CI Jenkins. Questo articolo spiega come eseguire questa operazione. Se sei interessato alla soluzione finale senza dettagli, leggi l’ultima sezione dell’articolo “Risolvere il problema”.

Rifletti attentamente prima di utilizzare Docker-in-Docker per CI o ambiente di test

Docker-in-Docker: "Buono"

Più di due anni fa ho inserito Docker bandiera –privilegiato e scrisse prima versione di dind. L'obiettivo era aiutare il team principale a sviluppare Docker più rapidamente. Prima di Docker-in-Docker, il tipico ciclo di sviluppo era simile al seguente:

  • hack;
  • costruire;
  • fermare un demone Docker in esecuzione;
  • lanciare un nuovo demone Docker;
  • test;
  • ripetere il ciclo.

Se volevi creare un assemblaggio bello e riproducibile (cioè in un contenitore), allora diventava più complicato:

  • hack;
  • assicurarsi che sia in esecuzione una versione funzionante di Docker;
  • costruire un nuovo Docker con il vecchio Docker;
  • arrestare il demone Docker;
  • avviare un nuovo demone Docker;
  • test;
  • fermare il nuovo demone Docker;
  • ripetere.

Con l'avvento di Docker-in-Docker, il processo è diventato più semplice:

  • hack;
  • montaggio + lancio in una fase;
  • ripetere il ciclo.

Non è molto meglio così?

Rifletti attentamente prima di utilizzare Docker-in-Docker per CI o ambiente di test

Docker-in-Docker: "Cattivo"

Tuttavia, contrariamente alla credenza popolare, Docker-in-Docker non è composto al 100% da stelle, pony e unicorni. Ciò che intendo è che ci sono diversi problemi di cui uno sviluppatore deve essere a conoscenza.

Uno di questi riguarda gli LSM (moduli di sicurezza Linux) come AppArmor e SELinux: durante l'esecuzione di un container, il "Docker interno" potrebbe tentare di applicare profili di sicurezza che entreranno in conflitto o confonderanno il "Docker esterno". Questo è il problema più difficile da risolvere quando si tenta di unire l'implementazione originale del flag –privileged. Le mie modifiche hanno funzionato e tutti i test sarebbero passati sulla mia macchina Debian e sulle VM di test di Ubuntu, ma si sarebbero bloccati e bruciati sulla macchina di Michael Crosby (aveva Fedora, se ricordo bene). Non riesco a ricordare la causa esatta del problema, ma potrebbe essere dovuto al fatto che Mike è un ragazzo saggio che lavora con SELINUX=enforce (ho usato AppArmor) e le mie modifiche non hanno tenuto conto dei profili SELinux.

Docker-in-Docker: "Il male"

Il secondo problema riguarda i driver di archiviazione Docker. Quando esegui Docker-in-Docker, Docker esterno viene eseguito su un normale file system (EXT4, BTRFS o qualunque cosa tu abbia) e Docker interno viene eseguito su un sistema copy-on-write (AUFS, BTRFS, Device Mapper , ecc.). , a seconda di cosa è configurato per utilizzare Docker esterno). Questo crea molte combinazioni che non funzioneranno. Ad esempio, non sarai in grado di eseguire AUFS sopra AUFS.

Se esegui BTRFS sopra BTRFS, all'inizio dovrebbe funzionare, ma una volta che ci sono sottovolumi nidificati, l'eliminazione del sottovolume genitore fallirà. Il modulo Device Mapper non ha uno spazio dei nomi, quindi se più istanze Docker lo eseguono sulla stessa macchina, saranno tutte in grado di vedere (e influenzare) le immagini l'una sull'altra e sui dispositivi di backup del contenitore. Questo non va bene.

Esistono soluzioni alternative per risolvere molti di questi problemi. Ad esempio, se desideri utilizzare AUFS nel Docker interno, trasforma semplicemente la cartella /var/lib/docker in un volume e tutto andrà bene. Docker ha aggiunto alcuni spazi dei nomi di base ai nomi di destinazione di Device Mapper in modo che se più chiamate Docker sono in esecuzione sulla stessa macchina, non si intralciano a vicenda.

Tuttavia, tale configurazione non è affatto semplice, come si può vedere da questi articoli nel repository dind su GitHub.

Docker-in-Docker: c'è di peggio

E la cache di build? Anche questo può essere piuttosto difficile. Le persone spesso mi chiedono "se sto eseguendo Docker-in-Docker, come posso utilizzare le immagini ospitate sul mio host invece di riportare tutto nel mio Docker interno"?

Alcune persone intraprendenti hanno provato a collegare /var/lib/docker dall'host a un contenitore Docker-in-Docker. A volte condividono /var/lib/docker con più contenitori.

Rifletti attentamente prima di utilizzare Docker-in-Docker per CI o ambiente di test
Vuoi corrompere i tuoi dati? Perché questo è esattamente ciò che danneggerà i tuoi dati!

Il demone Docker è stato chiaramente progettato per avere accesso esclusivo a /var/lib/docker. Nient'altro dovrebbe "toccare, colpire o stimolare" i file Docker presenti in questa cartella.

Perché è così? Perché questo è il risultato di una delle lezioni più difficili apprese durante lo sviluppo di dotCloud. Il motore del contenitore dotCloud veniva eseguito facendo in modo che più processi accedessero simultaneamente a /var/lib/dotcloud. Trucchi astuti come la sostituzione atomica dei file (invece della modifica sul posto), l'infiltrazione del codice con blocchi consultivi e obbligatori e altri esperimenti con sistemi sicuri come SQLite e BDB non sempre hanno funzionato. Quando stavamo riprogettando il nostro motore per container, che alla fine è diventato Docker, una delle grandi decisioni di progettazione è stata quella di consolidare tutte le operazioni del container sotto un unico demone per eliminare tutte le sciocchezze della concorrenza.

Non fraintendetemi: è del tutto possibile realizzare qualcosa di buono, affidabile e veloce che coinvolga più processi e un moderno controllo parallelo. Ma pensiamo che sia più semplice e facile scrivere e mantenere il codice utilizzando Docker come unico player.

Ciò significa che se condividi la directory /var/lib/docker tra più istanze Docker, avrai problemi. Naturalmente, questo può funzionare, soprattutto nelle prime fasi del test. "Ascolta, mamma, posso eseguire Ubuntu come docker!" Ma prova qualcosa di più complesso, come estrarre la stessa immagine da due istanze diverse, e vedrai il mondo bruciare.

Ciò significa che se il tuo sistema CI esegue build e ricostruzioni, ogni volta che riavvii il tuo contenitore Docker-in-Docker, rischi di far cadere un'arma nucleare nella sua cache. Questo non è affatto bello!

La soluzione

Facciamo un passo indietro. Hai davvero bisogno di Docker-in-Docker o vuoi semplicemente essere in grado di eseguire Docker e creare ed eseguire contenitori e immagini dal tuo sistema CI mentre il sistema CI stesso si trova in un contenitore?

Scommetto che la maggior parte delle persone desidera quest'ultima opzione, il che significa che desiderano che un sistema CI come Jenkins sia in grado di eseguire contenitori. E il modo più semplice per farlo è semplicemente inserire un socket Docker nel contenitore CI e associarlo al flag -v.

In poche parole, quando esegui il tuo contenitore CI (Jenkins o altro), invece di hackerare qualcosa insieme a Docker-in-Docker, avvialo con la riga:

docker run -v /var/run/docker.sock:/var/run/docker.sock ...

Questo contenitore avrà ora accesso al socket Docker e quindi sarà in grado di eseguire contenitori. Solo che invece di eseguire contenitori “figlio”, lancerà contenitori “fratelli”.

Prova questo utilizzando l'immagine docker ufficiale (che contiene il binario Docker):

docker run -v /var/run/docker.sock:/var/run/docker.sock 
           -ti docker

Sembra e funziona come Docker-in-Docker, ma non è Docker-in-Docker: quando questo contenitore crea contenitori aggiuntivi, questi verranno creati nel Docker di livello superiore. Non si verificheranno gli effetti collaterali della nidificazione e la cache di assembly verrà condivisa tra più chiamate.

Nota: le versioni precedenti di questo articolo consigliavano di collegare il file binario Docker dall'host al contenitore. Questo è ora diventato inaffidabile poiché il motore Docker non copre più le librerie statiche o quasi statiche.

Quindi, se desideri utilizzare Docker da Jenkins CI, hai 2 opzioni:
installando la CLI Docker utilizzando il sistema di confezionamento delle immagini di base (ovvero, se la tua immagine è basata su Debian, utilizza i pacchetti .deb), utilizzando l'API Docker.

Alcuni annunci 🙂

Grazie per stare con noi. Ti piacciono i nostri articoli? Vuoi vedere contenuti più interessanti? Sostienici effettuando un ordine o raccomandando agli amici, cloud VPS per sviluppatori da $ 4.99, un analogo unico dei server entry-level, che è stato inventato da noi per te: Tutta la verità su VPS (KVM) E5-2697 v3 (6 core) 10 GB DDR4 480 GB SSD 1 Gbps da $ 19 o come condividere un server? (disponibile con RAID1 e RAID10, fino a 24 core e fino a 40 GB DDR4).

Dell R730xd 2 volte più economico nel data center Equinix Tier IV ad Amsterdam? Solo qui 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TV da $199 In Olanda! Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - da $99! Leggi Come costruire Infrastructure Corp. classe con l'utilizzo di server Dell R730xd E5-2650 v4 del valore di 9000 euro per un centesimo?

Fonte: habr.com

Aggiungi un commento