Creșterea implementării software - Un tutorial Docker Swarm
Publicat: 2022-03-11Dacă nu ați locuit într-un container de transport, probabil ați auzit despre containere. Industria a făcut o trecere distinctă de la infrastructura persistentă la cea efemeră, iar containerele sunt pătrate în mijlocul acelei mișcări. Motivul este destul de simplu: în timp ce containerele ajută cu siguranță echipele de dezvoltatori să funcționeze rapid, ele au și mai mult potențial de a schimba complet fața operațiunilor.
Dar cum arată exact asta? Ce se întâmplă când ești gata să faci saltul rulării containerelor la nivel local sau manual pe câteva servere? Într-o lume ideală, vrei să-ți arunci aplicația la un grup de servere și să spui „rulează-l!”
Ei bine, din fericire, acolo suntem astăzi.
În acest articol, vom explora ce este Docker Swarm, împreună cu unele dintre caracteristicile grozave pe care le are de oferit. Apoi vom arunca o privire la cum arată de fapt utilizarea modului Swarm și implementarea într-un roi și vom încheia cu câteva exemple despre cum sunt operațiunile zilnice cu un roi desfășurat. Cu siguranță sunt recomandate cunoștințe de bază despre Docker și containere, dar puteți consulta mai întâi această postare excelentă de blog dacă sunteți nou în domeniul containerelor.
Ce este Docker Swarm?
Înainte de a ne aprofunda în crearea și implementarea în primul nostru roi, este util să avem o idee despre ce este Docker Swarm. Docker în sine există de ani de zile, iar majoritatea oamenilor de astăzi îl consideră un timp de rulare a containerului. În realitate, totuși, Docker este compus din multe piese diferite, toate lucrând împreună. De exemplu, porțiunea de rulare a containerului este gestionată de două componente mai mici, numite runC și containerd. Pe măsură ce Docker a evoluat și a dat înapoi comunității, ei au descoperit că crearea acestor componente mai mici este cea mai bună modalitate de a crește și de a adăuga rapid funcții. Ca atare, avem acum SwarmKit și modul Swarm, care este integrat direct în Docker.
Docker Swarm este un motor de orchestrare a containerelor. La un nivel înalt, este nevoie de mai multe motoare Docker care rulează pe gazde diferite și vă permite să le utilizați împreună. Utilizarea este simplă: declarați aplicațiile ca stive de servicii și lăsați Docker să se ocupe de restul. Serviciile pot fi orice, de la instanțe de aplicație la baze de date sau utilitare precum Redis sau RabbitMQ. Acest lucru ar trebui să sune familiar dacă ați lucrat vreodată cu docker-compose în dezvoltare, deoarece este exact același concept. De fapt, o declarație de stivă este literalmente doar un fișier docker-compose.yml
cu sintaxa versiunii 3.1. Aceasta înseamnă că puteți utiliza o configurație de scriere similară (și, în multe cazuri, identică) pentru dezvoltare și implementare roi, dar sunt puțin înaintea mea aici. Ce se întâmplă când aveți instanțe de Docker în modul Swarm?
Nu cazi de pe pluta
Avem două tipuri de noduri (servere) în lumea Swarm: manageri și lucrători. Este important să rețineți că managerii sunt și lucrători, ei au doar responsabilitatea suplimentară de a menține lucrurile în funcțiune. Fiecare roi începe cu un nod manager desemnat ca lider. De acolo, este doar o chestiune de a rula o singură comandă pentru a adăuga în siguranță noduri la roi.
Swarm este foarte disponibil datorită implementării algoritmului Raft. Nu voi intra în prea multe detalii despre Raft, deoarece există deja un tutorial grozav despre cum funcționează, dar iată ideea generală: nodul lider se verifică în mod constant cu nodurile sale manageriale și își sincronizează stările. Pentru ca o schimbare de stare să fie „acceptată”, nodurile managerului ajung la un consens, ceea ce se întâmplă atunci când majoritatea nodurilor recunosc schimbarea de stare.
Frumusețea acestui lucru este că nodurile de manager se pot lăsa sporadic fără a compromite consensul roiului. Dacă o schimbare de stat ajunge la un consens, știm că este garantat să existe pe majoritatea nodurilor managerului și va persista chiar dacă liderul actual eșuează.
Să presupunem că avem trei noduri de manager numite A, B și C. Desigur, A este liderul nostru neînfricat. Ei bine, într-o zi, o eroare tranzitorie de rețea îl pune pe A offline, lăsând-o pe B și C în pace. N-au auzit de A de mult timp (câteva sute de milisecunde), B și C așteaptă o perioadă de timp generată aleatoriu înainte de a se prezenta la alegeri și de a-l anunța pe celălalt. Desigur, va fi ales primul care va urca la alegeri, în acest caz. În acest exemplu, B devine noul lider și se restabilește cvorumul. Dar apoi, întorsătură: ce se întâmplă când A revine online? Va crede că este încă liderul, nu? Fiecare alegere are un mandat asociat, așa că A a fost de fapt ales în mandatul 1. De îndată ce A revine online și începe să comande B și C, îi vor anunța cu amabilitate că B este liderul mandatului 2 și A va demisiona.
Același proces funcționează la o scară mult mai mare, desigur. Puteți avea mai mult de trei noduri de manager. Totuși, voi adăuga o altă notă rapidă. Fiecare Swarm poate suporta doar un anumit număr de pierderi de manager. Un roi de n noduri manageri poate pierde (n-1)/2
manageri fără a pierde cvorumul. Asta înseamnă că pentru un roi de trei manageri poți pierde unul, pentru cinci poți pierde doi etc. Motivul care stau la baza acestui lucru se întoarce la ideea despre consensul majoritar și este cu siguranță ceva de reținut atunci când mergi la producție.
Programarea sarcinilor și reconcilierea
Până acum, am stabilit că managerii noștri sunt foarte buni să rămână sincronizați. Grozav! Dar ce fac ei de fapt? Îți amintești cum am spus că implementezi un teanc de servicii în Swarm? Când vă declarați serviciile, îi oferiți lui Swarm informații importante despre modul în care doriți să fie executate serviciile dvs. Aceasta include parametri precum câte replici doriți pentru fiecare serviciu, cum ar trebui distribuite replicile, dacă ar trebui să fie rulate numai pe anumite noduri și multe altele.
Odată ce un serviciu este implementat, este sarcina managerilor să se asigure că orice cerințe de implementare pe care le setați continuă să fie îndeplinite. Să presupunem că implementați un serviciu Nginx și specificați că ar trebui să existe trei replici. Managerii vor vedea că nu rulează niciun container și vor distribui uniform cele trei containere în nodurile disponibile.
Ceea ce este și mai tare, totuși, este că, dacă un container ar eșua (sau un întreg nod ar fi deconectat), Swarm va crea automat containere pe nodurile rămase pentru a compensa diferența. Dacă spui că vrei trei containere în funcțiune, vei avea trei containere în funcțiune, în timp ce Swarm se ocupă de toate detaliile esențiale. În plus, și acesta este un mare plus, scalarea în sus sau în jos este la fel de ușoară ca oferirea lui Swarm de o nouă setare de replicare.
Descoperirea serviciului și echilibrarea sarcinii
Vreau să subliniez un detaliu important, dar subtil din ultimul exemplu: dacă Swarm pornește în mod inteligent containerele pe nodurile alese, nu știm neapărat unde vor rula acele containere. Poate sună înfricoșător la început, dar este de fapt una dintre cele mai puternice caracteristici ale lui Swarm.
Continuând același exemplu Nginx, imaginați-vă că i-am spus lui Docker că acele containere ar trebui să expună portul 80. Dacă îndreptați browserul către un nod care rulează acel container pe portul 80, veți vedea conținutul acelui container. Nu e nicio surpriză acolo. Ceea ce poate fi surprinzător este că, dacă trimiteți solicitarea către un nod care nu rulează acel container, veți vedea în continuare același conținut! Ce se intampla aici?
Swarm folosește de fapt o rețea de intrare pentru a trimite cererea dvs. către un nod disponibil care rulează acel container și o echilibrează în același timp. Deci, dacă faceți trei solicitări către același nod, probabil că veți lovi cele trei containere diferite. Atâta timp cât cunoașteți IP-ul unui singur nod din roi, puteți accesa orice rulează în el. În schimb, acest lucru vă permite să îndreptați un echilibrator de sarcină (cum ar fi un ELB) către toate nodurile din roi, fără a fi nevoie să vă faceți griji cu privire la ce rulează unde.
Nu se oprește la conexiunile externe. Serviciile care rulează pe aceeași stivă au o rețea suprapusă care le permite să comunice între ele. În loc să codificați adresele IP în codul dvs., puteți utiliza pur și simplu numele serviciului ca nume de gazdă la care doriți să vă conectați. De exemplu, dacă aplicația dvs. trebuie să comunice cu un serviciu Redis numit „redis”, poate folosi pur și simplu „redis” ca nume de gazdă, iar Swarm își va direcționa cererea către containerul corespunzător. Și pentru că acest lucru funcționează perfect în dezvoltare cu docker-compose și în producție cu Docker Swarm, este un lucru mai puțin de care să vă faceți griji atunci când implementați aplicația.
Actualizări continuă
Dacă sunteți în operațiuni, probabil că ați experimentat un atac de panică atunci când o actualizare de producție merge groaznic de rău. Ar putea fi o actualizare proastă a codului sau chiar o eroare de configurare, dar brusc producția scade! Sunt șanse ca șefului să nu-i pese în nici un caz. Vor ști doar că e vina ta. Ei bine, nu-ți face griji, Swarm te sprijină și pe acesta.
Când actualizați un serviciu, puteți defini câte containere trebuie actualizate simultan și ce ar trebui să se întâmple dacă noile containere încep să eșueze. După un anumit prag, Swarm poate fie să oprească actualizarea, fie (începând cu Docker 17.04) să returneze containerele la imaginea și setările anterioare. Nu-ți face griji că trebuie să-ți aduci șefului o cafea mâine dimineață.
Securitate
În sfârșit, dar nu în ultimul rând, Docker Swarm vine cu funcții de securitate grozave din cutie. Când un nod se alătură roiului, folosește un simbol care nu numai că se verifică pe sine, ci și că se alătură roiului pe care îl crezi. Din acel moment, toată comunicarea între noduri are loc folosind criptarea TLS reciprocă. Această criptare este asigurată și gestionată automat de către Swarm, așa că nu trebuie să vă faceți niciodată griji cu privire la reînnoirea certificatelor și alte probleme tipice de securitate. Și, desigur, dacă doriți să forțați o rotație a tastei, există o comandă pentru asta.
Cea mai recentă versiune de Docker Swarm vine și cu managementul secretelor încorporat. Acest lucru vă permite să implementați în siguranță secrete, cum ar fi cheile și parolele, pentru serviciile care au nevoie de ele și numai pentru serviciile care au nevoie de ele. Când furnizați un serviciu cu un secret, containerele pentru serviciul respectiv vor avea un fișier special montat în sistemul lor de fișiere care include valoarea secretului. Este de la sine înțeles, dar acest lucru este mult mai sigur decât utilizarea variabilelor de mediu, care erau abordarea tradițională.
Scufundarea în Roi
Dacă sunteți ca mine, vă simțiți dornici să săriți și să folosiți toate aceste caracteristici pentru o învârtire! Așa că, fără alte prelungiri, haideți să ne scufundăm!
Exemplu de aplicație Docker Swarm
Am creat o aplicație Flask foarte rudimentară pentru a demonstra puterea și ușurința utilizării Docker Swarm. Aplicația web afișează pur și simplu o pagină care vă spune ce container a servit solicitarea dvs., câte solicitări au fost servite în total și care este parola „secretă” a bazei de date.
Este împărțit în trei servicii: aplicația actuală Flask, un proxy invers Nginx și un depozit de chei Redis. La fiecare solicitare, aplicația incrementează cheia num_requests
în Redis, astfel încât, indiferent de instanța Flask pe care o accesați, veți vedea numărul corect de solicitări reflectate.
Tot codul sursă este disponibil pe GitHub dacă doriți să „vedeți” ce se întâmplă.
Joacă-te cu Docker!
Simțiți-vă liber să vă folosiți propriile servere în timp ce parcurgeți acest tutorial, dar vă recomand cu căldură să utilizați play-with-docker.com dacă doriți doar să intrați. Este un site condus de câțiva dezvoltatori Docker, care vă permite să creați mai multe rețea. noduri care au Docker preinstalat. Vor fi închise după patru ore, dar este suficient pentru acest exemplu!
Crearea unui roi
Bine, iată-ne! Continuați și creați trei instanțe în PWD (play-with-docker) sau porniți trei servere în serviciul VPS (server privat virtual) preferat și instalați motorul Docker pe toate. Rețineți că puteți oricând să creați o imagine și să o reutilizați atunci când adăugați noduri în viitor. Nu există nicio diferență în ceea ce privește software-ul între un nod manager și un nod lucrător, așa că nu trebuie să mențineți două imagini diferite.

Încă se învârte? Nu-ți face griji, voi aștepta. Bine, acum vom crea primul nostru manager și nod lider. În primul rând, inițializați un roi:
docker swarm init --advertise-addr <node ip here>
Înlocuiți <node_ip_here>
cu adresa IP a nodului dvs. Pe PWD, adresa IP este afișată în partea de sus și, dacă utilizați propriul VPS, nu ezitați să utilizați adresa IP privată a serverului, atâta timp cât este accesibilă de la celelalte noduri din rețea.
Acum ai un roi! Totuși, este un roi destul de plictisitor, deoarece are un singur nod. Să mergem mai departe și să adăugăm celelalte noduri. Veți observa că atunci când ați rulat init
, a afișat un mesaj lung care explică cum să utilizați simbolul de alăturare. Nu îl vom folosi pe acesta pentru că ar face ca celelalte noduri să lucreze și ne dorim ca ei să fie manageri. Să obținem simbolul de alăturare pentru un manager rulând acest lucru pe primul nod:
docker swarm join-token manager
Copiați comanda rezultată și rulați-o pe al doilea și al treilea nod. Iată, un roi cu trei noduri! Să verificăm că toate nodurile noastre există cu adevărat. Comanda docker node ls
va lista toate nodurile din roiul nostru. Ar trebui să vezi așa ceva:
$ docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS su1bgh1uxqsikf1129tjhg5r8 * node1 Ready Active Leader t1tgnq38wb0cehsry2pdku10h node3 Ready Active Reachable wxie5wf65akdug7sfr9uuleui node2 Ready Active Reachable
Observați cum primul nostru nod are un asterisc lângă ID. Acest lucru ne spune pur și simplu că este nodul la care suntem conectați în prezent. De asemenea, putem vedea că acest nod este în prezent Leader, iar celelalte noduri sunt accesibile dacă s-ar întâmpla ceva cu el.
Luați un moment pentru a aprecia cât de ușor a fost și haideți să implementăm prima noastră aplicație!
Trimite-l!
Tocmai în momentul de față, echipa de dezvoltare a afacerii a promis unui client că noua lor aplicație va fi implementată și gata într-o oră! Tipic, știu. Dar nu vă temeți, nu vom avea nevoie de atât de mult timp, deoarece a fost construit folosind Docker! Dezvoltatorii au fost destul de amabili să ne împrumute fișierul docker-compose
:
version: '3.1' services: web: image: lsapan/docker-swarm-demo-web command: gunicorn --bind 0.0.0.0:5000 wsgi:app deploy: replicas: 2 secrets: - db_password nginx: image: lsapan/docker-swarm-demo-nginx ports: - 8000:80 deploy: mode: global redis: image: redis deploy: replicas: 1 secrets: db_password: external: true
O vom descompune într-o clipă, dar încă nu avem timp pentru asta. Să-l punem în aplicare! Continuați și creați un fișier pe primul nod numit docker-compose.yml
și completați-l cu configurația de mai sus. Puteți face asta cu ușurință cu echo "<pasted contents here>" > docker-compose.yml
.
De obicei, am putea implementa acest lucru, dar configurația noastră menționează că folosim un secret numit db_password
, așa că haideți să creăm rapid acel secret:
echo "supersecretpassword" | docker secret create db_password -
Grozav! Acum tot ce trebuie să facem este să îi spunem lui Docker să folosească configurația noastră:
docker stack deploy -c docker-compose.yml demo
Când rulați această comandă, veți vedea Docker creând cele trei servicii pe care le-am definit: web
, nginx
și redis
. Cu toate acestea, pentru că ne-am numit demo-ul stivei, serviciile noastre sunt de fapt denumite demo_web
, demo_nginx
și demo_redis
. Ne putem uita la serviciile care rulează rulând comanda docker service ls
, care ar trebui să arate ceva de genul acesta:
$ docker service ls ID NAME MODE REPLICAS IMAGE PORTS cih6u1t88vx7 demo_web replicated 2/2 lsapan/docker-swarm-demo-web:latest u0p1gd6tykvu demo_nginx global 3/3 lsapan/docker-swarm-demo-nginx:latest *:8000->80/ tcp wa1gz80ker2g demo_redis replicated 1/1 redis:latest
Voila! Docker a descărcat imaginile noastre în nodurile corespunzătoare și a creat containere pentru serviciile noastre. Dacă replicile dvs. nu sunt încă la capacitate maximă, așteptați un moment și verificați din nou. Probabil că Docker încă descarcă imaginile.
A vedea e a crede
Totuși, nu mă crede pe cuvânt (sau pe cuvântul lui Docker). Să încercăm să ne conectăm la aplicația noastră. Configurația noastră de serviciu îi spune lui Docker să expună NGINX pe portul 8000. Dacă sunteți pe PWD, ar trebui să existe un link albastru în partea de sus a paginii care spune „8000”. PWD a detectat automat că avem un serviciu care rulează pe acel port! Faceți clic pe acesta și vă va direcționa către nodul selectat pe portul 8000. Dacă v-ați rulat propriile servere, pur și simplu navigați la unul dintre IP-urile serverelor dvs. de pe portul 8000.
Veți fi întâmpinat cu un ecran frumos stilat, care vă oferă câteva informații de bază:
Notați ce container a servit solicitarea dvs. și apoi reîmprospătați pagina. Sunt șanse să se fi schimbat. Dar de ce? Ei bine, i-am spus lui Docker să creeze două replici ale aplicației noastre Flask și distribuie cereri la ambele instanțe. Tocmai s-a întâmplat să lovești celălalt container a doua oară. Veți observa, de asemenea, că numărul de solicitări a crescut deoarece ambele containere Flask comunică cu singura instanță Redis pe care am specificat-o.
Simțiți-vă liber să încercați să accesați portul 8000 de la orice nod. Veți fi în continuare direcționat corect către aplicație.
Demistificarea magiei
În acest moment, totul funcționează și, sperăm, că ați găsit procesul nedureros! Să aruncăm o privire mai atentă la acel fișier docker-compose.yml
și să vedem ce i-am spus de fapt lui Docker. La un nivel înalt, vedem că am definit trei servicii: web
, nginx
și redis
. La fel ca un fișier de compunere obișnuit, am oferit Docker o imagine de utilizat pentru fiecare serviciu, precum și o comandă de rulat. În cazul nginx
, am specificat, de asemenea, că portul 8000 de pe gazdă ar trebui să se mapeze la portul 80 din container. Toate acestea sunt o sintaxă de compunere standard până acum.
Ceea ce este nou aici sunt cheile de implementare și secrete. Aceste chei sunt ignorate de docker-compose
, deci nu vă vor afecta mediul de dezvoltare, ci sunt folosite de docker stack
. Să ne uităm la serviciul web. Destul de simplu, îi spunem lui Docker că am dori să rulăm două replici aplicația noastră Flask. De asemenea, informăm Docker că serviciul web necesită secretul db_password
. Acesta este ceea ce asigură că containerul va avea un fișier numit /run/secrets/db_password
care conține valoarea secretului.
Trecând în jos la Nginx, putem vedea că modul de implementare este setat la global
. Valoarea implicită (care este utilizată implicit în web) este replicated
, ceea ce înseamnă că vom specifica câte replici dorim. Când specificăm global
, îi spune lui Docker că fiecare nod din roi ar trebui să ruleze exact o instanță a serviciului. Rulați din nou docker service ls
, veți observa că nginx
are trei replici, câte una pentru fiecare nod din roiul nostru.
În cele din urmă, i-am cerut lui Docker să ruleze o singură instanță de Redis undeva în roi. Nu contează unde, deoarece containerele noastre web sunt direcționate automat către acesta atunci când solicită gazda Redis.
Folosind Swarm zi cu zi
Felicitări pentru implementarea primei aplicații într-un Docker Swarm! Să luăm un moment pentru a revizui câteva comenzi comune pe care le veți folosi.
Inspectându-ți roiul
Trebuie să vă verificați serviciile? Încercați docker service ls
și docker service ps <service name>
. Primul vă arată o prezentare generală la nivel înalt a fiecărui serviciu, iar cel de-al doilea vă oferă informații despre fiecare container care rulează pentru serviciul specificat. Acesta este deosebit de util atunci când doriți să vedeți ce noduri rulează un serviciu.
Actualizări continuă
Dar când ești gata să actualizezi o aplicație? Ei bine, lucrul interesant despre docker stack deploy
este că de fapt va aplica actualizări și unei stive existente. Să presupunem că ați trimis o nouă imagine Docker în depozitul dvs. De fapt, puteți rula aceeași comandă de implementare pe care ați folosit-o prima dată și roiul dvs. va descărca și implementa noua imagine.
Desigur, este posibil să nu doriți să actualizați întotdeauna fiecare serviciu din stiva dvs. Putem efectua actualizări și la nivel de serviciu. Să presupunem că am actualizat recent imaginea pentru serviciul meu web. Pot lansa această comandă pentru a actualiza toate containerele mele web:
docker service update \ --image lsapan/docker-swarm-demo-web:latest \ demo_web
Un avantaj suplimentar al acestei comenzi este că va aplica o actualizare continuă dacă ați specificat acest lucru în configurația originală. Și chiar dacă nu ați făcut-o, puteți transmite semnale de actualizare care îi vor instrui să facă o actualizare continuă, astfel:
docker service update \ --image lsapan/docker-swarm-demo-web:latest \ --update-parallelism 1 --update-delay 30s \ demo_web
Aceasta va actualiza câte un container la un moment dat, așteptând 30 de secunde între actualizări.
Scalarea serviciilor în sus sau în jos
Să ai două containere web este grozav, dar știi ce este mai bine? Avand zece! Scalarea serviciilor în sus și în jos într-un roi este la fel de ușor ca:
docker service scale demo_web=10
Rulați acea comandă și verificați rezultatul docker service ps demo_web
. Veți vedea că acum avem zece containere, iar opt dintre ele au fost pornite cu doar o clipă în urmă. Dacă sunteți interesat, puteți, de asemenea, să reveniți la aplicația web și să reîmprospătați pagina de câteva ori pentru a vedea că acum obțineți mai mult decât cele două ID-uri de containere originale.
Eliminarea stivelor și serviciilor
Stivele și serviciile dvs. sunt implementate și scalate, minunat! Dar acum vrei să le iei offline. Acest lucru se poate face cu comanda rm
respectivă. Pentru a elimina stiva noastră demo, rulați comanda:
docker stack rm demo
Sau, dacă preferați să eliminați un singur serviciu, utilizați:
docker service rm demo_web
Noduri de drenare
Vă amintiți când am rulat docker node ls
mai devreme pentru a verifica nodurile din roiul nostru? A furnizat o mulțime de informații despre fiecare nod, inclusiv Disponibilitatea acestuia. În mod implicit, nodurile sunt active , ceea ce înseamnă că sunt un joc corect pentru a rula containere. Cu toate acestea, există momente în care este posibil să fie necesar să deconectați temporar un nod pentru a efectua întreținere. Sigur, ai putea să-l închizi și roiul își va reveni, dar este frumos să-i anunți Moby (balena Docker).
Aici intervine nodurile de drenare. Când marcați un nod ca Drain , Docker Swarm va delega orice container care rulează pe el altor noduri și nu va porni niciun container de pe nod până când nu îi schimbați disponibilitatea înapoi la Active .
Să presupunem că vrem să node1
. Putem rula:
docker node update --availability drain node1
Uşor! Când sunteți gata să-l puneți înapoi la lucru:
docker node update --availability active node1
Încheierea
După cum am văzut, Docker cuplat cu modul Swarm ne permite să implementăm aplicații mai eficient și mai fiabil decât oricând. Merită menționat că Docker Swarm nu este în niciun caz singurul motor de orchestrare a containerelor de acolo. De fapt, este unul dintre cei mai tineri. Kubernetes există de mai mult timp și este cu siguranță folosit în mai multe aplicații de producție. Acestea fiind spuse, Swarm este cel dezvoltat oficial de Docker și lucrează la adăugarea și mai multe funcții în fiecare zi. Indiferent de ce alegeți să utilizați, păstrați containerele!