这个问题出现有一段时间了,最开始的时候从一天3-5次左右到最近的一天出现10多次的告警邮件...

因为Puppet同步采取了主动触发和定时同步两种策略,几乎每次的报错都是在定时同步时出现...

Puppet Server采用双主结构,Web ui使用Foreman,为了确定这个报错是出现在那台服务器上, 通过对源代码的log增加主机标记最终定位到了这个错误只是出现在一台服务器上...,出现的很偶然,但所有的错误标记中,都是它....

LevelResourcemessageerrPuppetCouldnotretrievecatalogfromremoteserver:Error400onSERVER:Failedwhensearchingfornodexxx:001。,Couldnotloadexternalnoderesultsforxxx:undefinedmethod`inject'forfalse:FalseClass::---falsenoticePuppetUsingcachedcatalogerrPuppetCouldnotretrievecatalog;skippingrun

最后面的 :: --- false 其中::是在log中追加的分解符,方便区分, --- false 是返回的output的信息..

在Puppet源代码中 , 通过indirector与enc相关的find方法中可以看到这个find方法接受一个参数 request

indirector/node/exec.rbdeffind(request)output=superorreturnnil#Translatetheoutputtoruby.result=translate(request.key,output)create_node(request.key,result)end

output 是调用父方法的find

父方法的find会调用enc脚本获取返回值,如果失败或调用不成功则为Nil..

这时会继续通过translate方法,将yaml输出转为ruby的对象

如果output为nil,这时yaml在读取这个数据的时候就会抛出异常,异常就是收到的Puppet邮件告警的内容了。

deftranslate(name,output)YAML.load(output).inject({})do|hash,data|casedata[0]whenStringhash[data[0].intern]=data[1]whenSymbolhash[data[0]]=data[1]elseraisePuppet::Error,"keyisa#{data[0].class},notastringorsymbol"endhashendrescue=>detailraisePuppet::Error,"001,Couldnotloadexternalnoderesultsfor#{name}:#{detail}::#{output}"end

罗嗦了一大堆,其实就是node.rb的脚本在通过api取参数的时候,没有获得200...导致的。

通过指向一个错误的WEB服务器地址,可以看到 开头--- false。。。。

[root@testpuppet]#rubynode1.rbtest---falseErrorretrievingnodetest:Net::HTTPNotFound

分析node.rb

defenc(certname)foreman_url="#{url}/node/#{certname}?format=yml"uri=URI.parse(foreman_url)req=Net::HTTP::Get.new(uri.request_uri)http=Net::HTTP.new(uri.host,uri.port)http.use_ssl=uri.scheme=='https'ifhttp.use_ssl?ifSETTINGS[:ssl_ca]&&!SETTINGS[:ssl_ca].empty?http.ca_file=SETTINGS[:ssl_ca]http.verify_mode=OpenSSL::SSL::VERIFY_PEERelsehttp.verify_mode=OpenSSL::SSL::VERIFY_NONEendifSETTINGS[:ssl_cert]&&!SETTINGS[:ssl_cert].empty?&&SETTINGS[:ssl_key]&&!SETTINGS[:ssl_key].empty?http.cert=OpenSSL::X509::Certificate.new(File.read(SETTINGS[:ssl_cert]))http.key=OpenSSL::PKey::RSA.new(File.read(SETTINGS[:ssl_key]),nil)endendres=http.start{|http|http.request(req)}raise"Errorretrievingnode#{certname}:#{res.class}"unlessres.code=="200"res.bodyend

脚本的前面都是在构造一个http的对象...,直接看倒数第三行

可以清楚的看到一个判断,然后抛出异常,没有任何的重试机制....,为此我很确信我的web,它如果能有一次重试的机会,那么下一次一定能正常获得返回值, 然后我就给了它很多次的机会。。。

#raise"Errorretrievingnode#{certname}:#{res.class}"unlessres.code=="200"whileres.code!="200"res=http.start{|http|http.request(req)}puts"Errorretrievingnode#{certname}:#{res.class}"sleep3end

这时有些人可能会想,while 循环,加3秒重试,,如果一直不成功怎么办?

在脚本最开头会有配置timeout的地方,在timeout到了之后,会关闭http连接,然后读取cache。

#queryExternalnodebeginresult=""timeout(tsecs)doresult=enc(certname)cache(certname,result)endrescueTimeoutError,SocketError,Errno::EHOSTUNREACH,Errno::ECONNREFUSED#Readfromcache,wegotsomesortofanerror.result=read_cache(certname)

这段代码可以很清晰的看出,在timeout没超时时会调用enc这个方法返回结果,然后在调用cache方法写入到cache文件

如果超时或http错误,则读取cache,但是这里的异常不包括...,HTTP的...,如果如果是4XX的错误,不会触发读取cache的异常..