惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

B
Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Security Blog
Microsoft Security Blog
B
Blog RSS Feed
云风的 BLOG
云风的 BLOG
G
Google Developers Blog
Recent Announcements
Recent Announcements
A
About on SuperTechFans
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Google Online Security Blog
Google Online Security Blog
Google DeepMind News
Google DeepMind News
S
Schneier on Security
S
Secure Thoughts
T
The Exploit Database - CXSecurity.com
Martin Fowler
Martin Fowler
P
Proofpoint News Feed
Security Latest
Security Latest
Jina AI
Jina AI
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Recorded Future
Recorded Future
T
Tor Project blog
有赞技术团队
有赞技术团队
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
News | PayPal Newsroom
博客园 - 三生石上(FineUI控件)
MyScale Blog
MyScale Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Last Week in AI
Last Week in AI
F
Full Disclosure
Hacker News: Ask HN
Hacker News: Ask HN
Forbes - Security
Forbes - Security
D
DataBreaches.Net
人人都是产品经理
人人都是产品经理
NISL@THU
NISL@THU
C
Cisco Blogs
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Google DeepMind News
Google DeepMind News
Project Zero
Project Zero
IT之家
IT之家
T
Threatpost
Cyberwarzone
Cyberwarzone
O
OpenAI News
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
J
Java Code Geeks
P
Proofpoint News Feed
The Last Watchdog
The Last Watchdog
月光博客
月光博客
Latest news
Latest news
MongoDB | Blog
MongoDB | Blog
Apple Machine Learning Research
Apple Machine Learning Research

二丫讲梵

学习周刊-总第258期-2026年第15周 学习周刊-总第257期-2026年第14周 学习周刊-总第256期-2026年第13周 学习周刊-总第255期-2026年第12周 学习周刊-总第254期-2026年第11周 学习周刊-总第253期-2026年第10周 临时插播一条羊毛,免费领取450元大模型API代金券 学习周刊-总第252期-2026年第09周 学习周刊-总第251期-2026年第08周 学习周刊-总第250期-2026年第07周 学习周刊-总第249期-2026年第06周 诚邀评论,聊聊你所知欲知的我 学习周刊-总第248期-2026年第05周 我的QQ动态之2015年 我的QQ动态之2014年 我的QQ动态之2013年 我的QQ动态之2012年 我的QQ动态-2010-2011年 我的QQ动态-创栏小叙 学习周刊-总第247期-2026年第04周 Nexus社区版权益阉割--一文告诉你有哪些版本可以选择 学习周刊-总第246期-2026年第03周 学习周刊-总第245期-2026年第02周 学习周刊-总第244期-2026年第01周 学习周刊-总第243期-2025年第52周 学习周刊-总第242期-2025年第51周 开源项目ZenOps:带你领略禅意运维 学习周刊-总第241期-2025年第50周 用京东金融,享负债人生 学习周刊-总第240期-2025年第49周 学习周刊-总第239期-2025年第48周 整理我在静态服务透明代理上的极致求索之路,最后一个你绝想不到 学习周刊-总第238期-2025年第47周 我们输了,但收获颇多 太多了,太多了 学习周刊-总第237期-2025年第46周 学习周刊-总第236期-2025年第45周 学习周刊-总第235期-2025年第44周 二五年国庆二三事 学习周刊-总第234期-2025年第43周 学习周刊-总第233期-2025年第42周 学习周刊-总第232期-2025年第41周 学习周刊-总第231期-2025年第40周 学习周刊-总第230期-2025年第39周 西湖毅行 2025年开源世界逸闻三则 学习周刊-总第229期-2025年第38周 手抄《与妻书》 CNB开发与构建基于docker-cache缓存复用的配置实践心得 学习周刊-总第228期-2025年第37周 父亲善行录 学习周刊-总第227期-2025年第36周 带你认识我之看看我的高中同学录(其二) 学习周刊-总第226期-2025年第35周 带你认识我之看看我的高中同学录(其一) 认识神级MCP工具系列--用anyquery和数据库交互 学习周刊-总第225期-2025年第34周 学习周刊-总第224期-2025年第33周 学习周刊-总第223期-2025年第32周 学习周刊-总第222期-2025年第31周 学习周刊-总第221期-2025年第30周 CNB云原生开发环境届的瑞士军刀,详解qifei项目 vuepress-vdoing主题配置自建不蒜子统计 学习周刊-总第220期-2025年第29周 写在博客发表文章1000篇的节点 从claude cli的体验聊聊最大的敌人是我们自己的成见 学习周刊-总第219期-2025年第28周 学习周刊-总第218期-2025年第27周 学习周刊-总第217期-2025年第26周 理论正确,事实错误 学习周刊-总第216期-2025年第25周 学习周刊-总第215期-2025年第24周 学习周刊-总第214期-2025年第23周 学习周刊-总第213期-2025年第22周 学习周刊-总第212期-2025年第21周 从赵心童世锦赛夺冠聊聊我的斯诺克情缘 学习周刊-总第211期-2025年第20周 记录二五年五一之短暂回归家庭 学习周刊-总第210期-2025年第19周 学习周刊-总第209期-2025年第18周 学习周刊-总第208期-2025年第17周 Go开发实践之Gin框架将前端的dist目录embed到二进制 学习周刊-总第207期-2025年第16周 近期关于cobra库的一些实践心得总结 学习周刊-总第206期-2025年第15周 我的嗜好--嗑瓜子 记大宝参加幼儿园组织的清明烈士陵园扫墓活动 学习周刊-总第205期-2025年第14周 学习周刊-总第204期-2025年第13周 前端开发小笔记--在数组中给字段添加值与删除字段的操作 学习周刊-总第203期-2025年第12周 人生实苦,何以自渡 学习周刊-总第202期-2025年第11周 近期发现的一些优秀的工具提名(一) 学习周刊-总第201期-2025年第10周 学习周刊-总第200期-2025年第09周 记一次女友喝醉使我忍术破功 爱情风波--一次分手又复合的经历 学习周刊-总第199期-2025年第08周 整理我的信息源
使用docker-compose部署最新版loki+promtail+grafana采集Nginx的日志流程记录
二丫讲梵 · 2023-05-15 · via 二丫讲梵

# 前言

在一些轻量化的场景之中,使用 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;

1
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

1
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

1
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  #日志保留周期

1
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

1
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 化,因此通过正则拿到一些关键信息。

启动查看:

启动之后,就可以在 grafana 中配置链接并查看日志了。

申明

原创文章eryajf,未经授权,严禁转载,侵权必究!此乃文中随机水印,敬请读者谅解。

# 其他内容

# 调试日志

一开始配置了日志采集之后,发现一个问题,在 grafana 看到的日志时间,并不是日志中的时间,后来发现,原因出在 format 没有匹配成功。

一个简单的调试办法是,你可以取一条日志在一个文件内:

$ tail -n1 /data/log/tmp.log > tmp.log

1

下载 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"}

1
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

1

安装完成之后,需要重启 docker:

$ systemctl restart docker

1

重启成功之后,运行新的容器时,增加如下指令,即可实现日志自动 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

1
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"

1
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"
    }
}

1
2
3
4
5
6
7
8
9
10

这样重启 docker 之后,所有 docker 容器的标准输出日志就会 push 给 Loki 了。