Hadoop運行在Kubernetes平臺實踐

Hadoop與Kubernetes就好像江湖裏的兩大絕世高手,一個是成名已久的長者,至今仍然名聲遠揚,一個則是初出茅廬的青澀少年,骨骼驚奇,不走尋常路,一出手便驚詫了整個武林。Hadoop與Kubernetes之間有很深的淵源,由於都出自IT豪門——Google,只不過,後者是親兒子,正由於有大佬背書,因此Kubernetes一出山,江湖各路門派便都蜂擁而至,擁護稱王。node

不知道是由於Hadoop是乾兒子的緣故仍是由於「廉頗老矣」,總之,Hadoop朋友圈的後輩們如Spark、Storm等早都有了在Kubernetes上部署運行的各類資料和案例,但Hadoop卻一直遊離於Kubernetes體系以外,本文咱們給出Hadoop在Kubernetes上的實踐案例,以彌補這種缺憾。sql

Hadoop容器化的資料很多,但Hadoop部署在Kubernetes上的資料幾乎沒有,這主要是如下幾個緣由致使的:api

  • 第一, Hadoop集羣重度依賴DNS機制,一些組件還使用了反向域名解析,以肯定集羣中的節點身份,這對Hadoop在Kubernetes上的建模和運行帶來極大挑戰,須要深刻了解Hadoop集羣工做原理而且精通Kubernetes,才能很好解決這一難題。
  • 第二, Hadoop新的Map-Reduce計算框架Yarn的模型出現的比較晚,它的集羣機制要比HDFS複雜,資料也相對較少,增長了Hadoop總體建模與遷移Kubernetes平臺的難度。
  • 第三, Hadoop與Kubernetes分別屬於兩個不一樣的領域,一個是傳統的大數據領域,一個是新興的容器與微服務架構領域,這兩個領域之間交集原本很小,加之Hadoop最近幾年已經失去焦點(這點從百度搜索關鍵詞就能發現),因此,沒有多少人關注和研究Hadoop在Kubernetes的部署問題,也是情理之中的事情。

Hadoop 2.0實際上是由兩套完整的集羣所組成,一個是基本的HDFS文件集羣,一個是YARN資源調度集羣,以下圖所示:瀏覽器

所以在Kubernetes建模以前,咱們須要分別對這兩種集羣的工做機制和運行原理作出深刻的分析,下圖是HDFS集羣的架構圖:bash

咱們看到,HDFS集羣是由NameNode(Master節點)和Datanode(數據節點)等兩類節點所組成,其中,客戶端程序(Client)以及DataNode節點會訪問NameNode,所以,NameNode節點須要建模爲Kubernetes Service以提供服務,如下是對應的Service定義文件:服務器

apiVersion: v1 
kind: Service 
metadata: 
name: k8s-hadoop-master 
spec: 
type: NodePort 
selector: 
app: k8s-hadoop-master 
ports: 
- name: rpc 
  port: 9000 
  targetPort: 9000 
- name: http 
  port: 50070 
  targetPort: 50070 
  nodePort: 32007 

其中,NameNode節點暴露2個服務端口:網絡

  • 9000端口用於內部IPC通訊,主要用於獲取文件的元數據
  • 50070端口用於HTTP服務,爲Hadoop 的Web管理使用

爲了減小Hadoop鏡像的數量,咱們構建了一個鏡像,而且經過容器的環境變量HADOOP_NODE_TYPE來區分不一樣的節點類型,從而啓動不一樣的Hadoop組件,下面是鏡像裏的啓動腳本startnode.sh的內容:架構

#!/usr/bin/env bash 
sed -i "s/@HDFS_MASTER_SERVICE@/$HDFS_MASTER_SERVICE/g" $HADOOP_HOME/etc/hadoop/core-site.xml 
sed -i "s/@HDOOP_YARN_MASTER@/$HDOOP_YARN_MASTER/g" $HADOOP_HOME/etc/hadoop/yarn-site.xml 
yarn-master 
HADOOP_NODE="${HADOOP_NODE_TYPE}" 
if [ $HADOOP_NODE = "datanode" ]; then 
    echo "Start DataNode ..." 
    hdfs datanode  -regular 
 
else 
if [  $HADOOP_NODE = "namenode" ]; then 
    echo "Start NameNode ..." 
    hdfs namenode 
else 
    if [ $HADOOP_NODE = "resourceman" ]; then 
        echo "Start Yarn Resource Manager ..." 
        yarn resourcemanager 
    else 
 
         if [ $HADOOP_NODE = "yarnnode" ]; then 
             echo "Start Yarn Resource Node  ..." 
             yarn nodemanager    
         else               
            echo "not recoginized nodetype " 
         fi 
    fi 
fi   
 
fi 

咱們注意到,啓動命令裏把Hadoop配置文件(core-site.xml與yarn-site.xml)中的HDFS Master節點地址用環境變量中的參數HDFS_MASTER_SERVICE來替換,YARN Master節點地址則用HDOOP_YARN_MASTER來替換。下圖是Hadoop HDFS 2節點集羣的完整建模示意圖:併發

圖中的圓圈表示Pod,能夠看到,Datanode並無建模Kubernetes Service,而是建模爲獨立的Pod,這是由於Datanode並不直接被客戶端所訪問,所以無需建模Service。當Datanode運行在Pod容器裏的時候,咱們須要修改配置文件中的如下參數,取消DataNode節點所在主機的主機名(DNS)與對應IP地址的檢查機制:app

dfs.namenode.datanode.registration.ip-hostname-check=false 

若是上述參數沒有修改,就會出現DataNode集羣「分裂」的假象,由於Pod的主機名沒法對應Pod的IP地址,所以界面會顯示2個節點,這兩個節點都狀態都爲異常狀態。

下面是HDFS Master節點Service對應的Pod定義:

apiVersion: v1 
kind: Pod 
metadata: 
name: k8s-hadoop-master 
labels: 
app: k8s-hadoop-master 
spec: 
containers: 
- name: k8s-hadoop-master 
  image: kubeguide/hadoop 
  imagePullPolicy: IfNotPresent 
  ports: 
    - containerPort: 9000 
    - containerPort: 50070     
  env: 
    - name: HADOOP_NODE_TYPE 
      value: namenode 
    - name: HDFS_MASTER_SERVICE 
      valueFrom: 
        configMapKeyRef: 
          name: ku8-hadoop-conf 
          key: HDFS_MASTER_SERVICE 
    - name: HDOOP_YARN_MASTER 
      valueFrom: 
        configMapKeyRef: 
          name: ku8-hadoop-conf 
          key: HDOOP_YARN_MASTER 
restartPolicy: Always 

下面是HDFS的Datanode的節點定義(hadoop-datanode-1):

apiVersion: v1 
kind: Pod 
metadata: 
name: hadoop-datanode-1 
labels: 
  app: hadoop-datanode-1 
spec: 
containers: 
- name: hadoop-datanode-1 
  image: kubeguide/hadoop 
  imagePullPolicy: IfNotPresent 
  ports: 
    - containerPort: 9000 
    - containerPort: 50070     
  env: 
    - name: HADOOP_NODE_TYPE 
      value: datanode 
    - name: HDFS_MASTER_SERVICE 
      valueFrom: 
        configMapKeyRef: 
          name: ku8-hadoop-conf 
          key: HDFS_MASTER_SERVICE 
    - name: HDOOP_YARN_MASTER 
      valueFrom: 
        configMapKeyRef: 
          name: ku8-hadoop-conf 
          key: HDOOP_YARN_MASTER         
restartPolicy: Always 

實際上,Datanode能夠用DaemonSet方式在每一個Kubernerntes節點上部署一個,在這裏爲了清晰起見,就沒有用這個方式 定義。接下來,咱們來看看Yarn框架如何建模,下圖是Yarn框架的集羣架構圖:

咱們看到,Yarn集羣中存在兩種角色的節點:ResourceManager以及NodeManger,前者屬於Yarn集羣的頭腦(Master),後者是工做承載節點(Work Node),這個架構雖然與HDFS很類似,但由於一個重要細節的差異,沒法沿用HDFS的建模方式,這個細節就是Yarn集羣中的ResourceManager要對NodeManger節點進行嚴格驗證,即NodeManger節點的節點所在主機的主機名(DNS)與對應IP地址嚴格匹配,簡單來講,就是要符合以下規則:

NodeManger創建TCP鏈接時所用的IP地址,必須是該節點主機名對應的IP地址,即主機DNS名稱解析後返回節點的IP地址。

因此咱們採用了Kubernetes裏較爲特殊的一種Service——Headless Service來解決這個問題,即爲每一個NodeManger節點建模一個Headless Service與對應的Pod,下面是一個ResourceManager與兩個NodeManger節點所組成的Yarn集羣的建模示意圖:

Headless Service的特殊之處在於這種Service沒有分配Cluster IP,在Kuberntes DNS裏Ping這種Service的名稱時,會返回後面對應的Pod的IP地址,若是後面有多個Pod實例,則會隨機輪詢返回其中一個的Pod地址,咱們用Headless Service建模NodeManger的時候,還有一個細節須要注意,即Pod的名字(容器的主機名)必須與對應的Headless Service的名字同樣,這樣一來,當運行在容器裏的NodeManger進程向ResourceManager發起TCP鏈接的過程當中會用到容器的主機名,而這個主機名剛好是NodeManger Service的服務名,而這個服務名解析出來的IP地址又恰好是容器的IP地址,這樣一來,就巧妙的解決了Yarn集羣的DNS限制問題。

下面以yarn-node-1爲例,給出對應的Service與Pod的YAM文件,首先是yarn-node-1對應的Headless Service的YAM定義:

apiVersion: v1 
kind: Service 
metadata: 
name: yarn-node-1 
spec: 
clusterIP: None 
selector: 
app: yarn-node-1 
ports: 
 - port: 8040 

注意到定義中「clusterIP:None」這句話,代表這是一個Headless Service,沒有本身的Cluster IP地址,下面給出YAM文件定義:

apiVersion: v1 
kind: Pod 
metadata: 
name: yarn-node-1 
labels: 
app: yarn-node-1 
spec: 
containers: 
- name: yarn-node-1 
  image: kubeguide/hadoop 
  imagePullPolicy: IfNotPresent 
  ports: 
    - containerPort: 8040 
    - containerPort: 8041    
    - containerPort: 8042         
  env: 
    - name: HADOOP_NODE_TYPE 
      value: yarnnode 
    - name: HDFS_MASTER_SERVICE 
      valueFrom: 
        configMapKeyRef: 
          name: ku8-hadoop-conf 
          key: HDFS_MASTER_SERVICE 
    - name: HDOOP_YARN_MASTER 
      valueFrom: 
        configMapKeyRef: 
          name: ku8-hadoop-conf 
          key: HDOOP_YARN_MASTER           
restartPolicy: Always 

ResourceManager的YAML定義沒有什麼特殊的地方,其中Service定義以下:

apiVersion: v1 
kind: Service 
metadata: 
name: ku8-yarn-master 
spec: 
type: NodePort 
selector: 
app: yarn-master 
ports: 
 - name: "8030"        
   port: 8030 
 - name: "8031"      
   port: 8031 
 - name: "8032" 
   port: 8032      
 - name: http 
   port: 8088 
   targetPort: 8088 
   nodePort: 32088 

對應的Pod定義以下:

apiVersion: v1 
kind: Pod 
metadata: 
name: yarn-master 
labels: 
app: yarn-master 
spec: 
containers: 
- name: yarn-master 
  image: kubeguide/hadoop 
  imagePullPolicy: IfNotPresent 
  ports: 
    - containerPort: 9000 
    - containerPort: 50070     
  env: 
    - name: HADOOP_NODE_TYPE 
      value: resourceman 
    - name: HDFS_MASTER_SERVICE 
      valueFrom: 
        configMapKeyRef: 
          name: ku8-hadoop-conf 
          key: HDFS_MASTER_SERVICE 
    - name: HDOOP_YARN_MASTER 
      valueFrom: 
        configMapKeyRef: 
          name: ku8-hadoop-conf 
          key: HDOOP_YARN_MASTER           
restartPolicy: Always 

目前這個方案,還遺留了一個問題有待解決:HDFS NameNode節點重啓後的文件系統格式化問題,這個問題能夠經過啓動腳原本解決,即判斷HDFS文件系統是否已經格式化過,若是沒有,就啓動時候執行格式化命令,不然跳過格式化命令。

安裝完畢後,咱們能夠經過瀏覽器訪問Hadoop的HDFS管理界面,點擊主頁上的Overview頁籤會顯示咱們熟悉的HDFS界面:

切換到Datanodes頁籤,能夠看到每一個Datanodes的的信息以及當前狀態:

接下來,咱們能夠登陸到NameNode所在的Pod裏並執行HDSF命令進行功能性驗證,下面的命令執行結果是創建一個HDFS目錄,而且上傳一個文件到此目錄中:

root@hadoop-master:/usr/local/hadoop/bin# hadoop fs -ls  / 
root@hadoop-master:/usr/local/hadoop/bin# hadoop fs -mkdir /leader-us 
root@hadoop-master:/usr/local/hadoop/bin# hadoop fs -ls / 
Found 1 items 
drwxr-xr-x   - root supergroup          0 2017-02-17 07:32 /leader-us 
root@hadoop-master:/usr/local/hadoop/bin# hadoop fs -put hdfs.cmd /leader-us 

而後,咱們能夠在HDFS管理界面中瀏覽HDFS文件系統,驗證剛纔的操做結果:

接下來,咱們再登陸到hadoop-master對應的Pod上,啓動一個Map-Reduce測試做業——wordcount,做業啓動後,咱們能夠在Yarn的管理界面中看到做業的執行信息,以下圖所示:

看成業執行完成後,能夠經過界面看到詳細的統計信息,好比wordcount的執行結果以下圖所示:

最後,咱們進行了裸機版Hadoop集羣與Kubernetes之上的Hadoop集羣的性能對比測試,測試環境爲十臺服務器組成的集羣,具體參數以下:

硬件:

  • CPU:2*E5-2640v3-8Core
  • 內存:16*16G DDR4
  • 網卡:2*10GE多模光口
  • 硬盤:12*3T SATA

軟件:

  • BigCloud Enterprise Linux 7(GNU/Linux 3.10.0-514.el7.x86_64 x86_64)
  • Hadoop2.7.2
  • Kubernetes 1.7.4+ Calico V3.0.1

咱們執行了如下這些標準測試項:

  • TestDFSIO:分佈式系統讀寫測試
  • NNBench:NameNode測試
  • MRBench:MapReduce測試
  • WordCount:單詞頻率統計任務測試
  • TeraSort:TeraSort任務測試

綜合測試下來,Hadoop跑在Kuberntes集羣上時,性能有所降低,以TestDFSIO的測試爲例,下面是Hadoop集羣文件讀取的性能測試對比:

咱們看到,Kubernetes集羣上的文件讀性能與物理機相比,降低了差很少30%左右,而且任務執行時間也增長很多,再來對比文件寫入的性能,測試結果以下下圖所示:

咱們看到,寫文件性能的差距並不大,這裏的主要緣由是在測試過程當中,HDFS寫磁盤的速度遠遠低於讀磁盤的速度,所以沒法拉開差距。

之因此部署在Kuberntes上的Hadoop集羣的性能會有所降低,主要一個緣由是容器虛擬網絡所帶來的性能損耗,若是用Host Only模型,則二者之間的差距會進一步縮小,下圖是TestDFSIO測試中Hadoop集羣文件讀取的性能測試對比:

所以咱們建議在生產環境中採用Host Only的網絡模型,以提高Hadoop的集羣性能。

攻下Hadoop在Kubernetes上的部署,而且在生產中加以驗證,咱們能夠很自豪的說,如今沒有什麼可以難倒應用向Kubernetes的遷移的步伐,採用統一的PaaS構建企業的應用集羣和大數據集羣,實現資源的共享和服務的統一管理將會大大的提高企業的業務部署速度和管理的效率。

歡迎工做一到五年的Java工程師朋友們加入Java架構開發: 855835163

羣內提供免費的Java架構學習資料(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用本身每一分每一秒的時間來學習提高本身,不要再用"沒有時間「來掩飾本身思想上的懶惰!趁年輕,使勁拼,給將來的本身一個交代!

相關文章
相關標籤/搜索