A teljes projekt forrását elérhetővé tettem, így az első lépés a forrás megszerzése, és a Vagrant gépek elindítása. Az egyes gépek között 4.5 Gb memóriát osztottam szét (+oprendszer, +vagrant,+a böngésző amiben olvasod a cikket), amennyiben nincs elegendő memória, kézzel kell a főbb gépeket elindítani. Türelmetlenek a cassandra.sh fájlban letilthatják a Cassandra konténerek építését, egy darabig úgysem lesz rájuk szükség.
git clone https://github.com/mhmxs/vagrant-host-hadoop-cassadra-cluster.git cd vagrant-host-hadoop-cassadra-cluster git submodule update --init cd hadoop-docker && git checkout 2.6.0-static-ip cd .. && git checkout 2.6.0-static-ip vagrant upAmíg a letöltés, telepítés, és egyéb gyártási folyamatok zajlanak kukkantsunk kicsit a motor háztető alá, lesz rá időnk bőven, ugyanis ezen a ponton nem sokat optimalizáltam, így sok tartalom többször is letöltésre kerül az egyes virtuális gépeken (javaslatokat várom a hozzászólásban). Íme a vagrant konfiguráció:
config.vm.box = "ubuntu/vivid64" config.vm.provider "virtualbox" do |v| v.memory = 1024 end config.vm.box_check_update = false config.vm.define "slave1" do |slave| slave.vm.network "private_network", ip: "192.168.50.1" slave.vm.provision :shell, inline: "hostname slave1 && sh /vagrant/cassandra.sh" end config.vm.define "slave2" do |slave| slave.vm.network "private_network", ip: "192.168.50.2" slave.vm.provision :shell, inline: "hostname slave2 && sh /vagrant/cassandra.sh" end config.vm.define "slave3" do |slave| slave.vm.network "private_network", ip: "192.168.50.3" slave.vm.provision :shell, inline: "hostname slave3 && sh /vagrant/cassandra.sh" end config.vm.define "master" do |master| master.vm.network "private_network", ip: "192.168.50.4" config.vm.provision :shell, inline: "hostname master && sh /vagrant/hadoop.sh" config.vm.provider :virtualbox do |vb| vb.customize ["modifyvm", :id, "--memory", "1536"] end endTehát van 4 gépünk egy hálózaton, egy mester és 3 szolga. A host név beállítás sajnos nem perzisztens, de a hibát már csak a következő verzióban javítottam, elnézést. A gépek száma tovább növelhető, az ököl szabály, hogy ami 3 gépen működik, az jó eséllyel működik többön is, ezért én a minimális darabszámot preferáltam. Elég sok leírást olvastam végig a Hadoop fürtök konfigurálásáról, és azonnal meg is jegyezném az egyik legnagyobb problémát az ökoszisztémával kapcsolatban. Olyan ütemben fejlődik a platform, hogy amit ma megírtam, az lehet, hogy holnap már nem is érvényes, vonatkozik ez az architektúrára (természetesen nem messziről szemlélve, az ördög a részletekben lakik), a beállításokra, és a fellépő hibákra is. Számtalan esetben az általam használt 2.6.0-ás verziójú Hadoop komponensben már nem is létezett az a kapcsoló, amivel javasolták az elcsípett kivétel kezelését, és kivétel akadt bőven.
Vegyük sorra milyen változtatásokat eszközöltem a beállításokban, de előtte tisztázzunk pár paramétert:
- konténerek minimum memória foglalása: 4 Gb memória alatt 256 Mb az ajánlott
- konténerek száma: min (2 * processzor magok, 1.8 * merevlemezek száma, Összes memória / konténer minimum memória foglalásával) - min(2, 1.8, 1024 / 256) = 2 egy kis csalással
- rendszernek fenntartott memória: 4 Gb esetén 1 Gb, ennél kisebb értékre nincs ajánlás, így én az 512 Mb-ot választottam
- összes felhasználható memória: rendelkezésre álló - fenntartott, 1024 - 512 = 512 Mb (gépenként 2 konténer)
Dockerfile
- Telepítettem a Cassandrát
- Mivel a Cassandra függősége az Open JDK 8, ezért eredeti Java konfigurációt eltávolítottam
- Közös RSA kulcsot generáltam, hogy a konténerek között megoldható legyen a bejelentkezés jelszó nélkül
- A Hadoop classpathjára betettem a Cassandrát
- Pár további portot kitettem, exposoltam magyarul
- Kicsit átrendeztem a parancsok sorrendjét, hogy minél gyorsabban lehessen konfigurációt változtatni, hála a rétegelt fájl-rendszernek a Docker csak a különbségig görgeti vissza a konténert, és onnan folytatja az építési műveletet tovább
- fs.defaultFS: hdfs://master:9000 - a master gépen fog futni a NameNode, és ezt jó, ha mindenkinek tudja
- dfs.replication: 2 - a fájlrendszer replikációs faktorát emeltem meg 2-re
- dfs.client.use.datanode.hostname: true - a dfs kliensek alap értelmezetten IP alapján kommunikálnak, ha több interfészünk, vagy több hálózati rétegünk van, akkor ez a működés okozhat fejfájást
- dfs.datanode.use.datanode.hostname: true - szintén zenész
- dfs.namenode.secondary.http-address: master:50090 - a másodlagos NameNode elérhetősége
- dfs.namenode.http-address: master:50070 - az elsődleges NameNode elérhetősége
- mapreduce.jobtracker.address: master:8021 - szükségünk van egy JobTrackerre
- mapreduce.map.memory.mb: 256 - map műveletet végző konténer memória foglalása
- mapreduce.reduce.memory.mb: 512 - és a reducet végzőké
- yarn.app.mapreduce.am.resource.mb: 512 - az AppMaster által felhasználható memória
- yarn.app.mapreduce.am.job.client.port-range: 50200-50201 - alapértelmezetten un. ephemeral portot használ az AppMaster, az egyszerűség (tűzfal, port publikálás, stb.) kedvéért rögzítettem a tartományt
- yarn.app.mapreduce.am.command-opts: -Xmx409m - AppMaster processz konfigurációja, ez okozott egy kis fejfájást, végül úgy találtam meg a problémát, hogy az eredeti forrásra egyesével rápakoltam a változtatásaimat, jó móka volt
- mapreduce.jobhistory.address: master:10020 - elosztott környezetben szükség van JobHistory szerverre
- mapreduce.jobhistory.webapp.address: master:19888 - igény esetén webes felületre is
- mapreduce.application.classpath: ..., /usr/share/cassandra/*, /usr/share/cassandra/lib/* - az alapértelmezett útvonalakhoz hozzáadtam a Cassandrát
- yarn.resourcemanager.hostname: master
- yarn.nodemanager.vmem-check-enabled: false - Centos 6 specifikus hiba jött elő, amit a virtuális memória ellenőrzésének kikapcsolásával tudtam megoldani. Bővebben a hibáról (6. Killing of Tasks Due to Virtual Memory Usage).
- yarn.nodemanager.resource.cpu-vcores: 2 - a virtuális processzorok számát csökkentettem, így gépenként csak 2 osztható szét a konténerek között
- yarn.nodemanager.resource.memory-mb: 512 - az a memória, amivel a helyi NodeManager rendelkezik
- yarn.scheduler.minimum-allocation-mb: 256 - minimális memória foglalás konténerenként
- yarn.scheduler.maximum-allocation-mb: 512 - és a maximális párja
- yarn.nodemanager.address: ${yarn.nodemanager.hostname}:36123 - a konténer menedzser címe, alapértelmezetten a NodeManager választ portot magának, szintén specifikáltam
- yarn.application.classpath: ..., /usr/share/cassandra/*, /usr/share/cassandra/lib/* - a Cassandrát elérhetővé tettem a Yarn számára
echo $SLAVES | sed "s/,/\n/g" > /tmp/slaves while read line; do printf "\n$line" >> /etc/hosts; done < /tmp/slaves if [ -z "$(cat /etc/hosts | grep master)" ]; then printf "\n$MASTER_IP master" >> /etc/hosts fi
if [ -n "$MASTER" ]; then echo "Starting master" rm /tmp/*.pid echo "master" > $HADOOP_PREFIX/etc/hadoop/masters echo "master" > $HADOOP_PREFIX/etc/hadoop/slaves while read line; do echo $line | sed -e "s/.*\s//" >> $HADOOP_PREFIX/etc/hadoop/slaves; done < /tmp/slaves $HADOOP_PREFIX/sbin/start-dfs.sh $HADOOP_PREFIX/bin/hdfs dfs -mkdir -p /user/root $HADOOP_PREFIX/sbin/start-yarn.sh $HADOOP_PREFIX/sbin/mr-jobhistory-daemon.sh --config $HADOOP_PREFIX/etc/hadoop start historyserver else echo "Starting slave" if [[ $1 == "-bash" ]]; then echo "To read logs type: tail -f $HADOOP_PREFIX/logs/*.log" fi fiGondoskodtam a megfelelő DNS beállításokról, szétválasztottam a futás jellegét a MASTER környezeti változó alapján, amit a konténer futtatásakor tudunk megadni, valamint indítottam egy JobHistory szervert a masteren. Az echo "master" > $HADOOP_PREFIX/etc/hadoop/slaves sor gondoskodik arról, hogy a mester is végezzen szolgai munkát, igény szerint ez eltávolítható, és egyúttal némi memória is felszabadítható a mester gépen.
Miután befejeződött a Vagrant gépek előkészítése, és a konténerek is elkészültek, nincs más hátra, mint elindítani a konténereket, vagy mégsem? Sajnos nem, ugyanis egy kivételbe futunk a job futtatása során, de menjünk egy kicsit bele a részletekbe.
vagrant ssh slave[1-3] docker run --name hadoop -h slave[1-3] -e "MASTER_IP=192.168.50.4" -e "SLAVES=192.168.50.1 slave1,192.168.50.2 slave2,192.168.50.3 slave3" -p 50020:50020 -p 50010:50010 -p 50075:50075 -p 8040:8040 -p 8042:8042 -p 49707:49707 -p 2122:2122 -p 36123:36123 -p 50200-50210:50200-50210 -it mhmxs/hadoop-docker:2.6.0 /etc/bootstrap.sh -bash
vagrant ssh master docker run --name hadoop -h master -e "MASTER=true" -e "MASTER_IP=192.168.50.4" -e "SLAVES=192.168.50.1 slave1,192.168.50.2 slave2,192.168.50.3 slave3" -p 50020:50020 -p 50090:50090 -p 50070:50070 -p 50010:50010 -p 50075:50075 -p 9000:9000 -p 8021:8021 -p 8030:8030 -p 8031:8031 -p 8032:8032 -p 8033:8033 -p 8040:8040 -p 8042:8042 -p 49707:49707 -p 2122:2122 -p 8088:8088 -p 10020:10020 -p 19888:19888 -p 36123:36123 -p 50200-50210:50200-50210 -it mhmxs/hadoop-docker:2.6.0 /etc/bootstrap.sh -bashBizonyosodjunk meg róla, hogy mind a 4 konténer rendben elindult-e és, hogy a cluster összeállt-e megfelelően:
bin/hdfs dfsadmin -report bin/yarn node -listTegyünk egy próbát:
bin/hdfs dfs -mkdir -p input && bin/hdfs dfs -put $HADOOP_PREFIX/etc/hadoop/* input
bin/hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-2.6.0.jar wordcount input output
WARN [main] org.apache.hadoop.mapred.YarnChild: Exception running child : java.net.NoRouteToHostException: No Route to Host from master/172.17.0.155 to 172.17.0.159:40896 failed on socket timeout exception: java.net.NoRouteToHostException: No route to host; For more details see: http://wiki.apache.org/hadoop/NoRouteToHost at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:422) at org.apache.hadoop.net.NetUtils.wrapWithMessage(NetUtils.java:791) at org.apache.hadoop.net.NetUtils.wrapException(NetUtils.java:757) at org.apache.hadoop.ipc.Client.call(Client.java:1472) at org.apache.hadoop.ipc.Client.call(Client.java:1399) at org.apache.hadoop.ipc.WritableRpcEngine$Invoker.invoke(WritableRpcEngine.java:244) at com.sun.proxy.$Proxy8.getTask(Unknown Source) at org.apache.hadoop.mapred.YarnChild.main(YarnChild.java:132) Caused by: java.net.NoRouteToHostException: No route to host at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method) at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717) at org.apache.hadoop.net.SocketIOWithTimeout.connect(SocketIOWithTimeout.java:206) at org.apache.hadoop.net.NetUtils.connect(NetUtils.java:530) at org.apache.hadoop.net.NetUtils.connect(NetUtils.java:494) at org.apache.hadoop.ipc.Client$Connection.setupConnection(Client.java:607) at org.apache.hadoop.ipc.Client$Connection.setupIOstreams(Client.java:705) at org.apache.hadoop.ipc.Client$Connection.access$2800(Client.java:368) at org.apache.hadoop.ipc.Client.getConnection(Client.java:1521) at org.apache.hadoop.ipc.Client.call(Client.java:1438)Miközben újra futtattam a jobot megpróbáltam elcsípni a folyamatot, ami az imént a 40896 porton futott, most pedig a 39552-őt használta volna. Elég rövid volt az idő ablak, de én is elég fürge voltam.
# lsof -i tcp:39552 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME java 2717 root 235u IPv4 147112 0t0 TCP namenode:60573->172.17.0.15:39552 (SYN_SENT) java 2733 root 235u IPv4 147111 0t0 TCP namenode:60572->172.17.0.15:39552 (SYN_SENT) -bash-4.1# ps x | grep 2717 2717 ? Sl 0:01 /usr/lib/jvm/jre-1.8.0-openjdk/bin/java -Djava.net.preferIPv4Stack=true -Dhadoop.metrics.log.level=WARN -Xmx200m -Djava.io.tmpdir=/tmp/hadoop-root/nm-local-dir/usercache/root/appcache/application_1431716284376_0003/container_1431716284376_0003_01_000012/tmp -Dlog4j.configuration=container-log4j.properties -Dyarn.app.container.log.dir=/usr/local/hadoop/logs/userlogs/application_1431716284376_0003/container_1431716284376_0003_01_000012 -Dyarn.app.container.log.filesize=0 -Dhadoop.root.logger=INFO,CLA org.apache.hadoop.mapred.YarnChild 172.17.0.15 39552 attempt_1431716284376_0003_m_000008_1 12Elindul a szolgán egy YarnChild processz, ami az első paraméterben megadott IP-n szeretne kommunikálni a második paraméterben megadott ephemeral porton (a forráskódból derült ki), és a mester nem tud hozzá csatlakozni, hiszen nem a virtuális gép IP címe van megadva, amin a kommunikáció valójában zajlik, és nem is a szolga host neve, hanem a Docker konténer IP-je, amit a mester nem ér el. Kutakodtam a konfigurációk között, próbáltam a forrásból kideríteni, hogy mely beállítással lehetnék hatással erre a működésre, és szomorúan kellett tudomásul vennem, hogy erre nincs kapcsoló vagy nagyon elrejtették.
Miután a port publikálási megoldással zsákutcába jutottam az alábbi megoldások jöhettek számításba:
- Használom a --net=host Docker kapcsolót, és akkor a konténerek közvetlenül a virtuális gép hálózati csatolójára ülnek - tiszta és száraz érzés
- Privát hálózatot hozok létre pl. VPN - sajtreszelővel ...
- Konfigurálok egy Weave hálózatot - na ez már izgalmasan hangzik
vagrant ssh slave[1-3] docker run --name hadoop --net=host -e "MASTER_IP=192.168.50.4" -e "SLAVES=192.168.50.1 slave1,192.168.50.2 slave2,192.168.50.3 slave3" -it mhmxs/hadoop-docker:2.6.0 /etc/bootstrap.sh -bash
vagrant ssh master docker run --name hadoop --net=host -e "MASTER=true" -e "MASTER_IP=192.168.50.4" -e "SLAVES=192.168.50.1 slave1,192.168.50.2 slave2,192.168.50.3 slave3" -it mhmxs/hadoop-docker:2.6.0 /etc/bootstrap.sh -bashEzúttal tökéletesen lefutott a job, örülünk, de elégedettek még nem vagyunk. A jelenlegi állapot forrása itt érhető el, a Vagrantos környezeté pedig itt.
Előzetes a következő rész tartalmából. Hűsünk beállítja a Swarm clustert, majd némi küzdelem árán lecseréli a kézi DNS konfigurációt automatizált megoldásra. Vajon melyik implementációt választja? Kiderül a folytatásban...