CentOS7上Hadoop集群搭建与使用小记(五)——Spark的基本使用

Jupyter

启动Jupyter(内核是pyspark)的方法(这个应该是local本机模式):

1
PYSPARK_DRIVER_PYTHON=jupyter PYSPARK_DRIVER_PYTHON_OPTS="notebook" pyspark

加上一些其他的配置:

  • 设置Python版本为Python3
  • ip为0.0.0.0:7777(这样别的机器也可以通过7777访问)
  • 设置为YARN-client模式(利用集群资源)
  • executor个数为7,每个executor的CPU核心数为8
  • PYTHONHASHSEED为0 (否则会出错)

最后的我一般用的命令是:

1
#!/usr/bin/bash
PYTHONHASHSEED=0 PYSPARK_PYTHON=python3 PYSPARK_DRIVER_PYTHON=jupyter PYSPARK_DRIVER_PYTHON_OPTS="notebook --ip=0.0.0.0 --port=7777" pyspark --master=yarn-client --num-executors=7 --executor-cores=8

在Jupyter中,建立一个Python3的notebook,等待Kernel启动完成,即可开始使用。

另外,在notebook中,他是自动建立SparkContext变量sc的,可以直接使用。例如利用投针法计算Pi的近似值:

1
2
3
4
5
6
7
8
# Spark Pi
from random import random
def sample(_):
x, y = random(), random()
return 1 if x**2+y**2<=1 else 0

n_samples = 10**8
print(sc.parallelize(range(n_samples)).map(sample).sum()*4.0/n_samples)


spark-submit提交任务

Jupyter-notebook可以用作我们平常交互式测试时使用,但是如果要运行一些任务,时间很长,想看看执行的进度,或者还有很多其他附属的文件需要一起执行,内存占用之类的还需要个性配置,那么用spark-submit命令提交任务最好。

利用python运行main.py,提交任务,设置其依赖的文件a.py,b.py,c.py(因为每个executor是单独运行的,因此其依赖文件也要指定好,然后传输到各个executor上去)以及运行环境(YARN-Client, 7个Executor,每个8个CPU核心),写法如下:

1
PYSPARK_PYTHON=python3 spark-submit --master=yarn-client --num-executors=7 --executor-cores=8 --files a.py,b.py,c.py main.py

在这种情况下,我们的main.py中就要手动建立SparkContext也即sc变量了:

1
from pyspark import SparkContext, SparkConf, StorageLevel

conf = SparkConf()
        .setMaster("yarn-client")
        .setAppName("Spark-App")
        .set("spark.storage.memoryFraction", "0.9")
sc = SparkContext(conf=conf, pyFiles=['a.py', 'b.py', 'c.py'])

还可以传参数设置内存:

1
PYSPARK_PYTHON=python3 spark-submit --master=yarn-client --num-executors=7 --executor-cores=6 --driver-memory=2g --executor-memory=10g --files bikeUtil.py,weatherHoliday.py,stationFlow.py,predictor.py main.py

SparkContext, SparkConf

在SparkConf, SparkContext这里,我们也可以通过传递参数pyFiles来设置依赖的文件,通过SparkConf的set函数来设置一些参数。
据说SparkContext.addFile(…)可以提交依赖的文件(还未测试)

详情请见官方文档: https://spark.apache.org/docs/latest/configuration.html


Parallelize

在利用Python编写并行化程序时该如何思考呢?先讲一些程序结构。

实际上Spark的架构是分为Driver和Worker的,Driver负责执行总程序(额……意会意会),负责调用Spark的对象来并行化执行,从而Spark架构帮助我们将要执行的任务传输到各个worker上执行,最后再汇总回来。

因此,其实我们用Python编写程序,不涉及到sc相关的变量时,其实我们就是在跑Python程序,调用sc产生的rdd的map,reduceByKey,collect时会调用相关的Spark架构去并行化执行。

sc.parallelize是并行化的起点(当然还有其他函数,例如textFile):

1
rdd = sc.parallelize(...)

创造一个rdd,rdd可以transformation,但并不会真正执行,只有在action执行时才会,即lazy-evaluation,具体这里就不展开了。

关于他具体是如何并行化的呢?根据原理有两种方法,一种是基于Hash的Partitioner,一种是Ranger,HashPartitioner相当于一个Hash函数,将key转化到对应的Partition分区的编号。RangePartitioner的原理还有待探究。


数据的来源

我们可以用sc.textFile(..)来读取相关文本文件,默认是从HDFS读取的,不需要写前面的hdfs://...

另外,我们也可以通过本机读取,但文件名需要这样写:file:///...。但是本机读取需要注意的一点是,由于worker是分布式执行的,因此本机读取的文件需要放在各个分布式的机器上,推荐可以在Driver端读取完,然后broadcast到各个worker。


闭包与Broadcast

一般在__main__里面声明的变量,会自动打包并传送到worker中,在worker的代码中会访问的到。但是如果是在函数中建立的,需要用global实现。

但是如果可以的话最好用broadcast将变量广播出去,这样就不用每次task把信息重新传输一遍了。

共享变量broadcast ^SparkBroadcast

Q: 如何在传给spark transformations操作的函数中访问共享变量?
A: 根据官网Programming Guide文档说明(参见这里),当作为参数传给spark操作(如map或reduce)的函数在远程机器的节点上执行时,函数中使用到的每个变量的副本(separate copies)也会被拷贝到这些节点上以便函数访问,如果这些变量在节点上被修改,那这些修改不会被反传回spark driver program,即在实现业务代码时,应由实现者保证这些变量的只读特性。因为在不同任务间维护通用的、支持读/写的共享变量会降低spark效率。
举个例子,下面的代码说明了如何在传给spark操作的函数中借助全局变量实现共享访问:

Q: 除通过global variable共享变量外,spark还支持什么方式共享变量?
A: Spark还支持broadcast变量和accumulators这两种共享变量的方式。其中,broadcast允许开发者在spark集群的每个节点上保持变量的一份只读cache,本质上,broadcast变量也是global变量,只不过它是由开发者显式分发到集群节点上的,而非spark根据每个task调用的函数对变量的访问情况自动拷贝。至于accumulators,顾名思义,它只支持add操作,具体语法可参考spark programming guide关于accumulators部分的说明。

Q: broadcast变量与普通global变量有何关系?各自的适用场合?
A: 实际上,broadcast变量是一种global变量,它们均可以实现在分布式节点中执行函数时共享变量。其中,普通global变量是随着spark对task的调度根据实际情况由spark调度器负责拷贝至集群节点的,这意味着若有需访问某global变量的多个task执行时,每个task的执行均有变量拷贝过程;而broadcast变量则是由开发者主动拷贝至集群节点且会一直cache直至用户主动调用unpersist或整个spark作业结束。
PS: 实际上,即使调用unpersist也不会立即释放资源,它只是告诉spark调度器资源可以释放,至于何时真正释放由spark调度器决定,参见SPARK-4030。
结论:若共享变量只会被某个task使用1次,则使用普通global变量共享方式即可;若共享变量会被先后执行的多个tasks访问,则broadcast方式会节省拷贝开销。
再次提醒:若使用了broadcast方式共享变量,则开发者应在确定该变量不再需要共享时主动调用unpersist来释放集群资源。

Broadcast的机理详见:https://github.com/JerryLead/SparkInternals/blob/master/markdown/7-Broadcast.md


编码范式的改变

  1. 采用函数式编程方式,数据只有不断的转化(transform),不可更改;当我们要某些值时,使用action获取它。
  2. 尽可能并行化,不要产生过大的全局变量(即闭包传过去东西不要太多)
  3. 注意!只能在driver进行并行化, worker不行
  4. 不要传输(过大)的对象(即这些序列中的元素不要是对象)

Multiple SparkContext

一台机器只能启动一个Spark?否则会Address冲突,如何解决多人同时使用的问题?

1
16/03/21 06:38:23 WARN AbstractLifeCycle: FAILED SelectChannelConnector@0.0.0.0:4040: java.net.BindException: Address already in use

貌似可以重新注册一个ip,可以同时跑,关键是内存不够……


Lost Executor (内存不够)

如果Executor注册上了,但是执行时,又断掉了(ERROR YarnScheduler: Lost executor xxx),那很可能是由于内存不够的原因。

在执行的时候设置driver-memory, executor-memory, 试着调整Spark配置中spark.yarn.driver.memoryOverhead, executor.memoryOverhead的大小^SparkMemory1 ^SparkMemory3,memoryOverhead一般用处不太大。

下面是摘自网上性能调优Blog的一段话 ^SparkPerformanceTurning

在YARN模式下:

  1. 集群task并行度:SPARK_ EXECUTOR_INSTANCES* SPARK_EXECUTOR_CORES;

  2. 集群内存总量:(executor个数) * (SPARK_EXECUTOR_MEMORY+ spark.yarn.executor.memoryOverhead)+(SPARK_DRIVER_MEMORY+spark.yarn.driver.memoryOverhead)。

重点强调:Spark对Executor和Driver额外添加堆内存大小,Executor端:由spark.yarn.executor.memoryOverhead设置,默认值executorMemory 0.07与384的最大值。Driver端:由spark.yarn.driver.memoryOverhead设置,默认值driverMemory 0.07与384的最大值。

通过调整上述参数,可以提高集群并行度,让系统同时执行的任务更多,那么对于相同的任务,并行度高了,可以减少轮询次数。举例说明:如果一个stage有100task,并行度为50,那么执行完这次任务,需要轮询两次才能完成,如果并行度为100,那么一次就可以了。

但是在资源相同的情况,并行度高了,相应的Executor内存就会减少,所以需要根据实际实况协调内存和core。此外,Spark能够非常有效的支持短时间任务(例如:200ms),因为会对所有的任务复用JVM,这样能减小任务启动的消耗,Standalone模式下,core可以允许1-2倍于物理core的数量进行超配。

  • executor_memory是整个executor的内存(包括多个core), core多了,那么需要的内存就多,如果超过executor的内存,就会崩;因此内存和CPU的利用是一个tradeoff
  • 可以用rdd.cache()然后看一下内存变化,就知道这次数据集所占的空间了。
  • 尽量不要让对象拷贝,即使你认为是引用,也有可能是拷贝(占多份内存),可以改成先collect再broadcast的方式

性能调优详见:http://www.raychase.net/3546

另外,有一个参数最好需要设置一下,默认是0.6,表示我们给executor设置的memory中只有60%的资源是分配给运行时的内存,其余的分配给RDD.cache()时的空间。一般如果cache不是很大的话,那么放宽这个比例可以赢得更大的内存,以运行更多的cores:

1
conf = SparkConf().set("spark.storage.memoryFraction", "0.9")


源地址:https://www.zybuluo.com/Vany/note/319633

CentOS7上Hadoop集群搭建与使用小记(四)——难题集坑

这里汇集一些部署、使用过程中的一些问题。

Ambari Metric监视不到 (CPU,内存等显示不了)

很有可能是时间不匹配
除了时间,时区也要匹配
最好能配置NTP,使得整体同步

yum被占用

自动安装过程中可能会出现yum被占用的情况,执行:
kill xxx
杀死被占用的进程
其中xxx为占用yum的进程号

HDFS权限问题

由于权限问题,启动PySpark可能启动不起来(写权限错误)

两个原则:

  • hdfs是HDFS的super Administrator
  • 谁建立的文件夹就是谁的

解决方案:

  • 先在hdfs下hadoop fs -chmod 777 /user/设置权限
  • 以你想要的帐户登录
  • 再在对应的账户下hadoop fs -mkdir /user/xxx/
  • 这样建立的文件夹就是你的了

PYTHONHASHSEED相关的问题

在启动Spark前添加:

1
export SPARK_YARN_USER_ENV=PYTHONHASHSEED=0


源地址:https://www.zybuluo.com/Vany/note/318262

CentOS7上Hadoop集群搭建与使用小记(三)——部署过程

预备工作

首先要按照(一)——预备工作中写的设置好要设置的东西。

接下来安装expect软件: yum install expect -y

再下载script.zip文件包,在yumsource机器下解压,目录如下:

1
[root@yumsource script]# tree
.
├── addnewhost.sh
├── applyall.sh
├── configmaster.sh
├── hostlist
│   └── hostlist_new
├── init.sh
├── others
│   ├── ambari.repo
│   ├── configclient_template.sh
│   ├── deployclient.sh
│   ├── deployfiles.sh
│   ├── deploypubkeys.sh
│   ├── deploypythons.sh
│   ├── disSELinuxCfg
│   ├── hosts_template
│   ├── installmaster.sh
│   ├── installpython34.sh
│   ├── removefiles.sh
│   └── updatepython2.sh
├── remoterunall.sh
└── tf

设置好host列表(存在script文件夹的hostlist/hostlist_new文件下),例如:

1
10.15.198.204 master.hadoopcluster
10.15.198.205 slave1.hadoopcluster
10.15.198.206 slave2.hadoopcluster
10.15.198.207 slave3.hadoopcluster
10.15.198.208 slave4.hadoopcluster
10.15.198.200 slave5.hadoopcluster
10.15.198.202 slave6.hadoopcluster

最后需要特别说明的是,在script/others夹下有一个host_template文件,这里需要在最后手动加入yumsource的域名和ip地址,例如:

1
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
10.15.198.214 yumsource.hadoop

部署全新环境

在script目录下执行命令: ./init.sh hostlist/hostlist_new 即可。

如果在这个过程中发现:

  1. yum程序被卡死,需要手动切到对应的机器,kill到占用的进程
  2. 一些地方配置错误,导致整个程序不正常退出,那么再次运行本脚本即可

部署Python3 & Packages

在Spark中我需要集群跑Python3,因此每个机器都要安装Python3以及numpy, scipy, matplotlib等packages.

首先是Python3,下载官方的Python3源码,并且修改一些编译配置(很抱歉我忘了……出错上网搜就知道了),这样为了后面安装Python的package不出错。

然后安装一些必备的软件:

1
2
3
4
5
6
7
8
9
10
11
12
13
# for complier & python3 installation
yum install epel-release -y
yum install gcc-c++ -y
# for numpy or scipy or jupyter
yum install zlib-devel -y
yum install python34-devel -y
yum install blas-devel -y
yum install lapack-devel -y
# for easy_install-3.4
yum install python34-setuptools -y
# for matplotlib
yum install freetype-devel -y
yum install libpng-devel -y

接着在Python3的目录下,执行下面语句,Python就安装好了。

1
./configure --enable-shared
make clean
make
make install

下载好pip-8.1.0.tar.gz,接着安装pip3(利用之前安装好的python34-setuptools中的easy_install-3.4)

1
easy_install-3.4 pip-8.1.0.tar.gz

最后进入装有packages的文件夹,运行:

1
pip3 install --no-index --find-links=./ numpy
pip3 install --no-index --find-links=./ scipy
pip3 install --no-index --find-links=./ pandas
pip3 install --no-index --find-links=./ matplotlib

附:要安装对应的安装包是在别的有网的机器上下载下来的(注意:环境要一样,例如都是Linux-CentOS64位,且都是Python3的pip),将numpy下载到当前目录下的命令如下:

1
pip3 install --download ./ xxxpackage

以上过程我写成了一个脚本installPythonRelated.sh,虽然可以调用remoterunall.sh,但是由于安装过程过于漫长(而脚本是一个个机器顺序执行的),所以推荐利用applyall.sh写一个脚本(例如deployfiles.sh)将安装包分发的各个机器上,再手动在各个机器启动,同时并行安装。

部署Jupyter

如果安装了本文最开始的那些插件,那么Jupyter的安装方法也和部署numpy等安装包一样了:先下载下来相关的安装包,然后在对应的机器用pip3进行安装即可。


源地址:https://www.zybuluo.com/Vany/note/318186

CentOS7上Hadoop集群搭建与使用小记(二)——脚本介绍

这一部分主要介绍的是思想以及原理,如果要具体操作步骤,可以直接跳过看下一部分(三).

基本思想

抽象看来,集群的部署操作基本只有两种类型操作:

  • 在Server上执行脚本,对每个机器产生作用,例如集体分发一些文件这种,我称之为apply
  • Server端调用Client,相当于每个Client执行脚本,例如在Client上安装软件这类的,这种操作我称之为Remote-Run.

基本脚本

根据基本思想小节中提到的两种操作,我用两个脚本来抽象这两种操作,一个是applyall.sh,一个是remoterunall.sh。

传送文件tf (transfer file)

在正式介绍applyall和remoterunall之前,我先介绍一个基本功能,tf,即传送文件的功能。传送文件在部署过程中是非常常见的一个需求,因此我们将其抽象出来作为一个单独的“命令”。

因为传送文件的前提是目标目录存在,因此每次传送文件前需要先mkdir xxx一下以确保文件夹存在,再传送文件。

但在传送过程中很有可能需要密码或者输入yes以保存对方的fingerprint,因此这里采用了expect程序来做交互式处理,如果没有的话需要安装一下:yum install expect

具体实现的代码如下:

1
#!/usr/bin/expect

set source   [lindex $argv 0]
set dest     [lindex $argv 1]
set destfile [lindex $argv 2]
set serverip [lindex $argv 3]
set pwd      [lindex $argv 4]

spawn ssh root@$serverip "mkdir $dest"
expect {
"*continue connecting*" { send "yes\r"; exp_continue }
"*password*" { send "$pwd\r"; exp_continue }
"#" { send "\r" }
}

spawn scp -r $source root@$serverip:$dest$destfile
expect {
"*continue connecting*" { send "yes\r"; exp_continue }
"*password*" { send "$pwd\r"; exp_continue }
"#" { send "\r" }
}

Apply-All

先来说说applyall.sh. 调用脚本,传入hostlist(格式同hosts文件),以及要执行的bashfile,就会帮你依次执行,例如我想将本地的hosts文件传送到各个其他机器,则用法如下:
./applyall.sh hostlist deployhosts.sh mypassword
其中hostlist是预先定好的hosts列表(见上一个文章文末),deployhosts.sh内容如下(该脚本必须按照ip,name,pwd的顺序来取对应的参数):

1
2
3
4
5
6
7
#!/usr/bin/bash
hostip=$1
hostname=$2
pwd=$3

echo -e "\n===Sending hosts to client $1, $2..."
./tf hosts /etc/ hosts $hostip $pwd

这里的tf即上文提到的传送文件的tf。

applyall.sh的脚本如下所示,如果参数中没有传送pwd参数,那么会询问用户手动输入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/bash

if [ $# -lt 2 ]; then
echo "Error call format"
echo "Format: ./applyall.sh hostlist bashfile [pwd]"
exit
fi

hostlist=$1
bashfile=$2
if [ $# -lt 3 ]; then
read -p "Enter the client password:" -s pwd
else
pwd=$3
fi

echo
echo ===Enter Apply-All hostlist: $hostlist, bashfile: $bashfile

cat $hostlist | while read hostInfo
do
hostip=`echo $hostInfo | cut -d " " -f1`
hostname=`echo $hostInfo | cut -d " " -f2`
echo -e "\n===Applying Client $hostip $hostname..."
bash $bashfile $hostip $hostname $pwd
done

echo -e "\n===Leave Apply-All."

Remote-Run-All

remoterunall.sh,顾名思义,就是远程执行脚本的意思。假设我想每台机器更新python,那么我只要写一个小脚本updatepython2.sh在同一个目录下:

1
yum update python -y

然后运行remoterunall.sh hostlist updatepython2.sh即可。

remoterunall.sh的实现如下:

1
#!/usr/bin/bash

if [ $# -lt 2 ]; then
        echo "Error call format"
        echo "Format: ./remoterunall.sh hostlist bashfile"
        exit
fi

hostlist=$1
bashfile=$2

echo
echo ===Enter Remote-RunAll  hostlist: $hostlist, bashfile: $bashfile

cat $hostlist | while read hostInfo
do
        hostip=`echo $hostInfo | cut -d " " -f1`
        hostname=`echo $hostInfo | cut -d " " -f2`

        echo -e "\n===Run script in client $hostip $hostname..."
        ssh root@$hostip 'bash -s' < $bashfile
done

echo -e "\n===Leave Remote-RunAll."

这里假设执行remoterunall.sh的时候,所有机器之间的ssh-key已经配置好了,可以无密码访问,因此这里并没有需要密码。

部署脚本

接下来的脚本基本是建立在上面的“基本脚本”的基础上的,这里只介绍三个主要脚本:

  1. init.sh 负责初始化一个集群
  2. configmaster.sh 负责配置master节点 (主要是配置Ambari相关)
  3. addnewhost.sh 负责当集群汇总新加入一个节点时,配置新机器以及重新分发hosts等文件

init.sh

使用方法: ./init.sh hostlist
其中hostlist是一个以hosts格式存着主机名和ip的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#!/usr/bin/bash

if [ $# -lt 1 ]; then
echo "Error: parameter is not enough..."
echo "Format: ./init.sh hostlist"
exit
fi

hostlist=$1

read -p "Enter the client password(all should be same):" -s pwd

# generate sever ssh-key
filepath=~/.ssh/id_rsa
if [ -f $filepath ]; then
echo
echo "ssh-key already exists. it won't be generated again."
echo "if you want to generate new key, please remove the key file first."
else
echo
echo "===ssh key generating..."
ssh-keygen -f ~/.ssh/id_rsa -P ""
fi

echo
echo "===Clearing public key gathering folder..."
rm -f ~/.ssh/pub/*
echo
echo "===Copy this public key to that folder..."
cp ~/.ssh/id_rsa.pub ~/.ssh/pub/id_rsa.pub

echo
echo "===Generating hosts file...."
cat ./others/hosts_template $hostlist > hosts

# Deploying Clients...
./applyall.sh $hostlist others/deployclient.sh $pwd

echo
echo "===Gathering pub keys..."
cat ~/.ssh/pub/*.pub > authorized_keys

echo "===Deploying pub keys..."
./applyall.sh $hostlist others/deploypubkeys.sh $pwd

echo
echo "===Cleaning..."
rm -f hosts
rm -f authorized_keys
echo "===Clean finished."

echo
echo "===Reboot all the machine..."
cat $hostlist | while read hostinfo
do
hostip=`echo $hostinfo|cut -d " " -f1`
hostname=`echo $hostinfo|cut -d " " -f2`
ssh root@$hostip 'reboot' < /dev/null
done
echo "===reboot finished."

configmaster.sh

这里调用configmaster.sh,然后输入主机ip及其密码,即可自动配置ambari的repo地址,并且安装ambari,拷贝现有的jdk到master节点上(Amabri配置时使用)。

1
2
3
4
5
6
7
read -p "Enter your master ip:" masterip
read -p "Enter your password:" -s pwd
echo $masterip, $pwd
./tf others/ambari.repo /etc/yum.repos.d/ ambari.repo $masterip $pwd
ssh root@$masterip 'bash -s' < others/installmaster.sh
./tf ../jdk/jdk-8u60-linux-x64.tar.gz /var/lib/ambari-server/resources/ jdk-8u60-linux-x64.tar.gz $masterip $jdk
./tf ../jdk/jce_policy-8.zip /var/lib/ambari-server/resources/ jce_policy-8.zip $masterip $jdk

这调用了installmaster.sh,其实就是执行了三句话(即安装ambari):

1
2
3
yum clean all
yum repolist
yum install ambari-server -y

注意调用完该脚本仅仅是安装好了Ambari,接下来还需要到master节点上运行以下命令以配置、启动Amabri:

1
2
ambari-server setup
ambari-server start

addnewhost.sh

这个用法和init.sh类似,但是要注意传入一个新的hostlist,一个旧的hostlist。注意:程序并不会自动合并两个hostlist(为了避免任务失败,还需要重新建立hostist)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#!/usr/bin/bash
newhostlist=$1
orighostlist=$2

if [ $# -lt 2 ]; then
echo "Error: parameter is not enough..."
echo "Format: ./addnew.sh newhostlist orignhostlist"
exit
fi

echo
echo "Enter addnew scirpt: $newhostlist $orighostlist"

read -p "Enter the client password(all should be same):" -s pwd

echo
echo "===Generating new hosts file...."
updatedhostlist="updatedhostlist"
cat $orighostlist > $updatedhostlist
cat $newhostlist >> $updatedhostlist
cat ./others/hosts_template $updatedhostlist > hosts

# Deploying new clients...
./applyall.sh $newhostlist others/deployclient.sh $pwd

echo
echo "===reDeploying hosts file..."
./applyall.sh $updatedhostlist others/deployhosts.sh $pwd

echo -e "\n===gathering pub keys..."
cat ~/.ssh/pub/*.pub > authorized_keys

echo -e "\n===reDeploying pub keys for all clients..."
./applyall.sh $updatedhostlist others/deploypubkeys.sh $pwd

echo
echo "===Cleaning..."
rm -f hosts
rm -f $updatedhostlist
rm -f authorized_keys
echo "===Clean finished."

echo
echo "===reboot all the new machine..."
cat $newhostlist | while read hostinfo
do
hostip=`echo $hostinfo|cut -d " " -f1`
hostname=`echo $hostinfo|cut -d " " -f2`
ssh root@$hostip 'reboot' < /dev/null
done
echo "===reboot finished."

echo
echo "Leave addnew scirpt."

目录结构

其他的一些部署过程用到的脚本这里就不一一放上来了,整个脚本目录如下:

1
[root@yumsource script]# tree
.
├── addnewhost.sh
├── applyall.sh
├── configmaster.sh
├── hostlist
│   ├── hostlist_new
├── init.sh
├── others
│   ├── ambari.repo
│   ├── configclient_template.sh
│   ├── deployclient.sh
│   ├── deploypubkeys.sh
│   ├── deploypythons.sh
│   ├── disSELinuxCfg
│   ├── hosts_template
│   ├── installmaster.sh
│   ├── installpython34.sh
│   └── updatepython2.sh
├── remoterunall.sh
└── tf

源地址:https://www.zybuluo.com/Vany/note/318071

CentOS7上Hadoop集群搭建与使用小记(一)——预备工作

CentOS7上Hadoop集群搭建与使用小记(一)——预备工作

tags: Hadoop CentOS


最近折腾了一圈Hadoop的平台,利用Ambari(简直好用)在实体机上搭建了整个Hadoop集群(在虚拟机上也一样),踩过很多坑,总结了很多经验,在这里一步步记录下来,作为一个总结,也可以给别人作为一个参考。

整个系列将会包括从最开始的裸机的部署,到后面的Ambari, Hadoop软件的安装,以及Hadoop生态圈软件的原理与使用。本教程所采用的操作系统是基于CentOS7,在实体机上搭建的。不过在虚拟机上搭建也不是问题,注意设置好网络就好。操作系统如果不一样的话,可能系统配置以及安装对应的软件包的命令会有所差别。

机器准备

架构介绍

在需要部署的集群的机器以外,我们还需要一台机器,作为配置过程的Server,在预备工作中,所有的操作都是在这台机器上操作。如果是实体机集群的话,可以单独拿出一台来做Server,如果是单机Hadoop平台的话,可以直接建一个Server的虚拟机即可。架构图如下:
服务器架构图.png-58.5kB

最上面的主机我们后文都称作Server,下面的都称作Client。

机器安装

安装部分其实比较简单,但是有几个坑需要注意一下:

  1. 安装CentOS7选语言的时候,最好选择英文版,否则后面Ambari注册机器时死活注册不上(不要问我是怎么发现这个原因的,说多了都是泪,猜测要不是时区问题要不就是语言问题,反正选这个就好使了)

  2. 需要选择安装模式时,选GNOME Desktop模式就好,对大多数人都方便使用。如果选最小版的话,ifconfig命令等工具包还得自己安装,略麻烦。注意:在右面的附件组件中,最好把Development Tools等附加插件都装上,因为后面安装scipy等等python的packages时需要编译器,否则还要自己安装gcc,g++等C/C++编译器。

网络部署

安装好后,集群的机器要设置好IP, GATEWAY (网关), DNS,以确保各个机器之间可以互联,集群与Server之间也可以相连,而且最好是可以上网,因为之后需要使用yum命令安装、更新一些软件。

设置网络的地方:/etc/sysconfig/network-scripts/ifcfg-xxx,其中xxx是你的网卡名称,一般可能是enoXXXX或,emXXXXX或,eth0等等,具体我就不细说了。当然也可以在安装的过程中设置,另外注意确保配置文件中(即ifcfg-xxx)的ONBOOT的选项是yes,即网卡自动启动,这样远程重启我们也不怕啦。


Server的配置

这里的Server指的是上文的架构图中的yumsource.hadoop。

虽然机器名字叫yumsource.hadoop,但是其实我并没有配置CentOS的source(在我的环境下,可以直接采用yum install xxx来安装那些需要的软件),而是配置了Ambari、HDP(相当于Hadoop)的source。因为如果下载Ambari的话在墙内简直是龟速,有时还会莫名其妙的断线,配置好后 ,看着50MB/s的传输速度,简直爽。

下载离线安装包

首先我们下载Ambari, HDP, JDK等我们需要的软件包:

先到hortonworks上找到最新的文档,比如最新的Ambari的是2.2.1.1,选择Amabri 2.2->Install & Upgrade->Automated Install with Ambari->Using a Local Repository项,里面有个Obtain the Repositories,就可以看到Ambari和HDP Stack的Repository的地址了,两个页面如下:

找到对应系统的Tarball版本的地址,将其下载下来。CentOS7对应的Ambari和HDP2.3的版本的Tarball地址如下:

配置本地Repo

建立/var/www/html/hdp/文件夹,将我们下载的安装包传到Server上。看看能不能通过http直接访问该机的目录,假设该机的域名是yumsource.hadoop,那么可以在浏览器里输入

http://yumsource.hadoop/hdp/

看看能不能访问到。如果不能的话,可以安装(更新)下httpd试试。如果能访问到,那么就配置成功了。

接下来配置一下repo的文件,也就是说别人想用yum install ambari-server安装时,他要知道从哪里去下。建立ambari.repo,内容如下(这些内容根据你的具体地址和版本号改变而改变):

1
[Ambari-2.2.0.0]
name=Ambari-2.2.0.0
baseurl=http://yumsource.hadoop/hdp/ambari-2.2.0.0/centos7/2.2.0.0-1310/
gpgcheck=0
enabled=1

[HDP-2.3.4.0]
name=HDP-2.3.4.0
baseurl=http://yumsource.hadoop/hdp/HDP/centos7/2.x/updates/2.3.4.0/
gpgcheck=0
enabled=1

[HDP-UTILS-1.1.0.20]
name=HDP-UTILS-1.1.0.20
baseurl=http://yumsource.hadoop/hdp/HDP-UTILS-1.1.0.20/repos/centos7/
gpgcheck=0
enabled=1

这个文件要传送到master节点(是指集群的master)的/etc/yum.repos.d/目录下,这里我们先不传送,后面用脚本统一配置。

建立hosts列表

将我们集群的所有机器(不包括这个Server)配置成一个hosts列表,供后面的脚本使用,例如:

1
10.15.198.204 master.hadoopcluster
10.15.198.205 slave1.hadoopcluster
10.15.198.206 slave2.hadoopcluster
10.15.198.207 slave3.hadoopcluster
10.15.198.208 slave4.hadoopcluster

总结

到这里,我们的预备工作基本做完了,目前搭建好了互联的机器集群,和具有Ambari、HDP安装包的Server,以及一个hosts列表。

接下来,我会介绍部署所用的脚本,以及部署的过程。


源地址:https://www.zybuluo.com/Vany/note/318013

Ambari自动化离线批量部署Hadoop平台总结


面临的问题

有了Ambari的平台,理论上来说,其实应该很方便的就可以部署Hadoop平台,但是实际上对于一个集群来说,并没有那么简单。
现在,我们是有一堆机器(手动配置非常麻烦),如何将这堆机器利用Ambari快速的搭建成Hadoop平台是我们现在面临的问题。

Client的预配置:对于每台机器,要做很多预先的配置,例如关闭防火墙,更新、安装软件等。
另外,为了实现多个机器之间的无密码访问,我们要设置ssh-key,并且将自己的pub key分发给集群中的各个机器。我们希望这些操作都可以自动化的完成。

每台机器设置hosts:在自动化配置中,机器之间相互通信是根据域名(FQDN, Fully Qualified Domain Name)的,而域名用简单的hostname name命令设置是不对的,这个设置的是hostname命令显示出来的东西,而我们要的是hostname -f命令显示出来的名字,这个需要在hosts文件中或DNS解析中得到。在不自己搭建DNS的情况下,因此我们每台机器都要配置hosts。

没有快速的互联网连接:很多时候集群面临的环境并不是一个和Internet直接相连的环境,因此,对于Ambari这种需要在线从互联网上下载的情况,或者Internet网速不佳的情况,我们就要有相应的办法应对,一个简单的办法就是实现下好离线文件,自己搭建yum source. 在我这里的环境下可以有镜像更新yum,因此我们只要考虑要安装的Ambari整个库的配置就好。


集群配置

根据上面的问题,我们提出自己搭建yum source源,并利用脚本自动化部署的方法。

一共有3类机器,一台作为yumsource,一台作为master,其余的作为slaves.

配置如下

1
|机器|FQDN(域名)|描述|
|-|-|
|yum源|yumsource.hadoop|部署HDP、HDP-util、Ambari的源|
|master|master.hadoop|作为hadoop的master机器,安装ambari-server|
|N台slave|slaveX.hadoop|作为hadoop的slave机器,会自动安装ambari-agent|

这里每台机器2核CPU,4G内存(这个要大一些,使用过1G结果卡死了,服务多的机器8G最好),50-100GB硬盘,系统采用CentOS7完全版


用到的技术

  • bash script
  • expect
  • httpd

Goal

  1. 按照官方文档的说明,每台机器的需要配置的项如下:
  • 设置ip等网络配置,并且设置开机自动启动网络
  • 生成ssh-key,并且将public key传送给其他机器
  • 关闭SELinux(需要重启)
  • 关闭iptables(CentOS7中是firewalld),并设置开机启动
  • 安装ntp,启动ntpd,并设置开机启动
  • 更新openssl
  • 设置hosts
    注:其中有一些可以统一用脚本处理,有一些(例如ip的设置)就需要每台手动做了
  1. 配置好了基本配置后,我们还需要配置下Ambari、HDP、HDP-UTILS的服务器(yum source),使其成为一个yum-source server.

  2. 接下来就是安装Ambari了

下面会详细叙述整个过程。


Steps

安装Amabri之前我们需要做一些准备工作,主要分为:

  • 每台机器的预备工作,以使各机器连接到网络中,可以自动化部署
  • Server端执行自动化脚本,执行一些Server端的处理
  • Server端调用各个Client远程执行脚本,对于每个Client进行配置

Pre-work of each client

每台机器做简单的ip配置即可,只要实现其在集群的网络中并且可以连通,以及yum install xxx好使就可以了。

在我这里环境下,我是需要手动配置静态ip的,并且设置了DNS就可以使用yum install(如果不可以的话,那么就要自己配置centosssyum source了),另外我希望在不同网段的机器远程访问该服务器,因此我设置了两方面内容:

/etc/sysconfig/network-script/ifcfg-eth0中设置ip(eth0为对应的网卡,CentOS7中一般为enoXXXXXX)及DNS,并设置自动启动该网卡(ONBOOT=yes):

1
2
3
4
5
6
BOOTPROTO=static

ONBOOT=yes
IPADDR=xx.xx.xxx.xxx
NETMASK=255.255.255.0
DNS1=xx.xx.x.xx

/etc/sysconfig/network中设置gateway(用于其他网段的机器访问该机器,可选):

1
GATEWAY=xx.xx.xxx.x

Server端自动化脚本

这里的server是指master、slave之外的机器,我这里采用yumsource那台机器作为Server。

在Server端,主要做以下几件事情:

  1. 生成server的ssh-key, 并且分发给各个client(使用scp命令,并且用expect处理交互),为后面的无密码访问client做准备
  2. 给各个client传送hosts文件
  3. 给各个client传送selinux的配置文件(通过这种方式设置关闭)
  4. 调用client该执行的脚本,并用ssh远程执行: ssh root@$hostip 'bash -s' < client.sh
  5. 将client生成的公钥(public key)收集回server:同样使用scp,如下
    scp -r root@$hostip:~/.ssh/id_rsa.pub ~/.ssh/pub/$hostname.pub
  6. 当所有client都处理完毕后,收集、整合所有的公钥再分发给各个client(下文的deploypubkeys.sh)
  7. 最后再依次远程重启每个机器(ssh root@$hostip 'reboot' < /dev/null),以应用配置

建立一个hostlist文件(其实就是我们要传送的hosts)

1
2
3
10.xx.xxx.xxx master.hadoop
10.xx.xxx.xxx slave1.hadoop
10.xx.xxx.xxx slave2.hadoop

在这里我们用脚本读取hostlist文件的每个机器(包括master、slave)的ip与域名(hostname),然后依次执行上述的操作:

1
2
3
4
5
cat hostlist | while read hostinfo
do
hostip=`echo $hostinfo|cut -d " " -f1`
hostname=`echo $hostinfo|cut -d " " -f2`
...

具体实现主要采用bash脚本以及expect程序,expect程序用于需要输入密码等交互时使用,
这里将传送文件的那几个任务(1-3)都用expect程序实现(即下文的deployclient.sh),其余的用bash脚本实现。

关闭SELinux的配置文件需要提前存好,名字为disSELinuxCfg,内容如下:

1
2
SELINUX=disabled
SELINUXTYPE=targeted

下面就是脚本们,把所有的代码、配置放在一个文件夹下,运行initserver.sh即可。

initserver.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#!/usr/bin/bash

filepath=~/.ssh/id_rsa
# read -p "Plase specify the ip of this server:" serverip
serverip=xx.xx.xxx.xxx
read -p "Enter the client password(all should be same):" -s pwd

if [ -f $filepath ]; then
echo
echo "ssh-key already exists. it won't be generated again."
echo "if you want to generate new key, please remove the key file first."
else
echo
echo "===ssh key generating..."
ssh-keygen -f ~/.ssh/id_rsa -P ""
fi

rm -rf ~/.ssh/pub/
mkdir ~/.ssh/pub/
cp ~/.ssh/id_rsa.pub ~/.ssh/pub/id_rsa.pub
cat hosts_template hostlist > hosts

echo
echo "===Deploying Client..."
cat hostlist | while read hostinfo
do
hostip=`echo $hostinfo|cut -d " " -f1`
hostname=`echo $hostinfo|cut -d " " -f2`
echo "===Deploy Client $hostip..."

chmod +x deployclient.sh
./deployclient.sh $hostip $pwd

head1="clientip=$hostip\n"
head2="serverip=$serverip\n"
head3="password=$pwd\n"
head=$head1$head2$head3
echo -e $head > head.tmp
cat head.tmp client_template.sh > client.sh
ssh root@$hostip 'bash -s' < client.sh

echo
echo "===Transfer pub key to server..."
scp -r root@$hostip:~/.ssh/id_rsa.pub ~/.ssh/pub/$hostname.pub
done

echo
echo "===gathering pub keys..."
cat ~/.ssh/pub/*.pub > authorized_keys
echo "===deploy pub keys..."
cat hostlist | while read hostip
do
./deploypubkeys.sh $hostip $pwd
done

echo
echo "===Cleaning..."
rm -f head.tmp
rm -f client.sh
rm -f hosts
rm -f authorized_keys
echo "===Clean finished."

echo
echo "===reboot all the machine..."
cat hostlist | while read hostinfo
do
hostip=`echo $hostinfo|cut -d " " -f1`
hostname=`echo $hostinfo|cut -d " " -f2`
ssh root@$hostip 'reboot' < /dev/null
done
echo "===reboot finished."

expect脚本deployclient.sh(用于给各个client传送各种文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#!/usr/bin/expect

set clientip [lindex $argv 0]
set pwd [lindex $argv 1]

# deploy current pub key
spawn ssh root@$clientip "mkdir ~/.ssh"
expect {
"*continue connecting*" { send "yes\r"; exp_continue }
"*password*" { send "$pwd\r"; exp_continue }
"#" { send "\r" }
}

# deploy current pub key
spawn scp /root/.ssh/id_rsa.pub root@$clientip:~/.ssh/authorized_keys
expect {
"*continue connecting*" { send "yes\r"; exp_continue }
"*password*" { send "$pwd\r"; exp_continue }
"#" { send "\r" }
}

# deploy selinux config
spawn scp disSELinuxCfg root@$clientip:/etc/selinux/config
expect {
"*continue connecting*" { send "yes\r"; exp_continue }
"*password*" { send "$pwd\r"; exp_continue }
"#" { send "\r" }
}

# deploy hosts
spawn scp hosts root@$clientip:/etc/hosts
expect {
"*continue connecting*" { send "yes\r"; exp_continue }
"*password*" { send "$pwd\r"; exp_continue }
"#" { send "\r" }
}

expect脚本deploypubkeys.sh(用于server给各个client分发汇总的公钥)

1
2
3
4
5
6
7
8
9
10
11
!/usr/bin/expect

set serverip [lindex $argv 0]
set pwd [lindex $argv 1]

spawn scp authorized_keys root@$serverip:~/.ssh/authorized_keys
expect {
"*continue connecting*" { send "yes\r"; exp_continue }
"*password*" { send "$pwd\r"; exp_continue }
"#" { send "\r" }
}

Client端脚本(远程调用)

给每个client执行的脚本client_template.sh,在Server端通过ssh远程调用。

这里之所以是个template,这是因为我们在server端会根据不同的client生成前面的一些信息(包括clientip,serverip等),将信息放在这个template最前面,然后供client执行。(这里其实就是为了显示那一句echo,表示当前处理到哪个机器了……因此client_template.sh是可以直接被client执行的,不会有任何实质上的影响)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
hostname=`hostname -f`
firewall="firewalld" #centos7中是firewalld,之前是iptables

echo
echo "client $clientip hostname: $hostname, server: $serverip"

echo
echo "===disable $firewall & it won't start after boot..."
service $firewall stop
chkconfig $firewall off

echo
echo "===install, start, config ntpd..."
yum -y install ntp
systemctl disable chronyd #centos7中要关闭这个否则冲突
service ntpd restart
chkconfig ntpd on

echo
echo "===deal with ssh key..."
rm -f ~/.ssh/id_rsa*
ssh-keygen -f ~/.ssh/id_rsa -P ""

echo
echo "===update openssl..."
yum -y update openssl

echo
echo "===deal with sshd..."
service sshd restart
chkconfig sshd on

echo
echo "===set network start automatically..."
chkconfig network on

echo
echo "===Finished!!!"

Yum Source的配置

由于教育网可以直接更新CentOS的软件,因此我这里没有配置CentOS的yum-source。这里我们仅仅配置Ambari和HDP、HDP-UTIL包的source。

首先下载各个安装包,地址可以在文档中找到,如2.2.0.0的网址是:
http://docs.hortonworks.com/HDPDocuments/Ambari-2.2.0.0/bk_Installing_HDP_AMB/content/_obtaining_the_repositories.html

然后将各个安装包解压放在/var/www/html下的hdp文件夹(自己建一个hdp文件夹),然后利用httpd(没有的话自己安装一下)把html整个目录对外开放。如果在另一台机器访问这台机器的域名,能看到整个目录以及文件信息,那么就说明ok了。

Ambari安装与配置

在安装之前,我们还需要告诉yum去哪里下载我们的ambari,建立一个ambari.repo并放在/etc/yum.repos.d/下面,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Ambari-2.2.0.0]
name=Ambari-2.2.0.0
baseurl=http://yumsource.hadoop/hdp/ambari-2.2.0.0/centos7/2.2.0.0-1310/
gpgcheck=0
enabled=1

[HDP-2.3.4.0]
name=HDP-2.3.4.0
baseurl=http://yumsource.hadoop/hdp/HDP/centos7/2.x/updates/2.3.4.0/
gpgcheck=0
enabled=1

[HDP-UTILS-1.1.0.20]
name=HDP-UTILS-1.1.0.20
baseurl=http://yumsource.hadoop/hdp/HDP-UTILS-1.1.0.20/repos/centos7/
gpgcheck=0
enabled=1

配置好后, 调用yum install -y ambari-server即可开始安装ambari及其附属组件。
安装好后,调用ambari-server setup即可开始设置ambari,在这个过程中需要下载JDK,
我们可以提前下好JDK,然后放在对应的目录下:/var/lib/ambari-server/resources/,这样可以节约很多时间.

安装成功后,执行ambari-server start即可启动,接着就在浏览器中查看master这台机器的8080端口即可访问配置页面,按照提示一步步做就好。

节点重启怎么办

主节点调用ambari-server restart
子节点调用ambari-agent restart
剩下的具体Hadoop服务,直接在网页端就可以配置了


背景知识

expect

expect是一个解释器,可以处理交互式输入,例如:

1
spawn scp authorized_keys root@$serverip:~/.ssh/authorized_keys
expect {
"*continue connecting*" { send "yes\r"; exp_continue }
"*password*" { send "$pwd\r"; exp_continue }
"#" { send "\r" }
}

在调用scp命令时,会问你输入密码等;我们可以用spawn加个壳来调用scp,用expect来匹配可能的输出(例如\*password\*,这里*是通配符),从而做出动作;send "command"用于发送指令,exp_continue表示继续匹配,否则就直接进行下一条语句了。

另外,spawn套spawn貌似不是很好使。

无密钥SSH登录

ssh原理就是,自己生成一对公钥、私钥. 将公钥给要登录的服务器,自己是私钥. 登录时,client发送公钥给server,服务器看到自己这有匹配的公钥,加密一段信息返回给client. client用私钥解密,再给服务器,从而建立一个连接.

因此client要把公钥复制到服务器的~/.ssh/目录下,文件名为authorized_keys,如果有多个(多个client都想这么配置),直接cat起来就好。

配置好后,ssh 对方域名/ip,即可测试是否成功。

FQDN

Fully Qualified Domain Name, 是对应hosts文件中的名字
hostname -f查看

bash中指令结果存到变量

variable=`hostname -f`
echo $variable

用``把命令包起来即可。

Fibonacci sequence in Haskell

首先,利用iterate生成Fibonacci的pair序列,

1
fibpairlst = iterate (\(x1,x2)->(x1+x2,x1+2*x2)) (1,1)

运行结果如下,

1
2
3
*Main> let fibpairlst = iterate (\(x1,x2)->(x1+x2,x1+2*x2)) (1,1)
*Main> take 10 fibpairlst
[(1,1),(2,3),(5,8),(13,21),(34,55),(89,144),(233,377),(610,987),(1597,2584),(4181,6765)]

然后,再用foldr将pair序列展平(flat)即可得到一个斐波那契数列(Fibonacci Sequence):

1
foldr (\(x1,x2) xs->x1:x2:xs) [] fibpairlst

综上所述,

1
2
fiblst = let fibpairlst = iterate (\(x1,x2)->(x1+x2,x1+2*x2)) (1,1)
in foldr (\(x1,x2) xs->x1:x2:xs) [] fibpairlst

生成的序列是一个无穷序列,可以用take函数来选出前n个,也可以直接用!!运算符来选出特定的某个。

1
2
3
4
5
6
7
8
9
10
*Main> take 20 fiblst
[1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765]
*Main> fiblst !! 20
10946
*Main> fiblst !! 5
8
*Main> fiblst !! 3
3
*Main> fiblst !! 1
1

我想这可能是最快的递推生成的斐波那契数列的方法了吧,由于Haskell的惰性求值机制,当用到某个值时直接递推计算,没有产生任何中间冗余的结果,也没有重复运算。