4、expect自动化实现集群免密钥登录

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是调用函数时传递进来的参数,而不是启动脚本时传递进来的第一个参数

转载于:https://my.oschina.net/liufukin/blog/2218973

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值