Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Pastaba. vert.: Šiame straipsnyje „Banzai Cloud“ dalijasi pavyzdžiu, kaip jos pasirinktiniai įrankiai gali būti naudojami, kad „Kafka“ būtų lengviau naudoti „Kubernetes“. Šios instrukcijos iliustruoja, kaip galite nustatyti optimalų infrastruktūros dydį ir sukonfigūruoti pačią Kafka, kad pasiektų reikiamą pralaidumą.

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

„Apache Kafka“ yra paskirstyta srautinio perdavimo platforma, skirta kurti patikimas, keičiamo dydžio ir didelio našumo realiojo laiko srautinio perdavimo sistemas. Jo įspūdingas galimybes galima išplėsti naudojant Kubernetes. Tam mes sukūrėme Atvirojo kodo Kafka operatorius ir įrankis, vadinamas Supervamzdeliai. Jie leidžia paleisti „Kafka“ naudojant „Kubernetes“ ir naudoti įvairias jos funkcijas, pvz., patikslinti tarpininko konfigūraciją, metrika pagrįstą mastelį su perbalansavimu, stovo suvokimą, „minkštą“ (grakštus) naujinimų išleidimas ir kt.

Išbandykite „Supertubes“ savo grupėje:

curl https://getsupertubes.sh | sh и supertubes install -a --no-democluster --kubeconfig <path-to-eks-cluster-kubeconfig-file>

Arba susisiekite dokumentacija. Taip pat galite perskaityti apie kai kurias „Kafka“ galimybes, kurių darbas automatizuotas naudojant „Supertubes“ ir „Kafka“ operatorių. Apie juos jau rašėme tinklaraštyje:

Kai nuspręsite įdiegti „Kafka“ klasterį „Kubernetes“, greičiausiai susidursite su iššūkiu nustatyti optimalų pagrindinės infrastruktūros dydį ir poreikį tiksliai suderinti „Kafka“ konfigūraciją, kad ji atitiktų pralaidumo reikalavimus. Maksimalų kiekvieno brokerio našumą lemia pagrindinių infrastruktūros komponentų, tokių kaip atmintis, procesorius, disko greitis, tinklo pralaidumas ir kt., našumas.

Idealiu atveju tarpininko konfigūracija turėtų būti tokia, kad visi infrastruktūros elementai būtų išnaudoti maksimaliai. Tačiau realiame gyvenime ši sąranka yra gana sudėtinga. Labiau tikėtina, kad vartotojai sukonfigūruos brokerius, kad maksimaliai išnaudotų vieną ar du komponentus (disko, atminties ar procesoriaus). Paprastai tariant, brokeris demonstruoja maksimalų našumą, kai jo konfigūracija leidžia maksimaliai išnaudoti lėčiausią komponentą. Taip galime susidaryti apytikslį supratimą apie apkrovą, kurią gali atlaikyti vienas brokeris.

Teoriškai taip pat galime įvertinti brokerių skaičių, reikalingą tam tikram kroviniui tvarkyti. Tačiau praktikoje yra tiek daug skirtingų lygių konfigūravimo parinkčių, kad labai sunku (jei neįmanoma) įvertinti galimą konkrečios konfigūracijos veikimą. Kitaip tariant, labai sunku planuoti konfigūraciją pagal tam tikrą našumą.

„Supertubes“ naudotojams mes dažniausiai laikomės tokio požiūrio: pradedame nuo tam tikros konfigūracijos (infrastruktūra + nustatymai), tada išmatuojame jos našumą, koreguojame brokerio nustatymus ir kartojame procesą dar kartą. Tai vyksta tol, kol visiškai išnaudojamas lėčiausias infrastruktūros komponentas.

Tokiu būdu gauname aiškesnį supratimą, kiek brokerių reikia klasteriui, kad galėtų apdoroti tam tikrą apkrovą (tarpininkų skaičius taip pat priklauso nuo kitų veiksnių, pvz., minimalaus pranešimų kopijų skaičiaus, užtikrinančio atsparumą, skaidinių skaičiaus lyderiai ir kt.). Be to, gauname įžvalgų, kuriems infrastruktūros komponentams reikalingas vertikalus mastelio keitimas.

Šiame straipsnyje bus kalbama apie veiksmus, kurių imamės, kad išnaudotume visas lėčiausių pradinių konfigūracijų komponentų galimybes ir įvertintume Kafka klasterio pralaidumą. Labai atspari konfigūracija reikalauja bent trijų veikiančių brokerių (min.insync.replicas=3), paskirstytas trijose skirtingose ​​pasiekiamumo zonose. Norėdami sukonfigūruoti, išplėsti ir stebėti Kubernetes infrastruktūrą, naudojame savo hibridinių debesų konteinerių valdymo platformą - Naftotiekis. Jis palaiko vietinius (be metalo, VMware) ir penkių tipų debesis (Alibaba, AWS, Azure, Google, Oracle), taip pat bet kokį jų derinį.

Mintys apie Kafka klasterio infrastruktūrą ir konfigūraciją

Toliau pateiktiems pavyzdžiams pasirinkome AWS kaip debesies tiekėją ir EKS kaip Kubernetes platinimą. Panaši konfigūracija gali būti įgyvendinta naudojant PKE - Kubernetes platinimas iš Banzai Cloud, sertifikuotas CNCF.

diskas

Amazon siūlo įvairius EBS tūrio tipai. Pagrinde gp2 и io1 Tačiau yra SSD diskų, kad būtų užtikrintas didelis pralaidumas gp2 sunaudoja sukauptus kreditus (I/O kreditai), todėl pirmenybę teikėme tipui io1, kuri užtikrina nuolatinį didelį pralaidumą.

Pavyzdžių tipai

Kafkos našumas labai priklauso nuo operacinės sistemos puslapio talpyklos, todėl mums reikia egzempliorių, turinčių pakankamai atminties tarpininkams (JVM) ir puslapio talpyklai. Instancija c5.2xdidelis - gera pradžia, nes jame yra 16 GB atminties ir optimizuotas darbui su EBS. Jo trūkumas yra tas, kad jis gali užtikrinti maksimalų našumą ne ilgiau kaip 30 minučių kas 24 valandas. Jei jūsų darbo krūvis reikalauja didžiausio našumo ilgesnį laiką, galbūt norėsite apsvarstyti kitus egzempliorių tipus. Būtent tai ir padarėme, sustodami c5.4xdidelis. Tai užtikrina maksimalų pralaidumą 593,75 Mb/s. Didžiausias EBS tūrio pralaidumas io1 didesnis nei egzempliorius c5.4xdidelis, todėl greičiausiai lėčiausias infrastruktūros elementas bus šio egzemplioriaus tipo įvesties / išvesties pralaidumas (ką taip pat turėtų patvirtinti mūsų apkrovos testai).

Tinklas

Tinklo pralaidumas turi būti pakankamai didelis, palyginti su VM egzemplioriaus ir disko našumu, kitaip tinklas tampa kliūtimi. Mūsų atveju tinklo sąsaja c5.4xdidelis palaiko greitį iki 10 Gb/s, o tai yra žymiai didesnis nei VM egzemplioriaus I/O pralaidumas.

Brokerio diegimas

Tarpininkai turėtų būti dislokuoti (suplanuoti Kubernetes) tam skirtuose mazguose, kad būtų išvengta konkurencijos su kitais procesais dėl procesoriaus, atminties, tinklo ir disko išteklių.

Java versija

Logiškas pasirinkimas yra „Java 11“, nes jis suderinamas su „Docker“ ta prasme, kad JVM teisingai nustato konteineryje, kuriame veikia brokeris, turimus procesorius ir atmintį. Žinodamas, kad procesoriaus ribos yra svarbios, JVM viduje ir skaidriai nustato GC gijų ir JIT gijų skaičių. Naudojome Kafkos atvaizdą banzaicloud/kafka:2.13-2.4.0, kuri apima 2.4.0 „Kafka“ versiją („Scala 2.13“) „Java 11“.

Jei norite sužinoti daugiau apie „Java“ / JVM „Kubernetes“, peržiūrėkite šiuos mūsų įrašus:

Brokerio atminties nustatymai

Yra du pagrindiniai tarpininko atminties konfigūravimo aspektai: JVM ir Kubernetes pod nustatymai. Atminties limitas, nustatytas podeliui, turi būti didesnis nei maksimalus krūvos dydis, kad JVM turėtų vietos Java metaerdvei, kuri yra jos atmintyje, ir operacinės sistemos puslapio talpyklai, kurią aktyviai naudoja Kafka. Atlikdami bandymus paleidome Kafka brokerius su parametrais -Xmx4G -Xms2G, o bloko atminties limitas buvo 10 Gi. Atminkite, kad JVM atminties nustatymus galima gauti automatiškai naudojant -XX:MaxRAMPercentage и -X:MinRAMPercentage, remiantis bloko atminties limitu.

Tarpininko procesoriaus nustatymai

Paprastai tariant, galite pagerinti našumą padidindami lygiagretumą padidindami Kafkos naudojamų gijų skaičių. Kuo daugiau „Kafka“ procesorių, tuo geriau. Savo bandymą pradėjome nuo 6 procesorių limito ir palaipsniui (kartos) padidinome jų skaičių iki 15. Be to, nustatėme num.network.threads=12 tarpininko nustatymuose, kad padidintumėte gijų, kurios gauna duomenis iš tinklo ir siunčia juos, skaičių. Iškart sužinoję, kad pasekėjai brokeriai negali pakankamai greitai gauti kopijų, jie pakėlė num.replica.fetchers iki 4, kad pasekėjų brokeriai atkartotų lyderių pranešimus.

Įkrovos generavimo įrankis

Turėtumėte užtikrinti, kad pasirinkto apkrovos generatoriaus pajėgumas neišsenktų, kol Kafka klasteris (kuris yra lyginamasis) nepasieks maksimalios apkrovos. Kitaip tariant, būtina atlikti preliminarų apkrovos generavimo įrankio galimybių įvertinimą, taip pat pasirinkti jam egzempliorių tipus, turinčius pakankamai procesorių ir atminties. Tokiu atveju mūsų įrankis sukurs daugiau apkrovos, nei gali atlaikyti Kafka klasteris. Po daugybės eksperimentų apsistojome ties trimis egzemplioriais c5.4xdidelis, kurių kiekvienoje veikė generatorius.

Lyginamoji analizė

Našumo matavimas yra kartotinis procesas, kurį sudaro šie etapai:

  • infrastruktūros nustatymas (EKS klasteris, Kafka klasteris, apkrovos generavimo įrankis, taip pat Prometheus ir Grafana);
  • apkrovos generavimas tam tikram laikotarpiui, kad būtų galima filtruoti atsitiktinius surinktų veiklos rodiklių nuokrypius;
  • koreguoti brokerio infrastruktūrą ir konfigūraciją pagal stebimus veiklos rodiklius;
  • kartojant procesą, kol pasiekiamas reikiamas Kafkos klasterio pralaidumo lygis. Tuo pačiu metu jis turi būti nuosekliai atkuriamas ir parodyti minimalius pralaidumo pokyčius.

Kitame skyriuje aprašomi veiksmai, kurie buvo atlikti atliekant bandomosios grupės lyginamosios analizės procesą.

Įrankiai

Šie įrankiai buvo naudojami norint greitai įdiegti bazinę konfigūraciją, generuoti apkrovas ir įvertinti našumą:

  • Banzai debesų vamzdynas už EKS klasterio organizavimą iš Amazon c Prometėjas (rinkti Kafkos ir infrastruktūros metriką) ir grafana (kad vizualizuotų šias metrikas). Mes pasinaudojome integruotas в Naftotiekis paslaugas, kurios teikia susietą stebėjimą, centralizuotą žurnalų rinkimą, pažeidžiamumo nuskaitymą, atkūrimą po nelaimės, įmonės lygio saugą ir daug daugiau.
  • Sangrenel — Kafka klasterio apkrovos testavimo įrankis.
  • Grafana prietaisų skydeliai, skirti vizualizuoti Kafka metriką ir infrastruktūrą: Kubernetes Kafka, Mazgo eksportuotojas.
  • „Supertubes CLI“, kad būtų lengviausia nustatyti Kafka klasterį „Kubernetes“. „Zookeeper“, „Kafka“ operatorius, „Envoy“ ir daugelis kitų komponentų yra įdiegti ir tinkamai sukonfigūruoti, kad „Kubernetes“ veiktų gamybai paruoštas „Kafka“ klasteris.
    • Montavimui supertubes CLI naudokitės pateiktomis instrukcijomis čia.

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

EKS klasteris

Paruoškite EKS klasterį su tam skirtais darbuotojų mazgais c5.4xdidelis skirtingose ​​prieinamumo zonose podams su Kafka brokeriais, taip pat specialiuose mazguose apkrovos generatoriui ir stebėjimo infrastruktūrai.

banzai cluster create -f https://raw.githubusercontent.com/banzaicloud/kafka-operator/master/docs/benchmarks/infrastructure/cluster_eks_202001.json

Kai EKS klasteris pradės veikti, įgalinkite jo integraciją stebėjimo paslauga — ji paskirs Prometėją ir Grafaną į grupę.

Kafka sistemos komponentai

Įdiekite Kafka sistemos komponentus (Zookeeper, kafka-operator) į EKS naudodami supertubes CLI:

supertubes install -a --no-democluster --kubeconfig <path-to-eks-cluster-kubeconfig-file>

Kafkos klasteris

Pagal numatytuosius nustatymus EKS naudoja EBS tipo tomus gp2, todėl jums reikia sukurti atskirą saugyklos klasę pagal apimtis io1 Kafka klasteriui:

kubectl create -f - <<EOF
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
  type: io1
  iopsPerGB: "50"
  fsType: ext4
volumeBindingMode: WaitForFirstConsumer
EOF

Nustatykite brokerių parametrą min.insync.replicas=3 ir diegti brokerių blokus mazguose trijose skirtingose ​​pasiekiamumo zonose:

supertubes cluster create -n kafka --kubeconfig <path-to-eks-cluster-kubeconfig-file> -f https://raw.githubusercontent.com/banzaicloud/kafka-operator/master/docs/benchmarks/infrastructure/kafka_202001_3brokers.yaml --wait --timeout 600

Temos

Lygiagrečiai vykdėme tris apkrovos generatoriaus egzempliorius. Kiekvienas iš jų rašo į savo temą, tai yra, mums iš viso reikia trijų temų:

supertubes cluster topic create -n kafka --kubeconfig <path-to-eks-cluster-kubeconfig-file> -f -<<EOF
apiVersion: kafka.banzaicloud.io/v1alpha1
kind: KafkaTopic
metadata:
  name: perftest1
spec:
  name: perftest1
  partitions: 12
  replicationFactor: 3
  retention.ms: '28800000'
  cleanup.policy: delete
EOF

supertubes cluster topic create -n kafka --kubeconfig <path-to-eks-cluster-kubeconfig-file> -f -<<EOF
apiVersion: kafka.banzaicloud.io/v1alpha1
kind: KafkaTopic
metadata:
    name: perftest2
spec:
  name: perftest2
  partitions: 12
  replicationFactor: 3
  retention.ms: '28800000'
  cleanup.policy: delete
EOF

supertubes cluster topic create -n kafka --kubeconfig <path-to-eks-cluster-kubeconfig-file> -f -<<EOF
apiVersion: kafka.banzaicloud.io/v1alpha1
kind: KafkaTopic
metadata:
  name: perftest3
spec:
  name: perftest3
  partitions: 12
  replicationFactor: 3
  retention.ms: '28800000'
  cleanup.policy: delete
EOF

Kiekvienos temos replikacijos koeficientas yra 3 – minimali rekomenduojama vertė labai prieinamoms gamybos sistemoms.

Įkrovos generavimo įrankis

Paleidome tris apkrovos generatoriaus kopijas (kiekvienas parašė atskiroje temoje). Apkrovos generatoriaus blokams turite nustatyti mazgų giminingumą, kad jie būtų suplanuoti tik jiems skirtuose mazguose:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    app: loadtest
  name: perf-load1
  namespace: kafka
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: loadtest
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: loadtest
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: nodepool.banzaicloud.io/name
                operator: In
                values:
                - loadgen
      containers:
      - args:
        - -brokers=kafka-0:29092,kafka-1:29092,kafka-2:29092,kafka-3:29092
        - -topic=perftest1
        - -required-acks=all
        - -message-size=512
        - -workers=20
        image: banzaicloud/perfload:0.1.0-blog
        imagePullPolicy: Always
        name: sangrenel
        resources:
          limits:
            cpu: 2
            memory: 1Gi
          requests:
            cpu: 2
            memory: 1Gi
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30

Keletas punktų, į kuriuos reikia atkreipti dėmesį:

  • Apkrovos generatorius generuoja 512 baitų ilgio pranešimus ir paskelbia juos Kafkai 500 pranešimų paketais.
  • Naudojant argumentą -required-acks=all Publikavimas laikomas sėkmingu, kai visas sinchronizuotas pranešimo kopijas gauna ir patvirtina Kafka brokeriai. Tai reiškia, kad etalone matavome ne tik lyderių greitį gaunant žinutes, bet ir jų sekėjų, atkartojusių žinutes. Šio testo tikslas nėra įvertinti vartotojo skaitymo greitį (vartotojai) neseniai gauti pranešimai, kurie vis dar lieka OS puslapio talpykloje, ir jo palyginimas su diske saugomų pranešimų skaitymo greičiu.
  • Apkrovos generatorius lygiagrečiai dirba 20 darbuotojų (-workers=20). Kiekvienas darbuotojas turi 5 gamintojus, kurie dalijasi darbuotojo ryšiu su Kafka klasteriumi. Todėl kiekvienas generatorius turi 100 gamintojų ir visi jie siunčia pranešimus Kafkos klasteriui.

Klasterio būklės stebėjimas

Atlikdami Kafka klasterio apkrovos bandymą, taip pat stebėjome jo būklę, siekdami užtikrinti, kad nebūtų iš naujo paleidžiamų blokų, nebūtų nesinchronizuojamų kopijų ir didžiausias pralaidumas su minimaliais svyravimais:

  • Apkrovos generatorius rašo standartinę statistiką apie paskelbtų pranešimų skaičių ir klaidų dažnį. Klaidų lygis turėtų išlikti toks pat 0,00%.
  • Cruise Control, kurį įdiegė kafka-operator, pateikia prietaisų skydelį, kuriame taip pat galime stebėti klasterio būseną. Norėdami peržiūrėti šį skydelį, atlikite šiuos veiksmus:
    supertubes cluster cruisecontrol show -n kafka --kubeconfig <path-to-eks-cluster-kubeconfig-file>
  • ISR lygis („sinchronizuotų“ kopijų skaičius) susitraukimas ir plėtimasis yra lygūs 0.

Matavimo rezultatai

3 brokeriai, žinutės dydis – 512 baitų

Tolygiai paskirstę pertvaras trims tarpininkams, galėjome pasiekti našumą ~500 Mb/s (apie 990 tūkst. pranešimų per sekundę):

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

JVM virtualios mašinos atminties sąnaudos neviršijo 2 GB:

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Disko pralaidumas pasiekė didžiausią įvesties / išvesties mazgo pralaidumą visuose trijuose egzemplioriuose, kuriuose veikė tarpininkai:

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Iš duomenų apie mazgų atminties naudojimą galima daryti išvadą, kad sistemos buferis ir talpinimas užtruko ~10-15 GB:

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

3 brokeriai, žinutės dydis – 100 baitų

Sumažėjus pranešimo dydžiui, pralaidumas sumažėja maždaug 15–20 %: kiekvieno pranešimo apdorojimo laikas jam turi įtakos. Be to, procesoriaus apkrova išaugo beveik dvigubai.

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Kadangi tarpininkų mazgai vis dar turi nenaudojamų branduolių, našumą galima pagerinti pakeitus Kafka konfigūraciją. Tai nėra lengva užduotis, todėl norint padidinti pralaidumą, geriau dirbti su didesniais pranešimais.

4 brokeriai, žinutės dydis – 512 baitų

Galite nesunkiai padidinti Kafka klasterio našumą tiesiog pridėdami naujų brokerių ir išlaikydami pertvarų balansą (tai užtikrina tolygų apkrovos paskirstymą tarp brokerių). Mūsų atveju, pridėjus brokerį, klasterio pralaidumas padidėjo iki ~580 Mb/s (~1,1 mln. pranešimų per sekundę). Augimas pasirodė mažesnis nei tikėtasi: tai daugiausia paaiškinama pertvarų disbalansu (ne visi brokeriai dirba maksimaliai išnaudodami savo galimybes).

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

JVM įrenginio atminties suvartojimas išliko mažesnis nei 2 GB:

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Brokerių su diskais darbui įtakos turėjo pertvarų disbalansas:

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

Nustatykite tinkamą Kafka klasterio dydį Kubernetes

išvados

Aukščiau pateiktą kartotinį metodą galima išplėsti, kad jis apimtų sudėtingesnius scenarijus, apimančius šimtus vartotojų, perskirstymą, nuolatinius naujinimus, blokų paleidimus iš naujo ir kt. Visa tai leidžia įvertinti Kafkos klasterio galimybių ribas įvairiomis sąlygomis, nustatyti jo veikimo kliūtis ir rasti būdų, kaip su jomis kovoti.

Sukūrėme „Supertubes“, kad galėtume greitai ir lengvai įdiegti klasterį, jį konfigūruoti, pridėti / pašalinti tarpininkus ir temas, reaguoti į įspėjimus ir užtikrinti, kad „Kafka“ apskritai tinkamai veiktų „Kubernetes“. Mūsų tikslas – padėti jums susikoncentruoti ties pagrindine užduotimi („generuoti“ ir „vartoti“ Kafka pranešimus), o visą sunkų darbą palikti Supertubes ir Kafka operatoriui.

Jei jus domina Banzai Cloud technologijos ir atvirojo kodo projektai, užsiprenumeruokite įmonę adresu GitHub, "LinkedIn arba Twitter.

PS iš vertėjo

Taip pat skaitykite mūsų tinklaraštyje:

Šaltinis: www.habr.com

Добавить комментарий