使用docker-compose部署最新版loki+promtail+grafana采集Nginx的日志流程记录
# 前言
在一些轻量化的场景之中,使用 ELK 方案来解决日志问题就会比较笨重,素闻 Loki 比较香,今天折腾了一番,特此记录一下整个流程。
本文不涉及 Loki 的深度使用与研究,但是参照本文,你可以快速把 Loki 拉起来,并且把 Nginx 日志正确采集进来。
# 部署
Loki 有三个组件,他们的功能简介如下:
- Loki
- 受 Prometheus 启发的可以水平扩展、高可用以及支持多租户的日志聚合系统
- 使用了和 Prometheus 相同的服务发现机制,将标签添加到日志流中而不是构建全文索引
- Promtail:用来将容器日志发送到 Loki 或者 Grafana 服务上的日志收集工具
- Grafana:这个大家都比较熟悉了,在这里主要提供 Loki 数据的检索,绘图等。
# 日志格式化
配置部署之前,先将 Nginx 的访问日志配置为 JSON 化:
log_format json escape=json '{"remote_addr": "$remote_addr",'
'"time": "$time_iso8601",'
'"request_uri": "$request_uri",'
'"verb": "$request_method",'
'"httpversion": "$server_protocol",'
'"response": "$status", '
'"body_bytes_sent": "$body_bytes_sent", '
'"referrer": "$http_referer", '
'"user_agent": "$http_user_agent", '
'"http_x_forwarded_for": "$http_x_forwarded_for", '
'"server_name": "$host",'
'"request_time": "$request_time",'
'"upstream_response_time": "$upstream_response_time",'
'"upstream_addr": "$upstream_addr",'
'"realpath_root": "$realpath_root",'
'"cookie": "$http_cookie",'
'"nginx_version": "$nginx_version",'
'"scheme": "$scheme"}';
access_log /data/log/tmp.log json;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 部署组件
部署使用 docker-compose 一键拉起,配置文件的目录结构如下:
$ tree -N -L 2
.
├── config
│ ├── loki-config.yaml
│ └── promtail-config.yaml
└── docker-compose.yaml
2
3
4
5
6
三个配置文件分别如下。
docker-compose.yaml:
$cat docker-compose.yaml
version: '3.5'
networks:
loki:
services:
loki:
image: docker.mirrors.sjtug.sjtu.edu.cn/grafana/loki:2.8.2
container_name: loki
restart: unless-stopped
ports:
- 3100:3100
volumes:
- ./config:/etc/loki/config
- ./loki/index:/opt/loki/index
- ./loki/chunks:/opt/loki/chunks
user: "0"
command: -config.file=/etc/loki/config/loki-config.yaml
networks:
- loki
promtail:
image: docker.mirrors.sjtug.sjtu.edu.cn/grafana/promtail:2.8.2
container_name: promtail
restart: unless-stopped
volumes:
- ./promtail/logs:/var/logs
- ./config:/etc/promtail/config
- /data/log:/logs
user: "0"
command: -config.file=/etc/promtail/config/promtail-config.yaml
networks:
- loki
grafana:
image: docker.mirrors.sjtug.sjtu.edu.cn/grafana/grafana:9.5.2
container_name: grafana
restart: unless-stopped
ports:
- 3000:3000
volumes:
- ./grafana:/var/lib/grafana
user: "0"
networks:
- loki
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
需要注意配置 user 参数,否则会出现创建目录没有权限的错误。
loki-config.yaml:
$ cat config/loki-config.yaml
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 3110
grpc_server_max_recv_msg_size: 1073741824 #grpc最大接收消息值,默认4m
grpc_server_max_send_msg_size: 1073741824 #grpc最大发送消息值,默认4m
ingester:
lifecycler:
address: 127.0.0.1
ring:
kvstore:
store: inmemory
replication_factor: 1
final_sleep: 0s
chunk_idle_period: 5m
chunk_retain_period: 30s
max_transfer_retries: 0
max_chunk_age: 20m #一个timeseries块在内存中的最大持续时间。如果timeseries运行的时间超过此时间,则当前块将刷新到存储并创建一个新块
schema_config:
configs:
- from: 2021-01-01
store: boltdb
object_store: filesystem
schema: v11
index:
prefix: index_
period: 168h
storage_config:
boltdb:
directory: /opt/loki/index #存储索引地址
filesystem:
directory: /opt/loki/chunks
limits_config:
enforce_metric_name: false
reject_old_samples: true
reject_old_samples_max_age: 168h
ingestion_rate_mb: 30 #修改每用户摄入速率限制,即每秒样本量,默认值为4M
ingestion_burst_size_mb: 20 #修改每用户摄入速率限制,即每秒样本量,默认值为6M
chunk_store_config:
#max_look_back_period: 168h #回看日志行的最大时间,只适用于即时日志
max_look_back_period: 0s
table_manager:
retention_deletes_enabled: false #日志保留周期开关,默认为false
retention_period: 0s #日志保留周期
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
这个配置文件没啥需要调整的,直接拷贝使用即可。
promtail-config.yaml:
$cat config/promtail-config.yaml
server:
http_listen_port: 9081
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: access
static_configs:
- targets:
- localhost
labels:
job: nginx-access-log
__path__: /logs/tmp.log
pipeline_stages:
- match:
selector: '{job="nginx-access-log"}'
stages:
- json: # 日志为json格式 选择需要的字段
expressions:
remote_addr:
request_uri:
verb:
httpversion:
response:
body_bytes_sent:
referrer:
user_agent:
http_x_forwarded_for:
server_name:
request_time:
upstream_response_time:
upstream_addr:
realpath_root:
cookie:
nginx_version:
scheme:
time:
- labels:
remote_addr:
request_uri:
verb:
httpversion:
response:
body_bytes_sent:
referrer:
user_agent:
http_x_forwarded_for:
server_name:
request_time:
upstream_response_time:
upstream_addr:
realpath_root:
cookie:
nginx_version:
scheme:
time:
- timestamp:
format: '2006-01-02T15:04:05+08:00'
source: time
location: Asia/Shanghai
- job_name: nginx-error
static_configs:
- targets:
- localhost
labels:
job: nginx-error-log
__path__: /logs/error.log
pipeline_stages:
- match:
selector: '{job="nginx-error-log"}'
stages:
- regex: # 使用正则选择要提取的字段
expression: '^(?P<timestamp>\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) \[(?P<level>\S+)\] (?P<pid>\d+)#(?P<tid>\d+): \*(?P<cid>\d+) (?P<msg>.*) client: (?P<client_ip>[^,]+), server: (?P<server_name>[^,]+), request: "(?P<request_method>\S+) (?P<request_path>\S+) (?P<request_protocol>\S+)", upstream: "(?P<upstream>[^"]+)", host: "(?P<host>[^"]+)"$'
- labels:
level:
pid:
tid:
cid:
client_ip:
server_name:
request_method:
request_path:
request_protocol:
upstream:
host:
- timestamp:
format: "2006/01/02 15:04:05"
source: time # 正则提取的字段
location: Asia/Shanghai
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
这里主要有几个注意点,需要单独拎出来说明:
clients.url
: 配置指向为容器名称进行连接。__path__: /logs/tmp.log
:此路径与 docker-compose 中挂载到容器内的日志路径对应,请注意。expressions
:这里配置要采集的字段。labels
:作为在 grafana 检索的字段 key。timestamp.format
: 是 go 语言风格的格式化方式,这个尤其需要注意。- 错误日志因为没有 json 化,因此通过正则拿到一些关键信息。
启动查看:
docker-compose up -d
启动之后,就可以在 grafana 中配置链接并查看日志了。
# 其他内容
# 调试日志
一开始配置了日志采集之后,发现一个问题,在 grafana 看到的日志时间,并不是日志中的时间,后来发现,原因出在 format 没有匹配成功。
一个简单的调试办法是,你可以取一条日志在一个文件内:
$ tail -n1 /data/log/tmp.log > tmp.log
下载 promtail 二进制,在这里 (opens new window)(有时候可能最新版本没有 promtail 二进制,可以往前翻一下版本)下载。
然后将配置文件也放置在同一目录,接着执行如下命令进行调试:
$ cat tmp.log | ./promtail --stdin -config.file promtail-config.yaml -log.level=debug -dry-run
Clients configured:
----------------------
url: http://loki:3100/loki/api/v1/push
batchwait: 1s
batchsize: 1048576
follow_redirects: false
enable_http2: false
backoff_config:
min_period: 500ms
max_period: 5m0s
max_retries: 10
timeout: 10s
tenant_id: ""
drop_rate_limited_batches: false
stream_lag_labels: ""
2023-05-13T12:44:59+0800 {__path__="/logs/tmp.log", body_bytes_sent="12", cookie="grafana_session=5d279fee02d9b14e16562ba0444273b3; grafana_session_expiry=1684037372", http_x_forwarded_for="", httpversion="HTTP/1.1", job="nginx-access-log", nginx_version="1.19.9", realpath_root="/usr/local/openresty/nginx/html", referrer="", remote_addr="122.231.226.2", request_time="0.006", request_uri="/api/live/ws", response="400", scheme="https", server_name="grafana.eryajf.net", time="2023-05-13T12:44:59+08:00", upstream_addr="127.0.0.1:3000", upstream_response_time="0.006", user_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36", verb="GET"} {"remote_addr": "122.231.226.2","@timestamp": "2023-05-14T12:44:59+08:00","time": "2023-05-13T12:44:59+08:00","request_uri": "/api/live/ws","verb": "GET","httpversion": "HTTP/1.1","response": "400", "body_bytes_sent": "12", "referrer": "", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36", "http_x_forwarded_for": "", "server_name": "grafana.eryajf.net","request_time": "0.006","upstream_response_time": "0.006","upstream_addr": "127.0.0.1:3000","realpath_root": "/usr/local/openresty/nginx/html","cookie": "grafana_session=5d279fee02d9b14e16562ba0444273b3; grafana_session_expiry=1684037372","nginx_version": "1.19.9","scheme": "https"}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
注意日志中的 time 字段是我故意改成 5 月 13 日,然后解析出来的时间,与这个字段的时间一致,说明匹配成功。
# 采集容器日志
Loki 官方提供了 docker 的插件,可以通过给 docker 安装 loki 插件,实现统一的采集,或者配置注入的方式进行单独采集。
安装插件:
$ docker plugin install docker.mirrors.sjtug.sjtu.edu.cn/grafana/loki-docker-driver:latest --alias loki --grant-all-permissions
安装完成之后,需要重启 docker:
$ systemctl restart docker
重启成功之后,运行新的容器时,增加如下指令,即可实现日志自动 push 到 Loki:
$ docker run -itd --name=test-nginx --log-driver=loki \
--log-opt loki-url="http://10.0.0.17:3100/loki/api/v1/push" \
--log-opt max-size=200m --log-opt max-file=10 \
grafana/grafana
2
3
4
使用 docker-compose 的配置如下:
services:
logger:
image: grafana/grafana
logging:
driver: loki
options:
loki-url: "http://10.0.0.17:3100/loki/api/v1/push"
max-size: "50m"
max-file: "10"
2
3
4
5
6
7
8
9
这样就实现了采集单个容器的日志能力,在页面中,则是通过 container_name
进行区分检索。
如果想要实现全局的容器日志的采集,则可以通过在 docker 主配置文件中添加如下配置:
$ cat /etc/docker/daemon.json
{
"log-driver": "loki",
"log-opts": {
"loki-url": "http://10.0.0.17:3100/loki/api/v1/push",
"max-size": "50m",
"max-file": "10"
}
}
2
3
4
5
6
7
8
9
10
这样重启 docker 之后,所有 docker 容器的标准输出日志就会 push 给 Loki 了。
- 01
- 学习周刊-总第175期-2024年第36周09-05
- 02
- 魔方复原-记录个人理解的教程09-01