Apache Bigtop i odabir Hadoop distribucije danas

Apache Bigtop i odabir Hadoop distribucije danas

Vjerojatno nije tajna da je prošla godina bila godina velikih promjena za Apache Hadoop. Prošle godine su se spojili Cloudera i Hortonworks (u biti akvizicija potonjeg), a Mapr je zbog ozbiljnih financijskih problema prodan Hewlett Packardu. I ako se nekoliko godina ranije, u slučaju on-premises instalacija, često moralo birati između Cloudere i Hortonworksa, danas, nažalost, nemamo taj izbor. Još jedno iznenađenje bila je činjenica da je Cloudera u veljači ove godine objavila da će prestati puštati binarne sklopove svoje distribucije u javni repozitorij, te su sada dostupni samo putem plaćene pretplate. Naravno, još uvijek je moguće preuzeti najnovije verzije CDH i HDP objavljene prije kraja 2019. godine, a podrška za njih očekuje se godinu do dvije. Ali što učiniti sljedeće? Za one koji su prethodno plaćali pretplatu ništa se nije promijenilo. A za one koji ne žele prijeći na plaćenu verziju distribucije, ali u isto vrijeme žele moći primati najnovije verzije komponenti klastera, kao i zakrpe i druga ažuriranja, pripremili smo ovaj članak. U njemu ćemo razmotriti moguće opcije za izlazak iz ove situacije.

Članak je više recenzentski. Neće sadržavati usporedbu distribucija i njihovu detaljnu analizu, niti će biti recepta za njihovu instalaciju i konfiguraciju. Što će se dogoditi? Ukratko ćemo govoriti o takvoj distribuciji kao što je Arenadata Hadoop, koja s pravom zaslužuje našu pozornost zbog svoje dostupnosti, što je danas vrlo rijetko. A onda ćemo pričati o Vanilla Hadoopu, uglavnom o tome kako se može “skuhati” pomoću Apache Bigtopa. Spreman? Onda dobrodošli u cat.

Arenadata Hadoop

Apache Bigtop i odabir Hadoop distribucije danas

Ovo je potpuno novi i još uvijek malo poznati distribucijski komplet domaćeg razvoja. Nažalost, trenutno na Habréu postoji samo ovaj članak.

Više informacija možete pronaći na službenoj Online projekt. Najnovije verzije distribucije temelje se na Hadoopu 3.1.2 za verziju 3 i 2.8.5 za verziju 2.

Informacije o planu puta možete pronaći здесь.

Apache Bigtop i odabir Hadoop distribucije danas
Sučelje upravitelja klastera Arenadata

Glavni proizvod Arenadata je Arenadata Cluster Manager (ADCM), koji se koristi za instalaciju, konfiguraciju i nadzor različitih softverskih rješenja tvrtke. ADCM se distribuira besplatno, a njegova funkcionalnost se proširuje dodavanjem bundleova, koji su skup ansible-playbooks. Paketi su podijeljeni u dvije vrste: enterprise i community. Potonji su dostupni za besplatno preuzimanje s web stranice Arenadata. Također je moguće razviti vlastiti bundle i povezati ga s ADCM-om.

Za implementaciju i upravljanje Hadoopom 3 nudi se verzija skupa za zajednicu u kombinaciji s ADCM-om, ali za Hadoop 2 postoji samo Apaš Ambari kao alternativa. Što se tiče repozitorija s paketima, oni su otvoreni za javni pristup, mogu se preuzeti i instalirati na uobičajeni način za sve komponente klastera. Sve u svemu, distribucija izgleda vrlo zanimljivo. Siguran sam da će biti onih koji su navikli na rješenja kao što su Cloudera Manager i Ambari, a kojima će se svidjeti i sam ADCM. Za neke će i distribucija biti veliki plus uključeni u registar softvera za supstituciju uvoza.

Ako govorimo o nedostacima, oni će biti isti kao i kod svih ostalih Hadoop distribucija. Naime:

  • Takozvano "zaključavanje dobavljača". Na primjerima Cloudere i Hortonworksa već smo uvidjeli da uvijek postoji rizik promjene politike tvrtke.
  • Značajan zaostatak za Apacheom uzvodno.

Vanilla Hadoop

Apache Bigtop i odabir Hadoop distribucije danas

Kao što znate, Hadoop nije monolitni proizvod, već zapravo cijela galaksija usluga oko svog distribuiranog datotečnog sustava HDFS. Malo će ljudi imati dovoljan jedan klaster datoteka. Nekima je potreban Hive, drugima Presto, a tu su i HBase i Phoenix; Spark se sve više koristi. Za orkestraciju i učitavanje podataka ponekad se mogu pronaći Oozie, Sqoop i Flume. A ako se pojavi problem sigurnosti, odmah mi pada na pamet Kerberos u kombinaciji s Rangerom.

Binarne verzije Hadoop komponenti dostupne su na web stranici svakog od projekata ekosustava u obliku tarballova. Možete ih preuzeti i započeti instalaciju, ali uz jedan uvjet: osim samostalnog sastavljanja paketa iz "sirovih" binarnih datoteka, što najvjerojatnije želite učiniti, nećete imati nimalo povjerenja u kompatibilnost preuzetih verzija komponenti sa svakim drugo. Preferirana opcija je izrada pomoću Apache Bigtopa. Bigtop će vam omogućiti izgradnju iz Apache maven repozitorija, pokretanje testova i izgradnju paketa. No, ono što je nama jako bitno, Bigtop će sastavljati one verzije komponenti koje će biti međusobno kompatibilne. O tome ćemo detaljnije govoriti u nastavku.

Apache Bigtop

Apache Bigtop i odabir Hadoop distribucije danas

Apache Bigtop je alat za izradu, pakiranje i testiranje niza
projekti otvorenog koda, kao što su Hadoop i Greenplum. Bigtop ima dosta
oslobađanja. U vrijeme pisanja, najnovije stabilno izdanje bila je verzija 1.4,
a u master je bio 1.5. Različite verzije izdanja koriste različite verzije
komponente. Na primjer, za 1.4 Hadoop osnovne komponente imaju verziju 2.8.5, a u master
2.10.0. Sastav podržanih komponenti također se mijenja. Nešto zastarjelo i
neobnovljivo odlazi, a na njegovo mjesto dolazi nešto novo, traženije i
nije nužno nešto iz same obitelji Apača.

Osim toga, Bigtop ih ima mnogo vilice.

Kad smo se počeli upoznavati s Bigtopom, prije svega smo bili iznenađeni njegovom skromnom, u usporedbi s drugim Apache projektima, rasprostranjenošću i slavom, kao i vrlo malom zajednicom. Iz toga proizlazi da postoji minimalan broj informacija o proizvodu, a traženje rješenja za probleme koji su se pojavili na forumima i mailing listama možda neće dati baš ništa. Isprva nam se pokazalo da je težak zadatak dovršiti kompletnu montažu distribucije zbog značajki samog alata, ali o tome ćemo malo kasnije.

Za zafrkanciju, onima koji su se nekada zanimali za takve projekte Linux univerzuma kao što su Gentoo i LFS moglo bi biti nostalgično ugodno raditi s ovom stvari i prisjetiti se onih “epskih” vremena kada smo i sami tražili (ili čak pisali) ebuilds i redovito obnavlja Mozilla s novim zakrpama.

Velika prednost Bigtopa je otvorenost i svestranost alata na kojima se temelji. Temelji se na Gradleu i Apache Mavenu. Gradle je prilično poznat kao alat koji Google koristi za izradu Androida. Fleksibilan je i, kako kažu, "provjeren u borbi". Maven je standardni alat za izgradnju projekata u samom Apacheu, a budući da se većina njegovih proizvoda izdaje kroz Maven, ni ovdje se bez njega nije moglo. Vrijedno je obratiti pozornost na POM (project object model) - "temeljnu" xml datoteku koja opisuje sve što je potrebno Mavenu za rad s vašim projektom, oko kojeg se gradi sav posao. Točno u
dijelovi Mavena i postoje neke prepreke s kojima se obično susreću prvi korisnici Bigtopa.

Praksa

Dakle, gdje biste trebali početi? Idite na stranicu za preuzimanje i preuzmite najnoviju stabilnu verziju kao arhivu. Tamo također možete pronaći binarne artefakte koje je prikupio Bigtop. Usput, među uobičajenim upraviteljima paketa podržani su YUM i APT.

Alternativno, najnovije stabilno izdanje možete preuzeti izravno s
github:

$ git clone --branch branch-1.4 https://github.com/apache/bigtop.git

Kloniranje u “bigtop”…

remote: Enumerating objects: 46, done.
remote: Counting objects: 100% (46/46), done.
remote: Compressing objects: 100% (41/41), done.
remote: Total 40217 (delta 14), reused 10 (delta 1), pack-reused 40171
Получение объектов: 100% (40217/40217), 43.54 MiB | 1.05 MiB/s, готово.
Определение изменений: 100% (20503/20503), готово.
Updating files: 100% (1998/1998), готово.

Rezultirajući direktorij ./bigtop izgleda otprilike ovako:

./bigtop-bigpetstore — demo aplikacije, sintetički primjeri
./bigtop-ci — CI alat, jenkins
./bigtop-data-generators — generiranje podataka, sintetika, za ispitivanja dima, itd.
./bigtop-deploy - alati za implementaciju
./bigtop-packages — konfiguracije, skripte, zakrpe za sklapanje, glavni dio alata
./bigtop-test-framework — okvir za testiranje
./bigtop-tests — sami testovi, opterećenje i dim
./bigtop_toolchain — okolina za montažu, priprema okoline za rad alata
./build — izgraditi radni imenik
./dl — direktorij za preuzete izvore
./docker — ugradnja docker slika, testiranje
./gradle - gradle config
./output – direktorij u koji idu artefakti izgradnje
./provisioner — opskrba opskrbom

Najzanimljivija stvar za nas u ovoj fazi je glavna konfiguracija ./bigtop/bigtop.bom, u kojem vidimo sve podržane komponente s verzijama. Ovdje možemo navesti drugu verziju proizvoda (ako ga iznenada želimo pokušati izgraditi) ili verziju međugradnje (ako smo, na primjer, dodali značajnu zakrpu).

Poddirektorij je također od velikog interesa ./bigtop/bigtop-packages, što je izravno povezano s procesom sastavljanja komponenti i paketa s njima.

Dakle, preuzeli smo arhivu, raspakirali je ili napravili klon s githuba, možemo li početi graditi?

Ne, prvo pripremimo okolinu.

Priprema okoliša

I ovdje nam treba mali odstupnica. Za izradu gotovo bilo kojeg više ili manje složenog proizvoda potrebno je određeno okruženje - u našem slučaju to je JDK, iste zajedničke knjižnice, datoteke zaglavlja itd., Alati, na primjer, ant, ivy2 i još mnogo toga. Jedna od opcija za dobivanje okruženja koje vam je potrebno za Bigtop je instaliranje potrebnih komponenti na hostu za izgradnju. Možda griješim u kronologiji, ali čini se da je s verzijom 1.0 postojala i opcija za ugradnju unaprijed konfiguriranih i dostupnih Docker slika, koje se mogu pronaći ovdje.

Što se tiče pripreme okoline, za to postoji pomoćnik - Lutka.

Možete koristiti sljedeće naredbe, pokrenuti iz korijenskog direktorija
alat, ./bigtop:

./gradlew toolchain
./gradlew toolchain-devtools
./gradlew toolchain-puppetmodules

Ili izravno putem lutke:

puppet apply --modulepath=<path_to_bigtop> -e "include bigtop_toolchain::installer"
puppet apply --modulepath=<path_to_bigtop> -e "include bigtop_toolchain::deployment-tools"
puppet apply --modulepath=<path_to_bigtop> -e "include bigtop_toolchain::development-tools"

Nažalost, već u ovoj fazi mogu se pojaviti poteškoće. Općeniti savjet ovdje je da koristite podržanu distribuciju, ažurnu na hostu za izgradnju, ili isprobajte docker rutu.

zbor

Što možemo pokušati prikupiti? Odgovor na ovo pitanje dati će izlaz naredbe

./gradlew tasks

U odjeljku Paket zadataka nalazi se niz proizvoda koji su konačni artefakti Bigtopa.
Mogu se identificirati sufiksom -rpm ili -pkg-ind (u slučaju zgrade
u dokeru). U našem slučaju najzanimljiviji je Hadoop.

Pokušajmo graditi u okruženju našeg poslužitelja za izgradnju:

./gradlew hadoop-rpm

Bigtop će sam preuzeti potrebne izvore potrebne za određenu komponentu i započeti montažu. Dakle, rad alata ovisi o Maven repozitoriju i drugim izvorima, odnosno zahtijeva pristup internetu.

Tijekom rada generira se standardni izlaz. Ponekad vam to i poruke o pogrešci mogu pomoći da shvatite što je pošlo po zlu. A ponekad trebate dobiti dodatne informacije. U ovom slučaju vrijedi dodati argumente --info ili --debug, a također može biti korisno –stacktrace. Postoji prikladan način za generiranje skupa podataka za kasniji pristup listama za slanje e-pošte, ključ --scan.

Bigtop će uz njegovu pomoć prikupiti sve informacije i staviti ih u gradle, nakon čega će dati link,
prateći koje će kompetentna osoba moći shvatiti zašto je montaža propala.
Imajte na umu da ova opcija može otkriti informacije koje ne želite, kao što su korisnička imena, čvorovi, varijable okruženja itd., stoga budite oprezni.

Često su pogreške posljedica nemogućnosti nabave bilo koje komponente potrebne za montažu. Obično problem možete riješiti stvaranjem zakrpe za popravak nečega u izvorima, na primjer, adrese u pom.xml u korijenskom direktoriju izvora. To se postiže stvaranjem i postavljanjem u odgovarajući imenik ./bigtop/bigtop-packages/src/common/oozie/ patch, na primjer, u obliku patch2-fix.diff.

--- a/pom.xml
+++ b/pom.xml
@@ -136,7 +136,7 @@
<repositories>
<repository>
<id>central</id>
- <url>http://repo1.maven.org/maven2</url>
+ <url>https://repo1.maven.org/maven2</url>
<snapshots>
<enabled>false</enabled>
</snapshots>

Najvjerojatnije, u vrijeme čitanja ovog članka, nećete morati sami izvršiti gornji popravak.

Prilikom uvođenja bilo kakvih zakrpa i promjena u mehanizam sklopa, možda ćete morati "resetirati" sklop pomoću naredbe čišćenja:

./gradlew hadoop-clean
> Task :hadoop_vardefines
> Task :hadoop-clean
BUILD SUCCESSFUL in 5s
2 actionable tasks: 2 executed

Ovom operacijom vratit će se sve promjene na sklop ove komponente, nakon čega će se sklop ponovno izvršiti. Ovaj put ćemo pokušati izgraditi projekt u docker slici:

./gradlew -POS=centos-7 -Pprefix=1.2.1 hadoop-pkg-ind
> Task :hadoop-pkg-ind
Building 1.2.1 hadoop-pkg on centos-7 in Docker...
+++ dirname ./bigtop-ci/build.sh
++ cd ./bigtop-ci/..
++ pwd
+ BIGTOP_HOME=/tmp/bigtop
+ '[' 6 -eq 0 ']'
+ [[ 6 -gt 0 ]]
+ key=--prefix
+ case $key in
+ PREFIX=1.2.1
+ shift
+ shift
+ [[ 4 -gt 0 ]]
+ key=--os
+ case $key in
+ OS=centos-7
+ shift
+ shift
+ [[ 2 -gt 0 ]]
+ key=--target
+ case $key in
+ TARGET=hadoop-pkg
+ shift
+ shift
+ [[ 0 -gt 0 ]]
+ '[' -z x ']'
+ '[' -z x ']'
+ '[' '' == true ']'
+ IMAGE_NAME=bigtop/slaves:1.2.1-centos-7
++ uname -m
+ ARCH=x86_64
+ '[' x86_64 '!=' x86_64 ']'
++ docker run -d bigtop/slaves:1.2.1-centos-7 /sbin/init
+
CONTAINER_ID=0ce5ac5ca955b822a3e6c5eb3f477f0a152cd27d5487680f77e33fbe66b5bed8
+ trap 'docker rm -f
0ce5ac5ca955b822a3e6c5eb3f477f0a152cd27d5487680f77e33fbe66b5bed8' EXIT
....
много вывода
....
Wrote: /bigtop/build/hadoop/rpm/RPMS/x86_64/hadoop-2.8.5-1.el7.x86_64.rpm
Wrote: /bigtop/build/hadoop/rpm/RPMS/x86_64/hadoop-hdfs-2.8.5-1.el7.x86_64.rpm
Wrote: /bigtop/build/hadoop/rpm/RPMS/x86_64/hadoop-yarn-2.8.5-1.el7.x86_64.rpm
Wrote: /bigtop/build/hadoop/rpm/RPMS/x86_64/hadoop-mapreduce-2.8.5-1.el7.x86_64.rpm
Wrote: /bigtop/build/hadoop/rpm/RPMS/x86_64/hadoop-hdfs-namenode-2.8.5-1.el7.x86_64.rpm
Wrote: /bigtop/build/hadoop/rpm/RPMS/x86_64/hadoop-hdfs-secondarynamenode-2.8.5-
1.el7.x86_64.rpm
Wrote: /bigtop/build/hadoop/rpm/RPMS/x86_64/hadoop-hdfs-zkfc-2.8.5-1.el7.x86_64.rpm
Wrote: /bigtop/build/hadoop/rpm/RPMS/x86_64/hadoop-hdfs-journalnode-2.8.5-
1.el7.x86_64.rpm
Wrote: /bigtop/build/hadoop/rpm/RPMS/x86_64/hadoop-hdfs-datanode-2.8.5-1.el7.x86_64.rpm
Wrote: /bigtop/build/hadoop/rpm/RPMS/x86_64/hadoop-httpfs-2.8.5-1.el7.x86_64.rpm
Wrote: /bigtop/build/hadoop/rpm/RPMS/x86_64/hadoop-yarn-resourcemanager-2.8.5-
1.el7.x86_64.rpm
Wrote: /bigtop/build/hadoop/rpm/RPMS/x86_64/hadoop-yarn-nodemanager-2.8.5-
1.el7.x86_64.rpm
Wrote: /bigtop/build/hadoop/rpm/RPMS/x86_64/hadoop-yarn-proxyserver-2.8.5-
1.el7.x86_64.rpm
Wrote: /bigtop/build/hadoop/rpm/RPMS/x86_64/hadoop-yarn-timelineserver-2.8.5-
1.el7.x86_64.rpm
Wrote: /bigtop/build/hadoop/rpm/RPMS/x86_64/hadoop-mapreduce-historyserver-2.8.5-
1.el7.x86_64.rpm
Wrote: /bigtop/build/hadoop/rpm/RPMS/x86_64/hadoop-client-2.8.5-1.el7.x86_64.rpm
Wrote: /bigtop/build/hadoop/rpm/RPMS/x86_64/hadoop-conf-pseudo-2.8.5-1.el7.x86_64.rpm
Wrote: /bigtop/build/hadoop/rpm/RPMS/x86_64/hadoop-doc-2.8.5-1.el7.x86_64.rpm
Wrote: /bigtop/build/hadoop/rpm/RPMS/x86_64/hadoop-libhdfs-2.8.5-1.el7.x86_64.rpm
Wrote: /bigtop/build/hadoop/rpm/RPMS/x86_64/hadoop-libhdfs-devel-2.8.5-1.el7.x86_64.rpm
Wrote: /bigtop/build/hadoop/rpm/RPMS/x86_64/hadoop-hdfs-fuse-2.8.5-1.el7.x86_64.rpm
Wrote: /bigtop/build/hadoop/rpm/RPMS/x86_64/hadoop-debuginfo-2.8.5-1.el7.x86_64.rpm
+ umask 022
+ cd /bigtop/build/hadoop/rpm//BUILD
+ cd hadoop-2.8.5-src
+ /usr/bin/rm -rf /bigtop/build/hadoop/rpm/BUILDROOT/hadoop-2.8.5-1.el7.x86_64
Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.uQ2FCn
+ exit 0
+ umask 022
Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.CwDb22
+ cd /bigtop/build/hadoop/rpm//BUILD
+ rm -rf hadoop-2.8.5-src
+ exit 0
[ant:touch] Creating /bigtop/build/hadoop/.rpm
:hadoop-rpm (Thread[Task worker for ':',5,main]) completed. Took 38 mins 1.151 secs.
:hadoop-pkg (Thread[Task worker for ':',5,main]) started.
> Task :hadoop-pkg
Task ':hadoop-pkg' is not up-to-date because:
Task has not declared any outputs despite executing actions.
:hadoop-pkg (Thread[Task worker for ':',5,main]) completed. Took 0.0 secs.
BUILD SUCCESSFUL in 40m 37s
6 actionable tasks: 6 executed
+ RESULT=0
+ mkdir -p output
+ docker cp
ac46014fd9501bdc86b6c67d08789fbdc6ee46a2645550ff6b6712f7d02ffebb:/bigtop/build .
+ docker cp
ac46014fd9501bdc86b6c67d08789fbdc6ee46a2645550ff6b6712f7d02ffebb:/bigtop/output .
+ docker rm -f ac46014fd9501bdc86b6c67d08789fbdc6ee46a2645550ff6b6712f7d02ffebb
ac46014fd9501bdc86b6c67d08789fbdc6ee46a2645550ff6b6712f7d02ffebb
+ '[' 0 -ne 0 ']'
+ docker rm -f ac46014fd9501bdc86b6c67d08789fbdc6ee46a2645550ff6b6712f7d02ffebb
Error: No such container:
ac46014fd9501bdc86b6c67d08789fbdc6ee46a2645550ff6b6712f7d02ffebb
BUILD SUCCESSFUL in 41m 24s
1 actionable task: 1 executed

Gradnja je izvedena pod CentOS-om, ali se može napraviti i pod Ubuntuom:

./gradlew -POS=ubuntu-16.04 -Pprefix=1.2.1 hadoop-pkg-ind

Osim izgradnje paketa za razne distribucije Linuxa, alat može stvoriti repozitorij s kompiliranim paketima, na primjer:

./gradlew yum

Također se možete sjetiti testova dima i implementacije u Dockeru.

Napravite klaster od tri čvora:

./gradlew -Pnum_instances=3 docker-provisioner

Pokrenite testove dima u grupi od tri čvora:

./gradlew -Pnum_instances=3 -Prun_smoke_tests docker-provisioner

Brisanje klastera:

./gradlew docker-provisioner-destroy

Dobijte naredbe za povezivanje unutar docker spremnika:

./gradlew docker-provisioner-ssh

Prikaži status:

./gradlew docker-provisioner-status

Više o zadacima postavljanja možete pročitati u dokumentaciji.

Ako govorimo o testovima, ima ih prilično velik broj, uglavnom dim i integracija. Njihova analiza je izvan okvira ovog članka. Samo da kažem da sastavljanje distribucijskog kompleta nije tako težak zadatak kao što se na prvi pogled čini. Uspjeli smo sastaviti i proći testove na svim komponentama koje koristimo u našoj proizvodnji, a također nismo imali problema s njihovom implementacijom i izvođenjem osnovnih operacija u testnom okruženju.

Uz postojeće komponente u Bigtop je moguće dodati bilo što drugo, čak i vlastiti razvoj softvera. Sve je to savršeno automatizirano i uklapa se u CI/CD koncept.

Zaključak

Očito, ovako sastavljena distribucija ne bi trebala biti odmah poslana u proizvodnju. Morate shvatiti da ako postoji stvarna potreba za izgradnjom i podrškom vaše distribucije, tada trebate uložiti novac i vrijeme u to.

No, u kombinaciji s pravim pristupom i stručnim timom, sasvim je moguće proći i bez komercijalnih rješenja.

Važno je napomenuti da je samom projektu Bigtop potreban razvoj i čini se da se danas ne razvija aktivno. Nejasna je i mogućnost da se u njemu pojavi Hadoop 3. Usput, ako imate stvarnu potrebu izgraditi Hadoop 3, možete pogledati vilica iz Arenadata, u kojem se uz standard
Postoji niz dodatnih komponenti (Ranger, Knox, NiFi).

Što se tiče Rostelecoma, za nas je Bigtop jedna od opcija koja se danas razmatra. Hoćemo li to izabrati ili ne, vrijeme će pokazati.

Dodatak

Da biste uključili novu komponentu u sklop, trebate dodati njen opis u bigtop.bom i ./bigtop-packages. Možete pokušati to učiniti po analogiji s postojećim komponentama. Pokušajte to shvatiti. Nije tako teško kao što se čini na prvi pogled.

Što misliš? Bit će nam drago vidjeti vaše mišljenje u komentarima i hvala vam na pažnji!

Članak je pripremio tim za upravljanje podacima Rostelecoma

Izvor: www.habr.com

Dodajte komentar