我有独特的日志收集技巧

本文同步发布于 糖菓・部落

随着服务器越开越多，各个服务器上运行的容器变得越来越杂，收集日志也变成了一件令喵烦恼的事情。平时习惯使用 docker 以方便管理，并且因为玩不明白 k8s 所以没有构建集群，收集日志就成了一件比较尴尬的事。使用 docker 或 docker-compose 自带的 log 指令来查询的话，不但要连上原服务器，进入服务目录，还要输入一串长长的指令，成功之后还要在一大串输出中寻找需要的部分，一不小心刚刚找到的内容又被新的日志刷下去了，实在有些不够优雅。于是就试着寻找一个能够快速方便地收集运行日志，并进行按需查询和整理的方案，正好前段时间在研究 Grafana 和 Loki ，加上确实找到了官方开发的 docker 日志插件，那就准备再水一篇文章记录一下用到的配置文件和指令，方便有需要的友友们也能快速解决类似的问题。

使用时请根据您的使用情况针对性调整相关配置文件，避免一些样例配置导致的干扰。

准备 Grafana + Loki

准备联合启动文件

Loki 可以理解为实际收集日志的服务器， Grafana 可以理解为从服务器里查询数据的前端，两者相互独立又相互关联，可以在同一局域网环境内部署，也可以分开之后使用公网进行连接。因为这边使用的是纯收集日志的特化场景，所以就直接使用一个 docker-compose 关联启动了。

并且由于这个服务器是直接单给日志收集使用的，无需与其他服务分享 443 端口，所以也直接将 Caddy 的配置写到了一起。

docker-compose.yml 文件如下：

Copy
version: '3.4'
services:

loki:
image: grafana/loki
container_name: grafana-loki
command: -config.file=/etc/loki/local-config.yml
networks:
- internal_network
volumes:
- ./config/loki.yml:/etc/loki/local-config.yml
- ./data/loki:/tmp/loki

grafana:
depends_on:
- grafana-db
- loki
image: grafana/grafana
container_name: grafana
networks:
- internal_network
- external_network
volumes:
- ./config/grafana.ini:/etc/grafana/grafana.ini
restart: unless-stopped

grafana-db:
image: postgres:15-alpine
restart: always
container_name: grafana-db
volumes:
- ./data/db:/var/lib/postgresql/data
environment:
POSTGRES_USER: grafana
POSTGRES_PASSWORD: password
POSTGRES_DB: grafana
POSTGRES_INITDB_ARGS: "--encoding='UTF8' --lc-collate='C' --lc-ctype='C'"
networks:
- internal_network

caddy:
image: caddy
restart: always
ports:
- "443:443"
networks:
- internal_network
- external_network
volumes:
- ./config/Caddyfile:/etc/caddy/Caddyfile
- ./ssl:/ssl:ro
- ./data/caddy/data:/data
- ./data/caddy/config:/config

networks:
internal_network:
internal: true
external_network:

暴露出来的唯一端口是 Caddy 的 443 端口，并在 CDN 处开启强制 HTTPS ，以确保不会有发向 80 端口的请求。

准备各个服务的配置

将配置文件统一归类放置在 config 目录下，以方便管理。

Grafana

grafana 的配置文件 grafana.ini 如下：

Copy
[database]
type = postgres
host = grafana-db:5432
name = grafana
user = grafana
password = password

Loki

loki 的配置文件 loki.yml 如下：

Copy
auth_enabled: false

server:
http_listen_port: 3100
grpc_listen_port: 9095

common:
path_prefix: /tmp/loki
storage:
filesystem:
chunks_directory: /tmp/loki/chunks
rules_directory: /tmp/loki/rules
replication_factor: 1
ring:
instance_addr: 127.0.0.1
kvstore:
store: inmemory

query_range:
results_cache:
cache:
embedded_cache:
enabled: true
max_size_mb: 100

schema_config:
configs:
- from: 2020-10-24
store: boltdb-shipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h

ruler:
alertmanager_url: http://localhost:9093

analytics:
reporting_enabled: false

# 参见 https://grafana.com/docs/loki/latest/configuration/
# 可以开启 S3 存储，暂时没必要就不管了

因为不知道 alertmanager_url 应该设置成什么，并且作为收集日志的服务暂时不考虑告警的需求，暂时就保留了样例值不变。

需要注意的是 auth_enabled 这个配置项，这个字段的意义是通过设置不同的区域请求头来实现多个组织共享同个 loki 服务，当开启后需要在 HTTP 调用中使用 X-Scope-OrgID 来区分发出请求的组织，在提交数据和拉取数据时都需要带上。而此处因为是单方使用，并不涉及分享数据相关的问题，所以关闭了这个选项。

loki 本身不带 HTTP 授权检验，因而推荐将授权请求头写在前端反向代理程序的配置中。例如此处放在 Caddy 配置文件中，仅要求对来自公网流量的请求进行授权检验，当 Grafana 使用内网连接时无需校验。

Caddy

这里使用了 CDN 提供商的源服务器证书，无需让 Caddy 申请，所以配置文件如下：

Copy
example.com {
tls /ssl/example.com/cert.pem /ssl/example.com/key.pem
reverse_proxy grafana:3000
}
loki.example.com {
tls /ssl/example.com/cert.pem /ssl/example.com/key.pem
reverse_proxy loki:3100
basicauth * {
user $2a$14$QZIF1JLM9lFPjAl6PQzBDeANmB4Ssufc0qUnP9nI7QYnv.QevEzyi
}
}
loki-grpc.example.com {
tls /ssl/example.com/cert.pem /ssl/example.com/key.pem
reverse_proxy loki:9095
basicauth * {
user $2a$14$QZIF1JLM9lFPjAl6PQzBDeANmB4Ssufc0qUnP9nI7QYnv.QevEzyi
}
}

其中授权部分使用的是 BasicAuth ，即用户名 + 密码的方式；密码并非明文存储，而是由 caddy 的 hash-password 功能预先计算得出，以尽可能提升安全性。

Copy
/srv # caddy hash-password
Enter password:
Confirm password:
$2a$14$QZIF1JLM9lFPjAl6PQzBDeANmB4Ssufc0qUnP9nI7QYnv.QevEzyi
/srv #

在配置完成后，还需要将对应的 SSL 证书放到指定位置，以避免因没有证书文件导致的报错。

当全部配置完成后，应该就可以使用 docker-compose up -d 命令启动啦。

管理 Loki 数据目录的权限

容器中的 Loki 不是以 root 用户启动的，所以 Docker 设置的 root 权限会导致 Loki 存取被拒绝，您需要手动执行形如以下的命令，来将 Loki 的数据目录权限开放给容器使用：

Copy
chown -R 10001:10001 ./data/loki/

修改完成后重启 Loki 容器即可。

关联 Loki 作为 Grafana 的数据来源

进入部署好的 Grafana ，使用默认账号密码 admin 登录，修改密码之后，就可以开始添加数据源了。

如果根据上述的 docker-compose 配置方案，则只需选择 Loki ，在 HTTP 的 URL 处输入 http://loki:3100 ，即可保存并测试了。此时 Loki 因为没有任何日志，会报出一个查不到 label 的错误提示，不用担心，等到之后出现了日志就可以了。

准备服务器

因为使用的是 docker 环境，所以具体的指令此处就不再赘述了，直接从安装 loki 日志插件开始。

此处参照的是 Docker Driver Client 给出的教程，请注意相关的内容可能会更新。

安装 loki-docker-driver 插件

为能将数据发送给 loki ，需要一个插件来将 Docker 的日志转化为带有详细标签信息的数据，并使用 Loki 兼容的接口发送给上面部署的服务器。使用这条指令可以安装这个插件：

Copy
docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions

安装完成后可以通过 docker plugin ls 查询 docker 安装的插件来进行检查。

如果后续有升级需要，可以参照原链接中给出的指令进行升级：

Copy
docker plugin disable loki --force
docker plugin upgrade loki grafana/loki-docker-driver:latest --grant-all-permissions
docker plugin enable loki
systemctl restart docker

然后就可以对插件进行具体的配置了。因为我需要收集的是宿主机上所有的 docker 日志，并且无需区分不同的组织，所以配置 /etc/docker/daemon.json 文件，写入以下内容：

Copy
{
"log-driver": "loki",
"log-opts": {
"loki-url": "https://user:password@loki.example.com/loki/api/v1/push"
}
}

配置完成后，需要重启 docker 进程，以使更改生效。

需要注意的是，此时并不会对所有的容器都实时生效，已经创建的容器的日志系统已经被固定，只有对新创建的容器才会有效。如果您非常急需日志收集的话，您可以尝试使用 docker-compose up -d --force-recreate 来重建所有的容器。

在 Grafana 上查询

当配置完成，且新创建的容器开始工作并产生日志后，应该就能在 Grafana 中查询到。因为只是单方使用，并不涉及详细可视化的需求，所以没有配置 Dashboard ，而是直接进入 Explore 功能进行交互式查询。

当日志出现后，即可通过 label 对来源进行筛选。这个插件会提供 host compose_project compose_service container_name source filename 六个筛选标签，其中：

host 指的是发出日志的宿主机主机名，

compose_project compose_service 或是 container_name 可用于定位容器，

source 表示日志来源是一般输出 stdout 还是错误日志 stderr ，

filename 指的是日志的原始文件（通常情况下并不重要），

利用这些标签配合 Grafana 的时间控制，就能快速查询某一具体容器的运行日志。再也没有日志分散而繁多的烦恼了！