1. 程式人生 > >AWS EC2 搭建 Hadoop 和 Spark 叢集

AWS EC2 搭建 Hadoop 和 Spark 叢集

前言

本篇演示如何使用 AWS EC2 雲服務搭建叢集。當然在只有一臺計算機的情況下搭建完全分散式叢集,還有另外幾種方法:一種是本地搭建多臺虛擬機器,好處是免費易操控,壞處是虛擬機器對宿主機配置要求較高,我就一臺普通的筆記本,開兩三個虛擬機器實在承受不起; 另一種方案是使用 AWS EMR ,是亞馬遜專門設計的叢集平臺,能快速啟動叢集,且具有較高的靈活性和擴充套件性,能方便地增加機器。然而其缺點是隻能使用預設的軟體,如下圖:

如果要另外裝軟體,則需要使用 Bootstrap 指令碼,詳見 https://docs.aws.amazon.com/zh_cn/emr/latest/ManagementGuide/emr-plan-software.html?shortFooter=true ,可這並不是一件容易的事情,記得之前想在上面裝騰訊的 Angel 就是死活都裝不上去。 另外,如果在 EMR 上關閉了叢集,則裡面的檔案和配置都不會儲存,下次使用時全部要重新設定,可見其比較適用於一次性使用的場景。

綜上所述,如果使用純 EC2 進行手工搭建,則既不會受本地資源限制,也具有較高的靈活性,可以隨意配置安裝軟體。而其缺點就是要手工搭建要耗費較多時間,而且在雲上操作和在本地操作有些地方是不一樣的,只要有一步出錯可能就要卡殼很久,鑑於網上用 EC2 搭建這方面資料很少,因此這裡寫一篇文章把主要流程記錄下來。

如果之前沒有使用過 EC2,可能需要花一段時間熟悉,比如註冊以及建立金鑰對等步驟,官方提供了相關教程 。另外我的本地機和雲端機使用的都是 Ubuntu 16.04 LTS 64位,如果你的本地機是 Windows,則需要用 Git 或 PuTTY 連線雲端機,詳情參閱 https://docs.aws.amazon.com/zh_cn/AWSEC2/latest/UserGuide/putty.html 。




建立 EC2 例項

下面正式開始,這裡設立三臺機器 (例項),一臺作主節點 (master node),兩臺作從節點 (slaves node)。首先建立例項,選擇 Ubuntu Server 16.04 LTS (HVM) ,例項型別選擇價格低廉的 t2.medium 。如果是第一次用,就不要選價格太高的型別了,不然萬一操作失誤了每月賬單可承受不起。


在第 3 步中,因為要同時開三臺機器,Number of Instances 可以直接選擇3。但如果是每臺分別開的話,下面的 Subnet 都要選擇同一個區域,不然機器間無法通訊,詳情參閱 https://docs.aws.amazon.com/zh_cn/AWSEC2/latest/UserGuide/using-regions-availability-zones.html 。


第 4 步設定硬碟大小,如果就搭個叢集可能不用動,如果還要裝其他軟體,可能就需要在這裡增加容量了,我是增加到了 15 GB:


第 5 和第 6 步直接Next 即可,到第 7 步 Launch 後選擇或新建金鑰對,就能得到建立好的 3 個例項,這裡可以設定名稱備註,如 master、slave01、slave02 等:


開啟 3 個終端視窗,ssh 連線3個例項,如 ssh -i xxxx.pem [email protected] ,其中 xxxx.pem 是你的本地金鑰對名稱,ec2-xx-xxx-xxx-xx.us-west-2.compute.amazonaws.com 是該例項的外部 DNS 主機名,每臺例項都不一樣。這裡需要說明一下,因為這是和本地開虛擬機器的不同之處: EC2 的例項都有公有 IP 和私有 IP 之分,私有 IP 用於雲上例項之間的通訊,而公有 IP 則用於你的本地機與例項之間的通訊,因此這裡 ssh 連線使用的是公有 IP (DNS) 。在下面搭建叢集的步驟中也有需要填寫公有和私有 IP ,注意不要填反了。關於二者的區別參閱 https://docs.aws.amazon.com/zh_cn/AWSEC2/latest/UserGuide/using-instance-addressing.html?shortFooter=true#using-instance-addressing-common 。




新增 hadoop 使用者、安裝 Java 環境

以下以 master 節點為例。登陸例項後,預設使用者為 ubuntu,首先需要建立一個 hadoop 使用者:

$ sudo useradd -m hadoop -s /bin/bash   # 增加 hadoop使用者
$ sudo passwd hadoop                    # 設定密碼,需要輸入兩次
$ sudo adduser hadoop sudo              # 為 hadoop 使用者增加管理員許可權
$ su hadoop                             # 切換到 hadoop 使用者,需要輸入密碼
$ sudo apt-get update                   # 更新 apt 源

這一步完成之後,終端使用者名稱會變為 hadoop,且 /home 目錄下會另外生成一個 hadoop 資料夾。


Hadoop 依賴於 Java 環境,所以接下來需要先安裝 JDK,直接從官網下載,這裡下的是 Linux x64 版本 jdk-8u231-linux-x64.tar.gz ,用 scp 遠端傳輸到 master 機。注意這裡只能傳輸到 ubuntu 使用者下,傳到 hadoop 使用者下可能會提示許可權不足。

$ scp -i xxx.pem jdk-8u231-linux-x64.tar.gz [email protected]:/home/ubuntu/  # 本地執行該命令

本篇假設所有軟體都安裝在 /usr/lib 目錄下:

$ sudo mv /home/ubuntu/jdk-8u231-linux-x64.tar.gz /home/hadoop         # 將檔案移動到 hadoop 使用者下
$ sudo tar -zxf /home/hadoop/jdk-8u231-linux-x64.tar.gz -C /usr/lib/   # 把JDK檔案解壓到/usr/lib目錄下
$ sudo mv /usr/lib/jdk1.8.0_231  /usr/lib/java                         # 重新命名java資料夾
$ vim ~/.bashrc                                                        # 配置環境變數,貌似EC2只能使用 vim

新增如下內容:

export JAVA_HOME=/usr/lib/java
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH
$ source ~/.bashrc   # 讓配置檔案生效
$ java -version    # 檢視 Java 是否安裝成功

如果出現以下提示則表示安裝成功:


在 master 節點完成上述步驟後,在兩個 slave 節點完成同樣的步驟 (新增 hadoop 使用者、安裝 Java 環境)




網路配置

這一步是為了便於 Master 和 Slave 節點進行網路通訊,在配置前請先確定是以 hadoop 使用者登入的。首先修改各個節點的主機名,執行 sudo vim /etc/hostname ,在 master 節點上將 ip-xxx-xx-xx-xx 變更為 Master 。其他節點類似,在 slave01 節點上變更為 Slave01,slave02 節點上為 Slave02。

然後執行 sudo vim /etc/hosts 修改自己所用節點的IP對映,以 master 節點為例,新增紅色區域內資訊,注意這裡的 IP 地址是上文所述的私有 IP:


接著在兩個 slave 節點的hosts中新增同樣的資訊。完成後重啟一下,在進入 hadoop 使用者,能看到機器名的變化 (變成 Master 了):


對於 ec2 例項來說,還需要配置安全組 (Security groups),使例項能夠互相訪問 :


選擇劃線區域,我因為是同時建立了三臺例項,所以安全組都一樣,如果不是同時建立的,這可能三臺都要配置。

進入後點擊 Inbound 再點 Edit ,再點選 Add Rule,選擇裡面的 All Traffic ,接著儲存退出:


三臺例項都設定完成後,需要互相 ping 一下測試。如果 ping 不通,後面是不會成功的:

$ ping Master -c 3   # 分別在3臺機器上執行這三個命令
$ ping Slave01 -c 3
$ ping Slave02 -c 3



接下來安裝 SSH server, SSH 是一種網路協議,用於計算機之間的加密登入。安裝完 SSH 後,要讓 Master 節點可以無密碼 SSH 登陸到各個 Slave 節點上,在Master節點執行:

$ sudo apt-get install openssh-server
$ ssh localhost                                         # 使用 ssh 登陸本機,需要輸入 yes 和 密碼
$ exit                                                  # 退出剛才的 ssh localhost, 注意不要退出hadoop使用者
$ cd ~/.ssh/                                            # 若沒有該目錄,請先執行一次ssh localhost
$ ssh-keygen -t rsa                                     # 利用 ssh-keygen 生成金鑰,會有提示,瘋狂按回車就行
$ cat ./id_rsa.pub >> ./authorized_keys                 # 將金鑰加入授權
$ scp ~/.ssh/id_rsa.pub Slave01:/home/hadoop/           # 將金鑰傳到 Slave01 節點
$ scp ~/.ssh/id_rsa.pub Slave02:/home/hadoop/           # 將金鑰傳到 Slave02 節點

接著在 Slave01和 Slave02 節點上,將 ssh 公匙加入授權:

$ mkdir ~/.ssh       # 如果不存在該資料夾需先建立,若已存在則忽略
$ cat ~/id_rsa.pub >> ~/.ssh/authorized_keys

這樣,在 Master 節點上就可以無密碼 SSH 到各個 Slave 節點了,可在 Master 節點上執行如下命令進行檢驗,如下圖所示變為 Slave01了,再按 exit 可退回到 Master:

至此網路配置完成。




安裝 Hadoop

去到映象站 https://archive.apache.org/dist/hadoop/core/ 下載,我下載的是 hadoop-2.8.4.tar.gz 。在 Master 節點上執行:

$ sudo tar -zxf /home/ubuntu/hadoop-2.8.4.tar.gz -C /usr/lib     # 解壓到/usr/lib中
$ cd /usr/lib/
$ sudo mv ./hadoop-2.8.4/ ./hadoop                               # 將資料夾名改為hadoop
$ sudo chown -R hadoop ./hadoop                                  # 修改檔案許可權

將 hadoop 目錄加到環境變數,這樣就可以在任意目錄中直接使用 hadoop、hdfs 等命令。執行 vim ~/.bashrc ,加入一行:

export PATH=$PATH:/usr/lib/hadoop/bin:/usr/lib/hadoop/sbin

儲存後執行 source ~/.bashrc 使配置生效。


完成後開始修改 Hadoop 配置檔案(這裡也順便配置了 Yarn),先執行 cd /usr/lib/hadoop/etc/hadoop ,共有 6 個需要修改 —— hadoop-env.shslavescore-site.xmlhdfs-site.xmlmapred-site.xmlyarn-site.xml


1、檔案 hadoop-env.sh 中把 export JAVA_HOME=${JAVA_HOME} 修改為 export JAVA_HOME=/usr/lib/java ,即 Java 安裝路徑。


2、 檔案 slaves 把裡面的 localhost 改為 Slave01和 Slave02 。


3、core-site.xml 改為如下配置:

<configuration>
        <property>
                <name>fs.defaultFS</name>
                <value>hdfs://Master:9000</value>
        </property>
        <property>
                <name>hadoop.tmp.dir</name>
                <value>file:/usr/lib/hadoop/tmp</value>
                <description>Abase for other temporary directories.</description>
        </property>
</configuration>


4、hdfs-site.xml 改為如下配置:

<configuration>
        <property>
                <name>dfs.namenode.secondary.http-address</name>
                <value>Master:50090</value>
        </property>
        <property>
                <name>dfs.replication</name>
                <value>2</value>
        </property>
        <property>
                <name>dfs.namenode.name.dir</name>
                <value>file:/usr/lib/hadoop/tmp/dfs/name</value>
        </property>
        <property>
                <name>dfs.datanode.data.dir</name>
                <value>file:/usr/lib/hadoop/tmp/dfs/data</value>
        </property>
</configuration>


5、檔案 mapred-site.xml (可能需要先重新命名,預設檔名為 mapred-site.xml.template):

<configuration>
        <property>
                <name>mapreduce.framework.name</name>
                <value>yarn</value>
        </property>
        <property>
                <name>mapreduce.jobhistory.address</name>
                <value>Master:10020</value>
        </property>
        <property>
                <name>mapreduce.jobhistory.webapp.address</name>
                <value>Master:19888</value>
        </property>
</configuration>


6、檔案 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>


配置好後,將 Master 上的 /usr/lib/hadoop 資料夾複製到各個 slave 節點上。在 Master 節點上執行:

$ cd /usr/lib
$ tar -zcf ~/hadoop.master.tar.gz ./hadoop   # 先壓縮再複製
$ scp ~/hadoop.master.tar.gz Slave01:/home/hadoop
$ scp ~/hadoop.master.tar.gz Slave02:/home/hadoop

分別在兩個 slave 節點上執行:

$ sudo tar -zxf ~/hadoop.master.tar.gz -C /usr/lib
$ sudo chown -R hadoop /usr/lib/hadoop

安裝完成後,首次啟動需要先在 Master 節點執行 NameNode 的格式化:

$ hdfs namenode -format       # 首次執行需要執行初始化,之後不需要

成功的話,會看到 “successfully formatted” 和 “Exitting with status 0” 的提示,若為 “Exitting with status 1” 則是出錯。



接著可以啟動 Hadoop 和 Yarn 了,啟動需要在 Master 節點上進行:

$ start-dfs.sh
$ start-yarn.sh
$ mr-jobhistory-daemon.sh start historyserver

通過命令 jps 可以檢視各個節點所啟動的程序。正確的話,在 Master 節點上可以看到 NameNode、ResourceManager、SecondrryNameNode、JobHistoryServer 程序,如下圖所示:

在 Slave 節點可以看到 DataNode 和 NodeManager 程序,如下圖所示:


通過命令 hdfs dfsadmin -report 可檢視叢集狀態,其中 Live datanodes (2) 表明兩個從節點都已正常啟動,如果是 0 則表示不成功:


可以通過下列三個地址檢視 hadoop 的 web UI,其中 ec2-xx-xxx-xxx-xx.us-west-2.compute.amazonaws.com 是該例項的外部 DNS 主機名,50070、8088、19888 分別是 hadoop、yarn、JobHistoryServer 的預設埠:

ec2-xx-xxx-xxx-xx.us-west-2.compute.amazonaws.com:50070
ec2-xx-xxx-xxx-xx.us-west-2.compute.amazonaws.com:8088
ec2-xx-xxx-xxx-xx.us-west-2.compute.amazonaws.com:19888




執行 Hadoop 分散式例項

$ hadoop fs -mkdir -p /user/hadoop   # 在hdfs上建立hadoop賬戶
$ hadoop fs -mkdir input
$ hadoop fs -put /usr/lib/hadoop/etc/hadoop/*.xml input  # 將hadoop配置檔案複製到hdfs中
$ hadoop jar /usr/lib/hadoop/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar grep input output 'dfs[a-z.]+'  # 執行例項

如果成功可以看到以下輸出:


最後關閉 Hadoop 叢集需要執行以下命令:

$ stop-yarn.sh
$ stop-dfs.sh
$ mr-jobhistory-daemon.sh stop historyserver




安裝 Spark

去到映象站 https://archive.apache.org/dist/spark/ 下載,由於之前已經安裝了Hadoop,所以我下載的是無 Hadoop 版本的,即 spark-2.3.3-bin-without-hadoop.tgz 。在 Master 節點上執行:

$ sudo tar -zxf /home/ubuntu/spark-2.3.3-bin-without-hadoop.tgz -C /usr/lib  # 解壓到/usr/lib中
$ cd /usr/lib/
$ sudo mv ./spark-2.3.3-bin-without-hadoop/ ./spark  # 將資料夾名改為spark
$ sudo chown -R hadoop ./spark                        # 修改檔案許可權

將 spark 目錄加到環境變數,執行 vim ~/.bashrc 新增如下配置:

export SPARK_HOME=/usr/lib/spark
export PATH=$PATH:$SPARK_HOME/bin:$SPARK_HOME/sbin

儲存後執行 source ~/.bashrc 使配置生效。


接著需要配置了兩個檔案,先執行 cd /usr/lib/spark/conf

1、 配置 slaves 檔案

mv slaves.template slaves  # 將slaves.template重新命名為slaves

slaves檔案設定從節點。編輯 slaves 內容,把預設內容localhost替換成兩個從節點的名字:

Slave01
Slave02


2、配置 spark-env.sh 檔案

mv spark-env.sh.template spark-env.sh

編輯 spark-env.sh 新增如下內容:

export SPARK_DIST_CLASSPATH=$(/usr/lib/hadoop/bin/hadoop classpath)
export HADOOP_CONF_DIR=/usr/lib/hadoop/etc/hadoop
export SPARK_MASTER_IP=172.31.40.68   # 注意這裡填的是Master節點的私有IP 
export JAVA_HOME=/usr/lib/java


配置好後,將 Master 上的 /usr/lib/spark 資料夾複製到各個 slave 節點上。在 Master 節點上執行:

$ cd /usr/lib
$ tar -zcf ~/spark.master.tar.gz ./spark
$ scp ~/spark.master.tar.gz Slave01:/home/hadoop
$ scp ~/spark.master.tar.gz Slave02:/home/hadoop

然後分別在兩個 slave 節點上執行:

$ sudo tar -zxf ~/spark.master.tar.gz -C /usr/lib
$ sudo chown -R hadoop /usr/lib/spark


在啟動 Spark 叢集之前,先確保啟動了 Hadoop 叢集:

$ start-dfs.sh
$ start-yarn.sh
$ mr-jobhistory-daemon.sh start historyserver
$ start-master.sh  # 啟動 spark 主節點
$ start-slaves.sh  # 啟動 spark 從節點

可通過 ec2-xx-xxx-xxx-xx.us-west-2.compute.amazonaws.com:8080 訪問 spark web UI 。




執行 Spark 分散式例項

1、通過命令列提交 JAR 包:

$ spark-submit --class org.apache.spark.examples.SparkPi --master spark://Master:7077 /usr/lib/spark/examples/jars/spark-examples_2.11-2.3.3.jar 100 2>&1 | grep "Pi is roughly" 

結果如下說明成功:


2、通過 IDEA 遠端連線執行程式:

可以在 本地 IDEA 中編寫程式碼,遠端提交到雲端機上執行,這樣比較方便除錯。需要注意的是 Master 地址填雲端機的公有 IP 地址。下面以一個 WordVec 程式示例,將句子轉換為向量形式:

import org.apache.spark.{SparkConf, SparkContext}
import org.apache.log4j.{Level, Logger}
import org.apache.spark.ml.feature.Word2Vec
import org.apache.spark.ml.linalg.Vector
import org.apache.spark.sql.Row
import org.apache.spark.sql.SparkSession

object Word2Vec {
  def main(args: Array[String]) {
    Logger.getLogger("org").setLevel(Level.ERROR)  // 控制輸出資訊
    Logger.getLogger("com").setLevel(Level.ERROR)

    val conf = new SparkConf()
      .setMaster("spark://ec2-54-190-51-132.us-west-2.compute.amazonaws.com:7077")  // 填公有DNS或公有IP地址都可以
      .setAppName("Word2Vec")
      .set("spark.cores.max", "4")
      .set("spark.executor.memory", "2g")
    val sc = new SparkContext(conf)

    val spark = SparkSession
      .builder
      .appName("Word2Vec")
      .getOrCreate()

    val documentDF = spark.createDataFrame(Seq(
      "Hi I heard about Spark".split(" "),
      "I wish Java could use case classes".split(" "),
      "Logistic regression models are neat".split(" ")
    ).map(Tuple1.apply)).toDF("text")

    val word2Vec = new Word2Vec()
      .setInputCol("text")
      .setOutputCol("result")
      .setVectorSize(3)
      .setMinCount(0)
    val model = word2Vec.fit(documentDF)

    val result = model.transform(documentDF)
    result.collect().foreach { case Row(text: Seq[_], features: Vector) =>
      println(s"Text: [${text.mkString(", ")}] => \nVector: $features\n") }
  }
}


IDEA 控制檯輸出:


關閉 Spark 和 Hadoop 叢集有以下命令:

$ stop-master.sh
$ stop-slaves.sh
$ stop-yarn.sh
$ stop-dfs.sh
$ mr-jobhistory-daemon.sh stop historyserver



當然最後也是最重要的是,使用完後不要忘了關閉 EC2 例項,不然會 24 小時不間斷產生費用的。





<