Cloister → gestionare simplă a clusterului OTP

Practically every successful business application eventually reaches a stage where horizontal scaling is required. In many cases, you can simply launch a new instance to reduce the average load. However, there are less trivial cases where we must ensure that different nodes are aware of each other and distribute the workload carefully.

Cloister → gestionare simplă a clusterului OTP

It just so happened that erlang, which we chose for its pleasant syntax and the hype surrounding it, has top-notch support for distributed systems. In theory, this sounds quite trivial:

Message passing between processes on different nodes, as well as between links and monitors, is seamless […]

In practice, things are a bit more complicated. The distributed erlang was developed when 'container' meant a large metal box for transportation and 'docker' was simply a synonym for a dockworker. In IP4 , there were many unused addresses, while network breakages were typically caused by rats chewing through cables, and the average uptime of a production system was measured in decades.

Now we are all unimaginably self-sufficient, packaged, and running a distributed erlang in an environment where dynamic IP addresses are assigned based on sheer randomness, and nodes can appear and disappear at the whim of the scheduler’s left foot. To avoid a pile of boilerplate code in every project running a distributed erlang, help is needed to cope with the hostile environment.

Notă: I’m aware that there's libcluster. It’s really cool, has more than a thousand stars, the author is well known in the community, and all that. If the ways this package offers to create and maintain a cluster are sufficient for you — I’m happy for you. Unfortunately, I need much more. I want to manage the configuration in detail and not be a bystander in the theater of cluster reformation.

Cerințe

What I personally needed was a library that would take over cluster management and have the following properties:

  • transparent operation with both a hard-coded list of nodes and dynamic discovery through services erlang;
  • fully functional callbacks for every topology change (node here, node there, network instability, splits);
  • o interfață transparentă pentru lansarea clusterului cu nume lungi și scurte, la fel ca și cu :nonode@nohost;
  • suport Docker din cutie, fără a necesita scrierea de cod de infrastructură.

Acest lucru înseamnă că, după ce am testat aplicația local în :nonode@nohost, sau într-un mediu de distribuție artificială folosind test_cluster_task, vreau doar să rulez docker-compose up --scale my_app=3 și să văd cum rulează trei instanțe în Docker fără niciun fel de modificări în cod. De asemenea, vreau ca aplicațiile dependente, de exemplu mnesia — atunci când topologia se schimbă, să restructureze clusterul în spatele scenei fără nicio intervenție suplimentară din partea aplicației.

Cloister nu a fost gândită ca o bibliotecă capabilă să facă totul: de la suportul clusterului până la prepararea cafelei. Nu este o soluție universală, care să încerce să acopere toate cazurile posibile, sau să fie o soluție academică completă în sensul în care o înțeleg teoreticienii din CS acest termen. Această bibliotecă este destinată unui obiectiv foarte clar, dar să își îndeplinească sarcinile relativ simple perfect. Acest obiectiv va consta în asigurarea unei transparențe complete între mediul de dezvoltare local și mediu distribuit elastic, plin de containere inamice.

Abordarea aleasă

Cloister se presupune a fi rulată ca aplicație, deși utilizatorii experimentați pot lucra cu asamblarea și întreținerea (assembly and maintenance) clusterului manual, lansând direct Cloister.Manager în arborele supervizorilor aplicației țintă.

Atunci când este lansată ca aplicație, biblioteca se bazează pe config, de unde citește următoarele valori importante:

config :cloister,
  otp_app: :my_app,
  sentry: :"cloister.local", # sau ~w|n1@foo n2@bar|a
  consensus: 3,              # numărul de noduri de luat în considerare
                             #    clusterul este activ
  listener: MyApp.Listener   # listener care va fi apelat când
                             #    inelul s-a schimbat

Parametrii de mai sus înseamnă în termeni literal: Cloister este utilizat pentru aplicația OTP :my_app, folosește descoperirea serviciului Erlang pentru a conecta nodurile, cel puțin trei, și MyApp.Listener modul (implementând @behaviour Cloister.Listener) este configurat să primească notificări despre schimbările de topologie. O descriere detaliată a configurației complete poate fi găsită în documentation.

Cu această configurație, aplicația Cloister va va fi lansată etapizat., amânând procesul de start al aplicației principale până la atingerea consensului (trei noduri conectate și legate, așa cum este exemplificat mai sus.) Aceasta oferă aplicației principale oportunitatea de a presupune că atunci când s-a pornit, clusterul este deja disponibil. La fiecare modificare a topologiei (vor exista multe, deoarece nodurile nu pornesc complet sincronizat), va fi apelat handler-ul MyApp.Listener.on_state_change/2. În cele mai multe cazuri, executăm o acțiune atunci când primim un mesaj cu starea %Cloister.Monitor{status: :up}, ceea ce înseamnă: „salut, clusterul este configurat”.

În cele mai multe cazuri, setarea consensus: 3 este optimă, deoarece chiar dacă ne așteptăm ca mai multe noduri să se conecteze, callback-ul va trece prin status: :rehashing → status: :up la orice nod nou adăugat sau eliminat.

Când rulează în modul de dezvoltare, este suficient să setăm consensus: 1 și Cloister va trece rapid peste așteptarea configurării clusterului, văzând :nonode@nohost, sau :node@host, sau :node@host.domain — în funcție de cum a fost configurat nodul (:none | :shortnames | :longnames).

Gestionarea aplicațiilor distribuite

Aplicațiile distribuite nu există în vid, de obicei, includ și dependențe distribuite, precum mnesia. Ne este ușor să gestionăm reconfigurarea lor din același callback on_state_change/2. Iată, de exemplu, o descriere detaliată a modului de reconfigurare mnesia în timpul rulării în documentation Cloister.

Principalul avantaj al utilizării Cloister este că execută toate operațiile necesare pentru reconstruirea clusterului după modificarea topologiei sub capotă. Aplicația pur și simplu pornește într-un mediu distribuit deja pregătit, cu toate nodurile conectate, indiferent dacă cunoaștem sau nu adresele IP și, prin urmare, numele nodurilor dinainte, sau dacă acestea au fost alocate/determinate dinamic. Nu necesită niciun setare specială de configurație Docker, iar din perspectiva dezvoltatorului de aplicații, nu există nicio diferență între rularea într-un mediu distribuit sau în local pe :nonode@nohost. Detalii suplimentare pot fi găsite în documentation.

Deși procesarea complexă a modificărilor topologiei este posibilă printr-o implementare proprie MyApp.Listener, întotdeauna pot exista cazuri limită în care aceste limitări ale bibliotecii și abordarea subiectivă de configurare devin o piatră de temelie în calea implementării. Este în regulă, doar luați în considerare cele menționate mai sus. libcluster, care este mai universal, sau chiar să gestionați un cluster de nivel inferior de unul singur. Scopul acestei biblioteci de cod nu este de a acoperi toate scenariile posibile, ci de a utiliza cel mai comun scenariu fără dureri inutile și fără copiile-stânga-dreapta greoaie.

Notă: în acest loc în original a fost fraza „Happy clustering!”, iar Yandex, pe care îl traduc (nu e cazul să caut în dicționare), mi-a propus varianta „Să ai un clustering fericit!”. O traducere mai bună, probabil, mai ales având în vedere situația geopolitică actuală — nu se poate imagina.

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster