docker 日志收集

docker 默认的日志驱动是 json-file,即保存在文件中,路径可以通过下面的命令查看:

1
docker inspect --format='{{.LogPath}}' NAME|ID

例如:

1
2
$ docker inspect --format='{{.LogPath}}' mysql
/var/lib/docker/containers/54571b675a6b52f70aea806977359d8bcd74543cb09e1aa139853113f835d1c4/54571b675a6b52f70aea806977359d8bcd74543cb09e1aa139853113f835d1c4-json.log

json-file 驱动会在启动某个容器时自动创建一个 json 文件,用于存储容器 stdout 与 stderr 输出的内容 ,当在宿主机中运行 docker logs -f 容器名 时,就会读取该文件的内容并显示在终端上。

如果日志文件特别大可以通过下面的命令清空:

1
truncate -s 0 <logfile>

/etc/docker/daemon.json 中可以设置日志文件的大小和数量的全局默认值:

1
2
3
4
5
6
7
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "10"
}
}

也可以在启动容器时指定配置,此时会覆盖全局默认值:

1
2
3
4
5
docker run \
--log-driver json-file \
--log-opt max-size=10m \
--log-opt max-file=10 \
alpine echo hello world

在 docker-compose 文件中的写法是:

1
2
3
4
5
6
7
8
9
10
11
version: '3.2'
services:
nginx:
image: 'nginx:latest'
ports:
- '80:80'
logging:
driver: "json-file"
options:
max-size: "1k"
max-file: "3"

以上方式并不能满足大规模 docker 集群的日志管理要求,需要有一个集中的日志收集平台,logspout 就是这样的一个开源项目,它可以将其所在主机上的其它 docker 容器的日志路由到所配置的任何地方,即能够捕获其它容器中的程序发送到 stdout 和 stderr 的日志,然后转发所有 docker 容器的日志到远程服务器。例如我们通过下面的命令启动了 syslog 中央服务器:

1
2
3
4
5
6
7
8
# 使用默认配置
docker run --name syslog -p 514:514 -d rsyslog/syslog_appliance_alpine
# 自定义配置
docker run --name syslog \
-p 514:514 -d \
-v /devops/syslog/rsyslog.conf:/etc/rsyslog.conf \
-v /devops/syslog/api.conf:/etc/rsyslog.conf.d/api.conf \
rsyslog/syslog_appliance_alpine

进入这个容器:

1
docker exec -it syslog sh

找到日志目录 /logs,下面有个 debug 文件,持续观察该文件的输出:

1
tail -f debug

注意:logspout 是一个无状态的容器化程序,并不是用来管理日志文件或查看日志的,它主要是用于将主机上容器的日志发送到其它地方,例如上面的 syslog 服务器。我们启动 logspout 容器:

1
2
3
4
docker run --name="logspout" \
--volume=/var/run/docker.sock:/var/run/docker.sock \
gliderlabs/logspout \
syslog+tls://syslog:514

logspout 会将日志发送给位于 syslog:514 的 syslog 程序,并且使用了 tls 加密传输选项。为了方便测试,可以用临时搭建一个中央 syslog 服务器:如果要指定多个日志目标终端,只需使用逗号将多个 URI 分隔开。这样就可以使用 logstash 从日志服务器读取 swarm 集群中所有容器的数据,并输出到 elasticsearch 中了,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
input {
syslog{
port => "5555"
type => "swarm"
}
}
output {
if [type] == "swarm" {
elasticsearch {
hosts => ["10.10.39.116:9200","10.10.39.117:9200"]
index => "syslog-%{+YYYY.MM.dd}"
workers => 1
flush_size => 1
idle_flush_time => 1
template_overwrite => true
}
stdout{codec => rubydebug}
}
}

logstash 的配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
version: "3"
services:
api:
image: xxx
command: xxx
ports:
- 10080:9900
logging:
driver: syslog
options:
syslog-address: "tcp://your-syslog-server:5555"
tag: "{{.ImageName}}/{{.Name}}/{{.ID}}"

也可以直接交给 logstash 预处理,不过需要重新构建 logspout 来增加或减少新的模块。默认的 logspout 包含很多内建模块,如果需要将日志路由到 kafka,logstash 等程序,需要通过编辑 modules.go 文件来添加第三方模块。GitHub 上有已经添加好的库 bekt 可以直接用:

1
2
3
4
5
6
logspout:
image: bekt/logspout-logstash
environment:
ROUTE_URIS: 'logstash://logstash:5000'
volumes:
- /var/run/docker.sock:/var/run/docker.sock

ROUTE_URIS=logstash://host:port 是 Logstash 服务的地址,默认情况下采用 UDP 协议,也可以使用 TCP:ROUTE_URIS=logstash+tcp://host:port

logstash 的配置:

1
2
3
4
logstash:
image: docker.elastic.co/logstash/logstash:6.4.0
volumes:
- ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf

logstash.config 文件配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
input {
udp {
port => 5000
codec => json
}
}

filter {
if [docker][image] =~ /logstash/ {
drop { }
}
}

output {
elasticsearch { hosts => ["elasticsearch:9200"] }
stdout { codec => rubydebug }
}
  • input:表示 logstash 监听在 udp 的 5000 端口收集数据。
  • filter:过滤器,表示过滤掉 image 为 logstash 的容器实例上报上来的数据。
  • output:表示如何上报过滤后的数据,这里是通过 9200 端口上报到 elasticsearch 数据库。

默认情况下,logspout 会将它所在主机上所有满足前面条件容器的日志都进行路由,如果需要它忽略某个容器,可以在启动容器时设置环境变量 -e 'LOGSPOUT=ignore',这样此容器的日志便会被logspout忽略。

在 swarm 集群下,需要在每个节点收集该节点下所有容器的日志,需要把 logspout 定义为全局服务(global service):

1
2
3
4
docker service create --name logspout \
--mode global \
--mount "type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock" \
gliderlabs/logspout syslog+tcp://$SYSLOG_HOST:514?filter.name=api*

可以通过下面的命令查看全局服务:

1
docker service ps logspout

或者使用 yaml 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
version: '3'

services:

logspout:
image: gliderlabs/logspout:latest
environment:
- SYSLOG_HOST=$SYSLOG_HOST
volumes:
- /etc/hostname:/etc/host_hostname:ro
- /var/run/docker.sock:/var/run/docker.sock
command:
syslog+tcp://$SYSLOG_HOST:514
ports:
- 8000:80
deploy:
mode: global

然后运行:

1
docker stack deploy --compose-file <name of your compose file> STACK

注意 /etc/host_hostname 用于记录 host name