基于日志的Openstack的故障监控

来源:互联网 发布:php web服务器搭建 编辑:程序博客网 时间:2024/05/29 16:15

声明:
本博客欢迎转载,但请保留原作者信息
作者:柯晓东
团队:华为杭州OpenStack团队

openstack上线后,剩下的就是解答各种疑问、恢复各种误操作、捕捉各种bug了。虽然ceilometer也做了监控,但是ceilometer里面提取的东西都是其他组件主动报过来的,它获得的信息量远远小于各个组件自身的日志。所以最终看问题,还是要靠日志。分布式系统、松耦合的组件,不对日志进行归档和整理,那么需要到N台机器的M个目录查看“ERROR”关键字,查看哪一个组件因为什么原因出错了,费时费力。因此首先需要对日志进行统一的归档和检索。

日志的统一归档,业界的思路基本都是:(1)通过一个无所不能的日志接收端,对接各种形式的日志。(2)然后交给一个基于apache lucene的搜索引擎去处理。(3)最后通过一个web框架,查询特定的关键字(例如ERROR),再结合上下文的日志来分析和监控日志。

当前有2个框架比较成熟,一个是ELK(elastic-search、logstash、kibana),另一个是SFK(Solr、Flume、Kafka)。

 

(1)ELK和SFK对比的优缺点

ELK的优点是:对接的文档全、上手快。直接从官网下载文档就能搭好环境。缺点是:elastic-search本身没有安全性设计,闭源的安全插件(shield)是要收费的。

因为ELK的半商业性质,他们有比较多的钱投入宣传。

 SFK都是apache的作品,文档多而杂,上手超级慢,但是安全方面作的很好。

 

(2)ELK配置详情

logstash shipper能够接收syslog-ng、unix socket的日志,这样已经把我们日志大部分都收编了。剩下的部分通过file插件单独一个个地增加到logstash的陪着文件里面。

logstash shipper的输出端当然是他们推荐的redis,再到logstash indexer,最终到elastic-search。

(2.1)elastic-search的配置

elastic-search是搜索引擎,不是数据库,所以在查找时,有很多地方很传统的数据库查找不一样。默认elastic-search里面搜索是不区分大小写的,这样对我们从日志里面查找大写的ERROR非常不方便,因此首先需要切换默认的索引方式。elastic-search的索引是可以配置的,“Index=Tokenizer+Token Filter+Char Filter”

名称说明预设值Tokenizer基础索引规则standard、edgeNGram、keyword、letter、lowercase、whitespace、pattern等Token Filter扩展索引规则standard、asciifolding、lowercase、uppercase、stop等Char Filter字符处理规则mapping、html_strip、pattern_replace等

这些属性看名字就能猜到什么意思,如果想了解具体解释,可以看官网的介绍(https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis.html),这里就不多说了。es就是通过组合使用上述3个属性来实现索引的配置。

内置的组合有:

   custom:3种都让你自己配置

   standard(默认):Standard Tokenizer + Standard Token Filter + Lower Case Token Filter + Stop Token Filter

   simple:Lower Case Token Filter

   whitespace:Whitespace Tokenizer

   stop:Lower Case Token Filter + Stop Token Filter

可以看出默认使用的索引是带着Lower Case Token Filter的,这就是会去掉大小写敏感来建立索引,这就是需要我在我的日志系统里面去掉的。

接着修改elastic-search的配置,在 elasticsearch-1.5.0/config/elasticsearch.yml 文件的最前面加上对默认索引的修改。 

index:    analysis:       analyzer:          default:              type:custom              tokenizer:myTokenizer1              filter:[mytokenFilter1, mytokenFilter2]        tokenizer:            myTokenizer1:                 type:standard                 max_token_length:900        filter:            mytokenFilter1:                 type:standard            mytokenFilter2:                 type:stop                 "stopwords":"_english_"

同时在这个文件里面,要修改一下集群名字(cluster.name: mycluster),免得加入了局域网里面其他集群中。

这样配置就改完了,可以启动elastic-search了。

启动后,可以用curl命令来测试es是否运行正常:

#查看集群状态curl -XGET http://localhost:9200/_cluster/health?pretty=true#组合条件查询curl -XGET http://localhost:9200/_search?pretty -d '{"query" :{"bool":{"must":{"match": {"message": "error"}},"must":{"match":{"message":"nova"}}}}}'#多条件查询curl 'http://localhost:9200/_search?pretty' -d '{  "size": 1000,   "sort": [ {"field1":{"order": "desc"}}, {"field2":{"order": "desc"}} ],  "query":{     "bool":{       "must":[           {"match" :{"field1": {"query":"value1", "minimum_should_match":"100%", "operator":"and"}}},           {"match" :{"field2": {"query":"value2", "operator":"and"}}},           {"range" :{"field3": {"from":"10", "to":"20"}}},           {"range" :{"field4": {"get":"2015-06-16", "lte": now }}}       ]     }  }}'

(2.2)logstash的配置

logstash主要用input和各种日志对接,我这里input直接对接日志文件。output可以先对接redis,再对接es,我这里直接对接es。

input {  file{     path=>"/var/log/nova.log"  }  file{     path=>"/var/log/neutron.log"  }  file{     path=>"/var/log/cinder.log"  }}output {  elasticsearch{    host=>"localhost"    cluster=>"mycluster"  }}

这样之后,用这个配置文件启动logstash,就可以把日志导入到es里面了。

(2.3)用python操作elastic-search

首先是从pypi下载对应的依赖包

https://pypi.python.org/pypi/pyelasticsearch/
https://pypi.python.org/pypi/elasticsearch/

在pyelasticsearch的介绍页面里面,已经有很好的python的例子了。不过我们要更直接的,所以我们直接使用elastic-recheck写好的类。

https://github.com/openstack-infra/elastic-recheck/blob/master/elastic_recheck/results.py

为了更好用一点,对代码进行了一点增强:

import calendarimport copyimport datetimeimport pprintimport dateutil.parser as dpimport pyelasticsearchimport pytzpp = pprint.PrettyPrinter()class SearchEngine(object):    def __init__(self, url):        self._url = url    def search(self, query, size=1000):        es = pyelasticsearch.ElasticSearch(self._url)        args = {'size': size}        results = es.search(query, **args)        return ResultSet(results)    def search_keyword(self, keyword1, keyword2 = None, size = 1000):        must_list = []        dict = {"match":{"message":{"query":keyword1, "operator":"and"}}}        must_list.append(dict)        if keyword2 != None:            dict = {"match":{"message":{"query":keyword2, "operator":"and"}}}            must_list.append(dict)        query={"sort":{"@timestamp":{"order":"desc"}},"query":{"bool":{"must":must_list}}}        return self.search(query, size)       class ResultSet(list):    def __init__(self, results={}):        self._results = results        if 'hits' in results:            self._parse_hits(results['hits'])    def _parse_hits(self, hits):        # why, oh why elastic search        hits = hits['hits']        for hit in hits:            list.append(self, Hit(hit))    def __getattr__(self, attr):        if attr in self._results:            return self._results[attr]class Hit(object):    def __init__(self, hit):        self._hit = hit    def index(self):        return self._hit['_index']    def __getitem__(self, key):        return self.__getattr__(key)    @property    def id(self):        return self._hit['_id']    @property    def type(self):        return self._hit['_type']    @property    def timestamp(self):        return self._hit['_source']['@timestamp']    @property    def host(self):        return self._hit['_source']['_host']            def __getattr__(self, attr):        def first(item):            if type(item) == list:                return item[0]            return item        result = None        at_attr = "@%s" % attr        if attr in self._hit['_source']:            result = first(self._hit['_source'][attr])        elif at_attr in self._hit['_source']:            result = first(self._hit['_source'][at_attr])        elif attr in self._hit['_source']['@fields']:            result = first(self._hit['_source']['@fields'][attr])        return result    def __repr__(self):        return pp.pformat(self._hit)

 

在web端用web.py实现,可以直接使用上述查询类来对elastic-search进行查询。

(3)ELK的安全增强

ELK的框架中,elastic-search本身没有安全性,安全插件shield需要另外收费(https://www.elastic.co/guide/en/shield/current/index.html);logstash是客户端不用考虑安全;kibana是一个可替换的web端几乎不会使用它;传输层使用的是redis已有很好的安全性。那么问题的关键就是elastic-search如何提升安全性。

从官网说明来看,官方希望把elastic-search部署在防火墙后面,只能内网访问。这种方案在我司是不允许的,因为最近就有某网站被内部人员删库的事情发生,所以内部人员也要防!所以我们需要找它的安全插件。

早在elastic-search 0.20,就有人把组件的serverlet从netty替换为jetty,再利用jetty做安全增强。github上有新的基于elastic-search1.5的jetty插件。该插件只解决了从外访问es的安全问题,没有解决es之间的nodes通讯的加密,所以插件下载量较小。地址如下:

https://github.com/tmayfield/elasticsearch-jetty

还有一个访问量多的全套安全的免费解决方案,地址是:

https://github.com/floragunncom/search-guard

这个插件还没有研究透,还在继续尝试中。

 

 (4)用solr代替ES

既然es安全性不好,那么就使用安全性好的搜索引擎,即apache的solr。日志搜集部分还是使用logstash。

(4.1)修改logstash的配置,在output使用solr_http插件。

input {  file{     path=>"/var/log/nova.log"  }  file{     path=>"/var/log/neutron.log"  }  file{     path=>"/var/log/cinder.log"  }}output{  solr_http{    codec=>"plain"    document_id=>nil    flush_size=>100    idle_flush_time=>1    solr_url=>"http://localhost:8983/solr/gettingstarded"    workers=>1  }}

当然,上述solr中的core(gettingstarded)需要事先配置好。

(4.2)修改solr的配置

先用java的keytool生成ssl使用的证书和密钥对

keytool -genkeypair -alias solr-ssl -keyalg RSA -keysize 2048 -keypass mysecret -storepass mysecret -validity 9999 -keystore solr-ssl.keystore.jks -ext SAN=DNS:localhost,IP:x.xxx,ip:127.0.0.1 -dname "CN=localhost,OU=Organization al Unit,O=Organization,L=Location,ST=State,C=Country"

keytool -importkeystore -srckeystore solr-ssl.keystone.jks -destkeystore solr-ssl.keystore.p12 -srcstoretype jks -deststoretype pkcs12

openssl pkcs12 -in solr-ssl.keystore.p12 -out solr-ssl.pem

然后打开solr/bin/solr.in.sh里面关于SSL的配置项。

SOLR_SSL_OPTS="-Djavax.net.ssl.keyStore=solr-ssl.keystore.jks -Djavax.net.ssl.keyStorePassword=mysecret -Djavax.net.ssl.trustStore=solr-ssl.keystore.jks -Djavax.net.ssl.trustStorePassword=mysecret"

然后重启solr。

 

(5)使用日志来作故障检测和恢复

有统一的搜索前,定位问题需要看n个主机的m个日志。有了统一搜索之后,特别是有了python的查询客户端后,就可以用python客户端,将一些先验的知识固化在查询脚本里面。

例如创建虚拟机失败:

(1)首先先查询有没有“Not Valid Host”判断是不是调度失败了,再通过调度器来判断是哪一个资源不够了

(2)查询“ERROR”查看是不是有特定的错误日志。例如libvirt abort,inject fail等,依次判断是发生了哪一个已知问题

(3)如果不是已知的问题,通过ERROR可以找到requestid,再找到对应的action。然后通过ERROR查看调用栈。最后以此推断出现什么未知的问题。

通过提取错误的关键信息,可以得到类似这样的一张表: 

错误信息原因Not Valid Host资源不足unexpected vif_type=binding_failed网口配置不对unsupported configurationlibvirt的xml产生的有问题error injecting data into image镜像不支持注入文件到指定分区。。。 

分析到原因后,再依据场景作相应的恢复操作。

0 0
原创粉丝点击