目錄
- 為什么要在Docker上搭建Spark集群
- 網絡拓撲
- Docker安裝及配置
- ssh安裝及配置
- 基礎環境安裝
- Zookeeper安裝及配置
- Hadoop安裝及配置
- Spark安裝及配置
- 集群部署
- 總結
- 參考資料
1 為什么要在Docker上搭建Spark集群
他:為什么要在Docker上搭建Spark集群啊?
我:因為……我行啊!
MR和Spark都提供了local模式,即在單機上模擬多計算節點來執行任務。但是,像我這等手賤的新手,怎么會滿足於“模擬”?很容易想到在單機上運行多個虛擬機作為計算節點,可是考慮到PC的資源有限,即使能將集群運行起來,再做其他的工作已經是超負荷了。Docker是一種相比虛擬機更加輕量級的虛擬化解決方案,所以在Docker上搭建Spark集群具有可行性。
2 網絡拓撲
搭建一個有意義的小規模集群,我選擇了3台服務器作為Spark計算節點(Worker)。集群中光有計算節點還不夠,這3台服務器同時也作為分布式文件系統(HDFS)的數據節點(DataNode)。指定了哪些服務器用來計算,哪些用來存儲之后,我們還需要指定來管理計算和存儲的主節點。一個簡單方案:我們可以讓cloud1作為管理計算節點的主節點(Master),同時它也作為管理數據節點的主節點(NameNode)。
很容易看到簡單方案不夠完美:首先,要是cloud1作為NameNode宕機,整個分布式文件系統則無法工作。此時,我們應當采用基於HA的HDFS方案:由多個NameNode共同管理DataNode,但是只有一個NameNode處於活動(Active)狀態,當活動的NameNode無法工作時,則需要其他NameNode候補。這里至少涉及2個關鍵技術:
- 如何共享NameNode的信息(EditLog)?NameNode存儲的信息包括但不限於:數據在各DataNode上如何存儲,哪些DataNode是可用的。所以,當活動的NameNode無法工作時,應當將這些信息傳遞給下一個被選中的NameNode。與其傳遞,不如所有的NameNode共享這些信息。這些信息將被分布式地存儲在JournalNode上。在本集群中,我們使用所有3台服務器都作為JournalNode。cloud1和cloud2作為NameNode。
- 如何確保只有一個NameNode是活動的?當活動的NameNode無法工作時,如何確定下一個活動的Namenode?Zookeeper可以解決這兩個問題,在本集群中,3台服務器都作為Zkserver節點。
再者,選用cloud1作為Master來管理計算(standalone)的方式對資源的利用率不比Yarn方式。所以,在本集群中選用cloud1做為ResourceManager,3台服務器都作為NodeManager)。
改進后的集群描述如下:
節點 | Zkserver | NameNode | JournalNode | ResourceManager | NodeManager | Master |
Worker |
cloud1 | √ | √ | √ | √ | √ | √ | √ |
cloud2 | √ | √ | √ | × | √ | × | √ |
cloud3 | √ | × | √ | × | √ | × | √ |
3 Docker安裝及配置
Docker有Windows/Mac/Linux版本。起初我處於對Docker的誤解選擇了Windows版本,Docker的核心程序必須運行在Linux上,故Windows版本的Docker實際上是利用VirtualBox運行着一個精簡的Linux,然后在此Linux上運行Docker,最后在Docker上運行安裝好應用的鏡像。好家伙,盜夢空間!最終,我選擇在CentOS上安裝Linux版本的Docker。關於Docker,我們需要理解一個重要的概念:容器(Container)。容器是鏡像運行的場所,可以在多個容器中運行同一個鏡像。
Docker安裝好之后,我們啟動Docker服務:
1 systemctl start docker.service
我們可以拉一個Ubuntu鏡像,基於該鏡像我們搭建Spark集群:
1 docker pull ubuntu
下載好鏡像到本地后,我們可以查看鏡像:
1 docker images
使用run命令,創建一個容器來運行鏡像:
1 docker run -it ubuntu
使用ps命令查看容器:
1 docker ps -a
使用commit命令來將容器提交為一個鏡像:
1 docker commit <container id|name>
使用tag命令來為一個鏡像打標簽:
1 docker tag <mirror id> <tag>
使用start命令來啟動一個容器:
1 docker start -a <container id|name>
在掌握了以上操作后,在Docker上搭建Spark集群的技術路線如下:
4 ssh安裝及配置
試想一下如何啟動集群?手動去每個節點啟動相應的服務?這顯然是不合理的。HDFS,Yarn,Spark都支持單命令啟動全部節點。在某個節點上執行的命令是如何發送至其他節點的呢?ssh服務幫助實現這一功能。關於ssh我們需要知道其分為服務端和客戶端,服務端默認監聽22號端口,客戶端可與服務端建立連接,從而實現命令的傳輸。
docker服務啟動后,可以看到宿主機上多了一塊虛擬網卡(docker0),在我的機器中為172.17.0.1。啟動容器后,容器的IP從172.17.0.2開始分配。我們不妨為集群分配IP地址如下:
域名 | IP |
cloud1 | 172.17.0.2 |
cloud2 | 172.17.0.3 |
cloud3 | 172.17.0.4 |
關閉所有容器后,新建一個容器,命名為cloud1:
1 #新建容器時需要指定這個容器的域名以及hosts文件 2 #參數: 3 #name:容器名稱 4 #h:域名 5 #add-host:/etc/hosts文件中的域名與IP的映射 6 docker --name cloud1 -h cloud1 --add-host cloud1:172.17.0.2 --add-host cloud2:172.17.0.3 --add-host cloud3:172.17.0.4 -it ubuntu
在容器cloud1中通過apt工具來安裝ssh:
1 apt-get install ssh
往~/.bashrc中加入ssh服務啟動命令:
1 /usr/sbin/sshd
客戶端不能任意地與服務端建立連接,或通過密碼,或通過密鑰認證。在這里我們使用密鑰認證,生成客戶端的私鑰和公鑰:
1 #私鑰(~/.ssh/id_rsa)由客戶端持有 2 #公鑰(~/.ssh/id_rsa.pub)交給服務端 3 #已認證的公鑰(~/.ssh/authorized_keys)由服務端持有,只有已認證公鑰的客戶端才能連接至服務端 4 #參數: 5 #t:加密方式 6 #P:密碼 7 ssh-keygen -t rsa -P ""
根據技術路線,由cloud1容器提交的鏡像將生成cloud2容器和cloud3容器。要實現cloud1對cloud2和cloud3的ssh密鑰認證連接,其實只要實現cloud1對本身的連接就可以了:
1 cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
測試是否能連接成功:
1 ssh root@cloud1
5 基礎環境安裝
Java與Scala版本需要與其他軟件的版本相匹配:
軟件 | 版本 |
Java | 1.8.0_77 |
Scala | 2.10.6 |
Zookeeper | 3.4.8 |
Hadoop | 2.6.4 |
Spark | 1.6.1 |
Java與Scala安裝包下載后,均解壓在/usr目錄下。在~/.bashrc中添加環境變量:
1 export JAVA_HOME=/usr/jdk1.8.0_77 2 export PATH=$PATH:$JAVA_HOME/bin 3 export SCALA_HOME=/usr/scala-2.10.6 4 export PATH=$PATH:$SCALA_HOME/bin
6 Zookeeper安裝及配置
Zookeeper安裝包下載后,解壓在/usr目錄下。在~/.bashrc中添加環境變量:
1 export ZOOKEEPER_HOME=/usr/zookeeper-3.4.8 2 export PATH=$PATH:$ZOOKEEPER_HOME/bin
生成Zookeeper配置文件:
1 cp /usr/zookeeper-3.4.8/conf/zoo_sample.cfg /usr/zookeeper-3.4.8/conf/zoo.cfg
修改Zookeeper配置文件:
1 #數據存儲目錄修改為: 2 dataDir=/root/zookeeper/tmp 3 #在最后添加Zkserver配置信息: 4 server.1=cloud1:2888:3888 5 server.2=cloud2:2888:3888 6 server.3=cloud3:2888:3888
設置當前Zkserver信息:
1 #~/zookeeper/tmp/myid文件中保存的數字代表本機的Zkserver編號 2 #在此設置cloud1為編號為1的Zkserver,之后生成cloud2和cloud3之后還需要分別修改此文件 3 echo 1 > ~/zookeeper/tmp/myid
7 Hadoop安裝及配置
Hadoop安裝包下載后,解壓在/usr目錄下。在~/.bashrc中添加環境變量:
1 export HADOOP_HOME=/usr/hadoop-2.6.4 2 export PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin
修改Hadoop啟動配置文件(/usr/hadoop-2.6.4/etc/hadoop/hadoop-env.sh):
1 #修改JAVA_HOME 2 export JAVA_HOME=/usr/jdk1.8.0_77
修改核心配置文件(/usr/hadoop-2.6.4/etc/hadoop/core-site.xml):
參數 | 說明 |
fs.defaultFS | 默認的文件系統 |
hadoop.tmp.dir | 臨時文件目錄 |
ha.zookeeper.quorum | Zkserver信息 |
1 <property> 2 <name>fs.defaultFS</name> 3 <value>hdfs://ns1</value> 4 </property> 5 <property> 6 <name>hadoop.tmp.dir</name> 7 <value>/root/hadoop/tmp</value> 8 </property> 9 <property> 10 <name>ha.zookeeper.quorum</name> 11 <value>cloud1:2181,cloud2:2181,cloud3:2181</value> 12 </property>
修改HDFS配置文件(/usr/hadoop-2.6.4/etc/hadoop/hdfs-site.xml):
參數 | 說明 |
dfs.nameservices | 名稱服務,在基於HA的HDFS中,用名稱服務來表示當前活動的NameNode |
dfs.ha.namenodes.<nameservie> | 配置名稱服務下有哪些NameNode |
dfs.namenode.rpc-address.<nameservice>.<namenode> | 配置NameNode遠程調用地址 |
dfs.namenode.http-address.<nameservice>.<namenode> | 配置NameNode瀏覽器訪問地址 |
dfs.namenode.shared.edits.dir | 配置名稱服務對應的JournalNode |
dfs.journalnode.edits.dir | JournalNode存儲數據的路徑 |
1 <property> 2 <name>dfs.nameservices</name> 3 <value>ns1</value> 4 </property> 5 <property> 6 <name>dfs.ha.namenodes.ns1</name> 7 <value>nn1,nn2</value> 8 </property> 9 <property> 10 <name>dfs.namenode.rpc-address.ns1.nn1</name> 11 <value>cloud1:9000</value> 12 </property> 13 <property> 14 <name>dfs.namenode.http-address.ns1.nn1</name> 15 <value>cloud1:50070</value> 16 </property> 17 <property> 18 <name>dfs.namenode.rpc-address.ns1.nn2</name> 19 <value>cloud2:9000</value> 20 </property> 21 <property> 22 <name>dfs.namenode.http-address.ns1.nn2</name> 23 <value>cloud2:50070</value> 24 </property> 25 <property> 26 <name>dfs.namenode.shared.edits.dir</name> 27 <value>qjournal://cloud1:8485;cloud2:8485;cloud3:8485/ns1</value> 28 </property> 29 <property> 30 <name>dfs.journalnode.edits.dir</name> 31 <value>/root/hadoop/journal</value> 32 </property> 33 <property> 34 <name>dfs.ha.automatic-failover.enabled</name> 35 <value>true</value> 36 </property> 37 <property> 38 <name>dfs.client.failover.proxy.provider.ns1</name> 39 <value> 40 org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider 41 </value> 42 </property> 43 <property> 44 <name>dfs.ha.fencing.methods</name> 45 <value> 46 sshfence 47 shell(/bin/true) 48 </value> 49 </property> 50 <property> 51 <name>dfs.ha.fencing.ssh.private-key-files</name> 52 <value>/root/.ssh/id_rsa</value> 53 </property> 54 <property> 55 <name>dfs.ha.fencing.ssh.connect-timeout</name> 56 <value>30000</value> 57 </property>
修改Yarn的配置文件(/usr/hadoop-2.6.4/etc/hadoop/yarn-site.xml):
參數 | 說明 |
yarn.resourcemanager.hostname | RescourceManager的地址,NodeManager的地址在slaves文件中定義 |
1 <property> 2 <name>yarn.resourcemanager.hostname</name> 3 <value>cloud1</value> 4 </property> 5 <property> 6 <name>yarn.nodemanager.aux-services</name> 7 <value>mapreduce_shuffle</value> 8 </property>
修改指定DataNode和NodeManager的配置文件(/usr/hadoop-2.6.4/etc/hadoop/slaves):
1 cloud1 2 cloud2 3 cloud3
8 Spark安裝及配置
Spark安裝包下載后,解壓在/usr目錄下。在~/.bashrc中添加環境變量:
1 export SPARK_HOME=/usr/spark-1.6.1-bin-hadoop2.6 2 export PATH=$SPARK_HOME/bin:$SPARK_HOME/sbin:$PATH
Spark啟動配置文件:
1 cp /usr/spark-1.6.1-bin-hadoop2.6/conf/spark-env.sh.template /usr/spark-1.6.1-bin-hadoop2.6/conf/spark-env.sh
修改Spark啟動配置文件(/usr/spark-1.6.1-bin-hadoop2.6/conf/spark-env.sh):
參數 | 說明 |
SPARK_MASTER_IP | Master的地址,Worker的地址在slaves文件中定義 |
1 export SPARK_MASTER_IP=cloud1 2 export SPARK_WORKER_MEMORY=128m 3 export JAVA_HOME=/usr/jdk1.8.0_77
4 export SCALA_HOME=/usr/scala-2.10.6
5 export SPARK_HOME=/usr/spark-1.6.1-hadoop2.6
6 export HADOOP_CONF_DIR=/usr/hadoop-2.6.4/etc/hadoop
7 export SPARK_LIBRARY_PATH=$$SPARK_HOME/lib 8 export SCALA_LIBRARY_PATH=$SPARK_LIBRARY_PATH 9 export SPARK_WORKER_CORES=1 10 export SPARK_WORKER_INSTANCES=1 11 export SPARK_MASTER_PORT=7077
修改指定Worker的配置文件(/usr/spark-1.6.1-bin-hadoop2.6/conf/slaves):
1 cloud1 2 cloud2 3 cloud3
9 集群部署
在宿主機上提交cloud1容器為新的鏡像,並打其標簽為Spark:
1 #提交cloud1容器,命令返回新鏡像的編號 2 docker commit cloud1 3 #為新鏡像打標簽為Spark 4 docker tag <mirror id> Spark
基於Spark鏡像創建cloud2和cloud3容器:
1 docker --name cloud2 -h cloud2 --add-host cloud1:172.17.0.2 --add-host cloud2:172.17.0.3 --add-host cloud3:172.17.0.4 -it Spark 2 docker --name cloud3 -h cloud3 --add-host cloud1:172.17.0.2 --add-host cloud2:172.17.0.3 --add-host cloud3:172.17.0.4 -it Spark
還記得之前提到的cloud2和cloud3的當前Zkserver還未配置嗎?分別在cloud2和cloud3容器中修改Zookeeper配置:
1 #在cloud2執行 2 echo 2 > ~/zookeeper/tmp/myid 3 #在cloud3執行 4 echo 3 > ~/zookeeper/tmp/myid
在所有節點啟動Zkserver(Zkserver並不是用ssh啟動的,呵呵):
1 zkServer.sh start
在所有節點查看Zkserver運行狀態:
1 #顯示連接不到Zkserver的錯誤,可稍后查看 2 #Master表示主Zkserver,Follower表示從Zkserver 3 Zkserver.sh status
初始化其中一個NameNode,就選cloud1吧:
1 #格式化zkfc 2 hdfs zkfc -formatZK 3 #格式化NameNode 4 hdfs namenode -format
在cloud1啟動HDFS,Yarn,Spark:
1 #啟動NameNode,DataNode,zkfc,JournalNode 2 start-dfs.sh 3 #啟動ResouceManager,NodeManager 4 start-yarn.sh 5 #啟動Master,Worker 6 start-all.sh
使用jps命令查看各節點服務運行情況:
1 jps
還可以登錄web管理台來查看運行狀況:
服務 | 地址 |
HDFS | cloud1:50070 |
Yarn | cloud1:8088 |
Spark | cloud1:8080 |
10 總結
- 環境搭建切不可知其然,但不知其所以然
- 明確自己的需求是什么,不可能一開始就弄懂所有配置項,掌握一個最小的知識集就好