ග්‍රැෆානා සඳහා ප්ලගිනයක් සංවර්ධනය: විශාල වෙඩි තැබීමේ ඉතිහාසයක්

ආයුබෝවන් සියල්ලටම! මාස කිහිපයකට පෙර, අපි අපගේ නව විවෘත-මූලාශ්‍ර ව්‍යාපෘතිය නිෂ්පාදනයට දියත් කළෙමු - අපි හැඳින්වූ kubernetes අධීක්ෂණය සඳහා Grafana ප්ලගිනය DevOpsProdigy KubeGraf. ප්ලගින මූල කේතය ලබා ගත හැක GitHub හි පොදු ගබඩාව. තවද මෙම ලිපියෙන් අපි ප්ලගිනය නිර්මාණය කළ ආකාරය, අප භාවිතා කළ මෙවලම් සහ සංවර්ධන ක්‍රියාවලියේදී අපට මුහුණ දුන් අන්තරායන් පිළිබඳ කතාව ඔබ සමඟ බෙදා ගැනීමට අපට අවශ්‍යය. අපි යමු!

0 කොටස - හඳුන්වාදීම: අපි මෙම ස්ථානයට පැමිණියේ කෙසේද?

Grafan සඳහා අපගේම ප්ලගිනයක් ලිවීමේ අදහස අප වෙත පැමිණියේ අහම්බෙන්. අපගේ සමාගම වසර 10 කට වැඩි කාලයක් තිස්සේ විවිධ මට්ටමේ සංකීර්ණත්වයේ වෙබ් ව්‍යාපෘති අධීක්‍ෂණය කර ඇත. මෙම කාලය තුළ, අපි විවිධ අධීක්ෂණ පද්ධති භාවිතා කිරීමේ විශේෂ ise දැනුම, රසවත් අවස්ථා සහ අත්දැකීම් විශාල ප්‍රමාණයක් රැස් කර ඇත්තෙමු. සහ යම් අවස්ථාවක දී අපි අපෙන්ම මෙසේ ඇසුවෙමු: "කුබර්නෙට්ස් නිරීක්ෂණය කිරීම සඳහා මැජික් මෙවලමක් තිබේද, එවිට ඔවුන් පවසන පරිදි, "එය සකසා එය අමතක කරන්න"? Prometheus + Grafana සංයෝජනය. මෙම තොගය සඳහා සූදානම් කළ විසඳුම් ලෙස, විවිධ වර්ගයේ මෙවලම් විශාල කට්ටලයක් ඇත: prometheus-operator, kubernetes-mixin උපකරණ පුවරු කට්ටලයක්, Grafana-kubernetes-app.

Grafana-kubernetes-app ප්ලගිනය අපට වඩාත්ම සිත්ගන්නා විකල්පය ලෙස පෙනුණි, නමුත් එය වසරකට වැඩි කාලයක් සඳහා සහය නොදක්වන අතර, එපමනක් නොව, node-exporter සහ kube-state-metrics හි නව අනුවාද සමඟ වැඩ කළ නොහැක. යම් අවස්ථාවක දී අපි තීරණය කළා: "අපි අපේම තීරණයක් ගත යුතු නොවේද?"

අපගේ ප්ලගිනය තුළ ක්‍රියාත්මක කිරීමට අප තීරණය කළ අදහස් මොනවාද:

  • "යෙදුම් සිතියම" දෘශ්‍යකරණය: නාම අවකාශ, යෙදවීම්...
  • “නියෝගය - සේවාව (+වරාය)” වැනි සම්බන්ධතා දෘශ්‍යකරණය.
  • පොකුරු නෝඩ් හරහා පොකුරු යෙදුම් බෙදා හැරීම දෘශ්‍යමාන කිරීම.
  • ප්‍රමිතික සහ තොරතුරු මූලාශ්‍ර කිහිපයකින් එකතු කිරීම: Prometheus සහ k8s api සේවාදායකය.
  • යටිතල පහසුකම් කොටස (CPU කාලය, මතකය, තැටි උප පද්ධතිය, ජාලය භාවිතා කිරීම) සහ යෙදුම් තර්කනය යන දෙකම නිරීක්ෂණය කිරීම - සෞඛ්‍ය තත්ත්‍වය කරල්, පවතින අනුරූ සංඛ්‍යාව, සජීවීභාවය/සූදානම පරීක්ෂණ සමත් වීම පිළිබඳ තොරතුරු.

1 කොටස: "Grafana ප්ලගිනය" යනු කුමක්ද?

තාක්ෂණික දෘෂ්ටි කෝණයකින්, Grafana සඳහා වන ප්ලගිනය කෝණික පාලකයකි, එය Grafana දත්ත නාමාවලියෙහි ගබඩා කර ඇත (/var/grafana/plugins/ /dist/module.js) සහ SystemJS මොඩියුලයක් ලෙස පූරණය කළ හැක. තවද මෙම නාමාවලියෙහි ඔබගේ ප්ලගිනය පිළිබඳ සියලුම මෙටා තොරතුරු අඩංගු plugin.json ගොනුවක් තිබිය යුතුය: නම, අනුවාදය, ප්ලගින වර්ගය, නිධිය/අඩවි/බලපත්‍රය වෙත සබැඳි, පරායත්තතා සහ යනාදිය.

ග්‍රැෆානා සඳහා ප්ලගිනයක් සංවර්ධනය: විශාල වෙඩි තැබීමේ ඉතිහාසයක්
module.ts

ග්‍රැෆානා සඳහා ප්ලගිනයක් සංවර්ධනය: විශාල වෙඩි තැබීමේ ඉතිහාසයක්
plugin.json

ඔබට තිර පිටපතෙහි දැකිය හැකි පරිදි, අපි plugin.type = යෙදුම සඳහන් කළෙමු. Grafana සඳහා ප්ලගීන වර්ග තුනකින් විය හැකි නිසා:

විද්වත් කමිටු: වඩාත් පොදු ප්ලගින වර්ගය - එය විවිධ උපකරණ පුවරු තැනීමට භාවිතා කරන ඕනෑම ප්‍රමිතික දෘශ්‍යමාන කිරීම සඳහා වූ පැනලයකි.
දත්ත මූලාශ්රය: යම් දත්ත මූලාශ්‍රයකට ප්ලගින සම්බන්ධකය (උදාහරණයක් ලෙස, Prometheus-datasource, ClickHouse-datasource, ElasticSearch-datasource).
යෙදුම: ග්‍රැෆානා තුළ ඔබේම ඉදිරිපස යෙදුමක් ගොඩනගා ගැනීමට, ඔබේම html පිටු නිර්මාණය කිරීමට සහ විවිධ දත්ත දෘශ්‍යමාන කිරීමට දත්ත මූලාශ්‍රයට අතින් ප්‍රවේශ වීමට ඔබට ඉඩ සලසන ප්ලගිනයක්. එසේම, වෙනත් වර්ගවල ප්ලගීන (දත්ත මූලාශ්‍රය, පැනලය) සහ විවිධ උපකරණ පුවරු පරායත්තතා ලෙස භාවිතා කළ හැක.

ග්‍රැෆානා සඳහා ප්ලගිනයක් සංවර්ධනය: විශාල වෙඩි තැබීමේ ඉතිහාසයක්
type=app සමඟ උදාහරණ ප්ලගින පරායත්තතා.

ඔබට ක්‍රමලේඛන භාෂාවක් ලෙස JavaScript සහ TypeScript යන දෙකම භාවිතා කළ හැකිය (අපි එය තෝරා ගත්තෙමු). ඔබට හැකි ඕනෑම වර්ගයක හෙලෝ-වර්ල්ඩ් ප්ලගීන සඳහා සූදානම් කිරීම් සබැඳිය සොයා ගන්න: මෙම ගබඩාවේ පෙර-ස්ථාපිත සහ වින්‍යාසගත තනන්නන් සහිත ආරම්භක-ඇසුරුම් විශාල සංඛ්‍යාවක් (ප්‍රතික්‍රියා හි ප්ලගිනයක පර්යේෂණාත්මක උදාහරණයක් පවා ඇත) අඩංගු වේ.

2 කොටස: දේශීය පරිසරය සකස් කිරීම

ප්ලගිනය මත වැඩ කිරීමට, අපට ස්වභාවිකවම පෙර ස්ථාපනය කර ඇති සියලුම මෙවලම් සහිත kubernetes පොකුරක් අවශ්‍ය වේ: prometheus, node-exporter, kube-state-metrics, grafana. පරිසරය ඉක්මනින්, පහසුවෙන් සහ ස්වභාවිකව සැකසිය යුතු අතර, උණුසුම් නැවත පූරණය කිරීම සහතික කිරීම සඳහා, Grafana දත්ත නාමාවලිය සංවර්ධකයාගේ යන්ත්රයෙන් සෘජුවම සවි කළ යුතුය.

අපගේ මතය අනුව, kubernetes සමඟ දේශීයව වැඩ කිරීම වඩාත් පහසුම ක්රමයයි minikube. ඊළඟ පියවර වන්නේ prometheus-operator භාවිතයෙන් Prometheus + Grafana සංයෝජනය ස්ථාපනය කිරීමයි. තුල මෙම ලිපිය minikube මත prometheus-operator ස්ථාපනය කිරීමේ ක්රියාවලිය විස්තරාත්මකව විස්තර කෙරේ. ස්ථීරභාවය සබල කිරීමට, ඔබ පරාමිතිය සැකසිය යුතුය persistence: ඇත්ත ප්‍රස්ථාර/grafana/values.yaml ගොනුවේ, ඔබේම PV සහ PVC එකතු කර persistence.existingClaim පරාමිතිය තුළ ඒවා සඳහන් කරන්න.

අපගේ අවසාන minikube දියත් කිරීමේ පිටපත මේ වගේ ය:

minikube start --kubernetes-version=v1.13.4 --memory=4096 --bootstrapper=kubeadm --extra-config=scheduler.address=0.0.0.0 --extra-config=controller-manager.address=0.0.0.0
minikube mount 
/home/sergeisporyshev/Projects/Grafana:/var/grafana --gid=472 --uid=472 --9p-version=9p2000.L

3 කොටස: සැබෑ සංවර්ධනය

වස්තු ආකෘතිය

ප්ලගිනය ක්‍රියාවට නැංවීම සඳහා සූදානම් වීමේ දී, අපි TypeScript පන්ති ආකාරයෙන් වැඩ කරන සියලුම මූලික Kubernetes ආයතන විස්තර කිරීමට තීරණය කළෙමු: Pod, deployment, daemonset, statefulset, job, cronjob, service, node, namespace. මෙම සෑම පන්තියක්ම පොදු BaseModel පන්තියෙන් උරුම වන අතර, එය නිර්මාපකය, විනාශ කරන්නා, දෘශ්‍යතාව යාවත්කාලීන කිරීම සහ මාරු කිරීම සඳහා ක්‍රම විස්තර කරයි. සෑම පංතියක්ම වෙනත් ආයතන සමඟ කැදලි සබඳතා විස්තර කරයි, උදාහරණයක් ලෙස, වර්ගයේ යෙදවීමේ ආයතනයක් සඳහා කරල් ලැයිස්තුවක්.

import {Pod} from "./pod";
import {Service} from "./service";
import {BaseModel} from './traits/baseModel';

export class Deployment extends BaseModel{
   pods: Array<Pod>;
   services: Array<Service>;

   constructor(data: any){
       super(data);
       this.pods = [];
       this.services = [];
   }
}

Getters සහ setters ආධාරයෙන්, අපට අවශ්‍ය entity metrics පහසු සහ කියවිය හැකි ආකාරයෙන් ප්‍රදර්ශනය කිරීමට හෝ සැකසීමට හැකිය. උදාහරණයක් ලෙස, වෙන් කළ හැකි cpu නෝඩ් වල ආකෘතිගත ප්‍රතිදානය:

get cpuAllocatableFormatted(){
   let cpu = this.data.status.allocatable.cpu;
   if(cpu.indexOf('m') > -1){
       cpu = parseInt(cpu)/1000;
   }
   return cpu;
}

පිටු

අපගේ සියලුම ප්ලගින පිටු ලැයිස්තුවක් මුලින් විස්තර කර ඇත්තේ අපගේ pluing.json හි පරායත්තතා අංශයේ ය:

ග්‍රැෆානා සඳහා ප්ලගිනයක් සංවර්ධනය: විශාල වෙඩි තැබීමේ ඉතිහාසයක්

සෑම පිටුවක් සඳහාම අවහිර කිරීමේදී අපි පිටුවේ නම සඳහන් කළ යුතුය (එය පසුව මෙම පිටුවට ප්‍රවේශ විය හැකි ස්ලග් එකක් බවට පරිවර්තනය වේ); මෙම පිටුවේ ක්‍රියාකාරිත්වය සඳහා වගකිව යුතු සංරචකයේ නම (සංරචක ලැයිස්තුව module.ts වෙත අපනයනය කෙරේ); මෙම පිටුව සමඟ වැඩ කළ හැකි පරිශීලක භූමිකාව සහ පැති තීරුව සඳහා සංචාලන සැකසුම් දක්වයි.

පිටුවේ ක්‍රියාකාරිත්වයට වගකිව යුතු සංරචකයේ, අපි templateUrl සැකසිය යුතු අතර, සලකුණු කිරීම සමඟ html ගොනුවට යන මාර්ගය එහි ගමන් කරයි. පාලකය තුළ, පරායත්ත එන්නත් කිරීම හරහා, අපට වැදගත් කෝණික සේවා 2ක් දක්වා ප්‍රවේශ විය හැක:

  • backendSrv - Grafana API සේවාදායකය සමඟ අන්තර්ක්‍රියා සපයන සේවාවක්;
  • datasourceSrv - ඔබගේ Grafana තුළ ස්ථාපනය කර ඇති සියලුම දත්ත මූලාශ්‍ර සමඟ දේශීය අන්තර්ක්‍රියා සපයන සේවාවකි (උදාහරණයක් ලෙස, .getAll() ක්‍රමය - ස්ථාපිත සියලුම දත්ත මූලාශ්‍ර ලැයිස්තුවක් ලබා දෙයි; .get( ) - නිශ්චිත දත්ත මූලාශ්‍රයක නිදර්ශන වස්තුවක් ලබා දෙයි.

ග්‍රැෆානා සඳහා ප්ලගිනයක් සංවර්ධනය: විශාල වෙඩි තැබීමේ ඉතිහාසයක්

ග්‍රැෆානා සඳහා ප්ලගිනයක් සංවර්ධනය: විශාල වෙඩි තැබීමේ ඉතිහාසයක්

ග්‍රැෆානා සඳහා ප්ලගිනයක් සංවර්ධනය: විශාල වෙඩි තැබීමේ ඉතිහාසයක්

4 කොටස: දත්ත මූලාශ්‍රය

ග්‍රැෆනාගේ දෘෂ්ටි කෝණයෙන්, දත්ත මූලාශ්‍රය අනෙක් සියල්ලටම සමාන ප්ලගිනයකි: එයට තමන්ගේම පිවිසුම් ලක්ෂ්‍ය module.js ඇත, මෙටා තොරතුරු plugin.json සහිත ගොනුවක් ඇත. වර්ගය = යෙදුම සමඟ ප්ලගිනයක් සංවර්ධනය කරන විට, අපට දැනට පවතින දත්ත මූලාශ්‍ර (උදාහරණයක් ලෙස, prometheus-datasource) සහ අපගේම ඒවා සමඟ අන්තර් ක්‍රියා කළ හැකිය, අපට ප්ලගින නාමාවලියෙහි (dist/datasource/*) සෘජුවම ගබඩා කළ හැකි හෝ පරායත්තයක් ලෙස ස්ථාපනය කළ හැක. අපගේ නඩුවේදී, දත්ත මූලාශ්රය ප්ලගින කේතය සමඟ පැමිණේ. config.html සැකිල්ලක් සහ ConfigCtrl පාලකයක් තිබීම ද අවශ්‍ය වේ, එය දත්ත මූලාශ්‍ර අවස්ථා වින්‍යාස කිරීමේ පිටුව සහ ඔබේ දත්ත මූලාශ්‍රයේ තර්කනය ක්‍රියාත්මක කරන දත්ත මූලාශ්‍ර පාලකය සඳහා භාවිතා කරනු ඇත.

KubeGraf ප්ලගිනය තුළ, පරිශීලක අතුරුමුහුණත දෘෂ්ටි කෝණයෙන්, දත්ත මූලාශ්‍රය යනු පහත හැකියාවන් ක්‍රියාත්මක කරන kubernetes පොකුරක උදාහරණයකි (මූලාශ්‍ර කේතය තිබේ. ලින්ක්):

  • k8s api-server වෙතින් දත්ත රැස් කිරීම (නාම අවකාශයන් ලැයිස්තුවක් ලබා ගැනීම, යෙදවීම්...)
  • proxying ඉල්ලීම් prometheus-datasource (එය එක් එක් විශේෂිත පොකුරු සඳහා ප්ලගින සැකසුම් තුළ තෝරා ඇත) සහ ස්ථිතික පිටු සහ උපකරණ පුවරු දෙකෙහි දත්ත භාවිතා කිරීමට ප්‍රතිචාර හැඩතල ගැන්වීම.
  • ස්ථිතික ප්ලගින පිටු මත දත්ත යාවත්කාලීන කිරීම (සැකසූ නැවුම් අනුපාතයක් සමඟ).
  • Grafana-dashboards (metriFindQuery() ක්‍රමය) තුළ සැකිලි පත්‍රයක් ජනනය කිරීමට විමසුම් සැකසීම

ග්‍රැෆානා සඳහා ප්ලගිනයක් සංවර්ධනය: විශාල වෙඩි තැබීමේ ඉතිහාසයක්

ග්‍රැෆානා සඳහා ප්ලගිනයක් සංවර්ධනය: විශාල වෙඩි තැබීමේ ඉතිහාසයක්

ග්‍රැෆානා සඳහා ප්ලගිනයක් සංවර්ධනය: විශාල වෙඩි තැබීමේ ඉතිහාසයක්

  • අවසාන k8s පොකුර සමඟ සම්බන්ධතා පරීක්ෂණය.
testDatasource(){
   let url = '/api/v1/namespaces';
   let _url = this.url;
   if(this.accessViaToken)
       _url += '/__proxy';
   _url += url;
   return this.backendSrv.datasourceRequest({
       url: _url,
       method: "GET",
       headers: {"Content-Type": 'application/json'}
   })
       .then(response => {
           if (response.status === 200) {
               return {status: "success", message: "Data source is OK", title: "Success"};
           }else{
               return {status: "error", message: "Data source is not OK", title: "Error"};
           }
       }, error => {
           return {status: "error", message: "Data source is not OK", title: "Error"};
       })
}

අපගේ මතය අනුව වෙනම සිත්ගන්නා කරුණක් වන්නේ දත්ත මූලාශ්‍රය සඳහා සත්‍යාපනය සහ බලය පැවරීමේ යාන්ත්‍රණයක් ක්‍රියාත්මක කිරීමයි. සාමාන්‍යයෙන්, කොටුවෙන් පිටත, අවසාන දත්ත ප්‍රභවයට ප්‍රවේශය වින්‍යාස කිරීම සඳහා අපට ගොඩනඟන ලද Grafana සංරචක datasourceHttpSettings භාවිතා කළ හැක. මෙම සංරචකය භාවිතා කරමින්, අපට url සහ මූලික සත්‍යාපන/අවසර සැකසීම් සඳහන් කිරීමෙන් http දත්ත මූලාශ්‍රය වෙත ප්‍රවේශය වින්‍යාසගත කළ හැක: පිවිසුම්-මුරපදය, හෝ සේවාලාභියා-සර්ට්/ක්ලයින්ට්-යතුර. බෙයර් ටෝකනයක් භාවිතයෙන් ප්‍රවේශය වින්‍යාස කිරීමේ හැකියාව ක්‍රියාත්මක කිරීම සඳහා (k8s සඳහා තථ්‍ය ප්‍රමිතිය), අපට කුඩා tweaking කිරීමට සිදු විය.

මෙම ගැටළුව විසඳීම සඳහා, ඔබට ගොඩනඟන ලද Grafana "ප්ලගින මාර්ග" යාන්ත්‍රණය භාවිතා කළ හැකිය (වැඩිදුර විස්තර නිල ලේඛන පිටුව) අපගේ දත්ත මූලාශ්‍රයේ සැකසීම් තුළ, අපට ග්‍රැෆානා ප්‍රොක්සි සේවාදායකය විසින් සකසන ලද මාර්ග නීති මාලාවක් ප්‍රකාශ කළ හැකිය. උදාහරණයක් ලෙස, එක් එක් අවසාන ලක්ෂ්‍යය සඳහා, සැකිලි කිරීමේ හැකියාව සහිත ශීර්ෂයන් හෝ url සැකසීමට හැකිය, ඒ සඳහා දත්ත jsonData සහ SecurityJsonData ක්ෂේත්‍රවලින් (මුරපද හෝ සංකේත සංකේතාත්මක ආකාරයෙන් ගබඩා කිරීම සඳහා) ගත හැක. අපගේ උදාහරණයේ, වැනි විමසුම් /__proxy/api/v1/namespaces පෝරමයේ url වෙත ප්‍රොක්සි කරනු ලැබේ
/api/v8/namespaces with Authorization: Bearer header.

ග්‍රැෆානා සඳහා ප්ලගිනයක් සංවර්ධනය: විශාල වෙඩි තැබීමේ ඉතිහාසයක්

ග්‍රැෆානා සඳහා ප්ලගිනයක් සංවර්ධනය: විශාල වෙඩි තැබීමේ ඉතිහාසයක්

ස්වාභාවිකවම, k8s api සේවාදායකය සමඟ වැඩ කිරීමට අපට කියවීමට පමණක් ප්‍රවේශය ඇති පරිශීලකයෙකු අවශ්‍ය වේ, ඔබට ද සොයා ගත හැකි නිර්මාණය සඳහා මැනිෆෙස්ට් ප්ලගින මූල කේතය.

5 කොටස: මුදා හැරීම

ග්‍රැෆානා සඳහා ප්ලගිනයක් සංවර්ධනය: විශාල වෙඩි තැබීමේ ඉතිහාසයක්

ඔබ ඔබේම Grafana ප්ලගිනයක් ලිවූ පසු, ඔබට එය ප්‍රසිද්ධියේ ලබා ගැනීමට අවශ්‍ය වනු ඇත. Grafana හි මෙය මෙහි ඇති ප්ලගීන පුස්තකාලයකි grafana.com/grafana/plugins

ඔබගේ ප්ලගිනය නිල වෙළඳසැලේ ලබා ගැනීමට නම්, ඔබ PR එකක් සෑදිය යුතුය මෙම ගබඩාවrepo.json ගොනුවට මෙවැනි අන්තර්ගතයක් එක් කිරීමෙන්:

ග්‍රැෆානා සඳහා ප්ලගිනයක් සංවර්ධනය: විශාල වෙඩි තැබීමේ ඉතිහාසයක්

මෙහි අනුවාදය යනු ඔබේ ප්ලගිනයේ අනුවාදය වන අතර, url යනු ගබඩාවට සබැඳියක් වන අතර, ප්ලගිනයේ නිශ්චිත අනුවාදයක් ලබා ගත හැකි කැපවීමේ හැෂ් එකයි.

ප්‍රතිදානයේදී ඔබට මෙවැනි අපූරු පින්තූරයක් පෙනෙනු ඇත:

ග්‍රැෆානා සඳහා ප්ලගිනයක් සංවර්ධනය: විශාල වෙඩි තැබීමේ ඉතිහාසයක්

ඒ සඳහා දත්ත ස්වයංක්‍රීයව ඔබගේ Readme.md, Changelog.md සහ plugin.json ගොනුවෙන් ප්ලගින විස්තරය සමඟ ග්‍රහණය කරගනු ඇත.

6 කොටස: නිගමන වෙනුවට

මුදා හැරීමෙන් පසු අපගේ ප්ලගිනය සංවර්ධනය කිරීම අපි නතර කළේ නැත. දැන් අපි පොකුරු නෝඩ් වල සම්පත් භාවිතය නිවැරදිව අධීක්ෂණය කිරීම, UX වැඩිදියුණු කිරීම සඳහා නව විශේෂාංග හඳුන්වා දීම සහ අපගේ සේවාදායකයින් සහ GitHub හි සිටින පුද්ගලයින් විසින් (ඔබ ඉවත්ව ගියහොත්) ප්ලගිනය ස්ථාපනය කිරීමෙන් පසු ලැබෙන ප්‍රතිපෝෂණ විශාල ප්‍රමාණයක් ලබා ගැනීමට කටයුතු කරමින් සිටිමු. ඔබගේ ගැටලුව හෝ අදින්න ඉල්ලීම, මම ඉතා සතුටු වනු ඇත :)

ග්‍රැෆානා වැනි අපූරු මෙවලමක් තේරුම් ගැනීමට සහ සමහර විට ඔබේම ප්ලගිනයක් ලිවීමට මෙම ලිපිය ඔබට උපකාරී වනු ඇතැයි අපි බලාපොරොත්තු වෙමු.

ඔයාට ස්තූතියි!)

මූලාශ්රය: www.habr.com

අදහස් එක් කරන්න