03. [20p] Docker Networking

In cazul rulării unui container Docker există necesitatea stabilirii comunicației către exterior prin rețele virtuale sau prin configurații particulare de rețea. Avantajul major îl constituie faptul că multe din aspectele anevoioase de configurare sunt abstractizate, fiind reduse la un număr mic de obiecte predefinite ce pot fi utilizate pentru scenarii complexe. O parte din terminologia aferentă Docker networking se poate găsi în tabelul următor, unde vom defini Driverele Docker - acele obiecte de rețea predefinite cu care vine Docker Engine-ul și care facilitează comunicarea la nivelul containerelor:

Driver network Descriere
bridge tip de driver utilizat în mod implicit, util pentru aplicații care stau în containere individuale și care au nevoie de conectivitate între ele sau către exterior
host disponibil doar în SWARM mode, tip de network care partajează rețeaua cu mașina gazdă
overlay tip de driver care permite comunicarea între containere aflate pe noduri Docker diferite din rețele diferite - comunicare la nivel 2 utilizând VXLAN
macvlan tip de network care alocă containerelor adrese MAC și permit atașarea container-ului în aceiași rețea cu sistemul gazdă
none tip de driver care dezactivează conectivitatea la rețea

Referințe:

În continuare vom începe să construim aplicația de sharing de imagini, realizând în paralel și comunicația între serviciile componente, în concordanță cu modelul slab cuplat ales.

Expunerea către exterior a serviciilor dintr-un container

Să încercăm să lansăm serviciul de frontend. Întrucât acesta va fi expus către exterior, pentru a fi accesibil din browser, vom publica portul 80 al containerului:

student@aldebaran:~$ docker run -d --name=rl-frontend  \
   -e BACKEND_SERVER=rl-backend \
   -p 8080:80 \
   rlrules/docker-lab-frontend

Parametrul -p (publish) cu valoarea 8080:80 (<port sistem gazdă>:<port container>) expune/alocă pe mașina gazdă portul TCP 8080, urmând ca traficul orientat către acest port să fie trimis, mai departe, către socket-ul containerului expus pe portul TCP 80.

Unde ați mai văzut un astfel de setup? Cum se numește acest procedeu?

Prin abstractizarea implementată prin flag-ul publish, Docker crează, în backstage, o regulă iptables de DNAT (port-forwarding) care redirecționează traficul venit pe portul TCP/8080 al mașinii gazdă către portul TCP/80 de pe container. În plus, o altă regulă definită prin utilizarea publish, permite traficul destinat interfeței virtuale docker0 (network de tip bridge, creat implicit la instalarea Docker, util pentru a conecta toate containerele care nu au o configurație de rețea particulară).

student@aldebaran:~$ sudo iptables -L -nv -t filter
student@aldebaran:~$ sudo iptables -L -nv -t nat

Care sunt regulile de iptables care au fost generate de parametrul publish?

student@aldebaran:~$ sudo iptables -L -nv -t filter | grep -i 80 -A 3 -B 3
student@aldebaran:~$ sudo iptables -L -nv -t nat | grep -i 8080 -A 3 -B 3

Un bridge network, așa cum se auto-definește, este un echipament (appliance) virtual care permite comunicarea la nivel 2 a entităților de rețea conectate la acesta. În sensul terminologiei Docker, un bridge network adaugă suplimentar capabilității anterior definite, o configurație de Default Gateway la nivelul interfeței virtuale a containerului și implementarea de Masquerade - Network Address Translation/Port Address Translation (NAT/PAT). În acest fel, containerul va putea accesa, utilizând conectivitatea sistemului gazdă, resurse externe din afara rețelei sau Internet.

Dacă rulați pe platforma OpenStack a facultății, rețeaua 10.9.X.Y nu este accesibilă din Internet, și, din păcate, nici de pe Wifi-ul facultății :( Așadar, va trebui să facem proxy prin FEP:

# porniți terminal nou de pe stația locală:
ssh -D8080 <username>@fep.grid.pub.ro

Apoi configurați proxy SOCKS din browserul vostru preferat la adresa localhost:8080 (atenție: nu folosiți proxy HTTP, protocolul este obligatoriu SOCKS). Căutați pe Google pentru modul de configurare al sistemului vostru + browser (e.g., Windows 10). Apoi veți putea accesa containerul prin adresa IP a acestuia (e.g., http://10.9.X.Y:8080) NU ÎNCHIDEȚI TERMINALUL! Cât timp conexiunile SSH rămân deschise, veți putea accesa din browserul vostru pagina http://localhost:8080 care va duce către portul din container (prin proxy pe fep).

Dacă folosiți Play with Docker, pentru a accesa portul 8080 pentru serviciul de frontend, pur și simplu dati click pe 8080

Service Discovery

Am observat că serviciul de frontend funcționează (http://<IP mașină gazdă>:8080), însă pare că nu reușește să încarce conținut. Acest comportament se petrece deoarece aplicația încearcă să consume date de la un serviciu de backend (componentă ce înmagazinează toate funcțiile de procesare a informației) și așteaptă să îi devină disponibil. Este foarte important ca în design-ul unei aplicații, componenta de frontend să ofere doar interfața grafică (de interacțiune) și nimic mai mult, urmând ca procesele de manipulare a datelor să se petreacă în backend.

Ca în orice configurație loosely coupled (slab-cuplată), trebuie să ne asigurăm de faptul că elementele componente ale unei soluții pot comunica între ele. Pentru a obține acest rezultat, va trebui să creăm un Docker bridge în care să conectăm containerele aplicației. Pentru a crește un pic complexitatea din perspectiva izolării traficului, vom crea două rețele: o rețea între frontend și backend și o rețea între backend și baza de date:

student@aldebaran:~$ docker network create --driver bridge appnet
student@aldebaran:~$ docker network create --driver bridge dbnet

În continuare vom afișa cele două entități nou create:

student@aldebaran:~$ docker network ls --filter name=net

Întrucât rețelele sunt deja construite, vom lansa cele 2 servicii suplimentare din componența aplicației - backend și baza de date:

student@aldebaran:~$ docker run -d --name=rl-database \
   -e MYSQL_DATABASE=rl-database \
   -e MYSQL_USER=rl-user \
   -e MYSQL_PASSWORD=rl-specialpassword \
   -e MYSQL_ROOT_PASSWORD=root \
   -e TZ=Europe/Bucharest \
   --network=dbnet \
   mysql
[...]
student@aldebaran:~$ docker run -d --name=rl-backend  \
   -e DB_SERVER=rl-database \
   --network=dbnet \
   rlrules/docker-lab-backend
[...]
student@aldebaran:~$ docker network connect appnet rl-backend
[...]
student@aldebaran:~$ docker network connect appnet rl-frontend
[...]

Va trebui să așteptăm câteva secunde pentru inițializarea bazei de date. Să testăm funcționalitatea platformei accesând aplicația din Browser (http://<IP mașină gazdă>:8080).

Dacă Pacman a dispărut de pe ecran, iar în locul lui a apărut o colecție impresionantă de meme-uri, înseamnă că platforma funcționează! În cazul în care întâmpinați probleme:

student@aldebaran:~$ docker restart rl-frontend

Dar cum au reușit serviciile să se conecteze între ele?

În lumea Docker conectivitatea cu alte servicii se face pe bază de nume. Să presupunem că vrem să ne conectăm la baza de date. În conectivitatea clasică, de cele mai multe ori, trebuie specificată adresa IP a bazei de date, portul dacă nu este cel standard soluției de baze de date, user-ul bazei de date și parola. În Docker, simplul re-deployment al unui container nu asigură persistența adresei IP alocat interfeței virtuale și deci, nu putem utiliza adresarea IP pentru a asigura conectivitatea dintre două servicii. Pentru a abstractiza elementul de configurare, Docker propune un model de Service Discovery bazat pe DNS. În acest sens, un identificator care rămâne nemodificat între două rulări succesive ale aceluiași container, reprezintă numele pe care îl atribuim containerului (în exemplul nostru: --name rl-frontend, --name rl-backend, --name rl-database). În cele mai multe cazuri, aplicațiile împachetate în container au hardcodate astfel de nume pentru a stabili conectivitatea, și totodată un set implicit de credențiale. În setup-uri mai inteligente, coordonatele de conectare între servicii pot fi configurate prin variabile de mediu (parametrul -e) admise la runtime, prin fișiere de configurare partajate cu sistemul gazdă etc.

În continuare vom testa funcționalitatea mecanismului de service discovery. Ne vom conecta la instanța de backend:

student@aldebaran:~$ docker exec -it rl-backend bash

Și vom rula utilitarele clasice de testare a conectivității - ping, telnet și host (pentru validarea rezoluției de nume)

root@rl-backend:~$ ping rl-frontend
[...]
root@rl-backend:~$ apt update && apt install -y host && host rl-frontend
[...]
root@rl-backend:~$ telnet rl-frontend 80 
[...]

Pentru a ieși CTRL + ] sau comanda quit

root@rl-backend:~$ ping rl-database
[...]
root@rl-backend:~$ host rl-database
[...]
exit

Dacă ștergem containerul de frontend și îl reconstruim:

student@aldebaran:~$ docker rm -f rl-frontend
student@aldebaran:~$ docker run -d --name=rl-frontend \
   -e BACKEND_SERVER=rl-backend \
   -p 8080:80 --network appnet \
   rlrules/docker-lab-frontend
student@aldebaran:~$ docker exec -it rl-backend host rl-frontend
student@aldebaran:~$ docker exec -it rl-frontend ip a

Vom vedea că serviciul de DNS se va actualiza cu noua adresă IP a containerului, în cazul în care aceasta a fost schimbată între cele 2 rulări consecutive. În caz particular, este puțin probabil ca Docker engine să aloce o altă adresă IP.

În continuarea laboratorului, perspectiva va fi mutată pe date și persistența acestora.

rl/labs/virt-docker/contents/03.txt · Last modified: 2023/11/05 10:56 by vlad_iulius.nastase
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