一、实训目的
- 掌握Wordpress的部署方法;
- 掌握Apache访问日志格式的配置方法;
- 掌握基于Filebeat实现Apache访问日志的采集;
- 掌握基于Logstash实现Apache访问日志的清洗;
- 掌握基于Kibana实现Apache访问日志的可视化分析。
二、实训学时
4 学时
三、实训类型
综合性
四、实训需求
1、硬件
每人配备计算机 1 台。
2、软件
安装Edge、Firefox、Chrome等最新版本浏览器,安装Mobaxterm软件。
3、网络
本地主机能够访问教学云计算平台,虚拟机按照配置指南配置网络。
4、工具
无。
五、实训任务
1、完成Wordpress网站的部署;
2、完成Apache访问日志格式配置;
3、完成Apache访问日志的采集清洗与分析;
4、完成Apache访问日志的可视化分析。
六、实训环境
本实训使用《实训指南02:实现Linux Syslog日志分析》中创建的虚拟机Labs-ELK-VM-102。
七、实训内容及步骤
1、部署Wordpress
1.1 配置安全策略
(1)在主机Labs-ELK-VM-102上配置firewalld防火墙规则,放行http服务,允许任意地址访问主机的httpd服务,参考命令如下。
# 配置firewalld防火墙,放行http服务,允许任意地址访问主机的httpd服务
[root@Labs-ELK-VM-102 ~]# firewall-cmd --zone=public --add-service=http --permanent
[root@Labs-ELK-VM-102 ~]# firewall-cmd --reload
[root@Labs-ELK-VM-102 ~]# firewall-cmd --list-all
1.2 安装Apache HTTP Server
(1)在主机Labs-ELK-VM-102上安装Apache HTTP Server,参考命令如下。
# 安装Apache HTTP Server
[root@Labs-ELK-VM-102 ~]# yum install -y httpd
(2)启动Apache服务,设置服务开机自启,查看服务状态,参考命令如下。
# 启动Apache服务,设置开机自启
[root@Labs-ELK-VM-102 ~]# systemctl start httpd
[root@Labs-ELK-VM-102 ~]# systemctl enable httpd
# 查看Apache服务状态
[root@Labs-ELK-VM-102 ~]# systemctl status httpd
(3)使用浏览器访问http://10.10.2.102,出现欢迎界面,说明Apache Httpd Server安装成功,如图2-1所示。
1.3 安装PHP
(1)在主机Labs-ELK-VM-102上下载安装PHP及相应的模块,并查看php版本,参考命令如下。
# 下载安装PHP相应的模块
[root@Labs-ELK-VM-102 ~]# yum install -y php php-curl php-dom php-exif php-fileinfo php-fpm php-gd php-hash php-mbstring php-mysqli php-openssl php-pcre php-xml libsodium
# 查看PHP版本
[root@Labs-ELK-VM-102 ~]# php -v
(2)启动php-fpm服务,设置服务开机自启,查看服务状态,参考命令如下。
# 启动php-fpm服务,设置服务开机自启动
[root@Labs-ELK-VM-102 ~]# systemctl start php-fpm
[root@Labs-ELK-VM-102 ~]# systemctl enable php-fpm
# 查看php-fpm服务状态
[root@Labs-ELK-VM-102 ~]# systemctl status php-fpm
(3)修改php-fpm服务配置文件/etc/php-fpm.d/www.conf,将php-fpm的监听方式从Unix Socket改为TCP端口,参考命令如下。
[root@Labs-ELK-VM-102 ~]# vi /etc/php-fpm.d/www.conf
---------------/etc/php-fpm.d/www.conf---------------
...省略部分内容...
; listen = /run/php-fpm/www.sock
listen = 127.0.0.1:9000
...省略部分内容...
---------------/etc/php-fpm.d/www.conf---------------
(4)修改apache配置文件/etc/httpd/conf.d/php.conf,配置php请求转到php-fpm处理,重启php-fpm和apache服务,参考命令如下。
[root@Labs-ELK-VM-102 ~]# vi /etc/httpd/conf.d/php.conf
---------------/etc/httpd/conf.d/php.conf---------------
...省略部分内容...
DirectoryIndex index.php
# 配置文件中添加如下内容配置php请求转到php-fpm处理:
<FilesMatch .php$>
SetHandler "proxy:fcgi://127.0.0.1:9000"
</FilesMatch>
...省略部分内容...
---------------/etc/httpd/conf.d/php.conf---------------
[root@Labs-ELK-VM-102 ~]# systemctl restart php-fpm httpd
(5)在Apache网站根目录/var/www/html创建测试文件phpinfo.php验证php环境,重启Apache服务,参考命令如下。
# 在Apache网站的根目录创建测试文件
[root@Labs-ELK-VM-102 ~]# echo "<?php phpinfo(); ?>" > /var/www/html/phpinfo.php
# 重启Apache服务
[root@Labs-ELK-VM-102 ~]# systemctl restart httpd
(6)使用浏览器访问http://10.10.2.102/phpinfo.php查看php信息,如图2-2所示。
(7)php环境完成,删除验证文件/var/www/html/``phpinfo.php,参考命令如下。
# 删除验证文件/var/www/html/phpinfo.php
[root@Labs-ELK-VM-102 ~]# rm -rf /var/www/html/phpinfo.php
1.4 部署WordPress
(1)在主机Labs-ELK-VM-102上安装wget、tar软件包,使用wget获取WordPress应用程序,参考命令如下。
# 安装下载工具
[root@Labs-ELK-VM-102 ]# yum install -y wget tar
# 下载WordPress安装包
[root@Labs-ELK-VM-102 ]# wget https://cn.wordpress.org/latest-zh_CN.tar.gz
(2) 解压WordPress应用程序到Apache网站根目录/var/www/html,参考命令如下。
# 解压WordPress安装包
[root@Labs-ELK-VM-102 ]# tar zxvf latest-zh_CN.tar.gz -C /var/www/html
(3)进入目录/var/www/html/wordpress,复制WordPress数据库配置文件,参考命令如下。
# 进入目录/var/www/html/wordpress
[root@Labs-ELK-VM-102 ~]# cd /var/www/html/wordpress
# 复制WordPress数据库配置文件
[root@Labs-ELK-VM-102 wordpress]# cp -a wp-config-sample.php wp-config.php
(4)赋予apache用户对相关目录的操作权限,参考命令如下。
# 赋予Apache对相关目录的操作权限
[root@Labs-ELK-VM-102 wordpress]# chown -R apache:apache /var/www/html
[root@Labs-ELK-VM-102 wordpress]# chmod -R 755 /var/www/html
[root@Labs-ELK-VM-102 wordpress]# chown -R :apache /var/www/html/wordpress
(5)使用root用户本地连接MySQL数据库,创建WordPress数据库,创建用于管理WordPress数据库的管理用户,参考命令如下。
# 连接数据库
[root@Labs-ELK-VM-102 ~]# mysql -uroot -p
# 创建WordPress数据库
mysql> CREATE DATABASE wordpress;
# 创建数据库用户wordpressuser,并赋予用户权限、设置用户密码
mysql> CREATE USER 'wordpressuser'@'%' IDENTIFIED BY 'Wordpress@123';
# 设置权限,允许wordpressuser用户远程访问wordpress数据库
mysql> GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpressuser'@'%';
# 刷新权限使更改生效,退出
mysql> flush privileges;
mysql> exit;
📌 WordPress程序使用的数据库为《实训指南03:实现MySQL数据库日志分析》中创建的MySQL数据库。
(6)编辑WordPress数据库配置文件/var/www/html/wordpress/wp-config.php,修改数据库连接配置,参考命令如下。
# 编辑wp-config.php文件。根据已配置的WordPress数据库信息,修改MySQL相关配置信息
[root@Labs-ELK-VM-102 ]# vi /var/www/html/wordpress/wp-config.php
--------------------wp-config.php--------------------
# 修改如下配置项
# /** WordPress数据库的名称 */
define( 'DB_NAME', 'wordpress' );
# /** MySQL数据库用户名 */
define( 'DB_USER', 'wordpressuser' );
# /** MySQL数据库密码 */
define( 'DB_PASSWORD', 'Wordpress@123' );
# /** MySQL主机 */
define( 'DB_HOST', 'localhost' );
--------------------wp-config.php--------------------
(7)修改完成重启Apache服务,参考命令如下。
[root@Labs-ELK-VM-102 ~]# systemctl restart httpd
1.6 初始化配置WordPress
(1)使用浏览器访问http://10.10.2.102/wordpress,根据安装向导填写安装信息,点击【安装WordPress】按钮,完成安装并创建站点,如图2-3所示。
(2)等待初始化完成,单击【登录】,输入WordPress初始化用户名和密码完成登录,如图2-4所示。
2、配置Apache访问日志格式
(1)在主机Labs-ELK-VM-102上修改Apache的配置文件/etc/httpd/conf/httpd.conf,自定义Apache日志记录格式,参考命令如下。
# 自定义需要采集的日志格式
# Apache日志可通过/etc/httpd/conf/httpd.conf文件进行设置
[root@Labs-ELK-VM-102 ~]# vi /etc/httpd/conf/httpd.conf
#访问日志的配置信息,通过日志格式字符串可定义访问日志记录的字段
---------------------httpd.conf--------------------
.........省略部分内容......
<IfModule log_config_module>
#
# The following directives define some format nicknames for use with
# a CustomLog directive (see below).
#
LogFormat "%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"" combined
LogFormat "%h %l %u %t "%r" %>s %b" common
# 添加:定义名为“filebeat”的日志记录格式
LogFormat "%a | %h | %{remote}p | %u | \"%{Referer}i\" | \"%{User-Agent}i\" | %C | %t | %H | %m | %D | %U | %q | %I | %k | %X | %v | %A | %{local}p | %P | %f | %>s | %O | %b | %{c}a" filebeat
<IfModule logio_module>
# You need to enable mod_logio.c to use %I and %O
LogFormat "%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i" %I %O" combinedio
</IfModule>
#
# The location and format of the access logfile (Common Logfile Format).
# If you do not define any access logfiles within a <VirtualHost>
# container, they will be logged here. Contrariwise, if you *do*
# define per-<VirtualHost> access logfiles, transactions will be
# logged therein and *not* in this file.
#
#CustomLog "logs/access_log" common
#访问日志存放在目录/var/log/httpd/logs/access_log下,日志记录格式为filebeat定义的格式
CustomLog /var/log/httpd/logs/access_log filebeat
#
# If you prefer a logfile with access, agent, and referer information
# (Combined Logfile Format) you can use the following directive.
#
.........省略部分内容......
---------------------------------------------------
#创建新格式日志输出保存的目录,并重载Apache服务
[root@Labs-ELK-VM-102 ~]# mkdir -p /var/log/httpd/logs/
[root@Labs-ELK-VM-102 ~]# systemctl reload httpd
#验证查看配置后的日志
[root@Labs-ELK-VM-102 ~]# cat /var/log/httpd/logs/access_log
10.10.0.1 | 10.10.0.1 | 64580 | - | "http://10.10.2.102/wordpress/" | "Mozilla/5.0 (Windows NT 1 0.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0 .0.0" | - | [01/Aug/2025:18:56:07 +0800] | HTTP/1.1 | GET | 228 | /wordpress/wp-content/themes/t wentytwentyfive/assets/fonts/manrope/Manrope-VariableFont_wght.woff2 | | 444 | 1 | + | localhos t | 10.10.2.102 | 80 | 204225 | /var/www/html/wordpress/wp-content/themes/twentytwentyfive/asset s/fonts/manrope/Manrope-VariableFont_wght.woff2 | 200 | 53886 | 53600 | 10.10.0.1
📌小贴士:
Apache日志记录格式-LogFormat格式串的变量及其含义:
- %a:请求的客户端 IP 地址
- %h:来访主机名
- %{remote}p:来访端口
- %u:来访用户
- \”%{Referer}i\”:来源地址
- \”%{User-Agent}i\”:UA
- %C:Cookie
- %t:请求时间
- %H:请求协议
- %m:请求方法
- %D:请求耗时
- %U:请求的 URL 路径
- %q:请求参数
- %I:接收字节数
- %k:KeepAlive
- %X:响应完成时的连接状态
- %v:服务主机名
- %A:本地 IP 地址、
- %{local}p:服务端口
- %P:进程ID
- %f:请求文件地址
- %>s:响应状态
- %O:发送的字节数
- %b:响应大小(以字节为单位)
- %{c}a:连接的基础对等IP地址
3、安装Filebeat日志采集器
在主机Labs-ELK-VM-102上,解压Filebeat日志采集器安装包到/opt目录下,并重命名解压目录,参考命令如下。
#解压安装包到/opt目录下
[root@Labs-ELK-VM-102 ~]# tar -zxvf filebeat-9.0.4-linux-x86_64.tar.gz -C /opt
# 进入/opt目录下,重命名解压目录
[root@Labs-ELK-VM-102 ~]# cd /opt/
[root@Labs-ELK-VM-102 opt]# mv filebeat-9.0.4-linux-x86_64 filebeat-apache
4、配置Filebeat收集Apache访问日志
(1)在主机Labs-ELK-VM-102上创建filebeat配置文件/opt/filebeat-apache/filebeat-apache.yml,配置filebeat输入内容,参考命令如下。
# 创建filebeat配置文件/opt/filebeat-apache/filebeat-apache.yml
[root@Labs-ELK-VM-102 opt]# vi /opt/filebeat-apache/filebeat-apache.yml
-------------------/opt/filebeat-apache/filebeat-apache.yml-------------------
#配置Filebeat输入
filebeat.inputs:
#声明输入类型,指定要收集的日志类型为文件日志
- type: filestream
id: labs-elk-apache-id
#指示Filebeat启用该输入,即开始收集指定的日志
enabled: true
#定义要收集的日志文件路径的列表
paths:
- /var/log/httpd/logs/access_log
#定义日志字段
fields:
type: labs-elk-apache
#定义fields下的字段添加到根级别,这样所有的字段将作为日志事件的顶级字段
fields_under_root: true
--------------------/opt/filebeat-apache/filebeat-apache.yml--------------------
(2)修改filebeat配置文件/opt/filebeat-apache/filebeat-apache.yml,配置filebeat输出,参考命令如下。
#启用 Logstash 输出
-------------------/opt/filebeat-apache/filebeat-apache.yml-------------------
output.logstash:
#指定Logstash服务器和Logstash配置为侦听传入的端口 () 击败连接
#hosts5044
hosts: ["10.10.2.101:5046"]
-------------------/opt/filebeat-apache/filebeat-apache.yml-------------------
(3)在主机Labs-ELK-VM-102上创建自定义系统服务配置文件/lib/systemd/system/filebeat-apache.service将filebaet注册为系统服务filebeat-apache,参考命令如下。
# 将filebaet注册为系统服务
[root@Labs-ELK-VM-102 opt]# cat > /lib/systemd/system/filebeat-apache.service <<EOF
[Unit]
Description=filebeat
Wants=network-online.target
After=network-online.target
[Service]
User=root
ExecStart=/opt/filebeat-apache/filebeat -e -c /opt/filebeat-apache/filebeat-apache.yml
# 设置为掉线自动重启,进程强制杀掉后会自动重新启动
Restart=always
[Install]
WantedBy=multi-user.target
EOF
(4)启动filebeat-apache服务(通过filebeat收集Apache访问日志并推送至logstash),设置服务为开机自启动,并查看服务状态,参考命令如下。
# 启动filebeat-apache服务,设置服务为开机自启动
[root@Labs-ELK-VM-102 opt]# systemctl enable filebeat-apache --now
# 查看服务状态
[root@Labs-ELK-VM-102 opt]# systemctl status filebeat-apache
5、使用Logstash进行Apache日志清洗
(1)在主机Labs-ELK-VM-101上修改容器labs-elk-logstash03的配置文件/data/docker/volumes/labs-elk-logstash03-pipeline/_data/logstash.conf配置logstash输入,使用beats输入插件接收数据,参考命令如下。
#使用beats输入将日志导入Logstash,处理Beats送的日志事件
#修改Logstash配置文件/data/docker/volumes/labs-elk-logstash03-pipeline/_data/logstash.conf
[root@Labs-ELK-VM-101 ~]# vi /data/docker/volumes/labs-elk-logstash03-pipeline/_data/logstash.conf
----------------------logstash--------------------
# Sample Logstash configuration for creating a simple
# 创建一个从Beats到Logstash到Elasticsearch的数据管道
# Beats -> Logstash -> Elasticsearch pipeline.
input {
# 定义输入插件Beats,允许Logstash接收来自Beats(Filebeat、Winlogbeat)的数据
beats {
# 指定Logstash监听的端口号,以接收来自Beats的数据
port => 5044
}
}
---------------------------------------------------
(2)在主机Labs-ELK-VM-101上修改容器labs-elk-logstash03的配置文件,配置过滤器清洗日志,参考命令如下。
# 在“输入”下面添加以下过滤内容
----------------------logstash--------------------
filter {
# 指定字段类型,如果字段类型是'labs-elk-apache',则执行以下操作
if [type] == "labs-elk-apache" {
# 使用mutate插件,对消息字段进行修改和转换
mutate {
# 使用split插件,将消息字段(通常是日志行)按照指定的分隔符 " | " 进行拆分
# 将每个日志行被分割成多个部分,每个部分都成为一个单独的字段
split => { "message" => " | " }
# 使用mutate插件的add_filed功能
# 为每个新创建的字段添加一个字段名,并从拆分后的消息数组中选择相应的索引位置作为字段值
add_field => { "c_ip" => "%{[message][0]}" }
add_field => { "c_host" => "%{[message][1]}" }
add_field => { "c_port" => "%{[message][2]}" }
add_field => { "c_user" => "%{[message][3]}" }
add_field => { "cs_referer" => "%{[message][4]}" }
add_field => { "cs_user_agent" => "%{[message][5]}" }
add_field => { "cs_cookie" => "%{[message][6]}" }
add_field => { "cs_date_time_temp" => "%{[message][7]}" }
add_field => { "cs_protocol" => "%{[message][8]}" }
add_field => { "cs_method" => "%{[message][9]}" }
add_field => { "cs_time_taken" => "%{[message][10]}" }
add_field => { "cs_uri_stem" => "%{[message][11]}" }
add_field => { "cs_uri_query" => "%{[message][12]}" }
add_field => { "cs_bytes" => "%{[message][13]}" }
add_field => { "cs_keep_alive" => "%{[message][14]}" }
add_field => { "cs_conn_status" => "%{[message][15]}" }
add_field => { "s_servername" => "%{[message][16]}" }
add_field => { "s_ip" => "%{[message][17]}" }
add_field => { "s_port" => "%{[message][18]}" }
add_field => { "s_pid" => "%{[message][19]}" }
add_field => { "s_file_path" => "%{[message][20]}" }
add_field => { "sc_status" => "%{[message][21]}" }
add_field => { "sc_bytes" => "%{[message][22]}" }
add_field => { "sc_body_bytes" => "%{[message][23]}" }
add_field => { "r_ip" => "%{[message][24]}" }
}
# 使用date插件,用于从字符串中提取日期和时间,并将其转换为 Logstash 事件的时间戳
date {
# 指定日志中的时间信息所采用的时区
timezone => "GMT"
# 使用match指定原始数据中日期时间的格式:日/月/年:小时:分钟:秒 时区
match => ["cs_date_time_temp", "[dd/MMM/yyyy:HH:mm:ss Z]"]
# 使用target将解析后的日期时间存储在名为"cs_date_time"的字段中
target => "cs_date_time"
}
# 使用ruby插件提取时间戳,然后将其转换为特定格式的日期时间,并将其各个组成部分存储到新的时间字段中
ruby {
code => "
# 加载ruby的时间处理库
require 'time'
# 获取'cs_date_time'字段的值,并将其转换为字符串形式,存储在变量 timestamp 中
timestamp = event.get('cs_date_time').to_s
# 将时间戳转换为Time对象,进行时区调整(原始时间戳是 GMT 时间,因此需要添加 8 小时(86060 秒)来转换为中国标准时间(CST))
time = Time.parse(timestamp)+8*60*60
# 通过Time对象的方法获取年、月、日、时、分、秒、星期几和年内第几天的信息,并分别存储在变量中
year = time.year
month = time.month
day = time.day
hour = time.hour
minute = time.min
second = time.sec
weekday = time.wday
yearday = time.yday
# 通过event.set方法将获取到的各个时间组成部分存储到date_time_arr的新字段中
event.set('[date_time_arr][year]', year)
event.set('[date_time_arr][month]', month)
event.set('[date_time_arr][day]', day)
event.set('[date_time_arr][hour]', hour)
event.set('[date_time_arr][min]', minute)
event.set('[date_time_arr][sec]', second)
event.set('[date_time_arr][wday]', weekday)
event.set('[date_time_arr][yday]', yearday)
"
}
# 使用geoip插件,用于从 IP 地址中解析地理位置信息,并将解析结果存储到指定的字段中
geoip {
# 指定提取IP地址的字段名称为"c_ip"
source => "c_ip"
# 使用GeoIP数据库解析IP地址的地理位置信息
database => "/usr/share/logstash/vendor/bundle/jruby/3.1.0/gems/logstash-filter-geoip-7.3.1-java/vendor/GeoLite2-City.mmdb"
# 指定要从GeoIP解析结果中提取的字段列表,并将其存储到事件中
fields => ["ip","city_name","country_name","continent_code","continent_name","country_code2","country_code3","postal_code","region_name","region_code","region_iso_code","timezone","location","latitude","longitude","domain","isp","dma_code","organization"]
# 指定存储解析结果的目标字段名称为'c_ip_geo',并且解析结果将以一个嵌套的哈希结构存储在该字段中
target => "c_ip_geo"
}
# 使用mutate插件中的rename功能,将存储到'c_ip_geo'中的地理位置字段重命名为定义的字段
mutate {
rename => { "[c_ip_geo][geo][city_name]" => "[c_ip_geo][city_name]" }
rename => { "[c_ip_geo][geo][continent_code]" => "[c_ip_geo][continent_code]" }
rename => { "[c_ip_geo][geo][continent_name]" => "[c_ip_geo][continent_name]" }
rename => { "[c_ip_geo][geo][country_iso_code]" => "[c_ip_geo][country_iso_code]" }
rename => { "[c_ip_geo][geo][country_name]" => "[c_ip_geo][country_name]" }
rename => { "[c_ip_geo][geo][location][lat]" => "[c_ip_geo][location][lat]" }
rename => { "[c_ip_geo][geo][location][lon]" => "[c_ip_geo][location][lon]" }
rename => { "[c_ip_geo][geo][postal_code]" => "[c_ip_geo][postal_code]" }
rename => { "[c_ip_geo][geo][region_iso_code]" => "[c_ip_geo][region_iso_code]" }
rename => { "[c_ip_geo][geo][region_code]" => "[c_ip_geo][region_code]" }
rename => { "[c_ip_geo][geo][region_name]" => "[c_ip_geo][region_name]" }
rename => { "[c_ip_geo][geo][timezone]" => "[c_ip_geo][timezone]" }
rename => { "[c_ip_geo][mmdb][dma_code]" => "[c_ip_geo][dma_code]" }
}
# 同上述过滤方法一致
geoip {
source => "r_ip"
database => "/usr/share/logstash/vendor/bundle/jruby/3.1.0/gems/logstash-filter-geoip-7.3.1-java/vendor/GeoLite2-City.mmdb"
fields => ["ip","city_name","country_name","continent_code","continent_name","country_code2","country_code3","postal_code","region_name","region_code","region_iso_code","timezone","location","latitude","longitude","domain","isp","dma_code","organization"]
target => "r_ip_geo"
}
mutate {
rename => { "[r_ip_geo][geo][city_name]" => "[r_ip_geo][city_name]" }
rename => { "[r_ip_geo][geo][continent_code]" => "[r_ip_geo][continent_code]" }
rename => { "[r_ip_geo][geo][continent_name]" => "[r_ip_geo][continent_name]" }
rename => { "[r_ip_geo][geo][country_iso_code]" => "[r_ip_geo][country_iso_code]" }
rename => { "[r_ip_geo][geo][country_name]" => "[r_ip_geo][country_name]" }
rename => { "[r_ip_geo][geo][location][lat]" => "[r_ip_geo][location][lat]" }
rename => { "[r_ip_geo][geo][location][lon]" => "[r_ip_geo][location][lon]" }
rename => { "[r_ip_geo][geo][postal_code]" => "[r_ip_geo][postal_code]" }
rename => { "[r_ip_geo][geo][region_iso_code]" => "[r_ip_geo][region_iso_code]" }
rename => { "[r_ip_geo][geo][region_code]" => "[r_ip_geo][region_code]" }
rename => { "[r_ip_geo][geo][region_name]" => "[r_ip_geo][region_name]" }
rename => { "[r_ip_geo][geo][timezone]" => "[r_ip_geo][timezone]" }
rename => { "[r_ip_geo][mmdb][dma_code]" => "[r_ip_geo][dma_code]" }
}
# 使用useragent插件,从指定字段中解析用户代理字符串,并将解析后的结果存储在新的字段中
# 用户代理字符串通常包含了关于用户使用的浏览器、操作系统等信息
useragent {
# 指定字段'cs_user_agent'
source => 'cs_user_agent'
# 指定解析后的结果存储在新的字段 ua 中
target => "ua"
}
# 使用mutate插件中的rename功能,将存储到'ua'中的字段用户代理字段重命名为定义的字段
mutate {
# 用户代理信息中的版本信息字段重命名
rename => { "[ua][version]" => "[cs_user_agent_version]" }
# 操作系统全名字段重命名
rename => { "[ua][os][full]" => "[cs_user_agent_osname]" }
# 用户代理名称字段重命名
rename => { "[ua][name]" => "[cs_user_agent_name]" }
# 使用convert将字段'cs_user_agent'的类型转换为字符串类型
convert => {
"cs_user_agent" => "string"
}
}
# 使用ruby插件,执行logstash-apache.rb脚本,用来进行操作系统、浏览器渲染引擎、搜索引擎的统计,并进行来访IP地址类型判断
ruby {
# 将脚本通过docker cp命令复制到指定目录下
path => "/usr/share/logstash/config/logstash-apache.rb"
}
# 使用mutate插件中的rename功能,将存储到'ua'中的字段重命名为定义的字段
mutate {
rename => { "[ua_sb]" => "[cs_user_agent_device]" }
rename => { "[ua_so]" => "[cs_referer_source]" }
rename => { "[ua_se]" => "[cs_referer_search]" }
}
# 使用mutate插件中的convert功能,将字段的类型转换为定义的类型
mutate{
convert => {"c_port" => "integer"}
convert => {"cs_time_taken" => "integer"}
convert => {"cs_bytes" => "integer"}
convert => {"cs_keep_alive" => "integer"}
convert => {"s_port" => "integer"}
convert => {"s_pid" => "integer"}
convert => {"sc_bytes" => "integer"}
convert => {"sc_body_bytes" => "integer"}
remove_field => [ "cs_date_time_temp" ]
}
}
}
------------------------------------------------
(3)创建Ruby脚本文件/data/docker/volumes/labs-elk-logstash03-config/_data/logstash-apache.rb,参考命令如下。
[root@Labs-ELK-VM-101 ~]# vi /data/docker/volumes/labs-elk-logstash03-config/_data/logstash-apache.rb
----------------------logstash-apache.rb--------------------
# the value of `params` is the value of the hash passed to `script_params`
# in the logstash configuration
#用于注册插件并初始化其配置参数
def register(params)
end
# the filter method receives an event and must return a list of events.
# Dropping an event means not including it in the return array,
# while creating new ones only requires you to add a new instance of
# LogStash::Event to the returned array
# 用于处理事件数据
def filter(event)
# 用于根据用户代理字符串来判断设备类型,并将结果存储在事件中的一个新字段 ua_sb 中
# 操作系统统计
# 从事件中获取 cs_user_agent 字段的值,并将其转换为小写字母,以便后续的比较操作不区分大小写
user_agent=event.get('cs_user_agent').downcase
# 使用 index 方法检查用户代理字符串是否包含特定的子字符串
if(user_agent.index('android')||user_agent.index('iphone')||user_agent.index('ipad')||user_agent.index('windows phone'))
event.set('ua_sb', 'mobile')
elsif(user_agent.index('windows nt 5.0')||user_agent.index('windows nt 5.1')||user_agent.index('windows nt 5.2')||user_agent.index('windows nt 6.0')||user_agent.index('windows nt 6.1')||user_agent.index('windows nt 6.2')||user_agent.index('windows nt 6.3')||user_agent.index('windows nt')||user_agent.index('windows nt 10.0')||user_agent.index('mac')||user_agent.index('linux'))
event.set('ua_sb', 'pc')
end
# 使用index方法检查用户代理字符串是否包含特定的子字符串
# 浏览器渲染引擎
if user_agent.index('trident') !=nil
event.set('cs_user_agent_engine', 'Trident')
elsif user_agent.index('gecko')!=nil
event.set('cs_user_agent_engine', 'Gecko')
elsif user_agent.index('presto')!=nil
event.set('cs_user_agent_engine', 'Presto')
elsif user_agent.index('webkit')!=nil
event.set('cs_user_agent_engine', 'Webkit')
end
# 从事件中获取 cs_referer 字段的值,该字段包含了 HTTP 请求的引用来源(即来源网址)
# 将其转换为小写字母,以便后续的比较操作不区分大小写
#搜索引擎统计
http_referer=event.get('cs_referer').downcase
if http_referer.index('baidu.com')!=nil
event.set('ua_se', '百度')
event.set('ua_so', 'search')
end
if http_referer.index('google.com') !=nil
event.set('ua_se', 'Google')
event.set('ua_so', 'search')
end
if user_agent.index('baiduspider') !=nil
event.set('ua_se', '百度')
event.set('ua_so', 'search')
end
if user_agent.index('sogou+web+spider') !=nil
event.set('ua_se', '搜狗')
event.set('ua_so', 'search')
end
if http_referer.index('bing.com') !=nil
event.set('ua_se', 'Bing')
event.set('ua_so', 'search')
end
if http_referer.index('bingbot') !=nil
event.set('ua_se', 'Bing')
event.set('ua_so', 'search')
end
if http_referer.index('sogou.com') !=nil
event.set('ua_se', '搜狗')
event.set('ua_so', 'search')
end
if http_referer.index('yahoo.com') !=nil
event.set('ua_se', 'Yahoo')
event.set('ua_so', 'search')
end
if http_referer.index('youdao.com') !=nil
event.set('ua_se', '有道')
event.set('ua_so', 'search')
end
if http_referer.index('so.com') !=nil
event.set('ua_se', '360搜索')
event.set('ua_so', 'search')
end
if http_referer.index('163.com') !=nil
event.set('ua_se', '网易')
event.set('ua_so', 'search')
end
if http_referer.index('toutiao.com') !=nil
event.set('ua_se', '头条')
event.set('ua_so', 'search')
end
if http_referer.index('quark.sm.cn') !=nil
event.set('ua_se', '夸克')
event.set('ua_so', 'search')
end
if http_referer.index('soso.com') !=nil
event.set('ua_se', '搜搜')
event.set('ua_so', 'search')
end
if http_referer.index('semrush') !=nil
event.set('ua_se', 'SEMRush')
event.set('ua_so', 'search')
end
if http_referer.index('zznu.edu.cn') !=nil
event.set('ua_se', '-')
event.set('ua_so', 'direct')
end
if http_referer=="-"
event.set('ua_se', '-')
event.set('ua_so', 'direct')
end
if http_referer==""
event.set('ua_se', '-')
event.set('ua_so', 'other')
end
#来访IP地址类型判断
remote_addr=event.get("c_ip")
#使用获取到的IP地址创建一个 IPAddr 对象
remote_addr_info=IPAddr.new remote_addr
#将IP地址转换为整数表示
remote_addr_num=remote_addr_info.to_i
#用于判断A类私有IP地址段
#将IP地址段的起始和结束IP地址都转换为整数表示
a_b_ip=IPAddr.new "10.0.0.0"
a_e_ip=IPAddr.new "10.255.255.255"
a_b_num=a_b_ip.to_i
a_e_num=a_e_ip.to_i
#用于判断C类私有IP地址段
c_b_ip=IPAddr.new "192.168.0.0"
c_e_ip=IPAddr.new "192.168.255.255"
c_b_num=c_b_ip.to_i
c_e_num=c_e_ip.to_i
#用于判断D类私有IP地址段
d_b_ip=IPAddr.new "172.16.0.0"
d_e_ip=IPAddr.new "172.31.255.255"
d_b_num=d_b_ip.to_i
d_e_num=d_e_ip.to_i
#条件判断句判断访问事件的IP地址类型,并将结果存储在事件的字段中
if((remote_addr_num >= a_b_num && remote_addr_num < a_e_num)||(remote_addr_num >= c_b_num && remote_addr_num < c_e_num)||(remote_addr_num >= d_b_num && remote_addr_num < d_e_num))
event.set('c_ip_type', 'private')
else
event.set('c_ip_type', 'public')
end
return [event]
end
------------------------------------------------------------
Apache访问日志清洗字段标准如表4-1所示。
表4-1 Apache访问日志字段清洗标准
| 日志配置项 | 日志字段 | 字段清洗结果 | 字段说明 |
|---|---|---|---|
| %a | c_ip | c_ip | 来访地址 |
| c_ip_geo.city_name | 来访地址-城市 | ||
| c_ip_geo.continent_code | 来访地址-州代码 | ||
| c_ip_geo.continent_name | 来访地址-州 | ||
| c_ip_geo.country_iso_code | 来访地址-国家代码(ISO) | ||
| c_ip_geo.country_name | 来访地址-国家名称 | ||
| c_ip_geo.dma_code | 来访地址-DMA Code | ||
| c_ip_geo.location.lat | 来访地址-坐标-维度 | ||
| c_ip_geo.location.lon | 来访地址-坐标-经度 | ||
| c_ip_geo.postal_code | 来访地址-邮编 | ||
| c_ip_geo.region_iso_code | 来访地址-地区代码(ISO) | ||
| c_ip_geo.region_code | 来访地址-地区代码 | ||
| c_ip_geo.region_name | 来访地址-地区名称 | ||
| c_ip_geo.timezone | 来访地址-时区 | ||
| c_ip_type | 来访地址-类型 | ||
| c_ip_private_location | 来访地址-私有-地理位置 | ||
| %h | c_host | c_host | 来访主机名 |
| %{remote}p | c_port | c_port | 来访端口 |
| %u | c_user | c_user | 来访用户 |
| \”%{Referer}i\” | cs_referer | cs_referer | 来源地址 |
| cs_referer_source | 访问来源 | ||
| cs_referer_search | 来源搜索引擎 | ||
| \”%{User-Agent}i\” | cs_user_agent | cs_user_agent | UA |
| cs_user_agent_name | UA-浏览器名称 | ||
| cs_user_agent_version | UA-浏览器版本号 | ||
| cs_user_agent_engine | UA-浏览器渲染引擎 | ||
| cs_user_agent_osname | UA-操作系统-名称 | ||
| cs_user_agent_device | UA-设备类型 | ||
| %C | cs_cookie | cs_cookie | Cookie |
| %t | cs_date_time | cs_date_time | 请求时间 |
| date_time_arr.year | 请求时间-年 | ||
| date_time_arr.month | 请求时间-月 | ||
| date_time_arr.day | 请求时间-日 | ||
| date_time_arr.hour | 请求时间-时 | ||
| date_time_arr.min | 请求时间-分 | ||
| date_time_arr.sec | 请求时间-秒 | ||
| date_time_arr.yday | 请求时间-一年中的第几天 | ||
| date_time_arr.wday | 请求时间-周几 | ||
| date_time_arr.time_zone | 请求时间-时区 | ||
| %H | cs_protocol | cs_protocol | 请求协议 |
| %m | cs_method | cs_method | 请求方法 |
| %D | cs_time_taken | cs_time_taken | 请求耗时 |
| %U | cs_uri_stem | cs_uri_stem | 请求地址(不包括query_string) |
| %q | cs_uri_query | cs_uri_query | 请求参数 |
| cs_uri_query_arr | 请求参数数组 | ||
| %I | cs_bytes | cs_bytes | 接收字节数 |
| %k | cs_keep_alive | cs_keep_alive | KeepAlive |
| %X | cs_conn_status | cs_conn_status | 连接状态 |
| %v | s_servername | s_servername | 服务主机名 |
| %A | s_ip | s_ip | 服务地址 |
| %{local}p | s_port | s_port | 服务端口 |
| %P | s_pid | s_pid | 进程ID |
| %f | s_file_path | s_file_path | 请求文件地址 |
| %>s | sc_status | sc_status | 响应状态 |
| %O | sc_bytes | sc_bytes | 发送字节数 |
| %b | sc_body_bytes | sc_body_bytes | 发送内容字节数(不包括Header) |
| %{c}a | r_ip | r_ip | 代理来访地址 |
| r_ip_geo.city_name | 来访地址-城市 | ||
| r_ip_geo.continent_code | 来访地址-州代码 | ||
| r_ip_geo.continent_name | 来访地址-州 | ||
| r_ip_geo.country_iso_code | 来访地址-国家代码(ISO) | ||
| r_ip_geo.country_name | 来访地址-国家名称 | ||
| r_ip_geo.dma_code | 来访地址-DMA Code | ||
| r_ip_geo.location.lat | 来访地址-坐标-维度 | ||
| r_ip_geo.location.lon | 来访地址-坐标-经度 | ||
| r_ip_geo.postal_code | 来访地址-邮编 | ||
| r_ip_geo.region_iso_code | 来访地址-地区代码(ISO) | ||
| r_ip_geo.region_code | 来访地址-地区代码 | ||
| r_ip_geo.region_name | 来访地址-地区名称 | ||
| r_ip_geo.timezone | 来访地址-时区 | ||
| r_ip_type | 来访地址-类型 | ||
| r_ip_private_location | 来访地址-私有-地理位置 |
(4)在主机Labs-ELK-VM-101上修改容器labs-elk-logstash03的配置文件,配置输出模块为Elasticsearch,参考命令如下。
#在“过滤”下面添加以下过滤内容
#修改Logstash配置文件/data/docker/volumes/labs-elk-logstash03-pipeline/_data/logstash.conf
[root@Labs-ELK-VM-101 ~]# vi /data/docker/volumes/labs-elk-logstash03-pipeline/_data/logstash.conf
----------------------logstash.conf--------------------
# 定义输出插件的配置
output {
# 判断type 字段是否等于'labs-elk-apache'
if [type] == "labs-elk-apache" {
# 将清洗后的日志输出到标准输出(控制台),方便调试和测试
stdout {codec => rubydebug}
# 定义输出插件Elasticsearch
elasticsearch {
# 指定Elasticsearch的主机地址和端口号,Logstash将把日志事件发送到这个地址
hosts => ["https://labs-elk-es-node-1:9200"]
# 指定要索引到Elasticsearch中的索引名称
index => "labs-elk-apache-%{+YYYY.MM.dd}"
# 指定连接 Elasticsearch 集群所使用的用户名
user => "elastic"
# 指定连接 Elasticsearch 集群所使用的密码
password => "Labs#ELK@2025"
# 指定Logstash用于与Elasticsearch进行安全连接时的CA证书的路径
ssl_certificate_authorities => ["/usr/share/logstash/config/certs/ca/ca.crt"]
}
}
}
----------------------logstash.conf--------------------
(5)在主机Labs-ELK-VM-101上,重启容器labs-elk-logstash-3,通过docker logs命令查看日志的推送情况,验证完成后修改logstash配置文件,禁用输出到控制台,并重启容器labs-elk-logstash-3,参考命令如下。
#Logstash配置文件修改完成后,重启Logstash容器,使配置生效
[root@Labs-ELK-VM-101 ~]# docker restart labs-elk-logstash-3
#等待Logstash启动后,访问网站Wordpress生成访问日志,通过控制台实时查看Apache访问日志
[root@Labs-ELK-VM-101 ~]# docker logs labs-elk-logstash-3 --tail=100 -f
#此处省略部分日志内容
{
"cs_user_agent" => ""Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ( KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0"",
"tags" => [
[0] "beats_input_codec_plain_applied",
[1] "_geoip_lookup_failure"
],
"ecs" => {
"version" => "8.0.0"
},
"sc_status" => "200",
"s_file_path" => "proxy:fcgi://127.0.0.1:9000/var/www/html/wordpress/index.php",
"c_ip_type" => "private",
"s_pid" => 204938,
"cs_user_agent_device" => "pc",
"cs_user_agent_osname" => "Windows 10",
"cs_user_agent_name" => "Edge",
"r_ip" => "10.10.0.1",
"s_servername" => "localhost",
"sc_body_bytes" => 50639,
"cs_bytes" => 476,
"cs_uri_query" => "",
"ua" => {
"os" => {
"name" => "Windows",
"version" => "10"
},
"device" => {
"name" => "Other"
}
},
"s_port" => 80,
"cs_time_taken" => 96071,
"message" => [
[ 0] "10.10.0.1",
[ 1] "10.10.0.1",
[ 2] "62463",
[ 3] "-",
[ 4] ""-"",
[ 5] ""Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0"",
[ 6] "-",
[ 7] "[01/Aug/2025:22:38:25 +0800]",
[ 8] "HTTP/1.1",
[ 9] "GET",
[10] "96071",
[11] "/wordpress/index.php",
[12] "",
[13] "476",
[14] "0",
[15] "+",
[16] "localhost",
[17] "10.10.2.102",
[18] "80",
[19] "204938",
[20] "proxy:fcgi://127.0.0.1:9000/var/www/html/wordpress/index.php",
[21] "200",
[22] "51025",
[23] "50639",
[24] "10.10.0.1"
],
"c_port" => 62463,
"cs_cookie" => "-",
"event" => {
"original" => "10.10.0.1 | 10.10.0.1 | 62463 | - | "-" | "Mozilla/5.0 (Windows NT 10. 0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0 .0" | - | [01/Aug/2025:22:38:25 +0800] | HTTP/1.1 | GET | 96071 | /wordpress/index.php | | 476 | 0 | + | localhost | 10.10.2.102 | 80 | 204938 | proxy:fcgi://127.0.0.1:9000/var/www/html/word press/index.php | 200 | 51025 | 50639 | 10.10.0.1"
},
"cs_method" => "GET",
"cs_conn_status" => "+",
"cs_user_agent_version" => "138.0.0.0",
"sc_bytes" => 51025,
"c_ip" => "10.10.0.1",
"cs_user_agent_engine" => "Gecko",
"cs_date_time" => 2025-08-01T14:38:25.000Z,
"r_ip_geo" => {},
"input" => {
"type" => "log"
},
"cs_protocol" => "HTTP/1.1",
"cs_referer" => ""-"",
"host" => {
"name" => "Labs-ELK-VM-102"
},
"agent" => {
"name" => "Labs-ELK-VM-102",
"ephemeral_id" => "f63ec0b9-a25e-49f8-b732-0438220a4971",
"type" => "filebeat",
"id" => "8f30e8ee-8865-42cb-9bf8-22a46e136757",
"version" => "9.0.4"
},
"type" => "labs-elk-apache",
"cs_keep_alive" => 0,
"log" => {
"offset" => 6755,
"file" => {
"path" => "/var/log/httpd/logs/access_log"
}
},
"c_host" => "10.10.0.1",
"c_ip_geo" => {},
"@version" => "1",
"cs_uri_stem" => "/wordpress/index.php",
"s_ip" => "10.10.2.102",
"@timestamp" => 2025-08-01T14:38:33.268Z,
"c_user" => "-",
"date_time_arr" => {
"year" => 2025,
"day" => 1,
"wday" => 5,
"yday" => 213,
"min" => 38,
"sec" => 25,
"hour" => 22,
"month" => 8
}
}
#验证完成后将容器日志的输出关闭
[root@Labs-ELK-VM-101 ~]# vi /data/docker/volumes/labs-elk-logstash03-pipeline/_data/logstash.conf
----------------------logstash--------------------
#此处省略其他配置内容
# stdout {codec => rubydebug}
--------------------------------------------------
# 重启logstash容器
[root@Labs-ELK-VM-101 ~]# docker restart labs-elk-logstash-3
6、使用Kibana检索日志
(1)使用浏览器访问Kibana地址http://10.10.2.101:5601,输入用户和密码(用户名:elastic,密码:Labs#ELK@2025)登录Kibana,如图4-1所示。
(2)进入系统界面中,单击左上角的三条横线,导航到左侧菜单,下滑选择“Management”选项卡中“Stack Management”选项,如图4-2所示。
(3)在“Stack Management”界面,选择“Kibana”选项卡中“Data Views”选项,创建和管理数据视图,如图4-3所示。
(4)单击【Data Views】,在数据视图界面中单击【Create data view】,创建Linux Syslog日志的数据视图。如图4-4所示。
(5)输入视图名称为“labs-elk-apache”,索引模式为“labs-elk-apache-*”,Timestamp字段选择“cs_date_time”,单击【Save data view to Kibana】,保存视图配置,如图4-5、4-6所示。
(6)单击左上角导航栏,选择“Analytics”选项卡中“Discover”选项,进入日志分析视图界面。在左上角选择“labs-elk-apache”的数据视图,单击【Search entire time range】检索查看Apache访问日志,如图4-7所示。
(7)将鼠标移至左侧字段列表,单击左侧可用字段中【+】,可对Apache访问日志中的字段日志进行检索查看,如图4-8所示。
7、Apache服务日志可视化分析
(1)通过Apache访问日志字段清洗标准建立日志分析模型,实现Apache日志可视化分析。Apache日志分析模型如表4-2所示。
| 分类 | 分析模型 | 图表类型 | 分析模型用途 |
|---|---|---|---|
| 数据概览 | 请求量 | 数字 | 展示请求总量 |
| 发送字节量 | 数字 | 展示发送字节总量 | |
| 接收字节量 | 数字 | 展示接收字节总量 | |
| 访客量 | 数字 | 展示来访IP个数 | |
| 请求量趋势 | 折线图 | 展示请求量、来访IP数随时间的变化趋势 | |
| 网站请求次数统计 | 柱状图 | 展示每天的请求量 | |
| 网站请求量排行 | 表格 | 展示请求最多的网站,每个网站的请求次数 | |
| 搜索引擎请求排行 | 表格 | 展示每种搜索引擎请求的次数 | |
| 操作系统请求排行 | 表格 | 展示每种操作系统请求次数 | |
| 网站响应状态排行 | 柱状图 | 展示每种网站响应状态次数 | |
| 请求类型排行 | 柱状图 | 展示每种请求类型次数 | |
| 访问分析 | 一天中每小时的请求量统计 | 柱状图 | 展示每天每个时间段的请求量 |
| 请求量对比统计 | 折线图 | 展示目前与前一天请求量的对比分析 | |
| 请求协议统计 | 饼状图 | 展示每种请求协议的占比 | |
| 请求协议趋势统计 | 折线图 | 展示每种请求协议实时变化(X-时间,Y-次数)(多条线) | |
| 访客分析 | 操作系统统计 | 饼状图 | 展示每种操作系统的占比 |
| 操作系统趋势统计 | 折线图 | 展示每种操作系统请求量的实时变化 | |
| 操作系统请求详情统计 | 表格 | 展示操作系统名称、请求次数、独立IP数,体现出常用的操作系统 | |
| 设备类型分析 | 饼状图 | 展示设备类型请求占比 | |
| 设备类型趋势 | 折线图 | 展示设备类型时间请求趋势 | |
| 设备类型表格展示 | 表格 | 展示设备类型名称、请求次数、独立IP数 | |
| 网站分析 | 异常请求站点排行 | 柱状图 | 展示异常请求的网站排行 |
| 异常请求时间趋势 | 折线图 | 展示异常请求随时间的变化趋势 |
表4-2 Apache日志分析模型
(2)通过Kibana平台的可视化图表功能,绘制上述日志分析模型,如图4-9所示。