文章目录
- 背景和方案选择
- 前提
- 注册AWS账号
- 创建EC2实例
- 注意事项
- 在EC2实例上安装aws-cloudwatch-agent
- 注意事项
- 测试aws-cloudwatch-agent是否可用
- 使用Docker Compose部署ELK
- 使用Docker Compose部署Filebeat
- 配置文件说明
- docker-compose.yml说明
- filebeat配置文件说明
- input配置AWS CloudWatch插件
- output配置
- processors的配置
- Tomcat设置access_log输出格式
- 在Tomat的server.xml中配置access_log配置
- 在Spring Boot中配置access_log
- 测试方法
- 配置Logstash
- 配置文件说明
- 完整流程测试
- 故障排除思路
- 备注
背景和方案选择
最近一直在搞公司的日志系统,目前日志系统初步有两大部分:
- Tomcat的catalina.out
- Tomcat的access_log
机器都在AWS上,这两部分的日志都要发送到Docker搭建的ELK环境中。
对于第一点,使用Log4j2即可将应用日志实时传送到ELK中,详情参考使用Log4j2以ECS规范的格式将日志发送到Logstash中
对于第二点,方案则是:通过AWS CloudWatch Logs收集各个EC2 实例上的Tomcat的acceess_log,通过Filebeat拉取AWS CloudWatch Logs里收集到的日志,然后发送到ELK中。
注意:AWS CloudWatch 是一个很大的模块,里面既可以收集各种指标,也可以收集日志。详情参考AWS CloudWatch官方文档
前提
注册AWS账号
AWS注册成功之后,会有一年的免费套餐使用
创建EC2实例
在EC2控制台上找到EC2的文档
按照官方文档,启动EC2实例即可。
注意事项
- 要提前创建key pairs,检查是否存在kye pairs
- 要提前创建VPC,检查是否存在默认的VPC,如果没有,点击创建默认的VPC
- 要提前创建security group,检查是否存在security group。要特别注意Inbound rules中HTTP,HTTPS,SSH端口的暴露配置,否则有可能无法通过EC2 console连接
- 上述三项都确认存在,则按照官方文档开始创建EC2,创建完毕之后测试是否能够通过EC2 console连接到实例。
- 连接成功之后,查看IAM下是否有名字ec2-user的用户(因为EC2实例默认的用户名字是ec2-user),如果没有的话,为当前EC2实例创建一个.详细步骤参考下列文档
记得创建完用户之后,保存用户的access key和secret key
在EC2实例上安装aws-cloudwatch-agent
按照官方文档一步一步来即可
注意事项
- 先下载agent和创建用于AWS CoudWatch Logs的IAM Role
- 接着去创建agent所需的文件,创建配置文件时有一个collectd的选项,选择no即可,其他的选项默认即可。到第二段配置的时候,会问你想要监控哪些日志文件,输入文件路径即可
- 最后才是将刚刚创建好的IAM Role Attach到EC2实例上,并启动agent
官方文档有点跳跃
测试aws-cloudwatch-agent是否可用
在EC2实例上启动agent之后,修改agent监控的文件的内容,在AWS CLoudWatch控制台查看,是否有新增的log group和log stream。二者的名字是配置agent时设置的
如果无法发送到日志到AWS CloudWatch,则检查agent的日志和查阅对应的官方文档
请确保上面的所有流程都正确并且可正常在控制台看到新加的日志再进行下一步
使用Docker Compose部署ELK
部署单节点的ELK可快速使用Github上非常受欢迎的docker-elk项目。
非常简单,代码一拉,改改配置或者都不需要改配置,就可以启动ELK的docker环境。
详细步骤请参考该项目的文档。
进行下一步时,务必确定docker环境的ELK三个组件都正常启动并能访问
使用Docker Compose部署Filebeat
因为上述官方主要提供ELK的docker compose,虽然filebeat也作为extension提供了。但是我自己测试的时候是使用单独的Filebeat,当然最终在产品环境部署的时候肯定是和上面的docker-compose.yml一起的。
mkdir filebeat
cd filebeat
在filebeat文件下建立三个文件
- docker-compose文件
version: "3.7" services: filebeat-aws: image: docker.elastic.co/beats/filebeat:这里的版本号和上面ELK保持一致 container_name: filebeat user: root env_file: - .env volumes: - type: bind source: ./filebeat.docker.yml target: /usr/share/filebeat/filebeat.yml - type: bind source: /var/lib/docker/containers target: /var/lib/docker/containers read_only: true - type: bind source: /var/run/docker.sock target: /var/run/docker.sock read_only: true networks: - elk_elk networks: elk_elk: external: true
- filebeat配置文件filebeat.docker.yml
filebeat.inputs: - type: aws-cloudwatch enabled: true log_group_arn: 替换成自己的log_group_arn scan_frequency: 15s start_position: end access_key_id: ${AWS_ACCESS_KEY_ID} secret_access_key: ${AWS_SECRET_ACCESS_KEY} logging.metrics.enabled: false processors: - dissect: tokenizer: '%{clientIp} - - [%{access_timestamp}] %{response_time|integer} %{session_id} "%{http_request_method} %{url_path} %{http_version}" %{status_code|integer} %{http_request_body_bytes|integer} "%{http_request_referrer}" "%{user_agent_original}"' field: "message" target_prefix: "" ignore_failure: false - timestamp: field: "access_timestamp" layouts: - '2006-01-02T15:04:05Z' - '2006-01-02T15:04:05.999Z' - '2006-01-02T15:04:05.999-07:00' test: - '2019-06-22T16:33:51Z' - '2019-11-18T04:59:51.123Z' - '2020-08-03T07:10:20.123456+02:00' - drop_fields: fields: ["agent","log","cloud","event","message"] ignore_missing: true #output.console: # pretty: true output.logstash: hosts: ["logstash:5044"]
- 环境变量文件.env 这里配置的就是ec2-user的access_key和secret_key
AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY=
配置文件说明
docker-compose.yml说明
- 第一个docker-compose文件中,关于networks的配置做一下说明。因为我们ELK的docker compose环境已经部署好了,它有自己的专属网络。我们此时想要在另外一个docker compose中访问此网络而不需要再自己创建网络,那么就需要做两个事情。第一:在具体service下的networks声明那里的名字要是ELK的docker compose的网络名字。第二:在根节点的networks那里要加一个external的参数并设置为true。更多详细内容参考Compose中external解释
- service下network的名字是否修改要看你的docker-compose.yml所在的文件目录,我这里所在的文件目录是elk,然后网络名字是elk,所以docker默认创建出来的网络是elk_elk,如果你的文件目录不是elk,务必要修改正确。关于docker compose中网络的命名,参考Docker Compose官方文档有关NetWork的配置和Compose文件书写规范关于Network一节
- 再扩展一下,如果是docker swarm的话,其他docker容器想要访问swarm的网络的话,需要在swarm中添加attachable参数。详情参考Compose文件书写规范关于Network一节和Docker Engine中关于Network的说明
filebeat配置文件说明
input配置AWS CloudWatch插件
具体配置项含义参考官方文档
output配置
因为经过上面的配置,filebeat已经可以访问ELK的网络了。所以这里输出的host直接写ELK的docker-compose.yml中logstash的配置名即可。
注意:filebeat只能配置一个output,不能配置多个output
processors的配置
这里就比较灵活了,看实际情况中Tomcat中关于access_log的配置格式是什么样的。以我的Tomcat配置的access_log为例说明
Tomcat设置access_log输出格式
在Tomat的server.xml中配置access_log配置
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="access_log" suffix=".txt"
pattern="%h %l %u [%{yyyy-MM-dd'T'HH:mm:ss.SSSZ}t] %D %S "%r" %s %b "%{Referer}i" "%{User-Agent}i""
renameOnRotate="true"
requestAttributesEnabled="true" />
上面各个配置的具体含义参考Tomcat官方文档关于Access Log的配置
过滤掉局域网IP
<Valve className="org.apache.catalina.valves.RemoteIpValve"
internalProxies="172\.16\.\d{1,3}\.\d{1,3}" />
各项配置的具体含义参考Access_Control下的RemoteIp的配置
在Spring Boot中配置access_log
具体配置项的名字在
Spring Boot文档 > Application Properties > Server Properties下查看
server:
port: 18081
servlet:
context-path: /pool2
tomcat:
accesslog:
enabled: true
pattern: "%h %l %u [%{yyyy-MM-dd'T'HH:mm:ss.SSSZ}t] %D %S '%r' %s %b '%{Referer}i' '%{User-Agent}i'"
directory: /tmp/logs/tomcat
prefix: access_log
basedir: /var
需要指定basedir,否则tomcat的access_log也是不能生成的.详细文章参考这位博主的分享
对于RemoteIp的配置则是使用默认的,Spring Boot的默认值是
10\.\d{1,3}\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|169\.254\.\d{1,3}\.\d{1,3}|127\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.1[6-9]{1}\.\d{1,3}\.\d{1,3}|172\.2[0-9]{1}\.\d{1,3}\.\d{1,3}|172\.3[0-1]{1}\.\d{1,3}\.\d{1,3}|0:0:0:0:0:0:0:1|::1
进入下一步之前,务必确保filebeat能够正常连接到AWS CloudWatch Log并可以正常拉取日志
测试方法
- 可以先将filebeat的输出改为输出到控制台,启动filebeat,并确定无报错
- 在EC2中向监控的日志添加内容。例如加一条本地Tomcat的access_log
- 查看AWS CloudWatch控制台的log group下的log stream是否有刚才新添加的内容。正常情况下,一般2秒左右就可以在观察到有新的日志产生
- 查看filebeat日志,是否打印出刚刚在EC2实例中添加的日志,例如使用如下命令
docker logs --tail 10 -f filebeat
配置Logstash
下面根据上面的access_log的格式给出一个示例配置文件
input {
beats {
port => 5044
client_inactivity_timeout => 3600
codec => "json"
tags => [aws_access_log]
}
}
filter {
if "aws_access_log" in [tags] {
# handle internal ip
if [clientIp] =~ "^10.0.*|^192.168.*|^172.16.*|^127.0.*|0.0.0.0|^0:0:0:0*" {
mutate {
add_tag => ["private internets"]
}
}
# convert ip to IP type from String Type
if [clientIp] and "private internets" not in [tags] {
geoip {
source => "clientIp"
target => "[client][geo]"
}
}
useragent {
source => 'user_agent_original'
}
date {
match => ["access_timestamp", "yyyy-MM-dd'T'HH:mm:ss.SSSZ"]
}
mutate {
add_field => {
"[@metadata][server_name]" => "%{[awscloudwatch][log_stream]}"
}
rename => {
"clientIp" => "[client][ip]"
"status_code" => "[http][response][status_code]"
"http_request_method" => "[http][request][method]"
"http_request_referrer" => "[http][request][referrer]"
"http_request_body_bytes" => "[http][request][body][bytes]"
"http_version" => "[http][version]"
"url_path" => "[url][path]"
"log.file.path" => "[log][file][path]"
"user_agent_original" => "[user_agent][original]"
"ecs.version" => "[ecs][version]"
}
remove_field => ["[input]","[host]","access_timestamp"]
}
if [client][geo]{
mutate {
remove_field => ["[client][geo][ip]"]
}
}
}
}
output {
if "aws_access_log" in [tags] {
elasticsearch {
hosts => "elasticsearch:9200"
user => "logstash_internal"
password => "${LOGSTASH_INTERNAL_PASSWORD}"
index => "ecs-logstash-aws-access-log-%{[@metadata][server_name]}-%{+YYYY.MM.dd}"
}
}
}
配置文件说明
- 建议所有input都加入专属的tags,方便在filter中同其他input的处理隔离,因为实际情况中,很可能Logstash有多个input,每个input的处理逻辑都不相同。所以最好是用tags区分开
- 这里直接使用Elasticsearch自带的ecs-logstash的index template
- 在access_log中,将私有IP地址打上标记,并不去解析IP,因为这些IP解析不出来,还会报错
- 如果是正常的IP则使用logstash的geoip插件解析
- 对于access_log中的user agent使用logstash的useragent插件解析
- 使用logstash的date插件将access_log中的时间戳由字符串转为@timestamp字段
- 使用logstash的mutate插件将字段重命名为符合ECS规范的字段
- 为了便于查看具体的index,index命名时加入EC2的服务器名字以区分各个服务器的access_log,通过使用logstash的@metadata来达到此目的
完整流程测试
- 重启logstash,并确保无错误
- 将filebeat的输出配置改为logstash,并重启filebeat并确保无错误
- 在EC2中向监控的日志文件添加一些日志
- 在Kibana中查看是否有新的ecs-logstash-*的index产生
故障排除思路
如果进行每一步时都按照博客中说的步骤进行验证正确了之后才进行的下一步。那么最终完整流程应该非常顺利的串起来。
如果最终日志没发送到ES中,提供几个排查的思路
- 在Logstash中的output中加入stdout打印一下,看看是否真的收到了filebeat推送过了的日志
- 如果上一步没问题,则观察ES的日志,看看是否有错误产生.例如
docker logs --tail 5 -f elasticsearch
备注
- 上述很多配置都需要仔细阅读对应的官方文档,这篇博文更多的是整体的思路分享而不是手把手教学
- AWS所有的文档我这里没有给出链接,因为都可以登录到具体服务的控制台,在控制台页面那里仔细找找,就可以找到对应文档的入口点
- 上述配置文件如果真的看懂了,在实际解决问题中,应该经过简单修改或者照葫芦画瓢就可以基本满足你的实际业务需要