Apache Bigtop e la scelta di una distribuzione Hadoop oggi

Apache Bigtop e la scelta di una distribuzione Hadoop oggi

Probabilmente non è un segreto che l'anno scorso sia stato un anno di grandi cambiamenti per Apache Hadoop. L'anno scorso Cloudera e Hortonworks si sono fuse (sostanzialmente l'acquisizione di quest'ultima) e Mapr, a causa di gravi problemi finanziari, è stata venduta a Hewlett Packard. E se qualche anno prima, nel caso delle installazioni on-premise, spesso si doveva fare la scelta tra Cloudera e Hortonworks, oggi, ahimè, non abbiamo questa scelta. Un'altra sorpresa è stata il fatto che Cloudera ha annunciato nel febbraio di quest'anno che non avrebbe più rilasciato gli assembly binari della sua distribuzione nell'archivio pubblico e che ora sono disponibili solo tramite un abbonamento a pagamento. Naturalmente è ancora possibile scaricare le ultime versioni di CDH e HDP rilasciate prima della fine del 2019 e il loro supporto è previsto per uno o due anni. Ma cosa fare dopo? Per chi ha già pagato un abbonamento non è cambiato nulla. E per coloro che non vogliono passare alla versione a pagamento della distribuzione, ma allo stesso tempo vogliono poter ricevere le ultime versioni dei componenti del cluster, oltre a patch e altri aggiornamenti, abbiamo preparato questo articolo. In esso considereremo le possibili opzioni per uscire da questa situazione.

L'articolo è più una recensione. Non conterrà un confronto delle distribuzioni e un'analisi dettagliata delle stesse, e non ci saranno ricette per installarle e configurarle. Cosa accadrà? Parleremo brevemente di una distribuzione come Arenadata Hadoop, che merita giustamente la nostra attenzione per la sua accessibilità, oggi molto rara. E poi parleremo di Vanilla Hadoop, principalmente di come può essere “cucinato” utilizzando Apache Bigtop. Pronto? Allora benvenuto a cat.

Arenadata Hadoop

Apache Bigtop e la scelta di una distribuzione Hadoop oggi

Si tratta di un kit di distribuzione dello sviluppo domestico completamente nuovo e ancora poco conosciuto. Purtroppo al momento su Habré c'è solo questo articolo.

Maggiori informazioni possono essere trovate sul sito ufficiale sito web progetto. Le ultime versioni della distribuzione sono basate su Hadoop 3.1.2 per la versione 3 e 2.8.5 per la versione 2.

È possibile trovare informazioni sulla tabella di marcia qui.

Apache Bigtop e la scelta di una distribuzione Hadoop oggi
Interfaccia di gestione cluster Arenadata

Il prodotto principale di Arenadata è Gestore cluster Arenadata (ADCM), che viene utilizzato per installare, configurare e monitorare varie soluzioni software aziendali. ADCM è distribuito gratuitamente e le sue funzionalità vengono ampliate aggiungendo bundle, che sono un insieme di playbook ansible. I bundle si dividono in due tipologie: enterprise e community. Questi ultimi sono disponibili per il download gratuito dal sito Arenadata. È anche possibile sviluppare il proprio bundle e collegarlo ad ADCM.

Per la distribuzione e la gestione di Hadoop 3, viene offerta una versione comunitaria del bundle insieme ad ADCM, ma per Hadoop 2 è disponibile solo apache ambari come alternativa. Per quanto riguarda i repository con pacchetti, sono aperti all'accesso pubblico, possono essere scaricati e installati come di consueto per tutti i componenti del cluster. Nel complesso, la distribuzione sembra molto interessante. Sono sicuro che ci sarà chi è abituato a soluzioni come Cloudera Manager e Ambari e a chi piacerà lo stesso ADCM. Per alcuni, sarà anche un enorme vantaggio rispetto alla distribuzione incluso nel registro del software per la sostituzione delle importazioni.

Se parliamo degli svantaggi, saranno gli stessi di tutte le altre distribuzioni Hadoop. Vale a dire:

  • Il cosiddetto “vendor lock-in”. Usando gli esempi di Cloudera e Hortonworks, abbiamo già capito che esiste sempre il rischio di cambiare la politica aziendale.
  • Ritardo significativo rispetto a Apache upstream.

Hadoop alla vaniglia

Apache Bigtop e la scelta di una distribuzione Hadoop oggi

Come sapete, Hadoop non è un prodotto monolitico, ma, in realtà, un'intera galassia di servizi attorno al suo file system distribuito HDFS. Poche persone ne avranno abbastanza di un cluster di file. Alcuni hanno bisogno di Hive, altri di Presto, e poi ci sono HBase e Phoenix; Spark è sempre più utilizzato. Per l'orchestrazione e il caricamento dei dati, a volte si trovano Oozie, Sqoop e Flume. E se sorge la questione della sicurezza, viene subito in mente Kerberos insieme a Ranger.

Sul sito web di ciascuno dei progetti dell'ecosistema sono disponibili versioni binarie dei componenti Hadoop sotto forma di tarball. Puoi scaricarli e iniziare l'installazione, ma a una condizione: oltre ad assemblare in modo indipendente i pacchetti dai binari "grezzi", cosa che molto probabilmente vorrai fare, non avrai alcuna fiducia nella compatibilità delle versioni scaricate dei componenti con ciascuno altro. L'opzione preferita è creare utilizzando Apache Bigtop. Bigtop ti consentirà di creare da repository Apache Maven, eseguire test e creare pacchetti. Ma, cosa molto importante per noi, Bigtop assemblerà quelle versioni di componenti che saranno compatibili tra loro. Ne parleremo più dettagliatamente di seguito.

Apache Bigtop

Apache Bigtop e la scelta di una distribuzione Hadoop oggi

Apache Bigtop è uno strumento per creare, creare pacchetti e testare numerosi file
progetti open source, come Hadoop e Greenplum. Bigtop ne ha molti
rilascia. Al momento in cui scrivo, l'ultima versione stabile era la versione 1.4,
e nel master c'era 1.5. Versioni diverse dei rilasci utilizzano versioni diverse
componenti. Ad esempio, per 1.4 i componenti principali di Hadoop hanno la versione 2.8.5 e in master
2.10.0. Anche la composizione dei componenti supportati sta cambiando. Qualcosa di obsoleto e
l'irrinnovabile se ne va e al suo posto arriva qualcosa di nuovo, più richiesto, e
non è necessariamente qualcosa della stessa famiglia Apache.

Inoltre, Bigtop ne ha molti forchette.

Quando abbiamo iniziato a conoscere Bigtop, siamo rimasti sorpresi innanzitutto dalla sua modestia, rispetto ad altri progetti Apache, prevalenza e popolarità, nonché da una comunità molto piccola. Ne consegue che le informazioni sul prodotto sono minime e la ricerca di soluzioni ai problemi sorti nei forum e nelle mailing list potrebbe non portare a nulla. All'inizio si è rivelato un compito difficile per noi completare l'assemblaggio completo della distribuzione a causa delle caratteristiche dello strumento stesso, ma ne parleremo più avanti.

Come teaser, coloro che un tempo erano interessati a progetti dell'universo Linux come Gentoo e LFS potrebbero trovare nostalgicamente piacevole lavorare con questa cosa e ricordare quei tempi "epici" in cui noi stessi cercavamo (o addirittura scrivevamo) ebuild e ricostruisce regolarmente Mozilla con nuove patch.

Il grande vantaggio di Bigtop è l'apertura e la versatilità degli strumenti su cui si basa. È basato su Gradle e Apache Maven. Gradle è abbastanza noto come lo strumento utilizzato da Google per creare Android. È flessibile e, come si dice, “testato in battaglia”. Maven è uno strumento standard per la creazione di progetti nello stesso Apache e, poiché la maggior parte dei suoi prodotti vengono rilasciati tramite Maven, non si potrebbe farne a meno anche qui. Vale la pena prestare attenzione al POM (modello a oggetti del progetto): il file xml "fondamentale" che descrive tutto il necessario affinché Maven possa lavorare con il tuo progetto, attorno al quale è costruito tutto il lavoro. Esattamente alle
parti di Maven e ci sono alcuni ostacoli che di solito incontrano gli utenti di Bigtop per la prima volta.

Pratica

Allora da dove dovresti iniziare? Vai alla pagina di download e scarica l'ultima versione stabile come archivio. Qui puoi anche trovare artefatti binari raccolti da Bigtop. A proposito, tra i gestori di pacchetti comuni, sono supportati YUM e APT.

In alternativa, puoi scaricare l'ultima versione stabile direttamente da
Github:

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

Clonazione in “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), готово.

La directory ./bigtop risultante è simile alla seguente:

./bigtop-bigpetstore — applicazioni dimostrative, esempi sintetici
./bigtop-ci - Strumenti CI, Jenkins
./bigtop-data-generators — generazione di dati, sintetici, per prove di fumo, ecc.
./bigtop-deploy - strumenti di distribuzione
./bigtop-packages — configurazioni, script, patch per l'assemblaggio, la parte principale dello strumento
./bigtop-test-framework — quadro di prova
./bigtop-tests — i test stessi, caricano e fumano
./bigtop_toolchain — ambiente per l'assemblaggio, preparazione dell'ambiente affinché lo strumento possa funzionare
./build - crea la directory di lavoro
./dl — directory per le fonti scaricate
./docker - creazione di immagini docker, test
./gradle - configurazione graduale
./output – la directory in cui vanno gli artefatti di compilazione
./provisioner - approvvigionamento

La cosa più interessante per noi in questa fase è la configurazione principale ./bigtop/bigtop.bom, in cui vediamo tutti i componenti supportati con le versioni. Qui è dove possiamo specificare una versione diversa del prodotto (se improvvisamente vogliamo provare a costruirlo) o una versione build (se, ad esempio, abbiamo aggiunto una patch significativa).

Anche la sottodirectory è di grande interesse ./bigtop/bigtop-packages, che è direttamente correlato al processo di assemblaggio di componenti e pacchetti con essi.

Quindi, abbiamo scaricato l'archivio, lo abbiamo scompattato o creato un clone da github, possiamo iniziare a costruire?

No, prepariamo prima l'ambiente.

Preparare l'ambiente

E qui abbiamo bisogno di un piccolo ritiro. Per costruire quasi tutti i prodotti più o meno complessi, è necessario un determinato ambiente: nel nostro caso si tratta di JDK, le stesse librerie condivise, file header, ecc., Strumenti, ad esempio, ant, ivy2 e molto altro. Una delle opzioni per ottenere l'ambiente necessario per Bigtop è installare i componenti necessari sull'host di build. Potrei sbagliarmi nella cronologia, ma sembra che con la versione 1.0 ci fosse anche un'opzione per creare immagini Docker preconfigurate e accessibili, che può essere trovata qui.

Per quanto riguarda la preparazione dell'ambiente, c'è un assistente per questo: Puppet.

È possibile utilizzare i seguenti comandi, eseguiti dalla directory root
attrezzo, ./bigtop:

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

Oppure direttamente tramite pupazzo:

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"

Sfortunatamente, già in questa fase potrebbero sorgere delle difficoltà. Il consiglio generale qui è di utilizzare una distribuzione supportata, aggiornata sull'host di compilazione o provare il percorso docker.

montaggio

Cosa possiamo provare a raccogliere? La risposta a questa domanda sarà data dall'output del comando

./gradlew tasks

Nella sezione Attività del pacchetto sono presenti una serie di prodotti che sono artefatti finali di Bigtop.
Possono essere identificati dal suffisso -rpm oppure -pkg-ind (nel caso di building
nella finestra mobile). Nel nostro caso, il più interessante è Hadoop.

Proviamo a costruire nell'ambiente del nostro server di build:

./gradlew hadoop-rpm

Bigtop stesso scaricherà le fonti necessarie per un componente specifico e inizierà l'assemblaggio. Pertanto, il funzionamento dello strumento dipende dai repository Maven e da altre fonti, ovvero richiede l'accesso a Internet.

Durante il funzionamento viene generato un output standard. A volte questo e i messaggi di errore possono aiutarti a capire cosa è andato storto. E a volte è necessario ottenere ulteriori informazioni. In questo caso vale la pena aggiungere argomenti --info o --debug, e potrebbe anche essere utile –stacktrace. Esiste un modo conveniente per generare un set di dati per il successivo accesso alle mailing list, la chiave --scan.

Con il suo aiuto, bigtop raccoglierà tutte le informazioni e le inserirà in Gradle, dopodiché fornirà un collegamento,
in seguito a ciò una persona competente potrà comprendere il motivo per cui l'assemblea è fallita.
Tieni presente che questa opzione potrebbe esporre informazioni indesiderate, come nomi utente, nodi, variabili di ambiente, ecc., quindi fai attenzione.

Spesso gli errori sono una conseguenza dell'impossibilità di ottenere i componenti necessari per l'assemblaggio. In genere, è possibile risolvere il problema creando una patch per correggere qualcosa nei sorgenti, ad esempio gli indirizzi in pom.xml nella directory root dei sorgenti. Questo viene fatto creandolo e inserendolo nella directory appropriata ./bigtop/bigtop-packages/src/common/oozie/ patch, ad esempio, nel modulo 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>

Molto probabilmente, al momento della lettura di questo articolo, non dovrai eseguire tu stesso la soluzione sopra descritta.

Quando si introducono patch e modifiche al meccanismo dell'assembly, potrebbe essere necessario "reimpostare" l'assembly utilizzando il comando cleanup:

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

Questa operazione ripristinerà tutte le modifiche apportate all'assemblaggio di questo componente, dopodiché l'assemblaggio verrà eseguito nuovamente. Questa volta proveremo a costruire il progetto in un'immagine docker:

./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

La build è stata eseguita sotto CentOS, ma può essere eseguita anche sotto Ubuntu:

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

Oltre a creare pacchetti per varie distribuzioni Linux, lo strumento può creare un repository con pacchetti compilati, ad esempio:

./gradlew yum

Puoi anche ricordare i test del fumo e la distribuzione in Docker.

Crea un cluster di tre nodi:

./gradlew -Pnum_instances=3 docker-provisioner

Esegui test del fumo in un cluster di tre nodi:

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

Elimina un cluster:

./gradlew docker-provisioner-destroy

Ottieni i comandi per la connessione all'interno dei contenitori docker:

./gradlew docker-provisioner-ssh

Mostra stato:

./gradlew docker-provisioner-status

Puoi leggere ulteriori informazioni sulle attività di distribuzione nella documentazione.

Se parliamo di test, ce ne sono molti, principalmente fumo e integrazione. La loro analisi va oltre lo scopo di questo articolo. Lasciatemi solo dire che assemblare un kit di distribuzione non è un compito così difficile come potrebbe sembrare a prima vista. Siamo riusciti ad assemblare e superare i test su tutti i componenti che utilizziamo nella nostra produzione e non abbiamo avuto problemi nemmeno a distribuirli e ad eseguire le operazioni di base nell'ambiente di test.

Oltre ai componenti esistenti in Bigtop, è possibile aggiungere qualsiasi altra cosa, anche lo sviluppo del proprio software. Tutto questo è perfettamente automatizzato e si inserisce nel concetto CI/CD.

conclusione

Ovviamente la distribuzione così compilata non dovrà essere mandata subito in produzione. Devi capire che se c'è una reale necessità di costruire e supportare la tua distribuzione, allora devi investire tempo e denaro in questo.

Tuttavia, in combinazione con il giusto approccio e un team di professionisti, è del tutto possibile fare a meno delle soluzioni commerciali.

È importante notare che il progetto Bigtop stesso necessita di sviluppo e non sembra essere sviluppato attivamente oggi. Non è chiara nemmeno la prospettiva che appaia Hadoop 3. A proposito, se hai davvero bisogno di costruire Hadoop 3, puoi guardare forchetta da Arenadata, in cui, oltre allo standard
Esistono numerosi componenti aggiuntivi (Ranger, Knox, NiFi).

Per quanto riguarda Rostelecom, per noi Bigtop è una delle opzioni prese in considerazione oggi. Che lo scegliamo o no, lo dirà il tempo.

Appendice

Per includere un nuovo componente nell'assieme, è necessario aggiungere la sua descrizione a bigtop.bom e ./bigtop-packages. Puoi provare a farlo per analogia con i componenti esistenti. Prova a capirlo. Non è così difficile come sembra a prima vista.

Cosa ne pensi? Saremo lieti di vedere la tua opinione nei commenti e ti ringraziamo per la tua attenzione!

L'articolo è stato preparato dal team di gestione dei dati di Rostelecom

Fonte: habr.com

Aggiungi un commento