pentru

Cu toții avem limbile și cadrele noastre preferate, iar Node.js este cel mai bun lucru pentru mine. Am rulat Node.js în Docker încă din primele zile pentru aplicații critice pentru misiune. Am misiunea de a educa pe toată lumea cu privire la modul de a profita la maximum de acest cadru și instrumentele sale precum npm, Yarn și nodemon cu Docker.

Există o mulțime de informații despre utilizarea Node.js cu Docker, dar atât de mult este depășit de ani, și sunt aici pentru a vă ajuta să vă optimizați setările pentru Node.js 10+ și Docker 18.09+. Dacă preferați să urmăriți discuția mea despre DockerCon 2019 care acoperă aceste subiecte și multe altele, consultați-o pe YouTube.

Să parcurgem 4 pași pentru a face ca containerele dvs. Node.js să cânte! Voi include câteva „Prea mult; Nu am citit ”pentru cei care au nevoie de el.

Rămâneți cu distribuția actuală de bază

TL; DR: Dacă migrați aplicațiile Node.js în containere, utilizați imaginea de bază a sistemului de operare gazdă pe care îl aveți în producție astăzi. După aceea, imaginea mea de bază preferată este nodul oficial: ediții subțiri, mai degrabă decât nodul: alpin, care este încă bun, dar, de obicei, mai multă muncă de implementat și vine cu limitări.

Una dintre primele întrebări pe care cineva le pune atunci când pune o aplicație Node.js în Docker este „Din ce imagine de bază ar trebui să pornesc fișierul Docker Node.js?”

subțire și alpin sunt destul de mici decât imaginea implicită Există mai mulți factori care influențează acest lucru, dar nu faceți din „dimensiunea imaginii” o prioritate, cu excepția cazului în care aveți de-a face cu IoT sau dispozitive încorporate în care fiecare MB contează. În ultimii ani, imaginea subțire a scăzut ca dimensiune la 150 MB și funcționează cel mai bine în cel mai larg set de scenarii. Alpine este o distribuție de containere foarte minimă, cu cea mai mică imagine a nodului la doar 75 MB. Cu toate acestea, nivelul efortului de a schimba managerii de pachete (apt pentru apk), de a trata cazurile marginale și de a rezolva limitele de scanare a securității mă face să renunț la recomandarea nodului: alpin pentru majoritatea cazurilor de utilizare.

Când adoptați tehnologia containerelor, ca orice altceva, doriți să faceți tot ce puteți pentru a reduce rata de schimbare. Atât de multe instrumente și procese noi vin împreună cu containerele. Alegerea imaginii de bază dev-urile și operațiunile dvs. sunt cele mai utilizate pentru a avea multe beneficii neașteptate, deci încercați să rămâneți cu el atunci când are sens, chiar dacă acest lucru înseamnă să creați o imagine personalizată pentru CentOS, Ubuntu etc.

Tratarea modulelor nodului

TL; DR: Nu trebuie să mutați node_modules în containerele dvs., atâta timp cât respectați câteva reguli pentru o dezvoltare locală adecvată. O a doua opțiune este să mutați mode_modules în sus într-un director în fișierul Docker, să vă configurați containerul corect și va oferi cea mai flexibilă opțiune, dar este posibil să nu funcționeze cu fiecare cadru npm.

Acum suntem cu toții obișnuiți cu o lume în care nu scriem tot codul pe care îl rulăm într-o aplicație și asta înseamnă să ne ocupăm de dependențele cadrului aplicației. O întrebare comună este cum să gestionăm aceste dependențe de cod din containere atunci când acestea sunt un subdirector al aplicației noastre. Bind-mount-urile locale pentru dezvoltare vă pot afecta aplicația în mod diferit dacă aceste dependențe au fost proiectate să ruleze pe sistemul dvs. de operare gazdă și nu pe sistemul de operare container.

Nucleul acestei probleme pentru Node.js este că node_modules poate conține binare compilate pentru sistemul de operare gazdă și, dacă este diferit de sistemul de operare al containerului, veți primi erori încercând să rulați aplicația atunci când o legați-montând din gazdă pentru dezvoltare. Rețineți că, dacă sunteți un dezvoltator Linux pur și vă dezvoltați pe Linux x64 pentru Linux x64, această problemă legată de montare nu este de obicei o problemă.

Pentru Node.js vă ofer două abordări, care vin cu propriile avantaje și limitări:

Soluția A: Păstrați-l simplu

Nu mutați node_modules. Va rămâne în continuare în subdirectorul implicit al aplicației dvs. în container, dar acest lucru înseamnă că trebuie să împiedicați folosirea node_module create pe gazda dvs. în container în timpul dezvoltării.

Aceasta este metoda mea preferată atunci când fac dezvoltare pure-Docker. Funcționează excelent cu câteva reguli pe care trebuie să le respectați pentru dezvoltarea locală:

  1. Dezvoltați numai prin container. De ce? Practic, nu doriți să amestecați node_modules de pe gazda dvs. cu node_modules în container. Pe macOS și Windows, Docker Desktop leagă codul dvs. de-a lungul barierei sistemului de operare și acest lucru poate provoca probleme cu binarele pe care le-ați instalat cu npm pentru sistemul de operare gazdă, care nu pot fi rulate în containerul sistemului de operare.
  2. Rulați toate comenzile dvs. npm prin docker-compose. Aceasta înseamnă că instalarea inițială a NPM pentru proiectul dvs. ar trebui acum să fie instalată docker-compose run npm install .

Soluția B: Mutați modulele container și ascundeți modulele gazdă

Mutați node_modules pe calea fișierului în fișierul Docker, astfel încât să puteți dezvolta Node.js în interiorul și în afara containerului, iar dependențele nu vor intra în conflict, pe care le comutați între dezvoltarea nativă a gazdei și dezvoltarea bazată pe Docker.

Deoarece Node.js este conceput pentru a rula pe mai multe sisteme de operare și arhitecți, este posibil să nu doriți să vă dezvoltați întotdeauna în containere. Dacă doriți flexibilitatea pentru a dezvolta/rula uneori aplicația dvs. Node.js direct pe gazdă și apoi alteori o rotiți într-un container local, atunci Soluția B este blocajul dvs.
În acest caz, aveți nevoie de un node_modules pe gazdă care este construit pentru sistemul de operare respectiv și de un node_modules diferit în containerul pentru Linux.

Liniile de bază de care veți avea nevoie pentru a muta node_modules în sus pe cale Reguli pentru această soluție includ:

  1. Mutați node_modules într-un director în imaginea containerului. Node.js caută întotdeauna un node_modules ca subdirector, dar dacă lipsește, va merge pe calea directorului până când va găsi unul. Exemplu de a face acest lucru într-un fișier Docker aici.
  2. Pentru a preveni subdirectorul gazdă node_modules să apară în container, utilizați o soluție pe care o numesc „montare de legare goală” pentru a împiedica folosirea vreodată a serverului node_modules în container. În compunerea dvs. YAML ar arăta așa.
  3. Acest lucru funcționează cu majoritatea codului Node.js, dar unele cadre și proiecte mai mari par să codeze greu în ipoteza că node_modules este un subdirector, care va exclude această soluție pentru dvs.
Pentru ambele soluții, amintiți-vă întotdeauna să adăugați node_modules la fișierul dvs. .dockerignore (aceeași sintaxă ca și .gitignore), astfel încât să nu vă construiți niciodată accidental imaginile cu module de la gazdă. Întotdeauna doriți ca versiunile dvs. să ruleze o instalare npm interior construirea imaginii.

Utilizați Node User, Go Least Privilege

Toate imaginile oficiale Node.js au un utilizator Linux adăugat în imaginea din amonte numită nod. Acest utilizator nu este utilizat în mod implicit, ceea ce înseamnă că aplicația dvs. Node.js va rula în mod implicit ca root în container. Acesta nu este cel mai rău lucru, deoarece este încă izolat de acel container, dar ar trebui să activați toate proiectele în care nu aveți nevoie de nod pentru a rula ca root. Doar adăugați o nouă linie în nodul Dockerfile: USER

Iată câteva reguli de utilizare:

  1. Locația în fișierul Docker contează. Adăugați USER după comenzile apt/yum/apk și, de obicei, înainte de comenzile de instalare npm.
  2. Nu afectează toate comenzile, cum ar fi COPIA, care are propria sintaxă pentru controlul proprietarului fișierelor în care copiați.
  3. Puteți reveni oricând la rădăcina USER dacă aveți nevoie. În fișierele Docker mai complexe, acest lucru va fi necesar, cum ar fi exemplul meu în mai multe etape, care include teste și scanări de securitate în timpul etapelor opționale.
  4. Permisiunile pot deveni dificile în timpul dezvoltării, deoarece acum veți face lucruri în container ca utilizator non-root în mod implicit. Modul de a rezolva adesea acest lucru este să faci lucruri precum instalarea npm spunându-i lui Docker că vrei să rulezi acele comenzi unice ca root: docker-compose run -u root npm install


Nu utilizați manageri de proces în producție

TL; DR: Cu excepția dezvoltării locale, nu înfășurați comenzile de pornire ale nodului cu nimic. Nu utilizați npm, nodemon etc. Faceți ca Dockerfile CMD să fie ceva de genul [„nod”, „file-to-start.js”] și veți avea un timp mai ușor de gestionat și înlocuit containerele.

Nodemon și alți „observatori de fișiere” sunt necesari în dezvoltare, dar un mare câștig pentru adoptarea Docker în aplicațiile dvs. Node.js este că Docker preia sarcina a ceea ce obișnuiam să folosim pm2, nodemon, forever și systemd pentru servere.

Docker, Swarm și Kubernetes vor face treaba de a rula verificări de sănătate și de a reporni sau de a recrea containerul dacă nu reușește. Acum este și treaba orchestratorilor să mărească numărul de replici ale aplicațiilor noastre, pe care le-am folosit pentru a folosi instrumente precum pm2 și pentru totdeauna pentru. Amintiți-vă, Node.js este încă cu un singur fir în majoritatea cazurilor, deci, chiar și pe un singur server, probabil că doriți să faceți mai multe replici de containere pentru a profita de mai multe CPU.

Exemplul meu repo vă arată cum să utilizați nodul direct în fișierul Docker și apoi pentru dezvoltarea locală, fie construiți folosiți o etapă diferită a imaginii cu docker build --target, fie înlocuiți CMD în compunerea dvs. YAML.

Porniți nodul direct în fișierele Docker

TL; DR De asemenea, nu vă recomand să utilizați npm pentru a vă lansa aplicațiile în fișierul Docker. Lasă-mă să explic.

Vă recomand să apelați direct binarul nodului, în mare parte din cauza „Problemei PID 1”, unde veți găsi oarecare confuzie și dezinformare online despre cum să faceți acest lucru în aplicațiile Node.js. Pentru a elimina confuzia în blogosferă, nu este nevoie întotdeauna de un instrument „init” pentru a sta între Docker și Node.js și probabil că ar trebui să petreceți mai mult timp gândindu-vă la modul în care aplicația dvs. se oprește cu grație..

Node.js acceptă și transmite semnale precum SIGINT și SIGTERM din sistemul de operare, ceea ce este important pentru oprirea corectă a aplicației. Node.js lasă la latitudinea aplicației dvs. să decidă cum să gestioneze aceste semnale, ceea ce înseamnă că dacă nu scrieți cod sau nu utilizați un modul pentru a le gestiona, aplicația dvs. nu se va închide cu grație. Acesta va ignora aceste semnale și apoi va fi ucis de Docker sau Kubernetes după o perioadă de expirare (Docker va fi implicit la 10 secunde, Kubernetes la 30 de secunde.) Vă va păsa mult mai mult de acest lucru odată ce aveți o aplicație HTTP de producție pe care trebuie să vă asigurați că nu renunță doar la conexiuni atunci când doriți să vă actualizați aplicațiile.

Utilizarea altor aplicații pentru a porni Node.js pentru dvs., cum ar fi npm, de exemplu, rup adesea această semnalizare. npm nu va transmite aceste semnale aplicației dvs., deci este mai bine să nu o lăsați în afara Dockerfiles ENTRYPOINT și CMD. Acest lucru are, de asemenea, avantajul de a avea o funcție binară mai mică în container. Un alt bonus este că vă permite să vedeți în fișierul Docker exact ce va face aplicația dvs. atunci când containerul este lansat, mai degrabă trebuie să verificați și package.json pentru comanda de pornire adevărată.

Pentru cei care știu despre opțiunile init, cum ar fi docker run --init sau utilizarea tini în fișierul Dockerfile, acestea sunt opțiuni bune de rezervă atunci când nu puteți schimba codul aplicației, dar este o soluție mult mai bună să scrieți cod pentru a gestiona tratarea corectă a semnalului pentru închideri grațioase. Două exemple sunt câteva coduri de tip boilerplate pe care le am aici și care privesc module precum stopable.

Asta e tot?

Nu. Acestea sunt preocupări cu care se ocupă aproape fiecare echipă Node.js și există o mulțime de alte considerații care se potrivesc cu aceasta. Subiecte cum ar fi versiunile în mai multe etape, proxy-urile HTTP, performanța instalării npm, verificările de sănătate, scanarea CVE, înregistrarea containerelor, testarea în timpul construirii imaginilor și configurările de compunere a sistemului de andocare microservice sunt întrebări frecvente pentru clienții și studenții mei Node.js.

Dacă doriți mai multe informații despre aceste subiecte, puteți viziona videoclipul meu de sesiune DockerCon 2019 pe acest subiect sau puteți verifica videoclipurile mele de 8 ore de Docker for Node.js la https://www.bretfisher.com/node

Mulțumesc pentru lectură. Puteți să mă contactați pe Twitter, să primiți buletinul informativ săptămânal DevOps și Docker, să vă abonați la videoclipurile săptămânale de pe YouTube și la Live Show și să consultați celelalte resurse și cursuri ale mele Docker.