在编写集群脚本之前,先回顾下 shell
知识点
Shell
入门
创建 wc.sh
,并在文件写入以下内容:
#!/bin/bash
echo "hello shell"
解释下上面两行代码:
- 第一行注释表明执行此
shell
是/bin/bash
; - 第二行是
shell
具体要执行的脚本,输出hello shell
。
保存好文件内容后,增加执行权限 chmod u+x wc.sh
后执行 ./wc.sh
,输出如下:
hello shell
当然你也可以在首行加 -x
表示 debug
模式,会输出调试信息,整个文件内容:
#!/bin/bash -x
echo "hello shell"
执行后输出
+ echo 'hello shell'
hello shell
增加了调试信息 + echo 'hello shell'
,代表执行 echo 'hello shell'
语句
变量定义与引用
创建 variable.sh
,输入以下内容:
#!/bin/bash
hello='hello'
date1='date1'
date2=\`date\`
word=word
echo $hello
echo ${date1}
echo ${date2}
echo $word
执行输出:
hello
date1
Thu Aug 22 23:10:34 CST 2019
关于变量,有几个知识点:
$
后跟随的是变量名- `fun` 是表示
fun
的返回值,例子中就是date
命令返回值 - 字符串可以用
""
、''
包裹,也可以不用;但标准用法是用引号
当然也有需要注意点:
=
前后不能有空格- 变量名最好用
{}
,以免引起歧义 - 静态变量最好用大写
传递参数
创建 parameter.sh
,内容如下:
#!/bin/bash
echo $1
echo $2
echo "$#"
echo "$*"
echo "PID: $$"
执行 ./parameter.sh a b
,输出:
a
b
2
a b
PID: 12545
- 执行
shell
时,传递a
,b
两个参数 $1
得到第一个参数$2
得到第二个参数$#
是指参数的个数$*
是指参数的内容$$
是指当前shell
进程的PID
数组
创建 array.sh
,内容如下
#!/bin/bash
arr=("john li" wangwu zhangsan)
echo ${arr[@]}
echo ${arr[2]}
echo ${#arr[2]}
echo ${#arr[@]}
执行 ./array.sh
,输出:
john li wangwu zhangsan
zhangsan
8
3
- 数组用
()
表示 - 数组内容用空格分割
@
符号取数组所以内容#
可以类似len
函数,#变量
是变量长度,#arr[@]
数组arr
的元素个数
判断
创建 if.sh
,内容如下:
#!/bin/bash
A="abc"
B="john"
if [ ${A} == ${B} ];then
echo "=="
elif [ ${A} == 'abc' ];then
echo "=="
else
echo "!="
fi
执行 ./if.sh
,输出
==
[]
前后要有空格==
前后要有空格if
结束符号fi
循环
创建 loop.sh
,内容如下
#!/bin/bash
for((i=1;i<5;i++))
do
echo "i = "${i}
done
j=1 while(($j<5)) do echo “j = “${j} let “j++” done
执行 ./loop.sh
,输出
i = 1
i = 2
i = 3
i = 4
j = 1
j = 2
j = 3
j = 4
- 条件是用
(())
包裹 - 循环语句是在
do ... done
之内
分割
创建 split.sh
,内容如下:
#!/bin/bash
s="rz,j,xx,huhu,yt,co"
OLD_IFS="$IFS"
IFS=","
arr=($s)
IFS="$OLD_IFS"
for x in ${arr[*]}
do
echo $x
done
执行 ./split.sh
,输出
rz
j
xx
huhu
yt
co
AWK
[hadoop@node02 learn_shell]$ cat test.log
a b c
1 2 3
[hadoop@node02 learn_shell]$ cat test.log | awk '{print $1}'
a
1
[hadoop@node02 learn_shell]$ cat test.log | awk '{print $1,$2}'
a b
1 2
[hadoop@node02 learn_shell]$ cat test.log | awk 'NR==1{print}'
a b c
[hadoop@node02 learn_shell]$ cat test.log | awk 'NR==1{print $1}'
a
[hadoop@node02 learn_shell]$ cat test.log | awk 'NR>1{print}'
1 2 3
[hadoop@node02 learn_shell]$ cat test.log
a;b;c
1;2;3
[hadoop@node02 learn_shell]$ cat test.log | awk -F ";" '{print $2}'
b
2
-F
后面紧跟着的是分割点,默认的分割点是空格$index
表示输出index
的内容NR
表示行操作
sed
sed
命令是利用脚本来处理文本文件,主要用来自动编辑一个或多个文件、简化对文件的反复操作、编写转换程序等。
sed ‘s/要被取代的字串/新的字串/g’
[hadoop@node02 learn_shell]$ cat test.log
w;w;c
w;2;3
[hadoop@node02 learn_shell]$ sed -i 's/w/f/2' test.log
[hadoop@node02 learn_shell]$ cat test.log
w;f;c
w;2;3
[hadoop@node02 learn_shell]$ sed -i 's/w/f/' test.log
[hadoop@node02 learn_shell]$ cat test.log
f;f;c
f;2;3
[hadoop@node02 learn_shell]$ sed -i 's/f/m/g' test.log
[hadoop@node02 learn_shell]$ cat test.log
m;m;c
m;2;3
-i
选项可以直接修改文件内容s
是替换,可以搭配正规表示法g
代表每行中第几个要被替换的字符串,默认是第一个,g
代表全部
Hadoop
Hadoop
集群的有很多台机器,如果要在检测不同机器的状态,就需要登陆不同的机器;这是一件很麻烦的操作。但我们可以在一台机器上执行 ssh
登陆到其他机器来监控,这样就可以在一台机器检测整个集群的状态。
jps
查看集群上的机器每个进程是否运行正常
#!/bin/bash
echo "------------------danner001 process--------------------"
ssh danner001 "$JAVA_HOME/bin/jps"
echo "------------------danner002 process--------------------"
ssh danner002 "$JAVA_HOME/bin/jps"
echo "------------------danner003 process--------------------"
ssh danner003 "$JAVA_HOME/bin/jps"
启动集群
启动 Hadoop
集群
#!/bin/bash
# zookeeper
ssh danner001 "$ZOOKEEPER_HOME/bin/zkServer.sh start"
ssh danner002 "$ZOOKEEPER_HOME/bin/zkServer.sh start"
ssh danner003 "$ZOOKEEPER_HOME/bin/zkServer.sh start"
sleep 5
# start hdfs + yarn
start-all.sh
sleep 5
# readby resourcemanager
ssh danner002 "$HADOOP_HOME/sbin/yarn-daemon.sh start resourcemanager"
# jobhistory
mr-jobhistory-daemon.sh start historyserver
exit 0
此刻 ssh
直接远程执行会有环境变量问题,这种方式属于无登陆无交互 shell
(详细看参考资料)。此方式执行时只会加载.bashrc
,而我们的环境变量都是设置在/etc/proflie
和 .bash_profile
里。我们需要在 .bashrc
中添加需要的环境变量。当然我们也可以在执行 shell
时使用 /bin/bash -l zkServer.sh start
命令也可以,这是属于 有登陆 shell
,但每个都这么写太麻烦了。
停止集群
停止 Hadoop
集群
#!/bin/bash
#stop history+yarn+hdfs
mr-jobhistory-daemon.sh stop historyserver
ssh danner002 "$HADOOP_HOME/sbin/yarn-daemon.sh stop resourcemanager"
stop-all.sh
#stop zk
ssh danner001 "$ZOOKEEPER_HOME/bin/zkServer.sh stop"
ssh danner002 "$ZOOKEEPER_HOME/bin/zkServer.sh stop"
ssh danner003 "$ZOOKEEPER_HOME/bin/zkServer.sh stop"
./jps.sh
exit 0
监控 HDFS HA
HDFS
的高可用可以保证挂掉一个 namenode
后 ` standby namenode 立刻上线使用。但我们也是需要知道
namenode 什么时候挂了,以便后续的问题排查。本例是在
namenode` 挂掉后立刻发邮件。
先配置 mail 465
,以下都是在 root
下执行:
先打开 QQ邮箱
的SMTP
服务和开通机子的 485
端口
若没有安装 sendmail
则先安装
yum -y install sendmail
停止服务
systemctl stop sendmail.service
systemctl disable sendmail.service
调整 postfix
参数
vi /etc/postfix/main.cf
inet_interfaces = all
启动 postfix
服务
service postfix start
chkconfig postfix on
postfix check
systemctl status postfix
# 一定要查看下 postfix 状态
postfix.service - Postfix Mail Transport Agent
Loaded: loaded (/usr/lib/systemd/system/postfix.service; enabled; vendor preset: disabled)
Active: active (running) since Sat 2019-08-24 08:32:00 UTC; 1min 49s ago
创建认证
以下用 hadoop
用户执行
mkdir -p ~/.certs/
echo -n | openssl s_client -connect smtp.qq.com:465 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > ~/.certs/qq.crt
certutil -A -n "GeoTrust SSL CA" -t "C,," -d ~/.certs -i ~/.certs/qq.crt
certutil -A -n "GeoTrust Global CA" -t "C,," -d ~/.certs -i ~/.certs/qq.crt
certutil -L -d ~/.certs
cd ~/.certs
certutil -A -n "GeoTrust SSL CA - G3" -t "Pu,Pu,Pu" -d ./ -i qq.crt
# 成功会输出以下信息
Notice: Trust flag u is set automatically if the private key is present.
配置邮件发送
切换回 root
执行
vi /etc/mail.rc
set from=597819425@qq.com
set smtp=smtps://smtp.qq.com:465
set smtp-auth-user=597819425
#授权码
set smtp-auth-password=umiiwawxygqvbbfe
set smtp-auth=login
set ssl-verify=ignore
set nss-config-dir=/home/hadoop/.certs
发送邮件
切换回 hadoop
用户发送
若没有安装 mailx,则先安装 yum -y install mailx
echo hello mail | mail -s " title" 597819425@qq.com
在QQ邮箱收到邮件
获取 HDFS 状态
创建 get_hdfs_ha_state.sh
,输入以下内容
#!/bin/bash
ACTIVE_HOSTNAME=""
STANDBY_HOSTNAME=""
ACTIVE_SERVICEID=""
STANDBY_SERVICEID=""
ACTIVE_SERVICESTATE=""
STANDBY_SERVICESTATE=""
EMAIL=597819425@qq.com
BIN_HOME=/home/hadoop/app/hadoop/bin
ha_name=$(${BIN_HOME}/hdfs getconf -confKey dfs.nameservices) namenode_serviceids=$(${BIN_HOME}/hdfs getconf -confKey dfs.ha.namenodes.${ha_name})
for node in $(echo ${namenode_serviceids//,/ }); do
state=$(${BIN_HOME}/hdfs haadmin -getServiceState $node)
if [ "$state" == "active" ]; then
ACTIVE_SERVICEID="${node}"
ACTIVE_SERVICESTATE="${state}"
ACTIVE_HOSTNAME=`echo $(${BIN_HOME}/hdfs getconf -confKey dfs.namenode.rpc-address.${ha_name}.${node}) | awk -F ':' '{print $1}'`
#echo "${ACTIVE_HOSTNAME} : ${ACTIVE_SERVICEID} : ${ACTIVE_SERVICESTATE}"
elif [ "$state" == "standby" ]; then
STANDBY_SERVICEID="${node}"
STANDBY_SERVICESTATE="${state}"
STANDBY_HOSTNAME=`echo $(${BIN_HOME}/hdfs getconf -confKey dfs.namenode.rpc-address.${ha_name}.${node}) | awk -F ':' '{print $1}'`
#echo "${STANDBY_HOSTNAME} : ${STANDBY_SERVICEID} : ${STANDBY_SERVICESTATE}"
else
echo "hdfs haadmin -getServiceState $node: unkown"
fi
done
echo " "
echo "Hostname Namenode_Serviceid Namenode_State"
echo "${ACTIVE_HOSTNAME} ${ACTIVE_SERVICEID} ${ACTIVE_SERVICESTATE}"
echo "${STANDBY_HOSTNAME} ${STANDBY_SERVICEID} ${STANDBY_SERVICESTATE}"
# save current NN1/2_HOSTNAME state # active namenode 信息固定都是第一行 echo “${ACTIVE_HOSTNAME} ${ACTIVE_SERVICEID} ${ACTIVE_SERVICESTATE}” > HDFS_HA.log echo “${STANDBY_HOSTNAME} ${STANDBY_SERVICEID} ${STANDBY_SERVICESTATE}” » HDFS_HA.log
# 判断当前 active namenode 是否与上次相同,不同则发邮件提醒
if [ -f HDFS_HA_LAST.log ];then
HISTORYHOSTNAME=`cat HDFS_HA_LAST.log| awk 'NR==1{print $1}'`
if [ "$HISTORYHOSTNAME" != "${ACTIVE_HOSTNAME}" ];then
echo "send a mail"
echo -e "`date "+%Y-%m-%d %H:%M:%S"` : Please to check namenode log." | mail \
-r "From: alertAdmin <597819425@qq.com>" \
-s "Warn: CDH HDFS HA Failover!." ${EMAIL}
fi
fi
cat HDFS_HA.log > HDFS_HA_LAST.log
hdfs haadmin -failover nn2 nn1 // 将 active namenode 从 nn2 切换到 nn1
执行 ./get_hdfs_ha_state.sh
,输出
Hostname Namenode_Serviceid Namenode_State
danner001 nn1 active
danner002 nn2 standby
send a mail 查看 QQ 邮箱会收到一封信