背景 在做大批量运维的时候,DBA需要掌握和使用ansible。今天有同事使用ansible遇到了一个奇怪现象,和我交流了一下,我发现这个现象很奇怪,也很有趣,我认为是个BUG,于是排查,之后有了这篇文章。现象 同时使用的是ansible2。7。8,我使用的是最新版python3。11ansible2。14,我们都复现了这个现象。这个问题简而言之是,使用比较特殊的密码组合作为主机密码,ansible在使用上会遇到问题,导致正确的密码而无法连接。 以下是我的环境: 角色 hostname IP 使用的密码 ansibleserver 192168199121 192。168。199。121 不涉及 有问题的受控机 19216819999 192。168。199。99 1fander 没问题的受控机 192168199131 192。168。199。131 Root123〔root192168199121〕catetcansiblehosts〔fandervm〕192。168。199。99ansibleuserrootansiblesshpass1fander〔root192168199121〕ansible192。168。199。99mping192。168。199。99UNREACHABLE!{changed:false,msg:Invalidincorrectpassword:Permissiondenied,pleasetryagain。,unreachable:true}同事疑问1特殊密码,ansible无法连接会报错 同时设置了一个形如1xxxxxx的密码,使用sshroot192。168。199。99连接是完全没有问题的,但使用ansible连接则会报错,Invalidincorrectpassword:Permissiondenied,pleasetryagain。无法连接。这个密码经过我的研究,是有规律,1位到任意长度的数字‘’任意长度字符,ansible会报错。 也就是,以下密码会报错1fander12fander 以下密码不会报错1afander1fander同事疑问2此特殊密码,ansible无法连接会报错,但有时却能连接不报错排查过程1。ansiblevvvvv排查 v参数可以让ansible输出debug日志,v越多越详细。当然了,可能并不需要五个v,我不知道要打多少个v时,我就把v打满。(mysqlbinlog我打3个v) 如图红圈和横线,这两处很关键。 红圈告诉我,我在执行ansible时,其实底层调用的是sshpass和ssh。 横线这一处,报有个文件不存在,我对比了执行正常连接的机器(192。168。199。131),是不会报这个的,所以此处属于异常,需要排查。 这些日志输出其实一团糟,可以粘贴到notepad,用以下方法美化输出。 粘贴到notepad只有两行,通过替换r为换行符即可。 首先,我先把r替换为fanderissb 然后,再以扩展模式,替换回来 现在就比较好阅读了,这一处就是不正常的。 经过研究,这些debug日志其实来源于我前面红圈的命令行sshpassxxx的输出。 查阅资料和整理,原理大概是这样的: 正确连接是长这个样子的,命令结果是输出root,前面的0是命令?的返回码,0代表执行正常。 而我们密码有问题的服务器返回码是5,输出是空 sshpassd10sshvvvCoControlMasterautooControlPersist60soStrictHostKeyCheckingnooUserrootoConnectTimeout10oControlPathroot。ansiblecpe2f9f7759b192。168。199。99binshcechorootsleep0 在这句命令中,ControlPersist60s是ssh的参数,代表建立一个长链接,保持60秒,这个长链接就是建立在ControlPathroot。ansiblecpe2f9f7759b的路径下。 实际上,就是这个ControlPersist,可以解答同事疑问2此特殊密码,ansible无法连接会报错,但有时却能连接。 为了方便解释,我把ansible服务器的这个ControlPersist调大到600秒。〔root192168199121〕catetcansibleansible。cfg〔defaults〕hostkeycheckingFalse〔sshconnection〕sshargsCoControlMasterautooControlPersist600 首先,我登录远端服务器,先把密码改回常规密码。〔root19216819999〕echoRoot123passwdstdinrootChangingpasswordforuserroot。passwd:allauthenticationtokensupdatedsuccessfully。 接着,我配置好ansible服务器的hosts文件。〔root192168199121〕catetcansiblehosts〔fandervm〕192。168。199。99ansibleuserrootansiblesshpassRoot123 然后,开始测试。完全没有问题,能连通。〔root192168199121〕ansible192。168。199。99mping192。168。199。99SUCCESS{ansiblefacts:{discoveredinterpreterpython:usrbinpython},changed:false,ping:pong} 执行psef能观察到,我们第一次连接完后,ssh并没有断开,有一个背景执行的长链接,他实际上是一个多路复用的socket连接,后续我们再连远端服务器时就是复用他,不需要重新验证密码。〔root192168199121〕psefgrepsshroot8601017:18?00:00:00usrsbinsshdDroot945860017:18?00:00:04sshd:rootpts0,pts1root92201022:40?00:00:00ssh:root。ansiblecpe2f9f7759b〔mux〕root92671009022:41pts000:00:00grepcolorautossh 这个时候,我把远端的服务器密码修改为有问题的密码〔root19216819999〕echo1fanderpasswdstdinrootChangingpasswordforuserroot。passwd:allauthenticationtokensupdatedsuccessfully。 此时,我的ansible服务器的hosts配置里仍然用的旧密码〔root192168199121〕catetcansiblehosts〔fandervm〕192。168。199。99ansibleuserrootansiblesshpassRoot123 密码是错误的,那么我还能连吗?答案是能。这就是连接复用,不需要重新验证密码,直接复用前面的socket连接。 所以,这时你通过ansible,密码乱输或者不输密码都能连。〔root192168199121〕catetcansiblehosts〔fandervm〕192。168。199。99ansibleuserroot我这里直接去掉了密码〔root192168199121〕date;ansible192。168。199。99mshellalsSunNov1322:49:15CST2022我复用这个链接的时间192。168。199。99CHANGEDrc0anacondaks。cfg 那么这个长链接是创建后的600秒自动销毁吗?(提醒,前面我修改的ControlPersist600s) 答案否。因为我在创建连接后,中途复用过这个连接通道,时间是22:49:15,所以他消失时间不是创建时间22:40:2810分钟,而是22:49:1510分钟,也就是22:59:15。〔root192168199121cp〕pwdroot。ansiblecp〔root192168199121cp〕state2f9f7759b;dateFile:‘e2f9f7759b’Size:0Blocks:0IOBlock:4096socketDevice:fd00h64768dInode:68415916Links:1Access:(0600srw)Uid:(0root)Gid:(0root)Access:2022111322:40:28。6805779860800Modify:2022111322:40:28。6155779880800Change:2022111322:40:28。6155779880800Birth:SunNov1322:59:00CST2022〔root192168199121cp〕state2f9f7759b;datestat:cannotstat‘e2f9f7759b’:NosuchfileordirectorySunNov1322:59:30CST2022超过22:59:15,socket消失了。 那同事的疑问2,就可以解释了,有问题的密码依然不能通过ansible连接,能连接的假象是因为曾经用正确的密码建立过长链接(这个是ssh的参数功能,不是ansible),后面连接时复用了此连接,没有使用密码认证,所以也就不会报错了。待ControlPersist超时后,socket销毁,有问题的密码连接就开始报错了。 我们继续排查同事的疑问1。 根据我前面整理的原理,我标记1、2、3、4数字的这几步,我按这个顺序从下往上开始一一排除。 其实,本来应该从最顶上的4开始排查的,但4需要阅读源码,所以我从简单的1开始排查。2。排查ssh 首先,我测试标记为1的步骤,手敲这个密码,ssh是否认正常。结果是连接正常。 3。排查sshpass 然后,测试标记为2的步骤,测试sshpass传输密码是否正常的。结果也是连接正常。〔root19216819999〕sshpassp1fandersshCoControlMasterautooControlPersist60soStrictHostKeyCheckingnooUserrootoConnectTimeout10oControlPathroot。ansiblecpe2f9f7759b192。168。199。99binshcechorootsleep0root 这里,我调整了原命令,因为原命令用的是sshpassd10,这个文件描述符文件我不知道如何制造,所以我改为用sshpassp来测试。我去掉了vvv,因为如果一切正常,我不需要刷屏的debug日志。4。使用paramiko连接方式辅助排查 标记为3的步骤,我不知道如何测试,但我知道ansible除了ssh连接,还有一种叫paramiko的连接方式,他是旧版ansible的默认连接方式,他比较低效,他不使用ControlPersist,也就不会建立Controlsocket,而之前我们连接报错时,日志的关键信息就是Controlsocketroot。ansiblecpe2f9f7759bdoesnotexist 当然了,他也不会使用sshpass和不会把密码写入那个数字为10的文件描述符。所以,如果我能在paramiko的连接方式能复现报错,那么就和标记为3的步骤无关。〔root192168199121inventory〕catetcansibleansible。cfg〔defaults〕hostkeycheckingFalsecallbackwhitelisttimertransportparamiko 结果是,我使用paramiko的连接方式,也能复现连接报错。 他这个python抛出异常非常好,终于让我知道为什么密码会错误了,原来传进去的密码不是1fander,而是1。也就是问题肯定不在标记为3的步骤,而是标记为4的步骤。 根据报错,我在authhandler。py文件里打了两个print。 我们再看看输出。 那么,标记为1、2、3的步骤我们都排除了,问题出在标记为4的步骤,也就是现在的问题是: 为什么ansible传入给sshpass和ssh的密码不正确,应该传入1fander,但最终传入1?5。排查ansiblek 我们再来做个测试,不使用ansiblehosts文件传递密码,使用k参数手敲密码。发现一切正常。6。水落石出,是ansible的hosts设置问题 那问题完全能定位出来了,这个疑似bug,不是ssh也不是sshpass的问题,而是ansible的问题。并且,我们能确定,这个和ansible读取解析etcansiblehosts文件有关。 经过我查阅资料,ansible读取解析etcansiblehosts相关的代码在这个路径下,ini。py文件。cdusrlocalpython3libpython3。11sitepackagescdansiblecore2。14。0py3。11。eggansiblepluginsinventorylessini。py 通过阅读注释,发现这个不是bug,而是官方知道的问题,所以属于一个坑。 最后 我经常阅读源码都是通过阅读注释就解决的,这很有趣,适合我这种萌新coder。现在是2022年11月13日的23:56分,由于能力和时间的关系,我就写到这里了。大家应该看懂了解决办法,请大家避免这个坑,这个坑不单止针对密码,在hosts文件的所有变量设置都应该这么做(上图横线)。有兴趣深入研究的同学可以继续看看源代码。