1、集群机器文件格式
-
情况1、机器文件没有密码,需要输入密码。/etc/hosts前两行会跳过。
比如/etc/hosts ==>sh autoSSH.sh /etc/hosts 123456 使用这种/etc/hosts的方式方便快捷
-
情况2、配置文件本身带有密码 ==> sh autoSSH.sh filePath , 当然sh autoSSH.sh filePath 123456 也没有问题
192.168.0.186 clouderaManager 123456 192.168.0.187 clouderaAgent1 123456 192.168.0.189 clouderaAgent2 123456 192.168.0.190 clouderaAgent3 123456
2、脚本,执行方式 sh autoSSH.sh filePath 或者 sh autoSSH.sh filePath passwd
#!/bin/bash
# 获取关键路径:$0是启动的脚本名,dirname $0 是脚本所在的文件夹
cd `dirname $0`
workDir=`pwd`
hostFilePath=$1
commonPasswd=$2
# 判断启动脚本是是否输入了集群机器列表文件
if [[ ${#hostFilePath} -eq 0 ]];then
echo "请输入集群机器配置文件路径!"
exit 1
fi
# 判断集群列表文件是否是绝对路径,如果不是则转化为绝对路径
if [[ ${hostFilePath:0:1} != "/" ]];then
hostFilePath=$workDir/$hostFilePath
fi
if [[ ! -f $hostFilePath ]];then
echo "输入的路径{ ${hostFilePath} } 不是一个文件"
exit 1
fi
#定义一个处理expect的方法,用于自动化交互(==这个是重点==)
expectFun(){
# usage: expect [-div] [-c cmds] [[-f] cmdfile] [args]
# -c表示多名了,需要用" ..."括起来,注意:第一个" 必须要在-c之后,不能够换行
expect -c "
set timeout -1
spawn $1
# 多种期望情况,比如第一次ssh,需要发送yes;注意,expect 和 { 中间有空格
expect {
*(yes/no)* {send -- yes\r;exp_continue}
*(y/n)* {send -- y\r;exp_continue}
*assword:* {send -- $2\r;exp_continue}
*.ssh/id_* {send -- \r;exp_continue}
*passphrase* {send -- \r;exp_continue}
*same passphrase* {send -- \r;exp_continue}
eof {exit 0}
}"
}
# 判断是否是本机ip
# 关于shell返回值问题
# echovalue=`isLocalIp $1` // 得到echo 的值
# statevalue=$? // 得到return的值,或者exit值,exit 默认是0,如果exit 1,那么就是1
# // 注意::这两个语句中间不能有任何语句,否则$? 就不是isLocalIp $1的状态了
isLocalIp(){
# 获取本机ip,并且进行比较,可能有多个ip,所以需要一个个匹配或者包含判断
localIps=`ip addr | grep 'inet' | grep -v 'inet6' | grep -v 127.0.0.1 | awk '{print $2}' | cut -d'/' -f1`
# 方法一:利用默认IFS的' '将多个ip转化为数组,一个个匹配(准确)
localIpArr=($localIps)
for ipTemp in ${localIpArr[*]};do
# $1是调用函数传入进来的第一个参数,而不是调用脚本传递进来的参数
if [[ $ipTemp == $1 ]];then
echo 1
return 1
fi
done
# 方法二:字符串通配符匹配(有误差)
# if [[ $localIps == *$1* ]];then
# echo 1
# return 1
# fi
# 方法三:使用字符串包含运算符(有误差)
# if [[ $localIps =~ $1 ]];then
# echo 1
# return 1
# fi
echo 0
return 0
}
# 判断每一条机器信息,如果长度==0,返回1跳过,如果字段个数小于2,返回11告诉checkHostFile文件不合法
# 个数大于3个,就返回1跳过,如果长度为2,表示没有密码,则拼接密码
checkLine(){
if [[ ${#1} == 0 ]]; then
return 1
fi
hostArrTemp=($1)
if [[ ${#hostArrTemp[*]} -lt 2 ]]; then
echo "机器文件:{$hostFilePath} 中存在不合法的信息:{$1}"
return 11
fi
if [[ ${#hostArrTemp[*]} -gt 3 ]]; then
if [[ $1 == *127.0.0.1* || $1 == *::1* ]]; then
echo "跳过这行:{$1}"
fi
return 1
fi
if [[ ${#hostArrTemp[*]} -eq 2 ]]; then
if [[ ${#commonPasswd} == 0 ]]; then
echo "机器文件:{$hostFilePath} 中没有密码,需要启动脚本时加上密码:sh autoSSH.sh $hostFilePath passwd"
return 11
fi
echo "$1 $commonPasswd"
return 0
fi
echo "$1"
return 0
}
# 判断机器文件是否正确
checkHostFile(){
echo "开始检查hostFile是否正确"
cat $hostFilePath | while read hostLine;do
checkLine "$hostLine"
if [[ $? == 11 ]]; then
# 这个exit只是退出这个当前管道的子shell(subshell),但是无法停止最外层这个shell
# exit 默认的返回状态为0, 也就是 $? == 0。也可以使用return退出
exit 1
fi
done
# 这里要拿到subshell的返回值状态
if [[ $? == 1 ]]; then
exit
fi
}
# 从模板机器上,发送需要的文件到其他机器上
sendNeedFiles(){
cat $hostFilePath | while read hostLine;do
#获取主机和密码
hostMsgArr=(`checkLine "$hostLine"`)
if [[ $? != 0 ]]; then
continue
fi
hostip=${hostMsgArr[0]}
passwd=${hostMsgArr[2]}
if [[ `isLocalIp $hostip` -ne 1 ]];then
expectFun "scp /etc/hosts $hostip:/etc/" $passwd
fi
done
}
#删除并创建集群机器/root/.shh ,避免以前的密码影响
removeAndCreateSSH(){
cat $hostFilePath | while read hostLine;do
#获取主机和密码
hostMsgArr=(`checkLine "$hostLine"`)
if [[ $? != 0 ]]; then
continue
fi
hostip=${hostMsgArr[0]}
passwd=${hostMsgArr[2]}
# 删除ssh
expectFun "ssh $hostip rm -rf /root/.ssh" $passwd
# 创建ssh(不设置密码)
expectFun "ssh $hostip ssh-keygen -t rsa" $passwd
done
}
# 将远端所有机器的id_rsa.pub拉过来,然后cat 进authorized_keys,最后发送到其他机器
collectAndSendAuthorKey(){
cat $hostFilePath | while read hostLine;do
#获取主机和密码
hostMsgArr=(`checkLine "$hostLine"`)
if [[ $? != 0 ]]; then
continue
fi
hostip=${hostMsgArr[0]}
passwd=${hostMsgArr[2]}
# ssh到远端机器执行ssh-copy-id命令不支持
# expectFun "ssh $hostip ssh-copy-id ${localIpArr[0]}" $passwd
expectFun "scp $hostip:/root/.ssh/id_rsa.pub /root/.ssh/id_rsa.pubtemp" $passwd
cat /root/.ssh/id_rsa.pubtemp >> /root/.ssh/authorized_keys
rm -rf /root/.ssh/id_rsa.pubtemp
done
# 发送authorized_keys 到其他机器
cat $hostFilePath | while read hostLine;do
#获取主机和密码
hostMsgArr=(`checkLine "$hostLine"`)
if [[ $? != 0 ]]; then
continue
fi
hostip=${hostMsgArr[0]}
passwd=${hostMsgArr[2]}
if [[ `isLocalIp $hostip` -ne 1 ]];then
expectFun "scp /root/.ssh/authorized_keys $hostip:/root/.ssh/" $passwd
fi
done
}
# 模拟登陆一次,填充known_hosts,这样就省去后续第一次登陆还需要输入yes
sshFirstTime(){
# 公共部分:机器ip和hostname的ssh(所有机器都包含)
cat $hostFilePath | while read hostLine;do
#获取主机和密码
hostMsgArr=(`checkLine "$hostLine"`)
if [[ $? != 0 ]]; then
continue
fi
hostip=${hostMsgArr[0]}
hostname=${hostMsgArr[1]}
passwd=${hostMsgArr[2]}
expectFun "ssh $hostip ls /mnt" $passwd
expectFun "ssh $hostname ls /mnt" $passwd
done
cp /root/.ssh/known_hosts /root/.ssh/known_hostsbak
# 将下面的所有ip逐个和known_hostsbak中ip相比较,然后插入localhost和127.0.0.1
cat $hostFilePath | while read hostLine;do
#获取主机和密码
hostMsgArr=(`checkLine "$hostLine"`)
if [[ $? != 0 ]]; then
continue
fi
hostip=${hostMsgArr[0]}
passwd=${hostMsgArr[2]}
cp /root/.ssh/known_hostsbak /root/.ssh/known_hostsbak1
knownLine=`cat /root/.ssh/known_hostsbak1 | grep $hostip | cut -d ' ' -f2-`
echo "localhost $knownLine" >> /root/.ssh/known_hostsbak1
echo "127.0.0.1 $knownLine" >> /root/.ssh/known_hostsbak1
expectFun "scp /root/.ssh/known_hostsbak1 $hostip:/root/.ssh/known_hosts" $passwd
rm -rf /root/.ssh/known_hostsbak1
done
rm -rf /root/.ssh/known_hostsbak
}
checkHostFile
sendNeedFiles
removeAndCreateSSH
collectAndSendAuthorKey
sshFirstTime
注意细节:
-
1、cut 命令-d 后面接着分隔符,如果文件中分割是空格(' ')或者tab健(' '),-d后面都是-d' '。如果是其他分割符,比如 , ; . | 等等,直接在后面加上即可。
example
空格/tab键 ===> echo "liufu 123456" | cut -d' ' -f1 //-f 前面是有空格的 echo "liufu:123456" | cut -d':' -f1
-
2、花括号和其他命令一起时,两边需要空格,比如上面的
expect { //这里有空格 *(yes/no)* {send -- yes\r;exp_continue} *assword:* {send -- $2\r;exp_continue} *.ssh/id_rsa* {send -- $2\r;exp_continue} eof {exit 0} }
-
3、usage: expect [-div] [-c cmds] [[-f] cmdfile] [args]
-c表示多名了,需要用" ....... "括起来,注意:第一个" 必须要在-c之后,不能够换行
-
4、当脚本提示not such file/directory ,很有可能是因为脚本在window上编写的,换行符和Linux不一致
解决步骤: vim 脚本,进入命令行模式,然后输入set ff查看格式 如果是dos表示是window,需要改为linux。 set ff=unix
-
5、字符串包含判断 $localIps == $1 或者 $localIps =~ $1
-
6、获取本机ip,并且进行比较,可能有多个ip,所以需要一个个匹配或者包含判断
localIps=`ip addr | grep 'inet' | grep -v 'inet6' | grep -v 127.0.0.1 | awk '{print $2}' | cut -d'/' -f1`
-
7、函数里面的$1是调用函数时传递进来的参数,而不是启动脚本时传递进来的第一个参数