本篇文章主要紀錄如何使用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
由於我們最後須要使用瀏覽器打開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
以筆者的結果為例,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
需要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
嘗試看看ssh
master自己,看看是否可以不需輸入密碼:ssh 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傳送到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
來觀察分別有哪些元件被啟動:
啟動Yarn
一樣在master container上,以hadoop
user啟動Yarn:
$ master > sbin/start-yarn.sh
啟動後一樣在master, slave01, slave02上執行jps
來觀察分別有哪些元件被啟動:
瀏覽Web UI
關閉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) 詳解 這篇文章)。
疑難排解
筆者在架設的過程中曾經遇過一些錯誤訊息,簡單列在這邊,幫助也遇到同樣問題的讀者可以快速排除一些問題!
- 從master container要ssh連入slave01或slave02時出現錯誤訊息
ssh: connect to host slave01 port 22: Connection refused
這代表你的目標對象沒有把ssh服務打開,可以進去目標對象container上用service ssh start
指令啟動ssh服務。 - 透過
jps
指令查看發現某些服務 (e.g., Namenode)沒有啟動,不知道從何找起原因
這個時候可以到對應的container的$HADOOP_HOME/logs
路徑底下尋找log。 - 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
。