Differences

This shows you the differences between two versions of the page.

Link to this comparison view

cc:laboratoare:01 [2020/10/02 15:08]
radu.ciobanu [Exerciții]
cc:laboratoare:01 [2022/10/10 09:09] (current)
radu.ciobanu
Line 1: Line 1:
-===== Laboratorul 01 - Introducere în Docker =====+===== Laboratorul 01 - Docker =====
  
 ==== Introducere ==== ==== Introducere ====
  
-Docker este o platformă de containere software, folosită pentru a împacheta și rula aplicații atât local, cât și pe sisteme Cloud, eliminând probleme de genul „pe calculatorul meu funcționează”. Docker poate fi deci privit ca un mediu care permite rularea containerelor pe orice platformă, bazat pe //​containerd//​. Ca beneficii, oferă compilare, testare, deployment, actualizare și recuperare în caz de eroare mai rapide față de modul standard de deployment al aplicațiilor.+Docker este o platformă de containere software, folosită pentru a împacheta și rula aplicații atât local, cât și pe sisteme Cloud, eliminând probleme de genul „pe calculatorul meu funcționează”. Docker poate fi deci privit ca un mediu care permite rularea containerelor pe orice platformă, bazat pe **//​containerd//​**. Ca beneficii, oferă compilare, testare, deployment, actualizare și recuperare în caz de eroare mai rapide față de modul standard de deployment al aplicațiilor.
  
-Docker oferă un mediu uniform de dezvoltare și producție, unde nu se mai pune problema compatibilității aplicațiilor cu sistemul de operare și nu mai există conflicte între versiunile de biblioteci/​pachete de pe sistemul gazdă. Containerele sunt efemere, așa că stricarea sau închiderea unuia nu duce la căderea întregului ​sistemul. Ele ajută la asigurarea consistenței stricte între comportamentul în mediul de dezvoltare cu cel în mediul de producție.+Docker oferă un mediu uniform de dezvoltare și producție, unde nu se mai pune problema compatibilității aplicațiilor cu sistemul de operare și nu mai există conflicte între versiunile de biblioteci/​pachete de pe sistemul gazdă. Containerele sunt efemere, așa că stricarea sau închiderea unuia nu duce la căderea întregului ​sistem. Ele ajută la asigurarea consistenței stricte între comportamentul în mediul de dezvoltare cu cel în mediul de producție.
  
 De asemenea, Docker oferă flexibilitate maximă. Dacă, într-un proiect de mari dimensiuni, avem nevoie de unelte software noi pentru că se schimbă anumite cerințe, le putem împacheta în containere și apoi să le legăm foarte ușor la sistem. Dacă avem nevoie de replicarea infrastructurii pe alt mediu, putem refolosi imginile de Docker salvate în registru (un fel de repository de containere). Dacă avem nevoie de actualizarea anumitor componente, Docker ne permite să rescriem imaginile, ceea ce înseamnă că se vor lansa, mereu, cele mai noi versiuni ale componentelor sub formă de containere. De asemenea, Docker oferă flexibilitate maximă. Dacă, într-un proiect de mari dimensiuni, avem nevoie de unelte software noi pentru că se schimbă anumite cerințe, le putem împacheta în containere și apoi să le legăm foarte ușor la sistem. Dacă avem nevoie de replicarea infrastructurii pe alt mediu, putem refolosi imginile de Docker salvate în registru (un fel de repository de containere). Dacă avem nevoie de actualizarea anumitor componente, Docker ne permite să rescriem imaginile, ceea ce înseamnă că se vor lansa, mereu, cele mai noi versiuni ale componentelor sub formă de containere.
Line 11: Line 11:
 ==== Imagini și containere ==== ==== Imagini și containere ====
  
-Containerele Docker au la bază imagini, care sunt pachete executabile lightweight de sine stătătoare ce conțin tot ce este necesar pentru rularea unor aplicații software, incluzând cod, runtime, biblioteci, variabile de mediu și fișiere de configurare. Imaginile au o dimensiune variabilă, nu conțin versiuni complete ale sistemelor de operare, și sunt stocate în cache-ul local sau într-un registru. O imagine Docker are un sistem de fișiere de tip //union//, unde fiecare schimbare asupra sistemului de fișiere sau metadate este considerată ca fiind un strat (layer), mai multe astfel de straturi formând o imagine. Fiecare strat este identificat unic (printr-un hash) și stocat doar o singură dată.+Containerele Docker au la bază imagini, care sunt pachete executabile lightweight de sine stătătoare ce conțin tot ce este necesar pentru rularea unor aplicații software, incluzând cod, runtime, biblioteci, variabile de mediu și fișiere de configurare. Imaginile au o dimensiune variabilă, nu conțin versiuni complete ale sistemelor de operare, și sunt stocate în cache-ul local sau într-un registru. O imagine Docker are un sistem de fișiere de tip **//union//**, unde fiecare schimbare asupra sistemului de fișiere sau metadate este considerată ca fiind un strat (layer), mai multe astfel de straturi formând o imagine. Fiecare strat este identificat unic (printr-un hash) și stocat doar o singură dată.
  
 Un container reprezintă o instanță a unei imagini, adică ceea ce imaginea devine în memorie atunci când este executată. El rulează complet izolat de mediul gazdă, accesând fișiere și porturi ale acestuia doar dacă este configurat să facă acest lucru. Containerele rulează aplicații nativ pe nucleul mașinii gazdă, având performanțe mai bune decât mașinile virtuale, care au acces la resursele gazdei prin intermediul unui hipervizor. Containerele au acces nativ, fiecare rulând într-un proces discret, necesitând tot atât de multă memorie cât orice alt executabil. Din punct de vedere al sistemului de fișiere, un container reprezintă un strat adițional de read/write peste straturile imaginii. Un container reprezintă o instanță a unei imagini, adică ceea ce imaginea devine în memorie atunci când este executată. El rulează complet izolat de mediul gazdă, accesând fișiere și porturi ale acestuia doar dacă este configurat să facă acest lucru. Containerele rulează aplicații nativ pe nucleul mașinii gazdă, având performanțe mai bune decât mașinile virtuale, care au acces la resursele gazdei prin intermediul unui hipervizor. Containerele au acces nativ, fiecare rulând într-un proces discret, necesitând tot atât de multă memorie cât orice alt executabil. Din punct de vedere al sistemului de fișiere, un container reprezintă un strat adițional de read/write peste straturile imaginii.
Line 17: Line 17:
 {{:​cc:​laboratoare:​vm_2x.png?​direct&​250|}} {{:​cc:​laboratoare:​container_2x.png?​direct&​250|}} {{:​cc:​laboratoare:​vm_2x.png?​direct&​250|}} {{:​cc:​laboratoare:​container_2x.png?​direct&​250|}}
  
-În imaginea de mai sus (preluată din [[https://​docs.docker.com/​get-started/​|documentația oficială Docker]]), mașinile virtuale rulează sisteme de operare „oaspete”,​ lucru care consumă multe resurse, iar imaginea rezultată ocupă mult spațiu, conținând setări de sistem de operare, ​dependințe, patch-uri de securitate, etc. În schimb, containerele pot să împartă același nucleu, și singurele date care trebuie să fie într-o imagine de container sunt executabilul și pachetele de care depinde, care nu trebuie deloc instalate pe sistemul gazdă. Dacă o mașină virtuală abstractizează resursele hardware, un container Docker este un proces care abstractizează baza pe care rulează ​aplicatiile ​în cadrul unui sistem de operare și izolează resursele software ale sistemului de operare (memorie, access la rețea și fișiere, etc.).+În imaginea de mai sus (preluată din [[https://​docs.docker.com/​get-started/​|documentația oficială Docker]]), mașinile virtuale rulează sisteme de operare „oaspete”,​ lucru care consumă multe resurse, iar imaginea rezultată ocupă mult spațiu, conținând setări de sistem de operare, ​dependențe, patch-uri de securitate, etc. În schimb, containerele pot să împartă același nucleu, și singurele date care trebuie să fie într-o imagine de container sunt executabilul și pachetele de care depinde, care nu trebuie deloc instalate pe sistemul gazdă. Dacă o mașină virtuală abstractizează resursele hardware, un container Docker este un proces care abstractizează baza pe care rulează ​aplicațiile ​în cadrul unui sistem de operare și izolează resursele software ale sistemului de operare (memorie, access la rețea și fișiere, etc.).
  
 ==== Arhitectura Docker ==== ==== Arhitectura Docker ====
  
-Docker are o arhitectură de tip client-server,​ așa cum se poate observa în imaginea de mai jos (preluată din [[https://​docs.docker.com/​get-started/​|documentația oficială Docker]]). Clientul Docker comunică, prin intermediul unui API REST (peste sockeți UNIX sau peste o interfață de rețea), cu deamon-ul de Docker (serverul), care se ocupă de crearea, rularea și distribuția de containere Docker. Clientul și daemon-ul pot rula pe același sistem sau pe sisteme diferite. Un registru Docker are rolul de a stoca imagini.+Docker are o arhitectură de tip client-server,​ așa cum se poate observa în imaginea de mai jos (preluată din [[https://​docs.docker.com/​get-started/​|documentația oficială Docker]]). Clientul Docker comunică, prin intermediul unui API REST (peste sockeți UNIX sau peste o interfață de rețea), cu daemon-ul de Docker (serverul), care se ocupă de crearea, rularea și distribuția de containere Docker. Clientul și daemon-ul pot rula pe același sistem sau pe sisteme diferite. Un registru Docker are rolul de a stoca imagini.
  
 {{:​cc:​laboratoare:​architecture.png?​direct&​550|}} {{:​cc:​laboratoare:​architecture.png?​direct&​550|}}
Line 33: Line 33:
 Comenzile de mai jos sunt pentru Ubuntu. Pentru alte variante de Linux (Debian, CentOS, Fedora), găsiți informații suplimentare pe pagina de documentație oficială Docker. Comenzile de mai jos sunt pentru Ubuntu. Pentru alte variante de Linux (Debian, CentOS, Fedora), găsiți informații suplimentare pe pagina de documentație oficială Docker.
  
-Pentru instalarea Docker CE, este nevoie de una din următoarele versiuni de Ubuntu: Disco 19.04, Cosmic 18.10, Bionic 18.04 (LTS), Xenial 16.04 (LTS). Docker CE are suport pentru arhitecturile //x86_64//, //amd64//, //armhf//, //arm64//, //s390x// (IBM Z) și //ppc64le// (IBM Power).+Pentru instalarea Docker CE, este nevoie de una din următoarele versiuni de Ubuntu: ​Focal 20.04 (LTS), ​Disco 19.04, Cosmic 18.10, Bionic 18.04 (LTS), Xenial 16.04 (LTS). Docker CE are suport pentru arhitecturile ​**//x86_64//****//amd64//****//armhf//****//arm64//****//s390x//** (IBM Z) și **//ppc64le//** (IBM Power).
    
-Varianta recomandată de instalare a Docker CE presupune folosirea repository-ului oficial, deoarece update-urile sunt apoi instalate automat. La prima instalare a Docker CE pe o mașină, este necesară inițializarea repository-ului (exemplul de mai jos este pentru un sistem cu arhitectură //amd64//):+Varianta recomandată de instalare a Docker CE presupune folosirea repository-ului oficial, deoarece update-urile sunt apoi instalate automat. La prima instalare a Docker CE pe o mașină, este necesară inițializarea repository-ului (exemplul de mai jos este pentru un sistem cu arhitectură ​**//amd64//**):
  
 <code bash> <code bash>
Line 63: Line 63:
 </​code>​ </​code>​
  
-=== Windows === +<note tip> 
- +O variantă mai simplă de a instala ​Docker CE pe Linux este utilizarea [[https://get.docker.com|acestui]] script. 
-Versiunea minimă de Windows suportată de Docker CE este 10, și este disponibilă numai pentru arhitecturi //x86_64//. Pașii necesari de instalare presupun download-ului unui installer executabil și rularea luiMai multe informații pot fi găsite pe pagina de documentație oficială.+</​note>​
  
-=== MacOS ===+=== Windows și MacOS ===
  
-Pentru ​Mac-uri, există varianta ​Docker ​Desktop, care rulează pe minim MacOS Sierra 10.12, și necesită o versiune ​de VirtualBox cel puțin egală cu 4.3.30.+Pentru ​că Docker ​nu avea inițial suport nativ pentru Windows și MacOS, s-a introdus [[https://​docs.docker.com/​toolbox/​overview/​|Docker Toolbox]], care poate lansa un mediu Docker virtualizat (mai precis, se folosește o mașină virtuală VirtualBox pentru a fi baza mediului de Docker)RecentDocker Toolbox a fost marcat ca „legacy” ​și a fost înlocuit ​de [[https://​docs.docker.com/​docker-for-mac/​|Docker Desktop for Mac]] și [[https://​docs.docker.com/​docker-for-windows/​|Docker Desktop for Windows]], care oferă funcționalități similare ​cu performanțe mai buneMai mult, Windows Server 2016 și Windows 10 au acum suport pentru Docker nativ pentru arhitecturi **//​x86_64//​**.
  
 ==== Testarea instalării ==== ==== Testarea instalării ====
Line 110: Line 110:
 ==== Rularea unui container ==== ==== Rularea unui container ====
  
-Am văzut mai sus cum putem rula un Hello World într-un container simplu, însă putem rula imagini mult mai complexe. Putem să ne creăm propria imagine ​(așa cum vom vedea în [[cc:​laboratoare:​02|laboratorul 2]]) sau putem descărca o imagine dintr-un registru, cum ar fi [[https://​hub.docker.com|Docker Hub]] . Acesta conține imagini publice, care variază de la sisteme de operare (Ubuntu, Alpine, Amazon Linux, etc.) la limbaje de programare (Java, Ruby, Perl, Python, etc.), servere Web (NGINX, Apache), etc.+Am văzut mai sus cum putem rula un Hello World într-un container simplu, însă putem rula imagini mult mai complexe. Putem să ne creăm propria imagine sau putem descărca o imagine dintr-un registru, cum ar fi [[https://​hub.docker.com|Docker Hub]] . Acesta conține imagini publice, care variază de la sisteme de operare (Ubuntu, Alpine, Amazon Linux, etc.) la limbaje de programare (Java, Ruby, Perl, Python, etc.), servere Web (NGINX, Apache), etc.
  
 Pentru acest laborator, vom rula Alpine Linux, care este o distribuție lightweight de Linux (dimensiunea sa este de 5 MB). Primul pas constă în descărcarea imaginii dintr-un registru Docker (în cazul nostru, Docker Hub): Pentru acest laborator, vom rula Alpine Linux, care este o distribuție lightweight de Linux (dimensiunea sa este de 5 MB). Primul pas constă în descărcarea imaginii dintr-un registru Docker (în cazul nostru, Docker Hub):
Line 126: Line 126:
 alpine ​         latest ​     961769676411 ​   4 weeks ago     ​5.58MB alpine ​         latest ​     961769676411 ​   4 weeks ago     ​5.58MB
 </​code>​ </​code>​
 +
 +Se poate observa mai sus că imaginea pe care am descărcat-o are numele **//​alpine//​** și tag-ul **//​latest//​**. Tag-ul unei imagini reprezintă o etichetă care desemnează în general versiunea imaginii, iar **//​latest//​** este un alias pentru versiunea cea mai recentă, pus automat atunci când nu specificăm explicit niciun tag.
  
 Odată descărcată imaginea, o putem rula într-un container. Un mod de a face acest lucru este prin specificarea unei comenzi care să fie rulată în interiorul containerului (în cazul nostru, pe sistemul de operare Alpine Linux): Odată descărcată imaginea, o putem rula într-un container. Un mod de a face acest lucru este prin specificarea unei comenzi care să fie rulată în interiorul containerului (în cazul nostru, pe sistemul de operare Alpine Linux):
Line 146: Line 148:
 </​code>​ </​code>​
  
-Dacă dorim să vedem ce containere rulează la un moment de timp, putem folosi comanda //ls//. Dacă vrem să vedem lista cu toate containerele pe care le-am rulat, folosim și flag-ul //-a//:+Dacă dorim să vedem ce containere rulează la un moment de timp, putem folosi comanda ​**//ls//**. Dacă vrem să vedem lista cu toate containerele pe care le-am rulat, folosim și flag-ul ​**//-a//**:
  
 <code bash> <code bash>
Line 156: Line 158:
 </​code>​ </​code>​
  
-Pentru rularea unei imagini într-un container în background, putem folosi flag-ul //-d//. La pornire, va fi afișat ID-ul containerului pornit, informație pe care o putem folosi pentru a ne atașa la container, pentru a îl opri, pentru a îl șterge, etc.:+Pentru rularea unei imagini într-un container în background, putem folosi flag-ul ​**//-d//**. La pornire, va fi afișat ID-ul containerului pornit, informație pe care o putem folosi pentru a ne atașa la container, pentru a îl opri, pentru a îl șterge, etc.:
  
 <code bash> <code bash>
Line 203: Line 205:
 ==== Crearea unei imagini ==== ==== Crearea unei imagini ====
  
-Până acum, am rulat imagini deja existente, însă acum vom vedea cum putem să ne creăm și publicăm propria noastră aplicație. Vom porni de la nivelul de jos al unei astfel de ierarhii, care este reprezentat de container. Deasupra acestui nivel se află serviciile, care definesc modul în care containerele se comportă în producție, iar la nivelul cel mai sus se află stiva, care definește interacțiunile dintre servicii. Sursele pentru acest exemplu se găsesc în directorul //testapp// din arhiva de laborator. ​TODO+Până acum, am rulat imagini deja existente, însă acum vom vedea cum putem să ne creăm și publicăm propria noastră aplicație. Vom porni de la nivelul de jos al unei astfel de ierarhii, care este reprezentat de container. Deasupra acestui nivel se află serviciile, care definesc modul în care containerele se comportă în producție, iar la nivelul cel mai sus se află stiva, care definește interacțiunile dintre servicii. Sursele pentru acest exemplu se găsesc în {{:​cc:​laboratoare:​lab1.zip|arhiva de laborator}}.
  
-În acest exemplu, vom crea o aplicație Web folosind Flask (un framework Web scris în Python) care afișează o poză aleatoare la fiecare accesare. Ea va fi scrisă într-un fișier //app.py// care arată în felul următor:+În acest exemplu, vom crea o aplicație Web folosind Flask (un framework Web scris în Python) care afișează o poză aleatoare la fiecare accesare. Ea va fi scrisă într-un fișier ​**//app.py//** care arată în felul următor:
  
 <file python app.py> <file python app.py>
Line 231: Line 233:
 </​file>​ </​file>​
  
-După cum se observă în fișierul Python, la baza paginii Web se află un template în fișierul //​index.html//​ (care trebuie creat într-un director //​templates//​):​+După cum se observă în fișierul Python, la baza paginii Web se află un template în fișierul ​**//​index.html//​** (care trebuie creat într-un director ​**//​templates//​**):
  
 <file html index.html>​ <file html index.html>​
Line 263: Line 265:
 </​file>​ </​file>​
  
-Mai avem nevoie de un fișier //​requirements.txt//​ unde specificăm pachetele Python care trebuie instalate în imaginea pe care o creăm:+Mai avem nevoie de un fișier ​**//​requirements.txt//​** unde specificăm pachetele Python care trebuie instalate în imaginea pe care o creăm:
  
 <file txt requirements.txt>​ <file txt requirements.txt>​
Line 269: Line 271:
 </​file>​ </​file>​
  
-O imagine este definită de un fișier numit //​Dockerfile//,​ care specifică ce se întâmplă în interiorului containerului pe care vrem să îl creăm, unde accesul la resurse (cum ar fi interfețele de rețea sau hard disk-urile) este virtualizat și izolat de restul sistemului. Prin intermediul acestui fișier, putem specifica mapări de porturi, fișiere care vor fi copiate în container când este pornit, etc. Un fișier ​//Dockerfile// se aseamana ​cu un Makefile, iar fiecare rând din el descrie un strat din imagine. Odată ce am definit un //Dockerfile// corect, aplicația noastră se va comporta totdeauna identic, indiferent în ce mediu este rulată. Un exemplu de //Dockerfile// pentru aplicația noastră este următorul:+O imagine este definită de un fișier numit **//​Dockerfile//​**, care specifică ce se întâmplă în interiorului containerului pe care vrem să îl creăm, unde accesul la resurse (cum ar fi interfețele de rețea sau hard disk-urile) este virtualizat și izolat de restul sistemului. Prin intermediul acestui fișier, putem specifica mapări de porturi, fișiere care vor fi copiate în container când este pornit, etc. Un fișier Dockerfile se aseamănă ​cu un Makefile, iar fiecare rând din el descrie un strat din imagine. Odată ce am definit un Dockerfile corect, aplicația noastră se va comporta totdeauna identic, indiferent în ce mediu este rulată. Un exemplu de Dockerfile pentru aplicația noastră este următorul:
  
 <file txt Dockerfile>​ <file txt Dockerfile>​
 FROM alpine:edge FROM alpine:edge
  
-RUN apk add --update ​py2-pip+RUN apk add --update ​py3-pip
  
 COPY requirements.txt /​usr/​src/​app/​ COPY requirements.txt /​usr/​src/​app/​
Line 284: Line 286:
 EXPOSE 5000 EXPOSE 5000
  
-CMD ["python", "/​usr/​src/​app/​app.py"​]+CMD ["python3", "/​usr/​src/​app/​app.py"​]
 </​file>​ </​file>​
  
 În fișierul de mai sus, avem următoarele comenzi: În fișierul de mai sus, avem următoarele comenzi:
  
-  * FROM – specifică o imagine pe care noua noastră imagine este bazată (în cazul nostru, pornim de la o imagine de bază cu Alpine, care se află pe Docker Hub, și în interiorul căreia vom rula aplicația noastră Web scrisă în Python) +  ​* **FROM** – specifică o imagine pe care noua noastră imagine este bazată (în cazul nostru, pornim de la o imagine de bază cu Alpine, care se află pe Docker Hub, și în interiorul căreia vom rula aplicația noastră Web scrisă în Python) 
-  * COPY – copiază fișierele dintr-un director local în containerul pe care îl creăm +  ​* **COPY** – copiază fișierele dintr-un director local în containerul pe care îl creăm 
-  * RUN – rulează o comandă (în exemplul de mai sus, întâi instalează pachetul //pip// pentru Python, după care instalează pachetele Python enumerate în fișierul //​requirements.txt//​) +  ​* **RUN** – rulează o comandă (în exemplul de mai sus, întâi instalează pachetul ​**//pip//** pentru Python, după care instalează pachetele Python enumerate în fișierul ​**//​requirements.txt//​**
-  * EXPOSE – expune un port în afara containerului +  ​* **EXPOSE** – expune un port în afara containerului 
-  * CMD – specifică o comandă care va fi rulată atunci când containerul este pornit (în cazul de față, se rulează scriptul Python //​app.py//​).+  ​* **CMD** – specifică o comandă care va fi rulată atunci când containerul este pornit (în cazul de față, se rulează scriptul Python ​**//app.py//**). 
 + 
 +<note important>​ 
 +**Atenție!** 
 + 
 +Atunci când setăm o imagine de bază folosind FROM, se recomandă să specificăm explicit versiunea imaginii în loc să folosim tag-ul **//​latest//​**,​ pentru că este posibil ca, pe viitor, cea mai recentă versiune să nu mai fie compatibilă cu alte componente din aplicația noastră. 
 +</​note>​ 
 + 
 +<note tip> 
 +Instrucțiunea EXPOSE nu expune propriu-zis portul dat ca parametru, ci funcționează ca un tip de documentație între cine construiește imaginea și cine rulează containerul,​ în legătură cu ce porturi trebuie publicate. Pentru a publica un port la rularea unui container, se folosește flag-ul **//-p//** la comanda de **//docker run//** (cum se va vedea mai jos). 
 +</​note>​
  
 În final, vom avea următoarea structură de fișiere: În final, vom avea următoarea structură de fișiere:
Line 306: Line 318:
 </​code>​ </​code>​
  
-Pentru a construi aplicația, se rulează comanda de mai jos în directorul curent (flag-ul //-t// este folosit pentru a da un tag imaginii create):+Pentru a construi aplicația, se rulează comanda de mai jos în directorul curent (flag-ul ​**//-t//** este folosit pentru a da un tag imaginii create):
  
 <code bash> <code bash>
Line 314: Line 326:
 Step 1/8 : FROM alpine:edge Step 1/8 : FROM alpine:edge
 [...] [...]
-Step 2/8 : RUN apk add --update ​py2-pip+Step 2/8 : RUN apk add --update ​py3-pip
 [...] [...]
 Step 3/8 : COPY requirements.txt /​usr/​src/​app/​ Step 3/8 : COPY requirements.txt /​usr/​src/​app/​
Line 326: Line 338:
 Step 7/8 : EXPOSE 5000 Step 7/8 : EXPOSE 5000
  [...]  [...]
-Step 8/8 : CMD python ​/​usr/​src/​app/​app.py+Step 8/8 : CMD python3 ​/​usr/​src/​app/​app.py
  [...]  [...]
 </​code>​ </​code>​
Line 374: Line 386:
                 "​-c",​                 "​-c",​
                 "#​(nop) ",                 "#​(nop) ",
-                "CMD [\"python\" \"/​usr/​src/​app/​app.py\"​]"​+                "CMD [\"python3\" \"/​usr/​src/​app/​app.py\"​]"​
             ],             ],
             "​ArgsEscaped":​ true,             "​ArgsEscaped":​ true,
Line 406: Line 418:
 </​code>​ </​code>​
  
-Accesând dintr-un browser Web adresa [[http://​127.0.0.1:​8888]],​ vom vedea aplicația Web pe care am creat-o. Flag-ul //​-p// ​specifică o mapare între ​portul 5000 al aplicației și portul 8888 de pe mașina pe care rulează. Dacă dorim să rulăm aplicația în modul detached, o putem face folosind flag-ul //-d//.+Accesând dintr-un browser Web adresa [[http://​127.0.0.1:​8888]],​ vom vedea aplicația Web pe care am creat-o. Flag-ul ​**//-p//** expune ​portul 5000 al aplicației ​și specifică o mapare între el și portul 8888 de pe mașina pe care rulăm. Dacă dorim să rulăm aplicația în modul detașat, o putem face folosind flag-ul ​**//-d//**. 
 + 
 +==== Publicarea unei imagini într-un registru ==== 
 + 
 +Mai devreme, am creat o imagine de Docker pe care am rulat-o local într-un container. Pentru a putea rula imaginea creată în orice alt sistem, este necesar să o publicăm, deci să o urcăm într-un registru pentru a putea să facem deploy de containere cu imaginea noastră în producție. Un registru este o colecție de repository-uri,​ iar un repository este o colecție de imagini (similar cu GitHub, cu diferența că, într-un registru Docker, codul este deja construit și se rețin modificările făcute în straturile imaginilor de Docker, nu în cod). Există numeroase registre pentru imagini Docker (Docker Hub, Gitlab Registry, etc.), iar la laborator vom folosi registrul public Docker, pentru că este gratuit și pre-configurat. 
 + 
 +Pentru exemplificare,​ vom porni de la aplicația prezentată anterior, care afișează o poză aleatoare într-o pagină Web. Primul pas în publicarea unei imagini este crearea unui cont la [[https://​hub.docker.com]]. Mai departe, logarea în registru de pe mașina locală se realizează prin următoarea comandă: 
 + 
 +<code bash> 
 +$ docker login 
 + 
 +Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://​hub.docker.com to create one. 
 +Username:  
 +Password:  
 +Login Succeeded 
 +</​code>​ 
 + 
 +Putem specifica numele de utilizator și parola direct în comandă, iar varianta generică a acesteia este (unde serverul implicit, dacă alegem să omitem acel parametru, este Docker Hub): 
 + 
 +<code bash> 
 +$ docker login [–u <​UTILIZATOR>​ –p <​PAROLĂ>​] [SERVER] 
 +</​code>​ 
 + 
 +Înainte de a publica imaginea în registru, ea trebuie tag-uită după formatul **//​username/​repository:​tag//​**. Tag-ul este opțional, dar este util pentru că denotă versiunea unei imagini Docker. Se folosește următoarea comandă pentru tag-uirea unei imagini (în exemplul de mai jos, unde vrem să tag-uim imaginea pe care am creat-o anterior, utilizatorul se numește **//​raduioanciobanu//​**,​ repository-ul este **//​cloudcomputing//​**,​ iar tag-ul este **//​example//​**):​ 
 + 
 +<code bash> 
 +$ docker tag testapp raduioanciobanu/​cloudcomputing:​example 
 +</​code>​ 
 + 
 +<code bash> 
 +$ docker images 
 +REPOSITORY ​                      ​TAG ​                IMAGE ID            CREATED ​             SIZE 
 +testapp ​                         latest ​             74254b15e6ba ​       About a minute ago   ​62.9MB 
 +raduioanciobanu/​cloudcomputing ​  ​example ​            ​74254b15e6ba ​       About a minute ago   ​62.9MB 
 +alpine ​                          ​edge ​               f96c4363411f ​       4 weeks ago          5.58MB 
 +</​code>​ 
 + 
 +Odată tag-uită imaginea, ea poate fi publicată în registru: 
 + 
 +<code bash> 
 +$ docker push raduioanciobanu/​cloudcomputing:​example 
 +</​code>​ 
 + 
 +Din acest moment, imaginea va fi vizibilă pe [[https://​hub.docker.com]],​ de unde poate fi descărcată și rulată pe orice mașină, server sau sistem Cloud: 
 + 
 +<code bash> 
 +$ docker run -p 8888:5000 raduioanciobanu/​cloudcomputing:​example 
 + 
 +Unable to find image '​raduioanciobanu/​cloudcomputing:​example'​ locally 
 +example: Pulling from raduioanciobanu/​cloudcomputing 
 +cc5efb633992:​ Pull complete  
 +cd0af7ebab8a:​ Pull complete  
 +41c55a3da379:​ Pull complete  
 +a779b27637f8:​ Pull complete  
 +dfaeccf28d0c:​ Pull complete  
 +805843c75452:​ Pull complete  
 +Digest: sha256:​25af18fb4ffa9bf439e90bd4baee9adf0ab1e2999a44aeaa216ebf0454201ce8 
 +Status: Downloaded newer image for raduioanciobanu/​cloudcomputing:​example 
 + * Running on http://​0.0.0.0:​5000/​ (Press CTRL+C to quit) 
 +[...] 
 +</​code>​ 
 + 
 +Alternativ, în loc să rulăm comanda de publicare a unei imagini de fiecare dată când modificăm ceva la codul sursă, putem să configurăm [[https://​docs.docker.com/​docker-hub/​builds/​|build-uri automate]] din contul de Docker Hub. Pașii necesari sunt descriși în continuare. 
 +În primul rând, este necesară existența unui repository Docker Hub și a unui repository pe GitHub (Docker Hub funcționează și cu BitBucket, dar în acest exemplu ne vom concentra pe GitHub). Toate fișierele necesare creării unei imagini Docker (adică Dockerfile-ul și toate fișierele sursă și de configurare) trebuie să fie prezente în repository-ul GitHub. Mai departe, de pe pagina repository-ului de Docker Hub, se selectează tab-ul Builds și apoi opțiunea „Configure Automated Builds”, așa cum se poate observa în imaginea de mai jos. 
 + 
 +{{:​cc:​laboratoare:​dockerhub.png?​direct&​600|}} 
 + 
 +În continuare, va fi necesară completarea unor informații despre repository-ul GitHub și opțiuni de testare automată înainte de build, după care trebuie specificate regulile de build. O regulă de build conține informații despre: tipul build-ului (bazat pe un branch sau pe un tag Git), sursa (numele branch-ului sau tag-ului de pe care se face build-ul), tag-ul care va fi asignat noii imagini Docker construite, numele și adresa fișierului Dockerfile în repository-ul GitHub, calea către sursele ce vor fi compilate, opțiuni de auto-build (dacă se va face build automat la fiecare push pe branch-ul sau cu tag-ul specificat),​ opțiuni de build caching (dacă se vor cache-ui fișiere la build în cazul unor repository-uri de dimensiuni mari). În exemplul de mai jos, atunci când are loc un push pe branch-ul **//​master//​**,​ se va crea automat o imagine Docker cu tag-ul **//​latest//​** folosindu-se fișierul Dockerfile aflat în rădăcina repository-ului de GitHub. 
 + 
 +{{:​cc:​laboratoare:​dockerhub2.png?​direct&​500|}} 
 + 
 +În continuare, pe pagina de Builds de pe Docker Hub vor exista opțiuni pentru pornirea unui nou build, precum și informații despre build-urile precedente și statusurile lor. 
 + 
 +==== Networking ==== 
 + 
 +Subsistemul de networking Docker este de tip pluggable și folosește drivere. Mai multe astfel de drivere există implicit, ele oferind funcționalitate de bază pentru componenta de rețea. Driverul de rețea implicit este **bridge**, și presupune crearea unui bridge software care permite containerelor conectate la aceeași rețea de acest tip să comunice între ele, oferind totodată izolare față de containerele care nu sunt conectate la această rețea bridge. Driverul de bridge Docker instalează automat reguli pe mașina gazdă astfel încât containerele de pe rețele bridge diferite nu pot comunica direct unele cu altele. Rețelele de tip bridge se aplică doar containerelor care rulează pe aceeași mașină Docker. 
 + 
 +Pentru comunicație între containere care rulează pe mașini Docker diferite, se poate gestiona rutarea la nivel de sistem de operare, sau se poate folosi o rețea de tip **overlay**. Așa cum se va detalia în [[cc:​laboratoare:​02|laboratorul 2]], rețelele de tip overlay conectează mai multe mașini Docker și permit serviciilor dintr-un swarm să comunice între ele. Rețelele overlay se mai pot folosi și pentru a facilita comunicația între un serviciu swarm și un container de sine stătător, sau între două containere care rulează pe mașini Docker diferite. 
 + 
 +Alte drivere de rețea Docker mai sunt **host** (pentru containere de sine stătătoare,​ eliminând izolarea de rețea dintre container și gazda Docker, folosindu-se astfel infrastructura de rețea a gazdei direct), **macvlan** (permite asignarea de adrese MAC unui container, făcându-l să apară ca un dispozitiv fizic pe rețea), sau **none**. 
 + 
 +Containerele din aceeași rețea pot comunica fără să expună porturi, prin intermediul **named DNS**. Acest lucru înseamnă că putem să accesam un container nu prin IP, ci prin numele său. Pentru comunicarea cu lumea exterioară (gazda, containere din afara rețelei, etc.) trebuie expuse porturi. 
 + 
 +Pentru a demonstra modul în care funcționează rețelele de tip bridge în Docker, întâi vom porni două containere ce vor rula Alpine. În mod implicit, orice container Docker nou-creat se va afla într-o rețea numită „bridge”,​ așa că, pentru a demonstra faptul că două containere care nu sunt în aceeași rețea nu pot comunica, va trebui întâi să le scoate din acea rețea. 
 + 
 +<code bash> 
 +$ docker container run --name c1 -d -it alpine 
 + 
 +f5a8653a325e8092151614d5a6a80b04b9410ea8b8a5fcfc4028f1ad33239ad9 
 +</​code>​ 
 + 
 +<code bash> 
 +$ docker container run --name c2 -d -it alpine 
 + 
 +b063ad1ef7bd0ae82a7385582415e78938f7df531cef9eefc33e065af09cf92c 
 +</​code>​ 
 + 
 +<code bash> 
 +$ docker network disconnect bridge c1 
 +</​code>​ 
 + 
 +<code bash> 
 +$ docker network disconnect bridge c2 
 +</​code>​ 
 + 
 +<note tip> 
 +În comanda de **//docker run//** de mai sus, parametrul **//​%%--%%name//​** îi dă containerului un nume (sau alias) prin care îl putem referi mai ușor. 
 +</​note>​ 
 + 
 +În acest moment, containerele **c1** și **c2** nu fac parte din nicio rețea. Mai departe, vom încerca să dăm **//​ping//​** dintr-un container în altul. 
 + 
 +<code bash> 
 +$ docker exec -it c1 ash 
 + 
 +/ # ifconfig 
 +lo        Link encap:Local Loopback ​  
 +          inet addr:​127.0.0.1 ​ Mask:​255.0.0.0 
 +          UP LOOPBACK RUNNING ​ MTU:​65536 ​ Metric:1 
 +          RX packets:0 errors:0 dropped:0 overruns:0 frame:0 
 +          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 
 +          collisions:​0 txqueuelen:​1000  
 +          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B) 
 + 
 +/ # ping c2 
 +ping: bad address '​c2'​ 
 + 
 +/ # exit 
 +</​code>​ 
 + 
 +Se poate observa mai sus că **c1** nu are decât o adresă IP loopback și că nu poate accesa **c2**. De asemenea, ar fi interesant de menționat faptul că ne-am atașat la container folosind comanda **//docker exec//** rulând un shell (**//​ash//​** este shell-ul de pe Alpine). 
 + 
 +Pentru a crea o rețea de tip bridge în Docker, putem folosi următoarea comandă: 
 + 
 +<code bash> 
 +$ docker network create -d bridge c1-c2-bridge 
 + 
 +8644b8accd2a14d10c9911c36635ca6b161449b3aa527db878a727ec1bf980d0 
 +</​code>​ 
 + 
 +Mai departe, putem vizualiza rețele existente astfel: 
 + 
 +<code bash> 
 +$ docker network ls 
 + 
 +NETWORK ID          NAME                DRIVER ​             SCOPE 
 +ecd72738aa59 ​       bridge ​             bridge ​             local 
 +8644b8accd2a ​       c1-c2-bridge ​       bridge ​             local 
 +615363cafefa ​       host                host                local 
 +1e3b8e49b20d ​       none                null                local 
 +</​code>​ 
 + 
 +Putem adăuga un container într-o rețea fie atunci când pornim containerul,​ fie atunci când el deja a fost pornit. Pentru cazul de mai sus, unde **c1** și **c2** erau deja pornite, le putem adăuga în rețea astfel: 
 + 
 +<code bash> 
 +$ docker network connect c1-c2-bridge c1 
 +</​code>​ 
 + 
 +<code bash> 
 +$ docker network connect c1-c2-bridge c2 
 +</​code>​ 
 + 
 +Dacă **c1** și **c2** nu ar fi fost deja pornite, le-am fi putut porni deja atașate la rețeaua **c1-c2-bridge** astfel: 
 + 
 +<code bash> 
 +$ docker container run --name c2 -d -it --network=c1-c2-bridge alpine 
 + 
 +67dde5da9b793de63903ac85ff46574da77f0031df9b49acf44d58062687729c 
 +</​code>​ 
 + 
 +<code bash> 
 +$ docker container run --name c1 -d -it --network=c1-c2-bridge alpine 
 + 
 +4de3e000700f81d31e0458dbd034abe90dfce6b1b992d23d760a44f748c0de0d 
 +</​code>​ 
 + 
 +Putem vedea containerele dintr-o rețea astfel: 
 + 
 +<code bash> 
 +$ docker network inspect c1-c2-bridge 
 + 
 +[...] 
 +"​Containers":​ { 
 +    "​b063ad1ef7bd0ae82a7385582415e78938f7df531cef9eefc33e065af09cf92c":​ { 
 +        "​Name":​ "​c2",​ 
 +        "​EndpointID":​ "​a76463662d110804205e9211537e541eb0de2646fa90e8760d3419a6dc7d32c7",​ 
 +        "​MacAddress":​ "​02:​42:​ac:​12:​00:​03",​ 
 +        "​IPv4Address":​ "​172.18.0.3/​16",​ 
 +        "​IPv6Address":​ ""​ 
 +    }, 
 +    "​f5a8653a325e8092151614d5a6a80b04b9410ea8b8a5fcfc4028f1ad33239ad9":​ { 
 +        "​Name":​ "​c1",​ 
 +        "​EndpointID":​ "​95d9061b47f73f9b4cc7a82111924804bdc73d0b496549dec834216ee58c64ed",​ 
 +        "​MacAddress":​ "​02:​42:​ac:​12:​00:​02",​ 
 +        "​IPv4Address":​ "​172.18.0.2/​16",​ 
 +        "​IPv6Address":​ ""​ 
 +    } 
 +
 +[...] 
 +</​code>​ 
 + 
 +În acest moment, cele două containere fac parte din aceeași rețea și pot comunica: 
 + 
 +<code bash> 
 +$ docker exec -it c1 ash 
 + 
 +/ # ping -c2 c2 
 +PING c2 (172.18.0.3):​ 56 data bytes 
 +64 bytes from 172.18.0.3: seq=0 ttl=64 time=6.258 ms 
 +64 bytes from 172.18.0.3: seq=1 ttl=64 time=0.109 ms 
 + 
 +--- c2 ping statistics --- 
 +2 packets transmitted,​ 2 packets received, 0% packet loss 
 +round-trip min/avg/max = 0.109/​3.183/​6.258 ms 
 + 
 +/ # exit 
 +</​code>​ 
 + 
 +<code bash> 
 +$ docker exec -it c2 ash 
 + 
 +/ # ping -c2 c1 
 +PING c1 (172.18.0.2):​ 56 data bytes 
 +64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.111 ms 
 +64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.268 ms 
 + 
 +--- c1 ping statistics --- 
 +2 packets transmitted,​ 2 packets received, 0% packet loss 
 +round-trip min/avg/max = 0.111/​0.189/​0.268 ms 
 + 
 +/ # exit 
 +</​code>​ 
 +==== Volume și bind mounts ==== 
 + 
 +În Docker, datele dintr-un container nu sunt persistate în exterior. Pentru a ilustra acest lucru, putem rula un container simplu de Alpine, în care creăm un fișier apoi ieșim. 
 + 
 +<code bash> 
 +$ docker container run --name c1 -ti alpine sh 
 + 
 +Unable to find image '​alpine:​latest'​ locally 
 +latest: Pulling from library/​alpine 
 +88286f41530e:​ Pull complete  
 +Digest: sha256:​f006ecbb824d87947d0b51ab8488634bf69fe4094959d935c0c103f4820a417d 
 +Status: Downloaded newer image for alpine:​latest 
 + 
 +/ # mkdir /test && echo hello > /​test/​hello.txt 
 +/ # exit 
 +</​code>​ 
 + 
 +<code bash> 
 +$ docker container ls -a 
 + 
 +CONTAINER ID        IMAGE        COMMAND ​       CREATED ​            ​STATUS ​                     PORTS               ​NAMES 
 +97492cd1349b ​       alpine ​      "​sh" ​          15 minutes ago      Exited (0) 15 minutes ago                       c1 
 +</​code>​ 
 + 
 +Odată ce un container a fost rulat, chiar dacă execuția sa s-a oprit, layer-ele sale pot fi accesate până când containerul este șters cu comanda **//docker container rm//** (sau **//docker system prune//**). În mod implicit, Docker folosește [[https://​www.kernel.org/​doc/​Documentation/​filesystems/​overlayfs.txt|OverlayFS]] sau [[https://​www.thegeekstuff.com/​2013/​05/​linux-aufs/​|AUFS]] (ambele sisteme de fișiere de tip union) ca driver de storage pentru gestiunea imaginilor. Putem verifica acest lucru folosind **//docker info//**: 
 + 
 +<code bash> 
 +$ docker info | grep -i storage 
 + 
 +Storage Driver: overlay2 
 +</​code>​ 
 + 
 +Pentru fiecare layer dintr-un container Docker, se vor stoca în AUFS/​OverlayFS informații despre cum arăta inițial și despre ce fișiere s-au modificat (au fost adăugate, șterse sau schimbate). Aceste informații se găsesc în **///​var/​lib/​docker/​aufs/​diff//​** (pentru AUFS) sau **///​var/​lib/​docker/​overlay2//​** (pentru OverlayFS), unde există directoare pentru fiecare layer al fiecărui container care a fost rulat pe sistem fără să fi fost șters. Putem astfel să vedem din exteriorul containerului,​ după ce acesta a terminat de rulat, fișierul care a fost creat în interiorul containerului:​ 
 + 
 +<code bash> 
 +$ cd /​var/​lib/​docker/​overlay2/​ 
 +</​code>​ 
 + 
 +<code bash> 
 +$ ls -latr 
 + 
 +[...] 
 +drwx------ ​   4 root   ​root ​  4096 Oct 21 07:12 5b3f2aeff7a90abd5c1a2eb50e5bbf9bde38983bda84728ab3788a12ea2399dc-init 
 +drwx------ ​   4 root   ​root ​  4096 Oct 21 07:12 5b3f2aeff7a90abd5c1a2eb50e5bbf9bde38983bda84728ab3788a12ea2399dc 
 +</​code>​ 
 + 
 +<code bash> 
 +$ ls 5b3f2aeff7a90abd5c1a2eb50e5bbf9bde38983bda84728ab3788a12ea2399dc/​diff/​ 
 + 
 +root  test 
 +</​code>​ 
 + 
 +<code bash> 
 +$ cat 5b3f2aeff7a90abd5c1a2eb50e5bbf9bde38983bda84728ab3788a12ea2399dc/​diff/​test/​hello.txt  
 + 
 +hello 
 +</​code>​ 
 + 
 +Totuși, aceste date nu sunt persistente,​ ci sunt șterse împreuna cu layer-ul. Astfel, dacă se șterge containerul,​ datele vor fi pierdute: 
 + 
 +<code bash> 
 +$ docker container rm 97492cd1349b 
 +</​code>​ 
 + 
 +<code bash> 
 +$ ls 5b3f2aeff7a90abd5c1a2eb50e5bbf9bde38983bda84728ab3788a12ea2399dc/​ 
 + 
 +ls: 5b3f2aeff7a90abd5c1a2eb50e5bbf9bde38983bda84728ab3788a12ea2399dc:​ No such file or directory 
 +</​code>​ 
 + 
 +Pentru persistența datelor dintr-un container, în Docker se folosesc mecanisme de persistență numite **volume**, care sunt o mapare între fișierele din cadrul unui container și fișiere de pe sistemul gazdă sau NFS (Network File Storage). Volumele Docker au câteva proprietăți și beneficii:​ 
 + 
 +  * sunt ușor de salvat și migrat 
 +  * pot fi controlate și configurate cu comenzi CLI sau cu API-ul de Docker 
 +  * funcționează pe containere Linux și Windows 
 +  * pot fi partajate între containere 
 +  * prin driverele de volume, se pot stoca date persistente pe gazde remote sau pe provideri de cloud, se pot cripta datele, etc. 
 +  * conținutul unui volum nou poate fi pre-populat de un container 
 +  * utilizarea unui volum nu crește dimensiunea unui container care îl folosește, pentru că un volum există în afara ciclului de viață al containerului. 
 + 
 +Volumele se mai numesc și „named volumes” și sunt gestionate de Docker. Există mai multe metode pentru a defini și utiliza un volum atunci când se rulează un singur container de Linux. Dacă se creează o imagine custom, atunci volumul se poate defini în fișierul Dockerfile, prin comanda **//​VOLUME//​**. Dacă se rulează, de exemplu, un container bazat pe o imagine existentă (cum ar fi Alpine în exemplul de mai devreme), atunci se poate defini un volum la runtime. În exemplul de mai jos, rulăm o imagine de Alpine în background care face **//​ping//​** într-un fișier localizat într-un volum **///​test//​**,​ pe care îl creăm folosind flag-ul **//​-v//​**:​ 
 + 
 +<code bash> 
 +$ docker container run --name c2 -d -v /test alpine sh -c 'ping 8.8.8.8 > /​test/​ping.txt'​ 
 +</​code>​ 
 + 
 +<code bash> 
 +$ docker container ls 
 + 
 +CONTAINER ID        IMAGE               ​COMMAND ​                 CREATED ​             STATUS ​             PORTS               ​NAMES 
 +59d0785188a6 ​       alpine ​             "sh -c 'ping 8.8.8..." ​  About a minute ago   Up About a minute ​                      c2 
 +</​code>​ 
 + 
 +În timp ce containerul rulează, putem să îl inspectăm și observăm că este legat de o componentă de tip **//​Volume//​** cu destinația **///​test//​**. Astfel, putem afla unde este localizat volumul. Dacă ne uităm în acel director, vom vedea fișierul în care se face **//​ping//​** din container:​ 
 + 
 +<code bash> 
 +$ docker container inspect -f "{{ json .Mounts }}" c2 | python -m json.tool 
 + 
 +
 +    { 
 +        "​Destination":​ "/​test",​ 
 +        "​Driver":​ "​local",​ 
 +        "​Mode":​ "",​ 
 +        "​Name":​ "​2afac5683222a3435549131a931a4c0628b775ecd3d79cb3fd597b3501418288",​ 
 +        "​Propagation":​ "",​ 
 +        "​RW":​ true, 
 +        "​Source":​ "/​var/​lib/​docker/​volumes/​2afac5683222a3435549131a931a4c0628b775ecd3d79cb3fd597b3501418288/​_data",​ 
 +        "​Type":​ "​volume"​ 
 +    } 
 +
 +</​code>​ 
 + 
 +<code bash> 
 +$ ls /​var/​lib/​docker/​volumes/​2afac5683222a3435549131a931a4c0628b775ecd3d79cb3fd597b3501418288/​_data 
 + 
 +ping.txt 
 +</​code>​ 
 + 
 +<code bash> 
 +$ cat ping.txt  
 + 
 +PING 8.8.8.8 (8.8.8.8): 56 data bytes 
 +64 bytes from 8.8.8.8: seq=0 ttl=38 time=58.619 ms 
 +64 bytes from 8.8.8.8: seq=1 ttl=38 time=58.498 ms 
 +</​code>​ 
 + 
 +Dacă oprim și ștergem containerul,​ volumul va exista în continuare:​ 
 + 
 +<code bash> 
 +$ docker container stop c2 
 + 
 +c2 
 +</​code>​ 
 + 
 +<code bash> 
 +$ docker container rm c2 
 + 
 +c2 
 +</​code>​ 
 + 
 +<code bash> 
 +$ ls /​var/​lib/​docker/​volumes/​2afac5683222a3435549131a931a4c0628b775ecd3d79cb3fd597b3501418288/​_data 
 + 
 +ping.txt 
 +</​code>​ 
 + 
 +O a treia metodă de a lucra cu volume în Docker este direct prin API-ul de volume, adică prin comenzi CLI de genul **//docker volume create//**, **//docker volume ls//**, etc. Dacă vrem să creăm volume pentru o stivă de servicii, acest lucru poate fi făcut în fișierul YAML folosit pentru Docker Compose, așa cum vom vedea în [[cc:​laboratoare:​02|laboratorul 2]]. 
 + 
 +Pe lângă volume, mai există și noțiunea de **bind mounts**. Acestea sunt similare cu volumele, dar nu sunt gestionate de Docker, ci se pot afla oriunde în sistemul de fișiere al gazdei pe care rulăm containerele,​ și pot fi modificate extern de orice proces non-Docker. Diferența principală dintre un bind mount si un volum este că bind mount-ul este o cale fizică de pe mașina gazdă, în timp ce volumul este o entitate Docker care utilizează,​ în spate, un bind mount abstractizat. În imaginea de mai jos (preluată din [[https://​docs.docker.com/​storage/​bind-mounts/​|documentația oficială]]),​ se poate observa în mod grafic diferența dintre volume și bind mounts. 
 + 
 +{{:​cc:​laboratoare:​volumebind.png?​direct&​500|}} 
 + 
 +Atunci când pornim un container prin comanda **//docker container run//**, atât argumentul **//-v//** (sau **//​%%--%%volume//​**),​ cât și **//​%%--%%mount//​** permit utilizarea de bind mounts și volume. Totuși, în cadrul serviciilor (așa cum vom vedea în [[cc:​laboratoare:​02|laboratorul 2]]), nu putem folosi decât **//​%%--%%mount//​**. Acesta este totuși considerat oricum mai expresiv, pentru că necesită specificarea efectivă ​ tipului de legătura (volum sau bind mount). Astfel, exemplul anterior unde atașam un volum **///​test//​** containerului pe care îl rulam ar arăta în felul următor: 
 + 
 +<code bash> 
 +$ docker container run --name c2 -d --mount source=test,​target=/​test alpine sh -c 'ping 8.8.8.8 > /​test/​ping.txt'​ 
 +</​code>​ 
 + 
 +Pentru a verifica efectul acestei comenzi, putem rula comanda de inspectare:​ 
 + 
 +<code bash> 
 +$ docker container inspect -f "{{ json .Mounts }}" c2 | python -m json.tool 
 + 
 +
 +    { 
 +        "​Destination":​ "/​test",​ 
 +        "​Driver":​ "​local",​ 
 +        "​Mode":​ "​z",​ 
 +        "​Name":​ "​test",​ 
 +        "​Propagation":​ "",​ 
 +        "​RW":​ true, 
 +        "​Source":​ "/​var/​lib/​docker/​volumes/​test/​_data",​ 
 +        "​Type":​ "​volume"​ 
 +    } 
 +
 +</​code>​ 
 + 
 +==== Docker pe Windows și pe MacOS ==== 
 + 
 +Docker a fost creat nativ pentru Linux, utilizând componente de kernel specifice Linux, cum ar fi **//​cgroups//​** sau **//​namespaces//​**,​ folosite pentru a izola procese și alte componente ale sistemului de operare. Începând din 2016, el poate rula nativ și pe Windows, dar doar pentru versiunile Windows Server 2016 și Windows 10. De aceea, pentru a rula pe un sistem de operare desktop precum un Windows mai vechi sau MacOS, Docker necesită rularea virtualizată. 
 + 
 +Pe Windows, se folosește izolare Hyper-V pentru a rula un kernel Linux cu un set minimal de componente suficiente pentru a executa Docker. Pe MacOS, Docker for Mac este o aplicație nativă care conține un hypervizor bazat pe [[https://​github.com/​machyve/​xhyve|xhyve]] și o distribuție minimală de Linux, peste care rulează Docker. Astfel, se oferă o experiență mult mai apropiată de utilizarea Docker pe Linux, sistemul de operare pentru care a fost creat. 
 + 
 +Ca un exemplu, pentru a avea acces în mașina virtuală de Docker pentru MacOS, se poate folosi **//​screen//​** (pentru a se termina sesiunea, se folosește combinația de taste **//Ctrl+a, k//**): 
 + 
 +<code bash> 
 +$ screen /​Users/<​UID>/​Library/​Containers/​com.docker.docker/​Data/​vms/​0/​tty 
 + 
 +linuxkit-025000000001:​~#​ pwd 
 +/root 
 +</​code>​ 
 + 
 +O alternativă mai elegantă la comanda de **//​screen//​** de mai sus este utilizarea unei imagini speciale pentru accesul în mașina virtuală Docker: 
 + 
 +<code bash> 
 +$ docker run -it --privileged --pid=host justincormack/​nsenter1 
 +</​code>​ 
  
 ==== Comenzi utile ==== ==== Comenzi utile ====
Line 414: Line 854:
 </​note>​ </​note>​
  
-=== Comenzi de sistem ​===+=== Sistem ​===
  
 <code bash> <code bash>
Line 459: Line 899:
 $ docker logs <​ID> ​                         # afișează loguri din interiorul unui container $ docker logs <​ID> ​                         # afișează loguri din interiorul unui container
 $ docker top <​ID> ​                          # afișează procesele care rulează într-un container $ docker top <​ID> ​                          # afișează procesele care rulează într-un container
 +</​code>​
 +
 +<note tip>
 +Diferența dintre comenzile **//​exec//​** și **//​attach//​** (care pot părea similare) este că, la **//​attach//​**,​ se asociază un terminal la container, ceea ce înseamnă că, dacă se iese din acel terminal, se iese cu totul și din container.
 +</​note>​
 +
 +=== Lucru cu registre ===
 +
 +<code bash>
 +$ docker login [–u <​UTILIZATOR>​ –p <​PAROLĂ>​] [SERVER] ​  # loghează un utilizator într-un registru
 +$ docker tag <​IMAGINE>​ <​UTILIZATOR/​REPOSITORY:​TAG> ​     # dă un tag unei imagini pentru upload în registru
 +$ docker push <​UTILIZATOR/​REPOSITORY:​TAG> ​              # uploadează o imagine în registru
 +</​code>​
 +
 +=== Creare și interacțiune cu rețele ===
 +
 +<code bash>
 +$ docker network create -d <​DRIVER>​ <​REȚEA> ​         # creează o rețea cu un driver dat
 +$ docker network ls                                  # afișează rețelele existente
 +$ docker network rm                                  # șterge o rețea
 +$ docker network connect <​REȚEA>​ <​CONTAINER> ​        # conectează un container la o rețea
 +$ docker network disconnect <​REȚEA>​ <​CONTAINER> ​     # deconectează un container de la o rețea
 +$ docker network inspect <​REȚEA> ​                    # afișează informații despre o rețea
 +$ docker container run --network=<​REȚEA>​ <​IMAGINE> ​  # pornește un container într-o rețea
 +</​code>​
 +
 +=== Creare și interacțiune cu volume sau bind mounts ===
 +
 +<code bash>
 +$ docker volume create <​VOLUM> ​                                               # creează un volum
 +$ docker volume ls                                                            # afișează volumele existente
 +$ docker volume rm <​VOLUM> ​                                                   # șterge un volum
 +$ docker container run -v <​VOLUM>​ <​IMAGINE> ​                                  # rulează un container cu un volum atașat
 +$ docker container run -v <​SURSĂ>:<​DESTINAȚIE>​ <​IMAGINE> ​                     # rulează un container cu un volum sau un bind mount atașat
 +$ docker container run --mount source=<​SURSĂ>,​target=<​DESTINAȚIE>​ <​IMAGINE> ​  # rulează un container cu un volum sau bind mount atașat
 </​code>​ </​code>​
  
 ==== Exerciții ==== ==== Exerciții ====
  
-  ​- Aduceți în cache-ul local imaginea //busybox// din registrul oficial Docker +=== Comenzi de bază === 
-  - Rulați un container de //busybox// care să execute comanda //​uptime//​ + 
-  - Rulați un container interactiv de //​busybox//​. Odată ce ați intrat în el, executați comanda //wget google.com//,​ apoi ieșiți. +  ​- Aduceți în cache-ul local imaginea ​**//busybox//** din registrul oficial Docker. 
-  - Rulați un container interactiv detașat (daemon) de //​busybox//​. Odată ce l-ați pornit, atașați-vă la el și dați comanda //id//, apoi ieșiți.+  - Rulați un container de **//busybox//** care să execute comanda ​**//uptime//**. 
 +  - Rulați un container interactiv de **//busybox//**. Odată ce ați intrat în el, executați comanda ​**//wget google.com//​**, apoi ieșiți. 
 +  - Rulați un container interactiv detașat (daemon) de **//busybox//**. Odată ce l-ați pornit, atașați-vă la el și dați comanda ​**//id//**, apoi ieșiți.
   - Ștergeți toate containerele și imaginile create la punctele precedente.   - Ștergeți toate containerele și imaginile create la punctele precedente.
-  ​- Pornind de la cele două fișiere din această (TODO) ​arhivă, scrieți un //Dockerfile// care va crea o imagine urmărind pașii de mai jos: + 
-    - se va porni de la cea mai recentă versiune a imaginii oficiale de NodeJS, adică //node:latest// +=== Crearea unei imagini === 
-    - se va copia fișierul //​package.json//​ din arhivă în directorul curent (//.///); acest fișier are rolul de a specifica dependențele aplicației NodeJS (de exemplu, framework-ul Express.js) + 
-    - se va rula comanda //npm install// pentru a instala dependețele din fișierul de la pasul precedent +<note tip>​Pentru exercițiile următoare, veți porni de la {{:​cc:​laboratoare:​homework1.zip|această arhivă}}, care conține o aplicație simplă NodeJS.</​note>​ 
-    - se va copia sursa //​server.js//​ în directorul de lucru ///​usr/​src/​app///​+ 
 +  ​- Pornind de la cele două fișiere din arhivă, scrieți un Dockerfile care va crea o imagine urmărind pașii de mai jos: 
 +    - se va porni de la cea mai recentă versiune a imaginii oficiale de NodeJS, adică ​**//node:14.13.0-stretch//** 
 +    - se va copia fișierul ​**//​package.json//​** din arhivă în directorul curent (./); acest fișier are rolul de a specifica dependențele aplicației NodeJS (de exemplu, framework-ul Express.js) 
 +    - se va rula comanda ​**//npm install//** pentru a instala dependețele din fișierul de la pasul precedent 
 +    - se va copia sursa **//​server.js//​** în directorul de lucru **// /​usr/​src/​app/​ //**
     - se va expune portul 8080     - se va expune portul 8080
-    - în final, se va menționa comanda de rulare a aplicației;​ astfel, se va rula fișierul //"/​usr/​src/​app/​server.js"// cu binarul //node//. +    - în final, se va menționa comanda de rulare a aplicației;​ astfel, se va rula fișierul ​**///​usr/​src/​app/​server.js//​** cu binarul ​**//node//**
-  - Folosiți ​//Dockerfile//-ul scris anterior pentru a crea o imagine numita //​nodejstest//​ +  - Folosiți Dockerfile-ul scris anterior pentru a crea o imagine numita ​**//​nodejstest//​**. 
-  - Porniți un container care să ruleze imaginea //​nodejstest//​ pe portul 12345. Verificați că funcționează corect intrând pe +  - Porniți un container care să ruleze imaginea ​**//​nodejstest//​** pe portul 12345 în modul detașat (daemon). Verificați că funcționează corect intrând pe [[http://​127.0.0.1:​12345]]. 
 + 
 +=== Lucrul cu rețele, volume și bind mounts === 
 + 
 +<note tip>​Pentru exercițiile următoare, veți porni de la {{:​cc:​laboratoare:​homework2.zip|această arhivă}}, care conține o aplicație NodeJS care realizează un API de adăugare de cărți într-o bibliotecă peste o bază de date PostgreSQL. Exercițiile de mai jos vă trec prin pașii necesari pentru a rula un container pentru o bază de date PostgreSQL și containerul cu aplicația în aceeași rețea, având persistență la oprirea containerelor.</​note>​ 
 + 
 +  - Pe baza surselor și a fișierului Dockerfile din arhiva de mai sus, construiți o imagine cu numele (tag-ul) **//​api-laborator-1-image//​**. 
 +  - Creați o rețea bridge numită **//​laborator1-db-network//​**. 
 +  - Creați un volum numit **//​laborator1-db-persistent-volume//​**. 
 +  - Porniți în background un container pentru o bază de date cu următoarele caracteristici:​ 
 +    - se va atașa un bind mount care va face o mapare între fișierul **//​init-db.sql//​** de pe mașina locală (acesta va fi sursa la flag-ul de bind mount și se găsește în arhiva de laborator) și fișierul **///​docker-entrypoint-initdb.d/​init-db.sql//​** din containerul care se va rula (acesta va fi destinația) 
 +    - se va atașa volumul **//​laborator1-db-persistent-volume//​** creat anterior (sursa) la calea **///​var/​lib/​postgresql/​data//​** din containerul care se va rula (destinația) 
 +    - se va rula containerul în rețeaua **//​laborator1-db-network//​** creată anterior 
 +    - se vor specifica următoarele variabile de mediu (într-o comandă de **//docker run//**, acest se lucru se face astfel: **//docker run -e NUME=valoare//​**):​ 
 +      - variabila **//​POSTGRES_USER//​** cu valoare **//​admin//​** 
 +      - variabila **//​POSTGRES_PASSWORD//​** cu valoarea **//​admin//​** 
 +      - variabila **//​POSTGRES_DB//​** cu valoarea **//​books//​** 
 +    - containerul rulat se va numi **//​laborator1-db//​** 
 +    - se va rula imaginea **//​postgres//​** din registrul oficial. 
 +  - Porniți în background un container cu imaginea **//​api-laborator-1-image//​** creată anterior, cu următoarele caracteristici:​ 
 +    - se va rula containerul în rețeaua **//​laborator1-db-network//​** creată anterior 
 +    - se vor specifica următoarele variabile de mediu: 
 +      - variabila **//​PGUSER//​** cu valoare **//​admin//​** 
 +      - variabila **//​PGPASSWORD//​** cu valoarea **//​admin//​** 
 +      - variabila **//​PGDATABASE//​** cu valoarea **//​books//​** 
 +      - variabila **//​PGHOST//​** cu valoarea **//​laborator1-db//​** 
 +      - variabila **//​PGPORT//​** cu valoarea **//​5432//​** 
 +    - containerul rulat se va numi **//​laborator1-api//​** 
 +    - containerul va expune portul 80 și îl va mapa la portul 5555 de pe mașina locală. 
 +  - Verificați că cele două containere rulează corect și au conectivitate:​ 
 +    - folosind [[https://​www.postman.com|Postman]] sau orice altă aplicație similară, realizați cereri de GET și POST pe [[http://​localhost:​5555/​api/​books]] (pentru un tutorial de Postman, puteți intra [[https://​learning.postman.com/​docs/​getting-started/​sending-the-first-request/​|aici]]) 
 +    - la cererile de POST, se așteaptă un body JSON cu formatul ''​%%{"​title":"​titlu","​author":"​autor"​}%%''​ 
 +    - cererile de GET vor returna o listă de cărți adăugate prin cereri de POST. 
 +  - Verificați că volumul pe care l-ați adăugat păstrează persistența datelor: 
 +    - opriți și ștergeți cele două containere 
 +    - reporniți cele două containere cu aceleași comenzi ca anterior 
 +    - trimiteți o cerere de GET 
 +    - dacă ați configurat corect, veți primi o listă cu cărțile adăugate anterior.
cc/laboratoare/01.1601640501.txt.gz · Last modified: 2020/10/02 15:08 by radu.ciobanu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0