This is an old revision of the document!


Laboratorul 03 - Docker Swarm

Introducere

În acest laborator, vom face tranziția de la Docker Compose la Docker Swarm, orchestratorul de servicii oferit de Docker. Acesta are rolul de a gestiona servicii de Docker pe una sau mai multe mașini într-o rețea (într-un cluster) de mașini fizice și/sau virtuale. Spre deosebire de Docker Compose, care rulează containere pe o singură gazdă, Docker Swarm rulează servicii între mai multe gazde. La fel ca și Compose, Docker Swarm folosește fișiere de configurare YAML.

Arhitectura Docker Swarm

Docker Swarm se bazează pe algoritmul distribuit Raft, cu ajutorul căruia se menține consistentă starea internă a întregului cluster. În plus, traficul dintre nodurile din Swarm este criptat de către Raft. În imaginea de mai jos (preluată din documentația oficială), se poate observa arhitectura unui cluster Docker Swarm.

Mașinile gazdă care fac parte dintr-un Swarm se numesc noduri și pot avea două roluri:

  • manager - rol administrativ și funcțional; menține consistența clusterului, lansează în execuție servicii, expune endpoint-uri de rețea
  • worker - rol funcțional; execută serviciile.

Dintre toate nodurile manager, un singur nod este leader, care are rolul de a crea task-uri și de a face logging. Task-urile sunt distribuite apoi nodurilor manager.

Întotdeauna trebuie să existe un nod leader.

Deoarece toleranța este de (N - 1) / 2 noduri manager picate, este indicat să avem un număr impar de noduri și un număr impar de manageri. Docker recomanda 3, 5 sau 7 manageri.

Crearea unui Docker Swarm

Odată ce avem un cluster de mașini pe care rulează Docker, ne putem inițializa un Docker Swarm. Astfel, putem rula următoarea comandă pe nodul care va fi leader (opțiunea --advertise-addr este necesară atunci când nodul are mai multe interfețe de rețea și trebuie specificat pe care din ele se face advertising):

$ docker swarm init --advertise-addr 192.168.99.100
 
Swarm initialized: current node (qtyx0t5z275wp46wibcznx8g5) is now a manager.
To add a worker to this swarm, run the following command:
    docker swarm join --token SWMTKN-1-4hd41nyin8kn1wx4bscnnt3e98xtlvyxw578qwxijw65jp1a3q-32rl6525xriofd5xmv0c1k5vj 192.168.99.100:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

Așa cum se poate observa, comanda de mai sus generează alte două comenzi pe care le putem folosi pentru a introduce alte noduri în cluster, atât ca worker, cât și ca manager.

Putem verifica dacă swarm-ul a fost creat cu succes dând comanda de mai jos pe mașina leader (unde avem două noduri numite node1 și node2, primul din ele fiind leader, iar al doilea worker):

$ docker node ls
 
ID                            HOSTNAME     STATUS       AVAILABILITY      MANAGER STATUS
qtyx0t5z275wp46wibcznx8g5 *   node1        Ready        Active            Leader
0xbb9al1kuvn0jcapxiqni29z     node2        Ready        Active      

Servicii și stive de servicii Docker Swarm

Atunci când vorbim de deployment-ul unei aplicații în Docker Swarm, trecem de la noțiunea de container la noțiunea de serviciu. Un serviciu Docker reprezintă o colecție de task-uri (unul sau mai multe), iar un task reprezintă un container. Așadar, un serviciu este format din unul sau mai multe containere identice. Serviciul controlează ciclul de viață al containerelor, încercând întotdeauna să mențină starea containerelor oferite în configurație. Cu alte cuvinte, un serviciu reprezintă un set de containere cu orchestrare.

Mai departe, o stivă de servicii reprezintă mai multe astfel de servicii grupate în același spațiu de nume. Putem vizualiza o stivă de servicii ca fiind o aplicație Docker formată din mai multe servicii. Cel mai facil mod de a defini o stivă de servicii este prin intermediul unui fișier Docker Compose, așa cum am văzut în laboratorul 2. Comportamentul serviciilor dintr-o stivă este similar cu cel al containerelor din Docker Compose, doar că politica de denumire este diferită.

Orice entitate creată într-o stivă (serviciu, volum, rețea, secret) va fi prefixată de NUME-STIVA_.

Docker Swarm are acces la o colecție nouă de opțiuni în cadrul fișierului YAML de Compose, ce vor fi trecute în proprietatea deploy a unui serviciu. Imaginea de mai jos prezintă un fragment de fișier Docker Compose unde se exemplifică o parte din aceste opțiuni noi:

[...]
services:
  web:
    image: myimage
    deploy:
      replicas: 4
      resources:
        limits:
          cpus: "0.2"
          memory: 50M
      restart_policy:
        condition: on-failure
[...]

În fragmentul de fișier YAML de mai sus, se rulează un serviciu numit web care are patru copii. Astfel, vor exista patru containere diferite care rulează imaginea myimage, oricare din ele putând răspunde la cereri pentru serviciul web, în funcție de încărcare. De asemenea, fiecare instanță este limitată la 20% CPU (pe toate core-urile) și 50 MB de RAM. Nu în ultimul rând, un container al serviciului web se restartează imediat ce întâlnește o eroare (scopul final fiind ca, la orice moment de timp, să existe 4 copii ale containerului în rețea).

Rețele Swarm

Spre deosebire de Docker clasic și Compose, rețelele create în Swarm nu mai sunt de tip bridge, ci de tip overlay. O rețea de tip overlay este o rețea care se întinde peste toate nodurile dintr-un swarm. Din acest motiv, porturile publice expuse vor fi unice per rețea. Așadar, nu pot fi expuse două porturi 3000 din două servicii diferite care se conectează la aceeași rețea overlay.

Docker Swarm realizează balansare a încărcării la nivelul rețelei.

Un serviciu la care s-a făcut deploy pe un anumit port totdeauna va avea acel port rezervat, indiferent pe ce nod rulează de fapt containerul sau containerele sale. Diagrama de mai jos (preluată din documentația oficială) prezintă o situație în care avem un serviciu numit my-web publicat pe portul 8080 într-un cluster cu trei noduri. Se poate observa că, dacă ne conectăm pe portul 8080 de pe oricare adresă IP de nod din cluster, vom fi redirecționați către un container care rulează serviciul specific portului extern 8080, indiferent de nodul pe care rulează.

Secrete Swarm

Secretele din Swarm trebuie create înainte de a fi rulată configurația. Putem folosi următoarea comandă:

$ docker secret create mysecret file.txt
 
fm49ig0i8x9pdq0xxa8wdchoy

Putem lista secretele existente astfel:

$ docker secret ls
 
ID                          NAME       DRIVER    CREATED         UPDATED
fm49ig0i8x9pdq0xxa8wdchoy   mysecret             3 seconds ago   3 seconds ago

Secretele din Swarm sunt criptate în Raft și deci este recomandat să fie folosite în producție.

Diferențe între Docker Swarm și Docker Compose

Exista câteva diferențe între Swarm și Compose la nivelul fișierelor declarative YAML:

  • deoarece Swarm rulează servicii în rețea, nu poate exista cuvântul-cheie build; serviciile trebuie obligatoriu să fie rulate pe baza unor imagini deja existente într-un registru
  • stivele de servicii nu accepta fișiere .env (spre deosebire de Docker Compose)
  • Docker Compose rulează containere în mod single-host, pe când Docker Swarm orchestrează servicii în mod multi-host.

Pornirea unei stive de servicii în Docker Swarm

Odată ce swarm-ul Docker a fost creat și inițializat, comanda prin care se face deployment la o stivă de servicii este următoarea (unde configurația se găsește în fișierul my_stack.yml, iar numele stivei va fi lab3):

$ docker stack deploy -c my_stack.yml lab3

Dacă folosim un registru diferit de Docker (precum GitLab Container Registry), este nevoie să fim autentificați și să adăugăm opțiunea -–with-registry-auth atunci când lansăm o stivă de servicii.

Odată ce o stivă de servicii a fost pornită, putem să îi vedem statusul prin următoarea comandă:

$ docker stack ps lab3                                                                                                      
 
ID             NAME                   IMAGE                                   NODE      DESIRED STATE    CURRENT STATE           ERROR               PORTS
cuktma92gm62   lab3_adminer.1         adminer:latest                          myvm2     Running          Running 9 minutes ago                       
njak2qzaobtt   lab3_db.1              postgres:12                             myvm1     Running          Running 8 minutes ago                       
m811buil7e63   lab3_io-service.1      mobylab/cc-laborator3-io:latest         myvm1     Running          Running 9 minutes ago                       
jnfw37e34kz3   lab3_books-service.1   mobylab/cc-laborator3-books:latest      myvm1     Running          Running 9 minutes ago                       
pzlzkgsxxc00   lab3_gateway.1         mobylab/cc-laborator3-gateway:latest    myvm2     Running          Running 9 minutes ago                       
kpaahb931rbq   lab3_io-service.2      mobylab/cc-laborator3-io:latest         myvm1     Running          Running 9 minutes ago                       
num87yijgxrg   lab3_books-service.2   mobylab/cc-laborator3-books:latest      myvm2     Running          Running 9 minutes ago                       
d9m63k9h7ium   lab3_gateway.2         mobylab/cc-laborator3-gateway:latest    myvm1     Running          Running 9 minutes ago                       
lkmy60wpy0gv   lab3_io-service.3      mobylab/cc-laborator3-io:latest         myvm2     Running          Running 9 minutes ago                       
fy21iizn0reb   lab3_gateway.3         mobylab/cc-laborator3-gateway:latest    myvm2     Running          Running 9 minutes ago                       

De asemenea, putem vedea lista de stive pornite astfel:

$ docker stack ls                                                                                                           
 
NAME      SERVICES      ORCHESTRATOR
lab3      5             Swarm

Putem vedea lista de servicii (din toate stivele pornite) astfel:

$ docker service ls                                                                                                         
 
ID               NAME                 MODE           REPLICAS               IMAGE                                  PORTS
dekzzyais8g7     lab3_adminer         replicated     1/1                    adminer:latest                         *:8080->8080/tcp
74y84hvq4irn     lab3_books-service   replicated     2/2 (max 1 per node)   mobylab/cc-laborator3-books:latest     
ns9mxet1rkx5     lab3_db              replicated     1/1                    postgres:12                             
dh3sv3q74fy6     lab3_gateway         replicated     3/3 (max 2 per node)   mobylab/cc-laborator3-gateway:latest   *:3000->80/tcp
ru0rd7g2ypu8     lab3_io-service      replicated     3/3 (max 2 per node)   mobylab/cc-laborator3-io:latest

Deployment-ul unui cluster Docker

În cadrul laboratoarelor de Cloud Computing, avem două variante principale cu ajutorul cărora putem să ne creăm un cluster Docker format din mai multe mașini: Docker Machine sau Play with Docker.

Docker Machine

Docker Machine este un utilitar care permite rularea Docker Engine pe gazde virtuale (atât local, cât și în cloud, la provideri ca AWS, Azure sau DigitalOcean), precum și gestiunea facilă a acestora din terminal. Docker Machine trebuie instalat separat de Docker, conform documentației oficiale.

În Docker Machine, putem folosi drivere pentru a crea noduri virtuale configurate pentru a rula Docker. Exista drivere pentru a crea noduri direct în cloud, dar și drivere pentru a crea mașini virtuale pe gazda locală. În exemplul de mai jos, se creează un nod virtual local folosind driverul pentru VirtualBox:

$ docker-machine create --driver virtualbox myvm1

Running pre-create checks...
Creating machine...
(myvm1) Copying /home/radu/.docker/machine/cache/boot2docker.iso to /home/radu/.docker/machine/machines/myvm1/boot2docker.iso...
(myvm1) Creating VirtualBox VM...
(myvm1) Creating SSH key...
(myvm1) Starting the VM...
(myvm1) Check network to re-create if needed...
(myvm1) Waiting for an IP...
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with boot2docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env myvm1

Putem verifica rularea corectă a comenzilor și alte informații utile despre starea nodurilor virtuale astfel:

$ docker-machine ls
NAME    ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER        ERRORS
myvm1   -        virtualbox   Running   tcp://192.168.99.100:2376           v17.09.0-ce   

Prin comanda docker-machine ssh, putem trimite comenzi prin SSH către un nod virtual Docker pe care îl specificăm prin nume, așa cum se poate observa mai jos.

$ docker-machine ssh myvm1 "ls -la"

În mod similar, există și comanda docker-machine scp:

$ docker-machine scp file.txt myvm1:.

De asemenea, există și comenzi pentru oprirea, respectiv ștergerea, nodurilor virtuale create.

$ docker-machine stop myvm1
$ docker-machine rm myvm1

Play with Docker

Play with Docker este un mediu online de învățare Docker ce oferă mașini virtuale pe o durată de 4 ore.

Comenzi utile

Interacțiune servicii

Aceste comenzi pot fi rulate doar de pe noduri manager.

$ docker service create --name <NUME_SERVICIU> <IMAGINE_DOCKER> # creează un serviciu pornind de la o imagine
$ docker service ls                                             # afișează toate serviciile din sistem
$ docker service inspect <NUME_SERVICIU>                        # afișează informații despre un serviciu
$ docker service logs –f <NUME_SERVICIU>                        # afișează log-urile unui serviciu
$ docker service ps <NUME_SERVICIU>                             # afișează task-urile (și statusurile lor) pentru un serviciu
$ docker service update --replicas <N> <NUME_SERVICIU>          # actualizează serviciul, replicând containerele de N ori
$ docker service rm <NUME_SERVICIU>                             # șterge un serviciu

Interacțiune cluster

Aceste comenzi pot fi rulate doar de pe noduri manager.

$ docker node ls                            # afișează nodurile din cluster
$ docker node promote <NUME_NOD>            # promovează nodul din worker în manager
$ docker node demote <NUME_NOD>             # retrogradează nodul din manager în worker
$ docker swarm init [--advertise-addr <IP>] # creează un cluster Docker
$ docker swarm join --token <TOKEN> <IP>    # se alătură unui cluster Docker

Docker Machine

$ docker-machine create [--driver <DRIVER>] <NUME> # creează o mașină virtuală Docker
$ docker-machine start <NUME>                      # pornește o mașină virtuală Docker
$ docker-machine stop <NUME>                       # oprește o mașină virtuală Docker
$ docker-machine rm <NUME>                         # șterge o mașină virtuală Docker
$ docker-machine ls                                # listează toate mașinile virtuale Docker
$ docker-machine ssh <NUME>                        # se conectează prin SSH la o mașină virtuală Docker
$ docker-machine scp <FISIER> <NUME>:<DESTINATIE>  # copiază un fișier pe o mașină virtuală Docker

Exerciții

Descriere aplicație

Pentru exercițiile din acest laborator, vom lucra cu o variantă extinsă a aplicației bibliotecă din laboratorul 2. Astfel, codul (care se găsește pe repository-ul oficial) este format din trei mici microservicii scrise în NodeJS și un script de inițializare pentru o bază de date PostgreSQL. Cele trei microservicii sunt:

  • ApiGateway - mediază accesul dinspre lumea exterioară și redirecționează cererile HTTP către serviciul de cărți
  • Books - se ocupă de partea de „business logic” ce ține de cărți și apelează serviciul IO pentru date
  • IO - gestionează datele sistemului și realizează comunicația cu baza de date.

Scop exerciții

Scopul exercițiilor este ca, plecând de la codul propus, să realizați o stivă de servicii care să funcționeze corect. Funcționalitatea este reprezentată de trei acțiuni:

  • adăugarea unei cărți în bibliotecă
  • vizualizarea informațiilor despre toate cărțile din bibliotecă
  • vizualizarea detaliilor unei cărți.

Pe scurt, va trebui să scrieți unul sau mai multe fișier Docker Compose pe care să le folosiți pentru a rula configurația sub forma unei stive de servicii Docker Swarm. Stiva creată va fi formată din 5 servicii:

  • gateway - puteți să creați imaginea pe baza surselor din repository, sau puteți folosi imaginea mobylab/cc-laborator3-gateway
  • books-service - puteți să creați imaginea pe baza surselor din repository, sau puteți folosi imaginea mobylab/cc-laborator3-books
  • io-service - puteți să creați imaginea pe baza surselor din repository, sau puteți folosi imaginea mobylab/cc-laborator3-io
  • db - folosiți imaginea postgres:12
  • adminer - folosiți imaginea adminer.

Variabile de mediu

Variabilele de mediu ce vor trebui puse in fișierul Docker Compose sunt următoarele:

  • serviciul gateway:
    • BOOKS_SERVICE_API_ROUTE: books-service/api
    • NODE_ENV: development
  • serviciul books-service:
    • IO_SERVICE_API_ROUTE: io-service/api
    • NODE_ENV: development
  • serviciul io-service:
    • PGUSER: admin
    • PGPASSWORD: admin
    • PGHOST: db
    • PGPORT: 5432
    • PGDATABASE: books
    • NODE_ENV: development
  • serviciul db:
    • POSTGRES_DB: books
    • POSTGRES_USER: admin
    • POSTGRES_PASSWORD: admin.

Rețele

Pentru a izola accesul serviciilor asupra clusterului, sunt necesare mai multe rețele, astfel:

  • serviciul gateway va comunica doar cu serviciul books-service
  • serviciul books-service va comunica atât cu serviciul de gateway, cât și cu io-service
  • serviciul io-service va comunica atât cu serviciul books-service, cât și cu serviciul db
  • serviciul db va comunica atât cu serviciul io-service, cât și cu adminer.

Volume și bind mounts

Configurația de volume și bind mounts este la fel ca cea de la exercițiile din laboratorul 2. Astfel, serviciul de bază de date are nevoie de un volum pentru persistență și de un bind mount pentru a se injecta scriptul de inițializare.

Porturi publice

Singurele servicii accesibile din exterior vor fi gateway și adminer. Pentru gateway, trebuie expus portul intern 80, peste care se mapează alt port (de exemplu, 3000), iar pentru adminer trebuie expus portul intern 8080 (peste care se poate de asemenea mapa alt port extern).

Rute HTTP

Dacă reușiți să rulați configurația, puteți să o testați folosind cURL sau Postman pe următoarele rute:

  • POST localhost:PORT_PUBLIC/api/books cu corpul JSON {"title": "Harry Potter and the Prisoner of Azkaban", "author": "J.K. Rowling", "genre": "Fantasy"}
  • GET localhost:PORT_PUBLIC/api/books
  • GET localhost:PORT_PUBLIC/api/books/<ID_CARTE>.

Pentru a adăuga un corp JSON unei cereri POST in Postman, selectați Body → Raw → JSON și scrieți JSON-ul aferent.

Secrete

Opțional, puteți adăuga secrete aplicației, pentru numele de utilizator și parola necesare logării în baza de date. Astfel, serviciile care vor avea nevoie de secrete sunt io-service și db. În cele două servicii, va trebui să sufixați variabilele de mediu destinate numelui de utilizator și parolei cu _FILE și să puneți valorile acestor variabile de mediu în funcție de documentație:

  • pentru io-service, la variabilele de mediu pentru numele de utilizator și parolă se pune doar numele secretului iar NODE_ENV se setează la staging
  • pentru serviciul db, la variabilele de mediu se pune toată calea secretului (/run/secrets/nume-secret).

Enunțuri

  1. Scrieți un fișier Docker Compose care sa folosească un build local și rulați pe mașina voastră
  2. Construiți imaginile și urcați-le pe Docker Hub sau orice alt registru
  3. Scrieți un fișier Docker Compose care să folosească imaginile din registru și rulați pe mașina voastră
  4. Adăugați elemente de Docker Swarm la serviciile din fișierul Docker Compose (la proprietatea deploy) și rulați stiva de servicii într-un cluster (folosind Play with Docker sau Docker Machine)
  5. Modificați fișierul Docker Compose ca să utilizați secrete externe pentru numele de utilizator și parolă în containerele de IO și bază de date.
cc/laboratoare/03.1634911985.txt.gz · Last modified: 2021/10/22 17:13 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