Docker部署ELK(配置密码登录)及Elastalert企业微信告警配置
使用ELK相对应的docker镜像来部署ELK环境,可以避免自己搭建环境时遇到的各种问题,本文提供docker安装时需要的各项配置文件以及配置方法,和详细的参数、命令说明。相信能让人更快的理解并搭建到ELK系统。同时还提供了通过elastalert向企业微信推送告警的配置方法,让监控通知能及时的触达到管理者。
ELK部署记录
部署Elasticsearch、Kibana、Cerebro
通过docker进行部署,可以避免很多缺少依赖的问题,推荐使用centos7环境进行部署,请提前安装好docker服务、
docker-compose服务。
先新建一个安装目录:mkdir -p /data/elk ,创建文件 docker-compose.yaml,填写如下内容:
version: '2.2'
services:
cerebro:
image: lmenezes/cerebro:0.9.4
container_name: lrb_cerebro
ports:
- "9000:9000"
command:
- -Dhosts.0.host=http://elasticsearch:9200
networks:
- lrb_es7net
kibana:
image: docker.elastic.co/kibana/kibana:7.7.1
container_name: lrb_kibana7
environment:
- I18N_LOCALE=zh-CN
- XPACK_GRAPH_ENABLED=true
- TIMELION_ENABLED=true
- XPACK_MONITORING_COLLECTION_ENABLED="true"
ports:
- "5601:5601"
networks:
- lrb_es7net
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.7.1
container_name: es7_hot
environment:
- cluster.name=kllrb
- node.name=es7_hot
- node.attr.box_type=hot
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- discovery.seed_hosts=es7_hot,es7_warm,es7_cold
- cluster.initial_master_nodes=es7_hot,es7_warm,es7_cold
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- lrb_es7data_hot:/usr/share/elasticsearch/data
ports:
- 9200:9200
networks:
- lrb_es7net
elasticsearch2:
image: docker.elastic.co/elasticsearch/elasticsearch:7.7.1
container_name: es7_warm
environment:
- cluster.name=kllrb
- node.name=es7_warm
- node.attr.box_type=warm
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- discovery.seed_hosts=es7_hot,es7_warm,es7_cold
- cluster.initial_master_nodes=es7_hot,es7_warm,es7_cold
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- lrb_es7data_warm:/usr/share/elasticsearch/data
networks:
- lrb_es7net
elasticsearch3:
image: docker.elastic.co/elasticsearch/elasticsearch:7.7.1
container_name: es7_cold
environment:
- cluster.name=kllrb
- node.name=es7_cold
- node.attr.box_type=cold
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- discovery.seed_hosts=es7_hot,es7_warm,es7_cold
- cluster.initial_master_nodes=es7_hot,es7_warm,es7_cold
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- lrb_es7data_cold:/usr/share/elasticsearch/data
networks:
- lrb_es7net
volumes:
lrb_es7data_hot:
driver: local
lrb_es7data_warm:
driver: local
lrb_es7data_cold:
driver: local
networks:
lrb_es7net:
driver: bridge
启动容器前需要先设置一下虚拟机的配置
vim /etc/sysctl.conf ,添加 vm.max_map_count=262144,否则在启动时会因为值太小而无法启动。
添加完成后执行命令:sysctl -p ,再启动容器。
进入目录/data/elk,执行容器启动命令:docker-compose up -d 等待容器启动运行,注意kibana的启动时间稍长,建议等2-3分钟再打开kibana界面,以下是几个容器的登录页面:
http://ip地址:9000 Cerebro界面,可以查看elasticsearch集群信息,这里启动3个节点
http://ip地址:9200 elasticsearch信息界面
http://ip地址:5601 kibana界面
到这里elasticsearch、kibana、cerebro就部署完毕了,可以发现目前访问这些服务都是不需要账号密码的,出于安全性考虑,还是需要配置一下账号密码。
Elasticsearch账号密码配置方法
-
目前使用的docker部署方式,针对elasticsearch部署了3个节点,es7_hot,es7_warm,es7_cold
-
进入主节点es7_hot的容器,在bin目录下执行命令,生成秘钥文件
elasticsearch-certutil cert -out config/elastic-certificates.p12 -pass “”
秘钥文件elastic-certificates.p12会生成到/usr/share/elasticsearch/config目录下
-
退出容器,进入/data/elk目录,将es7_hot容器内的elastic-certificates.p12先拷贝出来,并赋权777,再分别复制到es7_hot,es7_warm,es7_cold容器的/usr/share/elasticsearch/config目录,命令如下:
docker cp [es7_hot容器id]:/usr/share/elasticsearch/config/elastic-certificates.p12 .
chmod 777 elastic-certificates.p12
docker cp elastic-certificates.p12 [容器id]:/usr/share/elasticsearch/config
-
在/data/elk目录下新建elasticsearch.yml文件,内容如下:
cluster.name: "docker-cluster" cluster.initial_master_nodes: ["es7_hot"] network.host: 0.0.0.0 http.cors.enabled: true http.cors.allow-origin: "*" http.cors.allow-headers: Authorization xpack.security.enabled: true xpack.security.transport.ssl.enabled: true xpack.security.audit.enabled: true xpack.license.self_generated.type: basic xpack.security.transport.ssl.verification_mode: certificate xpack.security.transport.ssl.keystore.path: elastic-certificates.p12 xpack.security.transport.ssl.truststore.path: elastic-certificates.p12
-
将elasticsearch.yml文件分别复制到es7_hot,es7_warm,es7_cold容器的/usr/share/elasticsearch/config目录下,并重启三个节点容器
docker cp elasticsearch.yml [容器id]:/usr/share/elasticsearch/config
-
进入主节点es7_hot容器,在bin目录下执行设置密码命令:(根据默认的不同账号设置密码)
elasticsearch-setup-passwords interactive
注意:设置密码千万不要在密码中包含【@】,否则在后续的告警模块elastalert中会无法正常连接到elasticsearch -
此时再进入Cerebro界面、elasticsearch信息界面就需要输入账号密码才能进入
-
因Elasticsearch已经设置了账号密码,那么kibana要连接elasticsearch也需要在Kibana的配置文件中添加账号密码的参数,在/data/elk目录下新建kibana.yml文件,内容如下:
# # ** THIS IS AN AUTO-GENERATED FILE ** # # Default Kibana configuration for docker target server.name: kibana server.host: "0" elasticsearch.username: "kibana" elasticsearch.password: "123456" elasticsearch.hosts: [ "http://elasticsearch:9200" ] xpack.monitoring.ui.container.elasticsearch.enabled: true
账号kibana是默认的,专门给kibana的角色进行连接的,密码则是上面手动设置的密码。
-
将kibana.yml文件复制到kibana容器的/usr/share/kibana/config目录下,并重启容器
docker cp kibana.yml [kibana容器id]:/usr/share/kibana/config
-
再次登录kibana会出现一个登录页,填写账号密码进入即可,这里用elastic的账号进入。elastic账号是一个超级管理员的账号。
-
登录后在设置中有一个安全性,可以根据需要自己配置用户和角色
logstash安装配置
-
去官网下载对应版本的安装包:https://www.elastic.co/cn/downloads/logstash 注意一定要与elasticsearch的版本一致,这里下载7.7.1版本
-
下载的logstash-7.7.1.tar.gz解压后,在logstash-7.7.1/config目录下新建logstash.conf文件
-
logstash.conf文件配置详解(只针对当前配置项进行说明,其余配置项请自行学习)
logstash通过管道完成数据的采集与处理,管道配置中包含input、output和filter(可选)插件,input和output用来配置输入和输出数据源、filter用来对数据进行过滤或预处理
3.1 input
file方法,从文件中读取数据,如常见的日志文件
【path】读取文件的路径
【type】需要配置多个日志读取时配置,用于区分读取的文件,后续filter、output都用这个做区分
【start_position】是否从头读取文件,beginning【是】end【否】
【codec => multiline】合并插件,处理多行数据内容,可以将换行的日志合并为一个日志
【pattern => "\d+"】以什么开头;\d表示数字;+至少一个。以至少一个数字开头
【negate => “true”】对pattern的结果取反,默认值为false(匹配成功),这里配置true(匹配失败)
【what => “previous”】表示当上述正则匹配失败时将当前数据与上一条数据进行合并,这个值可以是previous(向前)和next(向后),根据需要自己设置
【charset => “UTF-8”】字符编码
【auto_flush_interval】若3秒内还没有满足条件的则视为结束合并
3.2 filter
filter是Logstash功能强大的主要原因,它可以对Logstash Event进行丰富的处理,比如解析数据、删除字段、类型转换等等
【if[type]】根据input中设置的file-type值进行区分处理
【grok】正则匹配解析插件
【match】创建匹配规则,grok有一些默认的规则可使用,但是需要日志文件按照默认规则的格式输出才有效果,相比较之下还是根据日志输出内容,实时定制一个匹配规则更方便。定制规则需要先熟悉一下grok的参数:
https://github.com/logstash-plugins/logstash-patterns-core/blob/main/patterns/ecs-v1/grok-patterns
grok的规则可以根据日志打印的规则进行编写,例如nginx默认的日志格式如下:192.168.107.158 - - [14/Jun/2022:11:23:22 +0800] "GET /xapi/v1/user-centers HTTP/1.1" 200 9128 "http://dx.kuailelaorendaxue.test/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 NetType/WIFI MicroMessenger/7.0.20.1781(0x6700143B) WindowsWechat(0x6307001d)" "-"
对应的grok的规则如下:
%{IPV4:remote_addr} - (%{USERNAME:remote_user}|-) [%{HTTPDATE:time_local}] \"%{WORD:request_method} %{URIPATHPARAM:request_uri} HTTP/%{NUMBER:http_protocol}\" %{NUMBER:http_status} %{NUMBER:body_bytes_sent} \"%{GREEDYDATA:http_referer}\" \"%{GREEDYDATA:http_user_agent}\" \"(%{IPV4:http_x_forwarded_for}|-)\"
可以通过kibana的开发工具【Grok Debugger】进行实时调试,获得正确的grok语句,在调试时grok模式的规则需要针对【”】双引号,加上转义符【\】
3.3 output
output负责将处理后的日志输出到指定的位置,输出到elasticsearch ,是最常用的插件
【if[type]】根据input中设置的file-type值进行区分处理
【elasticsearch】将处理后的数据输出到elasticsearch中
【hosts】elasticsearch的地址、端口
【user】用户名,若elasticsearch设置了账号密码
【password】密码,若elasticsearch设置了账号密码
【index】输出到elasticsearch的日志名
【stdout {}】将数据打印出来
logstash.conf完整文件内容如下:input { # 从文件读取日志信息 file { path => "/Data/website/www/wechat-h5-onlinecourse/vhost/course/log/nginx/nginx.frontend.access.log" type => "nginx-dx-course" start_position => "beginning" } file { path => "/Data/website/www/wechat-h5-onlinecourse/project/dx.laoren.com/backend/runtime/logs/app.log" type => "applog-dx-backend" start_position => "beginning" codec => multiline { pattern => "^\d+" negate => "true" what => "previous" charset => "UTF-8" auto_flush_interval => 3 } } file { path => "/Data/website/www/wechat-h5-onlinecourse/project/dx.laoren.com/api/runtime/logs/app.log" type => "applog-dx-api" start_position => "beginning" codec => multiline { pattern => "^\d+" negate => "true" what => "previous" charset => "UTF-8" auto_flush_interval => 3 } } } filter { #根据日志打印信息进行grok的规则编写 if[type] == "nginx-dx-course" { grok { match => { "message" => "%{IPV4:remote_addr} - (%{USERNAME:remote_user}|-) [%{HTTPDATE:time_local}] \"%{WORD:request_method} %{URIPATHPARAM:request_uri} HTTP/%{NUMBER:http_protocol}\" %{NUMBER:http_status} %{NUMBER:body_bytes_sent} \"%{GREEDYDATA:http_referer}\" \"%{GREEDYDATA:http_user_agent}\" \"(%{IPV4:http_x_forwarded_for}|-)\"" } } } if[type] == "applog-dx-backend" { grok { match => { "message" => "%{TIMESTAMP_ISO8601:date} [(%{IPV4:source_ip}|)][-][-][%{WORD:logstate}][%{DATA:methods}] %{GREEDYDATA:logmessage}" } } } if[type] == "applog-dx-api" { grok { match => { "message" => "%{TIMESTAMP_ISO8601:date} [(%{IPV4:source_ip}|)][-][-][%{WORD:logstate}][%{DATA:methods}] %{GREEDYDATA:logmessage}" } } } } output { #将日志输出到es,如es有密码则需配置账号密码 if[type] == "nginx-dx-course" { elasticsearch { hosts => ["http://192.168.103.87:9200"] user => "elastic" password => "123456" index => "nginx-dx-course-%{+YYYY.MM.dd}" } } if[type] == "applog-dx-backend" { elasticsearch { hosts => ["http://192.168.103.87:9200"] user => "elastic" password => "123456" index => "applog-dx-backend-%{+YYYY.MM.dd}" } stdout {} } if[type] == "applog-dx-api" { elasticsearch { hosts => ["http://192.168.103.87:9200"] user => "elastic" password => "123456" index => "applog-dx-api-%{+YYYY.MM.dd}" } stdout {} } }
-
在logstash工作目录下执行:./bin/logstash -f ./config/logstash.conf 则数据会写入到ES中
若执行命令提示没有安装java,则通过如下命令安装:yum install -y java-1.8.0-openjdk.x86_64 vim /etc/profile #添加如下信息,然后重新进入服务器,执行命令 JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.342.b07-1.el7_9.x86_64 JRE_HOME=$JAVA_HOME/jre CLASS_PATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin export JAVA_HOME JRE_HOME CLASS_PATH PATH
-
logstash有内容记录功能,通过sincedb文件记录上一次的内容节点,方便服务重启后能直接从上一个节点继续读取。文件位置:[安装目录]/logstash-7.7.1/data/plugins/inputs/file/.sincedb_011b39dd02c9df1b75fe53301e474e6e 若想将日志文件重新读取,需要将sincedb文件删除,在重启服务
-
logstash启动建议后台运行,成功运行后,在kibana界面的索引管理可看见由output输出到elasticsearch的日志名
-
通过索引名称创建索引模式后,可在视图分析页面查看日志详细信息
删除ES的日志
ES保存日志是永久保存,所以需要定期删除一下日志,下面命令为删除指定时间的日志,如果ES设置有密码则需要加上用户名密码,命令只能删除某一天的日志。
curl -X DELETE http://127.0.0.1:9200/*-`date +%Y.%m.%d -d "-10 days"` -uelastic:123456
【date +%Y.%m.%d -d “-10 days”】获取10天前的日期
【*-】表示所有的日志,可根据需要自己决定需要删除哪些类型的日志
【-uelastic:123456】-u用户名:密码 当ES设置有密码时需要
elastalert安装与配置
-
通过docker-compose.yaml安装elastalert,在/data下新建elastalert目录,在/data/elastalert下新建
docker-compose.yaml文件,文件内容如下:version: '2.2' services: elastalert: image: anjia0532/elastalert-docker container_name: elastalert environment: - ELASTICSEARCH_HOST=192.168.103.78 - ELASTICSEARCH_PORT=9200 - TZ=Asia/Shanghai - ELASTICSEARCH_USER="elastic" - ELASTICSEARCH_PASSWORD="123456" volumes: - /data/elastalert/rules:/opt/elastalert/rules - /data/elastalert/elastalert_modules:/opt/elastalert/elastalert_modules
注意:修改es的地址、端口、账号、密码
-
在/data/elastalert下创建rules、elastalert_modules目录,用于容器启动时挂载用
-
在/data/elastalert/elastalert_modules下新建wechat_qiye_alert.py,文件内容如下:
#! /usr/bin/env python # -*- coding: utf-8 -*- import json import datetime from elastalert.alerts import Alerter, BasicMatchString from requests.exceptions import RequestException from elastalert.util import elastalert_logger,EAException #[感谢minminmsn分享](https://github.com/anjia0532/elastalert-wechat-plugin/issues/2#issuecomment-311014492) import requests ''' ################################################################# # 微信企业号推送消息 # # # # 作者: AnJia <anjia0532@gmail.com> # # 作者博客: https://anjia.ml/ # # Github: https://github.com/anjia0532/elastalert-wechat-plugin # # # ################################################################# ''' class WeChatAlerter(Alerter): #企业号id,secret,应用id必填 required_options = frozenset(['wechat_corp_id','wechat_secret','wechat_agent_id']) def __init__(self, *args): super(WeChatAlerter, self).__init__(*args) self.corp_id = self.rule.get('wechat_corp_id', '') #企业号id self.secret = self.rule.get('wechat_secret', '') #secret self.agent_id = self.rule.get('wechat_agent_id', '') #应用id self.party_id = self.rule.get('wechat_party_id') #部门id self.user_id = self.rule.get('wechat_user_id', '') #用户id,多人用 | 分割,全部用 @all self.tag_id = self.rule.get('wechat_tag_id', '') #标签id self.access_token = '' #微信身份令牌 self.expires_in=datetime.datetime.now() - datetime.timedelta(seconds=60) def create_default_title(self, matches): subject = 'ElastAlert: %s' % (self.rule['name']) return subject def alert(self, matches): if not self.party_id and not self.user_id and not self.tag_id: elastalert_logger.warn("All touser & toparty & totag invalid") # 参考elastalert的写法 # https://github.com/Yelp/elastalert/blob/master/elastalert/alerts.py#L236-L243 body = self.create_alert_body(matches) #matches 是json格式 #self.create_alert_body(matches)是String格式,详见 [create_alert_body 函数](https://github.com/Yelp/elastalert/blob/master/elastalert/alerts.py) # 微信企业号获取Token文档 # http://qydev.weixin.qq.com/wiki/index.php?title=AccessToken self.get_token() self.senddata(body) elastalert_logger.info("send message to %s" % (self.corp_id)) def get_token(self): #获取token是有次数限制的,本想本地缓存过期时间和token,但是elastalert每次调用都是一次性的,不能全局缓存 if self.expires_in >= datetime.datetime.now() and self.access_token: return self.access_token #构建获取token的url get_token_url = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s' %(self.corp_id,self.secret) try: response = requests.get(get_token_url) response.raise_for_status() except RequestException as e: raise EAException("get access_token failed , stacktrace:%s" % e) #sys.exit("get access_token failed, system exit") token_json = response.json() if 'access_token' not in token_json : raise EAException("get access_token failed , , the response is :%s" % response.text()) #sys.exit("get access_token failed, system exit") #获取access_token和expires_in self.access_token = token_json['access_token'] self.expires_in = datetime.datetime.now() + datetime.timedelta(seconds=token_json['expires_in']) return self.access_token def senddata(self, content): #如果需要原始json,需要传入matches # http://qydev.weixin.qq.com/wiki/index.php?title=%E6%B6%88%E6%81%AF%E7%B1%BB%E5%9E%8B%E5%8F%8A%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F # 微信企业号有字符长度限制(2048),超长自动截断 # 参考 http://blog.csdn.net/handsomekang/article/details/9397025 #len utf8 3字节,gbk2 字节,ascii 1字节 if len(content) > 2048: content = content[:2045] + "..." # 微信发送消息文档 # http://qydev.weixin.qq.com/wiki/index.php?title=%E6%B6%88%E6%81%AF%E7%B1%BB%E5%9E%8B%E5%8F%8A%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F send_url = 'https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%s' %( self.access_token) headers = {'content-type': 'application/json'} #最新微信企业号调整校验规则,tagid必须是string类型,如果是数字类型会报错,故而使用str()函数进行转换 payload = { "touser": self.user_id and str(self.user_id) or '', #用户账户,建议使用tag "toparty": self.party_id and str(self.party_id) or '', #部门id,建议使用tag "totag": self.tag_id and str(self.tag_id) or '', #tag可以很灵活的控制发送群体细粒度。比较理想的推送应该是,在heartbeat或者其他elastic工具自定义字段,添加标签id。这边根据自定义的标签id,进行推送 'msgtype': "text", "agentid": self.agent_id, "text":{ "content": content }, "safe":"0" } # set https proxy, if it was provided # 如果需要设置代理,可修改此参数并传入requests # proxies = {'https': self.pagerduty_proxy} if self.pagerduty_proxy else None try: response = requests.post(send_url, data=json.dumps(payload), headers=headers) response.raise_for_status() except RequestException as e: raise EAException("send message has error: %s" % e) elastalert_logger.info("send msg and response: %s" % response.text) def get_info(self): return {'type': 'WeChatAlerter'}
-
在/data/elastalert/rules下新建wechat_nginx-app-frontend.yaml,名字自定义以.yaml结尾即可,文件内容如下:
es_host: 192.168.103.78 es_port: 9200 es_username: "elastic" es_password: "123456" name: nginx-app-frontend #告警模板名 realert: #2分钟内不重复告警 minutes: 2 type: frequency index: nginx-app-* #要查询的索引的名称, ES中存在的索引 num_events: 5 #此参数特定于frequency类型,而且是触发警报时的阈值,周期内出现5次 timeframe: #监控周期为1分钟 minutes: 1 filter: #配置监控条件 - query_string: query: "http_status: (304|400|404|500|501)" alert: - "elastalert_modules.wechat_qiye_alert.WeChatAlerter" wechat_corp_id: "ww9d9xxxxxxxx5a279" #企业号id wechat_secret: "uoeDZNoxxxx4D-mr4lDxxxxxxxxx1Bmwtt-k" #应用秘钥 wechat_agent_id: "1000096" #应用id wechat_party_id: "2" #部门id wechat_user_id: "Gxxxo|Cxxxl|jxxxxy" #用户id,多人用 | 分割,全部用 @all wechat_tag_id: "" #标签id alert_text_type: alert_text_only alert_text: | 【测试环境】 发生了 {} 次告警 告警模块: {} 告警ip: {} 详细日志: {} alert_text_args: - num_hits - type - remote_addr - message
-
以上的配置都准备完毕后,在/data/elastalert下执行命令:docker-compose up -d ,启动elastalert容器,未下载elastalert镜像的需要耐心等待镜像下载完毕
-
elastalert容器运行后,查看运行状态是否正常
-
通过url访问,制造一些404的报错信息,通过docker logs 容器id,可查看容器日志
-
检查告警消息是否发送到企业微信
企业微信相关参数获取及配置
wechat_corp_id: "ww9d91xxxxx45a279" #企业号id
wechat_agent_id: "1000096" #应用id
wechat_secret: "uoeDZNoKxxxxxx4D-mr4lDuNxxxxxxxxx1Bmwtt-k" #应用秘钥
wechat_party_id: "2" #部门id
wechat_user_id: "Gxxxxo|Cxxxxxl|jxxxxy" #用户id,多人用 | 分割,全部用 @all
wechat_tag_id: "" #标签id
-
企业号id
进入企业微信管理端(管理员权限)【我的企业】【企业信息】【企业ID】
-
应用id,应用秘钥
【应用管理】【应用】【自建】【创建应用】,新建一个【告警通知】的应用,获取应用id、应用秘钥。设置【可见范围】可以让该应用只有被设置的人才可见。 -
部门id
【通讯录】下找到对应部门,点击更多详情后,可查看部门id -
用户id
【通讯录】【部门】下找到具体某个成员,查看成员详情,账号就是用户id
更多推荐
所有评论(0)