Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes
Tutulungan ka ng artikulong ito na maunawaan kung paano gumagana ang load balancing sa Kubernetes, kung ano ang nangyayari kapag nag-scale ng mga pangmatagalang koneksyon, at kung bakit dapat mong isaalang-alang ang client-side balancing kung gumagamit ka ng HTTP/2, gRPC, RSockets, AMQP, o iba pang pangmatagalang protocol . 

Kaunti tungkol sa kung paano muling ipinamamahagi ang trapiko sa Kubernetes 

Nagbibigay ang Kubernetes ng dalawang maginhawang abstraction para sa pag-deploy ng mga application: Mga Serbisyo at Deployment.

Inilalarawan ng mga deployment kung paano at gaano karaming mga kopya ng iyong aplikasyon ang dapat tumakbo sa anumang oras. Ang bawat application ay naka-deploy bilang isang Pod at nakatalaga ng isang IP address.

Ang mga serbisyo ay katulad ng paggana sa isang load balancer. Idinisenyo ang mga ito para ipamahagi ang trapiko sa maraming pod.

Tingnan natin kung ano ang hitsura nito.

  1. Sa diagram sa ibaba makikita mo ang tatlong pagkakataon ng parehong application at isang load balancer:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

  2. Ang load balancer ay tinatawag na isang Serbisyo at itinalaga ang isang IP address. Ang anumang papasok na kahilingan ay ire-redirect sa isa sa mga pod:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

  3. Tinutukoy ng deployment scenario ang bilang ng mga instance ng application. Halos hindi mo na kailangang direktang palawakin sa ilalim ng:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

  4. Ang bawat pod ay itinalaga ng sarili nitong IP address:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

Kapaki-pakinabang na isipin ang mga serbisyo bilang isang koleksyon ng mga IP address. Sa bawat oras na ma-access mo ang serbisyo, ang isa sa mga IP address ay pinipili mula sa listahan at ginagamit bilang patutunguhang address.

Parang ganito.

  1. Isang curl 10.96.45.152 na kahilingan ang natanggap sa serbisyo:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

  2. Pinipili ng serbisyo ang isa sa tatlong pod address bilang destinasyon:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

  3. Na-redirect ang trapiko sa isang partikular na pod:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

Kung ang iyong application ay binubuo ng frontend at backend, magkakaroon ka ng parehong serbisyo at deployment para sa bawat isa.

Kapag humiling ang frontend sa backend, hindi nito kailangang malaman kung gaano karaming mga pod ang inihahatid ng backend: maaaring mayroong isa, sampu, o isang daan.

Gayundin, walang alam ang frontend tungkol sa mga address ng mga pod na nagsisilbi sa backend.

Kapag humiling ang frontend sa backend, ginagamit nito ang IP address ng serbisyo sa backend, na hindi nagbabago.

Narito kung paano ito asta.

  1. Sa ilalim ng 1 ay humihiling ng panloob na bahagi ng backend. Sa halip na pumili ng partikular para sa backend, humihiling ito sa serbisyo:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

  2. Pinipili ng serbisyo ang isa sa mga backend pod bilang patutunguhang address:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

  3. Ang trapiko ay mula sa Pod 1 hanggang Pod 5, na pinili ng serbisyo:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

  4. Hindi alam ng Under 1 kung gaano karaming mga pod tulad ng wala pang 5 ang nakatago sa likod ng serbisyo:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

Ngunit paano eksaktong namamahagi ang serbisyo ng mga kahilingan? Parang round-robin balancing ang ginagamit? Alamin natin ito. 

Pagbabalanse sa mga serbisyo ng Kubernetes

Ang mga serbisyo ng Kubernetes ay hindi umiiral. Walang proseso para sa serbisyo na nakatalaga ng IP address at port.

Maaari mong i-verify ito sa pamamagitan ng pag-log in sa anumang node sa cluster at pagpapatakbo ng netstat -ntlp command.

Ni hindi mo mahahanap ang IP address na nakalaan sa serbisyo.

Ang IP address ng serbisyo ay matatagpuan sa control layer, sa controller, at naitala sa database - etcd. Ang parehong address ay ginagamit ng isa pang bahagi - kube-proxy.
Ang Kube-proxy ay tumatanggap ng isang listahan ng mga IP address para sa lahat ng mga serbisyo at bumubuo ng isang hanay ng mga panuntunan ng iptable sa bawat node sa cluster.

Sinasabi ng mga panuntunang ito: "Kung nakita namin ang IP address ng serbisyo, kailangan naming baguhin ang patutunguhang address ng kahilingan at ipadala ito sa isa sa mga pod."

Ang IP address ng serbisyo ay ginagamit lamang bilang isang entry point at hindi inihahatid ng anumang proseso sa pakikinig sa IP address at port na iyon.

Tingnan natin ito

  1. Isaalang-alang ang isang kumpol ng tatlong node. Ang bawat node ay may mga pod:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

  2. Ang mga nakatali na pod na pininturahan ng beige ay bahagi ng serbisyo. Dahil ang serbisyo ay hindi umiiral bilang isang proseso, ito ay ipinapakita sa kulay abo:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

  3. Ang unang pod ay humihiling ng serbisyo at dapat pumunta sa isa sa mga nauugnay na pod:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

  4. Ngunit ang serbisyo ay hindi umiiral, ang proseso ay hindi umiiral. Paano ito gumagana?

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

  5. Bago umalis ang kahilingan sa node, dumaan ito sa mga panuntunan ng iptables:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

  6. Alam ng mga tuntunin ng iptables na wala ang serbisyo at pinapalitan ang IP address nito ng isa sa mga IP address ng mga pod na nauugnay sa serbisyong iyon:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

  7. Ang kahilingan ay tumatanggap ng wastong IP address bilang patutunguhang address at pinoproseso nang normal:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

  8. Depende sa topology ng network, ang kahilingan sa kalaunan ay umaabot sa pod:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

Maaari bang mag-load ng balanse ang iptables?

Hindi, ang mga iptable ay ginagamit para sa pag-filter at hindi idinisenyo para sa pagbabalanse.

Gayunpaman, posible na magsulat ng isang hanay ng mga patakaran na gumagana tulad ng pseudo-balancer.

At ito mismo ang ipinatupad sa Kubernetes.

Kung mayroon kang tatlong pod, isusulat ng kube-proxy ang mga sumusunod na panuntunan:

  1. Piliin ang unang sub na may posibilidad na 33%, kung hindi, pumunta sa susunod na panuntunan.
  2. Piliin ang pangalawa na may posibilidad na 50%, kung hindi, pumunta sa susunod na panuntunan.
  3. Piliin ang pangatlo sa ilalim.

Ang sistemang ito ay nagreresulta sa bawat pod na napili na may posibilidad na 33%.

Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

At walang garantiya na susunod na pipiliin ang Pod 2 pagkatapos ng Pod 1.

Nota: Ang iptables ay gumagamit ng statistical module na may random na pamamahagi. Kaya, ang pagbabalanse algorithm ay batay sa random na pagpili.

Ngayong naiintindihan mo na kung paano gumagana ang mga serbisyo, tingnan natin ang mas kawili-wiling mga sitwasyon ng serbisyo.

Ang mga pangmatagalang koneksyon sa Kubernetes ay hindi nasusukat bilang default

Ang bawat kahilingan sa HTTP mula sa frontend hanggang sa backend ay inihahatid ng isang hiwalay na koneksyon sa TCP, na binubuksan at isinara.

Kung ang frontend ay nagpapadala ng 100 kahilingan sa bawat segundo sa backend, pagkatapos ay 100 iba't ibang koneksyon sa TCP ang bubuksan at isinara.

Maaari mong bawasan ang oras ng pagproseso at pag-load ng kahilingan sa pamamagitan ng pagbubukas ng isang koneksyon sa TCP at paggamit nito para sa lahat ng kasunod na kahilingan sa HTTP.

Ang HTTP protocol ay may feature na tinatawag na HTTP keep-alive, o connection reuse. Sa kasong ito, isang koneksyon sa TCP ang ginagamit upang magpadala at tumanggap ng maramihang mga kahilingan at tugon sa HTTP:

Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

Ang tampok na ito ay hindi pinagana bilang default: ang server at kliyente ay dapat na i-configure nang naaayon.

Ang setup mismo ay simple at naa-access para sa karamihan ng mga programming language at environment.

Narito ang ilang link sa mga halimbawa sa iba't ibang wika:

Ano ang mangyayari kung gagamitin namin ang keep-alive sa isang serbisyo ng Kubernetes?
Ipagpalagay natin na parehong sinusuportahan ng frontend at backend ang keep-alive.

Mayroon kaming isang kopya ng frontend at tatlong kopya ng backend. Ang frontend ay gumagawa ng unang kahilingan at nagbubukas ng TCP na koneksyon sa backend. Ang kahilingan ay umabot sa serbisyo, isa sa mga backend pod ang pipiliin bilang patutunguhang address. Nagpapadala ang backend ng tugon, at tinatanggap ito ng frontend.

Hindi tulad ng karaniwang sitwasyon kung saan sarado ang koneksyon ng TCP pagkatapos makatanggap ng tugon, pinananatiling bukas ito para sa karagdagang mga kahilingan sa HTTP.

Ano ang mangyayari kung magpapadala ang frontend ng higit pang mga kahilingan sa backend?

Upang ipasa ang mga kahilingang ito, isang bukas na koneksyon sa TCP ang gagamitin, ang lahat ng mga kahilingan ay mapupunta sa parehong backend kung saan napunta ang unang kahilingan.

Hindi ba dapat muling ipamahagi ng iptables ang trapiko?

Hindi sa kasong ito.

Kapag nalikha ang isang koneksyon sa TCP, dumadaan ito sa mga panuntunan ng iptables, na pumipili ng isang partikular na backend kung saan pupunta ang trapiko.

Dahil ang lahat ng kasunod na kahilingan ay nasa bukas na TCP na koneksyon, ang mga panuntunan sa iptables ay hindi na tinatawag.

Tingnan natin kung ano ang hitsura nito.

  1. Ang unang pod ay nagpapadala ng kahilingan sa serbisyo:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

  2. Alam mo na ang susunod na mangyayari. Ang serbisyo ay hindi umiiral, ngunit may mga iptables na panuntunan na magpoproseso ng kahilingan:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

  3. Pipiliin ang isa sa mga backend pod bilang patutunguhang address:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

  4. Ang kahilingan ay umabot sa pod. Sa puntong ito, ang isang patuloy na koneksyon sa TCP sa pagitan ng dalawang pod ay itatatag:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

  5. Anumang kasunod na kahilingan mula sa unang pod ay dadaan sa naitatag nang koneksyon:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

Ang resulta ay mas mabilis na oras ng pagtugon at mas mataas na throughput, ngunit nawawalan ka ng kakayahang i-scale ang backend.

Kahit na mayroon kang dalawang pod sa backend, na may tuluy-tuloy na koneksyon, palaging mapupunta ang trapiko sa isa sa mga ito.

Maaari ba itong ayusin?

Dahil hindi alam ng Kubernetes kung paano balansehin ang mga paulit-ulit na koneksyon, ang gawaing ito ay nasa iyo.

Ang mga serbisyo ay isang koleksyon ng mga IP address at port na tinatawag na mga endpoint.

Ang iyong aplikasyon ay maaaring makakuha ng isang listahan ng mga endpoint mula sa serbisyo at magpasya kung paano ipamahagi ang mga kahilingan sa pagitan ng mga ito. Maaari kang magbukas ng patuloy na koneksyon sa bawat pod at mga kahilingan sa balanse sa pagitan ng mga koneksyong ito gamit ang round-robin.

O mag-apply pa kumplikadong mga algorithm ng pagbabalanse.

Ang code sa panig ng kliyente na responsable para sa pagbabalanse ay dapat sumunod sa lohika na ito:

  1. Kumuha ng listahan ng mga endpoint mula sa serbisyo.
  2. Magbukas ng paulit-ulit na koneksyon para sa bawat endpoint.
  3. Kapag kailangang gumawa ng kahilingan, gamitin ang isa sa mga bukas na koneksyon.
  4. Regular na i-update ang listahan ng mga endpoint, gumawa ng mga bago o isara ang mga lumang paulit-ulit na koneksyon kung magbabago ang listahan.

Ito ang magiging hitsura nito.

  1. Sa halip na ipadala ng unang pod ang kahilingan sa serbisyo, maaari mong balansehin ang mga kahilingan sa panig ng kliyente:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

  2. Kailangan mong magsulat ng code na nagtatanong kung aling mga pod ang bahagi ng serbisyo:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

  3. Kapag mayroon ka nang listahan, i-save ito sa panig ng kliyente at gamitin ito para kumonekta sa mga pod:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

  4. Ikaw ang responsable para sa load balancing algorithm:

    Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

Ngayon ang tanong ay lumitaw: ang problemang ito ba ay nalalapat lamang sa HTTP keep-alive?

Pagbalanse ng load sa gilid ng kliyente

Ang HTTP ay hindi lamang ang protocol na maaaring gumamit ng mga patuloy na koneksyon sa TCP.

Kung ang iyong aplikasyon ay gumagamit ng isang database, ang isang koneksyon sa TCP ay hindi mabubuksan sa tuwing kailangan mong humiling o kumuha ng isang dokumento mula sa database. 

Sa halip, ang isang patuloy na koneksyon sa TCP sa database ay binuksan at ginagamit.

Kung naka-deploy ang iyong database sa Kubernetes at ibinibigay ang access bilang isang serbisyo, makakatagpo ka ng parehong mga problemang inilarawan sa nakaraang seksyon.

Ang isang replika ng database ay mas mai-load kaysa sa iba. Hindi makakatulong ang Kube-proxy at Kubernetes na balansehin ang mga koneksyon. Dapat kang mag-ingat upang balansehin ang mga query sa iyong database.

Depende sa kung aling library ang iyong ginagamit upang kumonekta sa database, maaari kang magkaroon ng iba't ibang mga opsyon para sa paglutas ng problemang ito.

Nasa ibaba ang isang halimbawa ng pag-access ng MySQL database cluster mula sa Node.js:

var mysql = require('mysql');
var poolCluster = mysql.createPoolCluster();

var endpoints = /* retrieve endpoints from the Service */

for (var [index, endpoint] of endpoints) {
  poolCluster.add(`mysql-replica-${index}`, endpoint);
}

// Make queries to the clustered MySQL database

Mayroong maraming iba pang mga protocol na gumagamit ng patuloy na koneksyon sa TCP:

  • WebSockets at secured WebSockets
  • HTTP / 2
  • gRPC
  • Mga RSocket
  • AMQP

Dapat ay pamilyar ka na sa karamihan ng mga protocol na ito.

Ngunit kung ang mga protocol na ito ay napakapopular, bakit walang standardized na solusyon sa pagbabalanse? Bakit kailangang baguhin ang lohika ng kliyente? Mayroon bang katutubong solusyon sa Kubernetes?

Ang Kube-proxy at mga iptable ay idinisenyo upang masakop ang pinakakaraniwang mga kaso ng paggamit kapag nagde-deploy sa Kubernetes. Ito ay para sa kaginhawaan.

Kung gumagamit ka ng serbisyo sa web na naglalantad ng REST API, swerte ka - sa kasong ito, hindi ginagamit ang mga tuluy-tuloy na koneksyon sa TCP, maaari kang gumamit ng anumang serbisyo ng Kubernetes.

Ngunit kapag nagsimula ka nang gumamit ng mga patuloy na koneksyon sa TCP, kakailanganin mong malaman kung paano pantay na ipamahagi ang load sa mga backend. Ang Kubernetes ay hindi naglalaman ng mga handa na solusyon para sa kasong ito.

Gayunpaman, tiyak na may mga opsyon na makakatulong.

Pagbalanse ng matagal nang koneksyon sa Kubernetes

Mayroong apat na uri ng mga serbisyo sa Kubernetes:

  1. ClusterIP
  2. NodePort
  3. LoadBalancer
  4. Walang ulo

Ang unang tatlong serbisyo ay gumagana batay sa isang virtual na IP address, na ginagamit ng kube-proxy upang bumuo ng mga panuntunan sa iptables. Ngunit ang pangunahing batayan ng lahat ng mga serbisyo ay isang walang ulo na serbisyo.

Ang walang ulo na serbisyo ay walang anumang IP address na nauugnay dito at nagbibigay lamang ng isang mekanismo para sa pagkuha ng isang listahan ng mga IP address at port ng mga pod (endpoint) na nauugnay dito.

Ang lahat ng mga serbisyo ay batay sa walang ulo na serbisyo.

Ang serbisyo ng ClusterIP ay isang serbisyong walang ulo na may ilang mga karagdagan: 

  1. Ang layer ng pamamahala ay nagtatalaga dito ng isang IP address.
  2. Binubuo ng Kube-proxy ang mga kinakailangang panuntunan sa iptables.

Sa ganitong paraan maaari mong balewalain ang kube-proxy at direktang gamitin ang listahan ng mga endpoint na nakuha mula sa walang ulo na serbisyo upang i-load ang balanse sa iyong aplikasyon.

Ngunit paano tayo makakapagdagdag ng katulad na lohika sa lahat ng mga application na naka-deploy sa cluster?

Kung ang iyong aplikasyon ay na-deploy na, ang gawaing ito ay maaaring mukhang imposible. Gayunpaman, mayroong isang alternatibong opsyon.

Tutulungan ka ng Service Mesh

Marahil ay napansin mo na na ang diskarte sa pagbabalanse ng load sa panig ng kliyente ay medyo karaniwan.

Kapag nagsimula ang application, ito ay:

  1. Nakakakuha ng listahan ng mga IP address mula sa serbisyo.
  2. Nagbubukas at nagpapanatili ng pool ng koneksyon.
  3. Pana-panahong ina-update ang pool sa pamamagitan ng pagdaragdag o pag-alis ng mga endpoint.

Kapag gusto ng application na humiling, ito ay:

  1. Pumili ng magagamit na koneksyon gamit ang ilang logic (hal. round-robin).
  2. Isinasagawa ang kahilingan.

Gumagana ang mga hakbang na ito para sa parehong mga koneksyon sa WebSockets, gRPC, at AMQP.

Maaari mong paghiwalayin ang logic na ito sa isang hiwalay na library at gamitin ito sa iyong mga application.

Gayunpaman, maaari mong gamitin ang mga mesh ng serbisyo tulad ng Istio o Linkerd sa halip.

Pinapalaki ng Service Mesh ang iyong aplikasyon sa isang proseso na:

  1. Awtomatikong naghahanap ng mga IP address ng serbisyo.
  2. Sinusuri ang mga koneksyon gaya ng WebSockets at gRPC.
  3. Binabalanse ang mga kahilingan gamit ang tamang protocol.

Tumutulong ang Service Mesh na pamahalaan ang trapiko sa loob ng cluster, ngunit ito ay medyo resource-intensive. Ang iba pang mga opsyon ay gumagamit ng mga third-party na aklatan tulad ng Netflix Ribbon o mga programmable proxy tulad ng Envoy.

Ano ang mangyayari kung balewalain mo ang mga isyu sa pagbabalanse?

Maaari mong piliing huwag gumamit ng load balancing at hindi pa rin mapapansin ang anumang mga pagbabago. Tingnan natin ang ilang mga sitwasyon sa trabaho.

Kung mayroon kang mas maraming kliyente kaysa sa mga server, hindi ito isang malaking problema.

Sabihin nating mayroong limang kliyente na kumonekta sa dalawang server. Kahit na walang pagbabalanse, parehong server ang gagamitin:

Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

Maaaring hindi pantay na ipamahagi ang mga koneksyon: marahil apat na kliyente ang nakakonekta sa parehong server, ngunit may magandang pagkakataon na ang parehong mga server ay gagamitin.

Ang mas problemado ay ang kabaligtaran na senaryo.

Kung mas kaunti ang mga kliyente mo at mas maraming server, maaaring hindi nagagamit ang iyong mga mapagkukunan at may lalabas na potensyal na bottleneck.

Sabihin nating mayroong dalawang kliyente at limang server. Sa pinakamagandang kaso, magkakaroon ng dalawang permanenteng koneksyon sa dalawang server sa lima.

Ang natitirang mga server ay magiging idle:

Pag-load ng pagbabalanse at pag-scale ng mga pangmatagalang koneksyon sa Kubernetes

Kung hindi mahawakan ng dalawang server na ito ang mga kahilingan ng kliyente, hindi makakatulong ang pahalang na pag-scale.

Konklusyon

Ang mga serbisyo ng Kubernetes ay idinisenyo upang gumana sa karamihan ng mga karaniwang sitwasyon ng web application.

Gayunpaman, kapag nagsimula kang magtrabaho kasama ang mga protocol ng application na gumagamit ng mga patuloy na koneksyon sa TCP, tulad ng mga database, gRPC o WebSockets, hindi na angkop ang mga serbisyo. Ang Kubernetes ay hindi nagbibigay ng mga panloob na mekanismo para sa pagbabalanse ng mga patuloy na koneksyon sa TCP.

Nangangahulugan ito na dapat kang magsulat ng mga application na nasa isip ang pagbabalanse sa panig ng kliyente.

Pagsasalin na inihanda ng pangkat Kubernetes aaS mula sa Mail.ru.

Ano pa ang mababasa sa paksa:

  1. Tatlong antas ng autoscaling sa Kubernetes at kung paano epektibong gamitin ang mga ito
  2. Kubernetes sa diwa ng piracy na may template para sa pagpapatupad.
  3. Ang aming Telegram channel tungkol sa digital transformation.

Pinagmulan: www.habr.com

Magdagdag ng komento