メインコンテンツまでスキップ

Dockerのログはローテーション設定しましょう

· 約8分
moritalous

手元のRaspberry Piのディスク使用率が上がっていたので原因を調査したところ、Dockerのログファイルがかなりのディスクスペースを使用していることがわかりました。ログファイルの仕様を把握し、ディスク使用量を抑える方法を調査しました。

Dockerコンテナのログはどこに出力されるの?

ひとつコンテナを立ち上げてみましょう。

docker run -d nginx

Nginxコンテナが起動しました。

dockerコマンド豆知識
  • 最後に作成したコンテナの情報を取得する
docker ps --latest
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS     NAMES
373cef551938 nginx "/docker-entrypoint.…" 6 minutes ago Up 6 minutes 80/tcp jolly_hofstadter
  • 最後に作成したコンテナのコンテナIDのみを取得する
docker ps --latest --quiet
373cef551938

コンテナが出力するログはdocker logsコマンドで確認できます。

docker logs `docker ps --latest --quiet`
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2022/12/25 00:40:50 [notice] 1#1: using the "epoll" event method
2022/12/25 00:40:50 [notice] 1#1: nginx/1.23.3
2022/12/25 00:40:50 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2022/12/25 00:40:50 [notice] 1#1: OS: Linux 5.4.0-1077-raspi
2022/12/25 00:40:50 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2022/12/25 00:40:50 [notice] 1#1: start worker processes
2022/12/25 00:40:50 [notice] 1#1: start worker process 29
2022/12/25 00:40:50 [notice] 1#1: start worker process 30
2022/12/25 00:40:50 [notice] 1#1: start worker process 31
2022/12/25 00:40:50 [notice] 1#1: start worker process 32

このログは当然のことながら、どこかにファイルとして永続化されています。 どこに出力されているかは、docker inspectで確認できます。

かなり巨大なJSONで出力されますので、jqで一部だけ出力します。

docker inspect `docker ps --latest --quiet` | jq .[].LogPath
"/var/lib/docker/containers/373cef551938ce4f73ccce3fa8e8c0cfb77a5ab1a8081c0fa6d5cbde68f4eb8f/373cef551938ce4f73ccce3fa8e8c0cfb77a5ab1a8081c0fa6d5cbde68f4eb8f-json.log"
docker inspect `docker ps --latest --quiet`
[
{
"Id": "373cef551938ce4f73ccce3fa8e8c0cfb77a5ab1a8081c0fa6d5cbde68f4eb8f",
"Created": "2022-12-25T00:40:42.339029246Z",
"Path": "/docker-entrypoint.sh",
"Args": [
"nginx",
"-g",
"daemon off;"
],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 1316447,
"ExitCode": 0,
"Error": "",
"StartedAt": "2022-12-25T00:40:50.75099734Z",
"FinishedAt": "0001-01-01T00:00:00Z"
},
"Image": "sha256:e7a312b21448316ac5bdc7f4b1ee5ab9e06ac7638bac879263b750d0bedb3633",
"ResolvConfPath": "/var/lib/docker/containers/373cef551938ce4f73ccce3fa8e8c0cfb77a5ab1a8081c0fa6d5cbde68f4eb8f/resolv.conf",
"HostnamePath": "/var/lib/docker/containers/373cef551938ce4f73ccce3fa8e8c0cfb77a5ab1a8081c0fa6d5cbde68f4eb8f/hostname",
"HostsPath": "/var/lib/docker/containers/373cef551938ce4f73ccce3fa8e8c0cfb77a5ab1a8081c0fa6d5cbde68f4eb8f/hosts",
"LogPath": "/var/lib/docker/containers/373cef551938ce4f73ccce3fa8e8c0cfb77a5ab1a8081c0fa6d5cbde68f4eb8f/373cef551938ce4f73ccce3fa8e8c0cfb77a5ab1a8081c0fa6d5cbde68f4eb8f-json.log",
"Name": "/jolly_hofstadter",
"RestartCount": 0,
"Driver": "overlay2",
"Platform": "linux",
"MountLabel": "",
"ProcessLabel": "",
"AppArmorProfile": "docker-default",
"ExecIDs": null,
"HostConfig": {
"Binds": null,
"ContainerIDFile": "",
"LogConfig": {
"Type": "json-file",
"Config": {
"max-file": "2",
"max-size": "10k"
}
},
"NetworkMode": "default",
"PortBindings": {},
"RestartPolicy": {
"Name": "no",
"MaximumRetryCount": 0
},
"AutoRemove": false,
"VolumeDriver": "",
"VolumesFrom": null,
"CapAdd": null,
"CapDrop": null,
"CgroupnsMode": "host",
"Dns": [],
"DnsOptions": [],
"DnsSearch": [],
"ExtraHosts": null,
"GroupAdd": null,
"IpcMode": "private",
"Cgroup": "",
"Links": null,
"OomScoreAdj": 0,
"PidMode": "",
"Privileged": false,
"PublishAllPorts": false,
"ReadonlyRootfs": false,
"SecurityOpt": null,
"UTSMode": "",
"UsernsMode": "",
"ShmSize": 67108864,
"Runtime": "runc",
"ConsoleSize": [
0,
0
],
"Isolation": "",
"CpuShares": 0,
"Memory": 0,
"NanoCpus": 0,
"CgroupParent": "",
"BlkioWeight": 0,
"BlkioWeightDevice": [],
"BlkioDeviceReadBps": null,
"BlkioDeviceWriteBps": null,
"BlkioDeviceReadIOps": null,
"BlkioDeviceWriteIOps": null,
"CpuPeriod": 0,
"CpuQuota": 0,
"CpuRealtimePeriod": 0,
"CpuRealtimeRuntime": 0,
"CpusetCpus": "",
"CpusetMems": "",
"Devices": [],
"DeviceCgroupRules": null,
"DeviceRequests": null,
"KernelMemory": 0,
"KernelMemoryTCP": 0,
"MemoryReservation": 0,
"MemorySwap": 0,
"MemorySwappiness": null,
"OomKillDisable": false,
"PidsLimit": null,
"Ulimits": null,
"CpuCount": 0,
"CpuPercent": 0,
"IOMaximumIOps": 0,
"IOMaximumBandwidth": 0,
"MaskedPaths": [
"/proc/asound",
"/proc/acpi",
"/proc/kcore",
"/proc/keys",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/proc/scsi",
"/sys/firmware"
],
"ReadonlyPaths": [
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger"
]
},
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/9d5f42a74f4242e8388f2d57ea31168f81ac32be3849bd182fcb731040d3e667-init/diff:/var/lib/docker/overlay2/88ef58394fbbf18e41d0c1008b0218d27fd3a776d511ef26f937810fb82b0e05/diff:/var/lib/docker/overlay2/0d26e42b3fef83998052fbb8f18b5bcbfe4449c226b179d7218317dd6d1c03c3/diff:/var/lib/docker/overlay2/7ac1499e95e1483a3ffbea22579efaea659a740d4c54b1b5c1ba3e49c7f6e6bf/diff:/var/lib/docker/overlay2/df67d5320e6002a808e70314b73c009348d7acf8a5043d9033840933ae492d75/diff:/var/lib/docker/overlay2/b7234623f4d53efd19f92917413fb660ea0b46dccc3a0f41cffca6e6536ea3e7/diff:/var/lib/docker/overlay2/02d7f602f3b4dc324e13be08e7bc344fc69b991ac91d74b4398b2d4ff317e9d8/diff",
"MergedDir": "/var/lib/docker/overlay2/9d5f42a74f4242e8388f2d57ea31168f81ac32be3849bd182fcb731040d3e667/merged",
"UpperDir": "/var/lib/docker/overlay2/9d5f42a74f4242e8388f2d57ea31168f81ac32be3849bd182fcb731040d3e667/diff",
"WorkDir": "/var/lib/docker/overlay2/9d5f42a74f4242e8388f2d57ea31168f81ac32be3849bd182fcb731040d3e667/work"
},
"Name": "overlay2"
},
"Mounts": [],
"Config": {
"Hostname": "373cef551938",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"80/tcp": {}
},
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"NGINX_VERSION=1.23.3",
"NJS_VERSION=0.7.9",
"PKG_RELEASE=1~bullseye"
],
"Cmd": [
"nginx",
"-g",
"daemon off;"
],
"Image": "nginx",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": [
"/docker-entrypoint.sh"
],
"OnBuild": null,
"Labels": {
"maintainer": "NGINX Docker Maintainers <[email protected]>"
},
"StopSignal": "SIGQUIT"
},
"NetworkSettings": {
"Bridge": "",
"SandboxID": "2634ff048a81ca6bf54385bd385a867d0eaeb7f38ef30f682f7a86e4700c7767",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {
"80/tcp": null
},
"SandboxKey": "/var/run/docker/netns/2634ff048a81",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "839cc04c1b865fe0ef14c0bf15b5b803848f20e5a869bc800b7f9e0572b7e4ec",
"Gateway": "172.17.0.1",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"MacAddress": "02:42:ac:11:00:02",
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "6e0a2c9a2d93f8fbd3b58dbb65617c484b26a2da1706f7a8c4aa25e63e83826a",
"EndpointID": "839cc04c1b865fe0ef14c0bf15b5b803848f20e5a869bc800b7f9e0572b7e4ec",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:02",
"DriverOpts": null
}
}
}
}
]

なるほど。

ファイルの中身を見てみましょう。root権限が必要です。

sudo tail /var/lib/docker/containers/373cef551938ce4f73ccce3fa8e8c0cfb77a5ab1a8081c0fa6d5cbde68f4eb8f/373cef551938ce4f73ccce3fa8e8c0cfb77a5ab1a8081c0fa6d5cbde68f4eb8f-json.log
{"log":"2022/12/25 00:40:50 [notice] 1#1: using the \"epoll\" event method\n","stream":"stderr","time":"2022-12-25T00:40:50.916229902Z"}
{"log":"2022/12/25 00:40:50 [notice] 1#1: nginx/1.23.3\n","stream":"stderr","time":"2022-12-25T00:40:50.916338623Z"}
{"log":"2022/12/25 00:40:50 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6) \n","stream":"stderr","time":"2022-12-25T00:40:50.916358363Z"}
{"log":"2022/12/25 00:40:50 [notice] 1#1: OS: Linux 5.4.0-1077-raspi\n","stream":"stderr","time":"2022-12-25T00:40:50.916375252Z"}
{"log":"2022/12/25 00:40:50 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576\n","stream":"stderr","time":"2022-12-25T00:40:50.916392159Z"}
{"log":"2022/12/25 00:40:50 [notice] 1#1: start worker processes\n","stream":"stderr","time":"2022-12-25T00:40:50.916409085Z"}
{"log":"2022/12/25 00:40:50 [notice] 1#1: start worker process 29\n","stream":"stderr","time":"2022-12-25T00:40:50.916424899Z"}
{"log":"2022/12/25 00:40:50 [notice] 1#1: start worker process 30\n","stream":"stderr","time":"2022-12-25T00:40:50.916441084Z"}
{"log":"2022/12/25 00:40:50 [notice] 1#1: start worker process 31\n","stream":"stderr","time":"2022-12-25T00:40:50.916457399Z"}
{"log":"2022/12/25 00:40:50 [notice] 1#1: start worker process 32\n","stream":"stderr","time":"2022-12-25T00:40:50.916691155Z"}

JSON形式でファイルに永続化されていることが確認できました。

ログドライバー仕様について

Dockerコンテナのログ出力機能はログドライバーで実現されており、デフォルトではJSON Fileログドライバーが採用されています。そのため、JSON形式でログが出力されています。

JSON Fileログドライバーには注意点があり、ドキュメントにもTIPSとして記載されています。

「ローカル」ログドライバーを使用し、ディスクの枯渇を防止する。

デフォルトでは、ログローテーションは行われません。そのため、デフォルトのjson-file logging driverログドライバによって保存されたログファイルは、多くの出力を生成するコンテナではかなりの量のディスクスペースを使用し、ディスクスペースの枯渇につながる可能性があります。 Dockerでは、旧バージョンのDockerとの後方互換性を保つため、およびDockerをKubernetesのランタイムとして使用する状況を考慮して、json-fileログドライバー(ログローテーションなし)をデフォルトとして維持しています。 その他の状況では、デフォルトでログローテートを実行し、より効率的なファイル形式を使用するLocalログドライバーを推奨します。

JSON Fileログドライバーはログローテーションをしないため、ローテーションさせたい場合は、Localログドライバーを使いましょうということです。

Localログドライバーの仕様はこちらに記載があります。

Localログドライバーは、コンテナの stdout/stderr からの出力をキャプチャし、パフォーマンスとディスク使用量を最適化した内部ストレージに書き込む。 デフォルトでは、Localログドライバーはコンテナごとに100MBのログメッセージを保存し、ディスク上のサイズを減らすために自動圧縮を使用します。100MBというデフォルト値は、各ファイルのデフォルトサイズを20Mとし、そのファイル数をデフォルトの5としたものです(ログのローテーションを考慮したもの)。

ログドライバーを変更する。

ということでログドライバーをLocalログドライバーに変更してみましょう。

/etc/docker/daemon.jsonに以下の設定を行います。 ファイルが存在しない場合はファイルを作成します。root権限が必要かもしれません。

daemon.json
{
"log-driver": "local"
}

これだけ!

設定したあとはDockerデーモンを再起動します。

sudo systemctl restart docker

以上で、ディスク枯渇対策は完了です。