您的位置:首页 > 运维架构

基于日志的Openstack的故障监控

2015-06-06 13:43 549 查看
声明:

本博客欢迎转载,但请保留原作者信息

作者:柯晓东

团队:华为杭州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 calendar
import copy
import datetime
import pprint

import dateutil.parser as dp
import pyelasticsearch
import pytz

pp = 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镜像不支持注入文件到指定分区
。。。
分析到原因后,再依据场景作相应的恢复操作。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: