手把手用Docker架設Hadoop Cluster — 1

隱市碼農
15 min readApr 6, 2021

--

本篇文章主要紀錄如何使用Docker containers建造一個Hadoop cluster (v3),包含一個master節點和兩個slaves (workers)節點。

本篇文章選用的Hadoop版本為v3.3.0,並且該版本支援Java 11,因此會安裝OpenJDK 11。

由於Hadoop在Docker Hub上沒有官方製作的Docker image,所以本文會使用 ubuntu:20.04 的image當作基底,透過Dockerfile來做出我們自己的Hadoop image,再啟動container並進行設定,最後完成Hadoop cluster的建立。

本篇文章所使用的Dockerfile沒有經過最佳化,所以建出來的image大小很大。
並且由於筆者把使用者密碼也寫在裡面,因此僅適用於測試或學習使用,請勿使用在production環境。

為了聚焦在建立Hadoop cluster的過程,因此本文簡化了網路設定,不使用DNS而是採用修改所有containers的/etc/hosts檔案,來達到IP和hostname之間的對應關係。

NOTE:本篇文章中,如果遇到需要執行在Host上的指令,前綴會用$>,如果是要在container中以default user執行的指令,
前綴會是$ <container name>,如果需要其他非default使用者 (e.g., root)來執行,則會是$ <container name> with <user name> >

本文所使用的Host系統資訊如下:

請到筆者的Github Repo下載筆者所撰寫的Dockerfile,並編成image:

$> git clone https://github.com/hsinyinfu/Hadoop_HandsOn.git$> cd Hadoop_HandsOn/hadoop && docker build -t hadoop:3.3.0 .

產生image後,我們要啟動三個container,一個擔任master,兩個擔任slaves:

$> docker run -itd — name master -p 9870:9870 -p 9868:9868 -p 8088:8088 hadoop:3.3.0
$> docker run -itd — name slave01 -p 9864:9864 -p 8042:8042 hadoop:3.3.0
$> docker run -itd — name slave02 -p 10864:9864 -p 9042:8042 hadoop:3.3.0
建立出三個containers,分別扮演master以及slaves的角色。

由於我們最後須要使用瀏覽器打開Namenode, Secondary Namenode, 以及Datanodes的Web UI來查看是否正確安裝,因此需要開放對應的ports。

其中由於兩個 slaves都需要用到同一個host port,因此筆者讓 slave02的container port (9864) map到 “container port number+1000”的host port (10864)。

接下來要讓三個containers之間彼此可以知道IP和hostname的對應關係,因此我們要先查詢三個containers被分配的IP:

$> docker inspect master slave01 slave02 | grep IPAddress
由於我們不使用DNS,因此必須查詢三個containers被分配的IP。

以筆者的結果為例,master, slave01, slave02被分配到的IP分別是172.17.0.2, 172.17.0.3, 172.17.0.4,因此我們修改三個containers的/etc/hosts檔案,新增內容如下 (以master container為例):

<master IP>  master
<slave01 IP> slave01
<slave02 IP> slave02
修改 /etc/hosts 把container hostname以及對應的IP填上。

重點在最後三行,其他的是本來就存在於檔案內的,就不用動他們。

注意,修改/etc/hosts需要root權限,因此透過docker exec進入container時,需要加上 -u 0 的option:
$> docker exec -it -u 0 master bash

接下來,我們需要以hadoop這個default user進入三個containers中,修改一些設定檔 (都在/usr/local/hadoop/etc/hadoop/路徑底下):

$> docker exec -it <master or slave01 or slave02> bash

hadoop-env.sh

找到第54行,把該行的註解拿掉,並修改成如下:
export JAVA_HOME="$(dirname $(dirname $(readlink -f $(which javac))))"

core-site.xml

把內容修改成如下:

<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://master:9000</value>
</property>
</configuration>

注意第22行的地方,”master”的地方必須和/etc/hosts裡面,master container IP對應的hostname相符。

hdfs-site.xml

修改內容成如下:

<configuration>
<property>
<name>dfs.replication</name>
<value>2</value>
</property>
</configuration>

由於我們總共有2個slaves,因此這裡的值設定為2。

workers

workers檔案裏原本只有一行localhost,把這行刪除,然後新增兩個slave containers的hostname:

slave01
slave02

mapred-site.xml

修改內容成如下:

<configuration>
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
</configuration>

yarn-site.xml

修改內容成如下:

<configuration>
<property>
<name>yarn.resourcemanager.hostname</name>
<value>master</value>
</property>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
</configuration>

SSH Password Authentication

最後我們需要配置ssh密碼認證,讓master container的hadoop user可以無需輸入密碼即登入slave containers的hadoop user。

先以hadoop user進入master container:

$> docker exec -it master bash

透過ssh-keygen來產生一組master的RSA公私鑰:

$ master > ssh-keygen -t rsa -b 4096

過程中所有需要使用者輸入的地方一律按Enter跳過即可。

最後將產生的public key寫入到authorized_keys檔案中,讓master可以不用輸入密碼就ssh登入自己:

$ master > cat ~/.ssh/id_rsa.pub > ~/.ssh/authorized_keys
產生的private key一定要小心保護好,不要外流囉!

嘗試看看ssh master自己,看看是否可以不需輸入密碼:ssh master

可以看到配置ssh authentication後,我們從master登入自己不用再輸入密碼

如果出現ssh: connect to host master port 22: Connection refused的錯誤訊息,很有可能是該對象的ssh daemon沒有啟動。
請以root user登入目標container並執行service ssh start

成功之後,將master container的~/.ssh/id_rsa.pub檔案複製一份給slave01, slave02,一樣放置於~/.ssh目錄底下,然後在slave01, slave02上執行

cat ~/.ssh/id_rsa.pub > ~/.ssh/authorized_keys

最後再從master container試登入兩台slaves,都可以不用輸入密碼的話就算成功了。

master現在可以不輸入密碼即登入slave01和slave02囉!

把檔案從master傳送到slaves,可以在master使用scp指令,也可以從host上使用docker cp

啟動HDFS

都配置完成後,我們就要來啟動HDFS,來讓我們的Hadoop cluster迎來最後的高潮時刻囉!

hadoop user進入master container後,我們要先格式化HDFS,然後才啟動它。

要注意的是,只有第一次啟動HDFS才要格式化!!!之後都直接啟動或關閉HDFS即可。

格式化指令 (只有第一次啟動HDFS前才要執行):

$ master > bin/hdfs namenode -format

current working dir: /usr/local/hadoop

啟動HDFS:

$ master > sbin/start-dfs.sh

接著在master, slave01, slave02上執行jps來觀察分別有哪些元件被啟動:

啟動HDFS後,master container上會啟動Namenode以及Secondary Namenode。
啟動HDFS後,slave01上會啟動Datanode。
啟動HDFS後,slave02上也會啟動Datanode。

啟動Yarn

一樣在master container上,以hadoop user啟動Yarn:

$ master > sbin/start-yarn.sh

啟動後一樣在master, slave01, slave02上執行jps來觀察分別有哪些元件被啟動:

啟動Yarn後,master上多啟動了一個ResourceManager。
啟動Yarn後,slave01上則是啟動了NodeManager。
啟動Yarn後,slave02上也是啟動了NodeManager。

瀏覽Web UI

透過9870 port可以連入Namenode Web UI,可以看到Live Nodes有2,代表我們的兩個Datanodes都有正確啟動。
從9868 port可以進入Secondary Namenode的Web UI。
從9864 port可以查看slave01的Datanode資訊。
前面解釋過,由於host的一個port只能被一個container使用,所以讓slave02的9864 port設定到host的10864 port,就可以透過10864 port查看slave02的Datanode資訊。
從8088 port可以連入master的ResourceManager查看Yarn相關的資訊。這裡的Active Nodes的值是2一樣代表我們的兩個slaves都有正確啟動。

關閉HDFS以及Yarn

如果要關閉Yarn,可以使用以下指令把master上的ResourceManager以及slaves上的NodeManager關掉:

$ master > sbin/stop-yarn.sh

current working directory: /usr/local/hadoop

如果要關掉HDFS,可以使用以下指令把master上的Namenode, Secondary Namenode以及slaves上的Datanode關掉:

$ master > sbin/stop-dfs.sh

一樣可以用jps指令檢查是否關閉成功。

懶人服務開關大法

前面我們用了start-dfs.sh以及start-yarn.sh兩個scripts分別開啟HDFS以及Yarn,但其實還有另一個懶人方法,可以一次取代這兩個scripts:

$ master > sbin/start-all.sh

使用start-all.sh可以一次把HDFS以及Yarn服務都打開! 嘿嘿,是不是很懶人很方便呢XD

既然打開服務有這麼方便的方法,關閉服務當然也會有囉!

我們可以用stop-all.sh來取代stop-dfs.sh以及stop-yarn.sh,一次將HDFS以及Yarn的服務通通關閉:

$ master > sbin/stop-all.sh

同場加映:SSH Tunneling

可能會有人遇到一個狀況:如果我的docker是執行在遠端的伺服器上 (e.g., cloud、公司的伺服器),而自己是透過ssh連進去遠端伺服器來操作的話,我要怎麼用本機上的瀏覽器 (e.g., Chrome)打開上面提到的那些Web UI?

這時候,我們就可以透過SSH Tunneling的技術,讓所有送到我們本機上的某個port的封包都轉送給遠端伺服器的某個port。舉例來說,我們讓本機上的8088 port連接到遠端伺服器的8088 port,這樣當我們在瀏覽器上搜尋http://localhost:8088時,封包就會自動送到遠端伺服器的8088 port,那我們就可以成功的在本機的瀏覽器上看到遠端伺服器上的Web UI囉!

由於這不是本文的重點,所以筆者就不多加贅述了! 需要用到這個技巧的讀者,網路上有很多可以參考的教學文 (e.g., SSH Tunneling (Port Forwarding) 詳解 這篇文章)。

疑難排解

筆者在架設的過程中曾經遇過一些錯誤訊息,簡單列在這邊,幫助也遇到同樣問題的讀者可以快速排除一些問題!

  1. 從master container要ssh連入slave01或slave02時出現錯誤訊息ssh: connect to host slave01 port 22: Connection refused
    這代表你的目標對象沒有把ssh服務打開,可以進去目標對象container上用service ssh start指令啟動ssh服務。
  2. 透過jps指令查看發現某些服務 (e.g., Namenode)沒有啟動,不知道從何找起原因
    這個時候可以到對應的container的$HADOOP_HOME/logs路徑底下尋找log。
  3. Namenode沒有啟動,並且log顯示錯誤訊息
    ERROR org.apache.hadoop.hdfs.server.namenode.NameNode: Failed to start namenode.
    org.apache.hadoop.hdfs.server.common.InconsistentFSStateException: Directory /usr/local/hadoop/tmp/dfs/name is in an inconsistent state: storage directory does not exist or is not accessible.

    這可能是因為忘記第一次要先執行HDFS格式化,就直接執行sbin/start-dfs.sh,可以試試先執行sbin/stop-dfs.sh把服務關掉,然後格式化HDFS,再試一次sbin/start-dfs.sh

--

--

No responses yet