为什么 docker 容器中的 shell 显示来自主机的 dmesg 内容?

Why is a shell within a docker container showing dmesg content from the host?

我在 Ubuntu yakkety 上有一个 docker 容器 运行ning Debian jessie。

当在 docker 内(例如通过 ssh 连接)时,我与主机隔离(这是预期的)。然而,我意识到 dmesg 向我显示了主机而不是容器的消息。 它如何访问其主机的信息?

docker容器的配置并不特殊(除了它使用特定的网桥,不同于docker0),特别是它在任何特权模式下都不运行 (下图"Privileged": false):

root@srv ~# docker inspect minecraft-1-8
[
    {
        "Id": "748cfdfbf3fb5526cb7151cbc0857117af3c7bd8ab9e086c4f2efb897290d66e",
        "Created": "2016-12-01T15:35:05.287672787Z",
        "Path": "/usr/bin/supervisord",
        "Args": [],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 28650,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2016-12-15T18:37:08.409564695Z",
            "FinishedAt": "2016-12-15T18:37:07.457274028Z"
        },
        "Image": "sha256:78a2f88d47e29523503c2196ed2faaa3d1039d948d73987edc03b2abd338595d",
        "ResolvConfPath": "/var/lib/docker/containers/748cfdfbf3fb5526cb7151cbc0857117af3c7bd8ab9e086c4f2efb897290d66e/resolv.conf",
        "HostnamePath": "/var/lib/docker/containers/748cfdfbf3fb5526cb7151cbc0857117af3c7bd8ab9e086c4f2efb897290d66e/hostname",
        "HostsPath": "/var/lib/docker/containers/748cfdfbf3fb5526cb7151cbc0857117af3c7bd8ab9e086c4f2efb897290d66e/hosts",
        "LogPath": "/var/lib/docker/containers/748cfdfbf3fb5526cb7151cbc0857117af3c7bd8ab9e086c4f2efb897290d66e/748cfdfbf3fb5526cb7151cbc0857117af3c7bd8ab9e086c4f2efb897290d66e-json.log",
        "Name": "/minecraft-1-8",
        "RestartCount": 0,
        "Driver": "overlay",
        "MountLabel": "",
        "ProcessLabel": "",
        "AppArmorProfile": "",
        "ExecIDs": null,
        "HostConfig": {
            "Binds": null,
            "ContainerIDFile": "",
            "LogConfig": {
                "Type": "json-file",
                "Config": {}
            },
            "NetworkMode": "docker",
            "PortBindings": {},
            "RestartPolicy": {
                "Name": "no",
                "MaximumRetryCount": 0
            },
            "AutoRemove": false,
            "VolumeDriver": "",
            "VolumesFrom": null,
            "CapAdd": null,
            "CapDrop": null,
            "Dns": [],
            "DnsOptions": [],
            "DnsSearch": [],
            "ExtraHosts": null,
            "GroupAdd": null,
            "IpcMode": "",
            "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,
            "CgroupParent": "",
            "BlkioWeight": 0,
            "BlkioWeightDevice": null,
            "BlkioDeviceReadBps": null,
            "BlkioDeviceWriteBps": null,
            "BlkioDeviceReadIOps": null,
            "BlkioDeviceWriteIOps": null,
            "CpuPeriod": 0,
            "CpuQuota": 0,
            "CpusetCpus": "",
            "CpusetMems": "",
            "Devices": [],
            "DiskQuota": 0,
            "KernelMemory": 0,
            "MemoryReservation": 0,
            "MemorySwap": 0,
            "MemorySwappiness": -1,
            "OomKillDisable": false,
            "PidsLimit": 0,
            "Ulimits": null,
            "CpuCount": 0,
            "CpuPercent": 0,
            "IOMaximumIOps": 0,
            "IOMaximumBandwidth": 0
        },
        "GraphDriver": {
            "Name": "overlay",
            "Data": {
                "LowerDir": "/var/lib/docker/overlay/e78ce9dbcedd6974429a4aada8f38913b7d35da41f586f203dd99a568f38b6c3/root",
                "MergedDir": "/var/lib/docker/overlay/e8422e4707d95db8ea747af2367626cc8bf16e95f8eb05dfad9a63461c9ade86/merged",
                "UpperDir": "/var/lib/docker/overlay/e8422e4707d95db8ea747af2367626cc8bf16e95f8eb05dfad9a63461c9ade86/upper",
                "WorkDir": "/var/lib/docker/overlay/e8422e4707d95db8ea747af2367626cc8bf16e95f8eb05dfad9a63461c9ade86/work"
            }
        },
        "Mounts": [],
        "Config": {
            "Hostname": "minecraft-1-8",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": true,
            "AttachStderr": true,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "/usr/bin/supervisord"
            ],
            "Image": "minecraft",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": {}
        },
        "NetworkSettings": {
            "Bridge": "",
            "SandboxID": "cf411634babad31138ab4572b9cd7306f74a54dd1baf4cd8d7706d7e7020c594",
            "HairpinMode": false,
            "LinkLocalIPv6Address": "",
            "LinkLocalIPv6PrefixLen": 0,
            "Ports": {},
            "SandboxKey": "/var/run/docker/netns/cf411634baba",
            "SecondaryIPAddresses": null,
            "SecondaryIPv6Addresses": null,
            "EndpointID": "",
            "Gateway": "",
            "GlobalIPv6Address": "",
            "GlobalIPv6PrefixLen": 0,
            "IPAddress": "",
            "IPPrefixLen": 0,
            "IPv6Gateway": "",
            "MacAddress": "",
            "Networks": {
                "docker": {
                    "IPAMConfig": {
                        "IPv4Address": "10.200.0.100"
                    },
                    "Links": null,
                    "Aliases": [
                        "748cfdfbf3fb"
                    ],
                    "NetworkID": "7b20560b36032d36ffe6c0ebece6b4408355d207f4e203a2957b0434ee0afdc1",
                    "EndpointID": "9fa4fc914dfe76022ce0db02e48a7e7c85c57bc2a15b0b3e5d81b1f24d95f376",
                    "Gateway": "10.200.0.1",
                    "IPAddress": "10.200.0.100",
                    "IPPrefixLen": 24,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:0a:c8:00:64"
                }
            }
        }
    }
]

在大多数发行版的 dmesg is not a privileged command. Any user can use the klogctl 接口中读取内核环形缓冲区。

$ id
uid=1001(matt) gid=1001(matt) groups=1001(matt)
$ dmesg | head -1
[    0.000000] Initializing cgroup subsys cpuset

但除了读取之外什么也做不了

$ dmesg -C
dmesg: klogctl failed: Operation not permitted

延伸至 Docker

$ sudo docker run debian dmesg | head -1
[    0.000000] Initializing cgroup subsys cpuset
$ sudo docker run debian dmesg -C
dmesg: klogctl failed: Operation not permitted

限制访问

您可以通过 /proc/sys/kernel/dmesg_restrict 将读取权限限制为 root 用户和具有 CAP_SYSLOG 或 CAP_SYS_ADMIN capabilities 的用户。

$ echo 1 > /proc/sys/kernel/dmesg_restrict

那么您应该会收到一条权限被拒绝的消息:

$ docker run ubuntu:yakkety dmesg
dmesg: read kernel buffer failed: Operation not permitted

运行 特权模式下的容器将重新获得对主机内核环缓冲区的访问权限

$ docker run --privileged ubuntu:yakkety dmesg
[146902.131915] br-fa26f1dc96a1: port 3(veth80d3d5d) entered disabled state
...

如果您永久需要它,请使用 sysctl 配置 kernel.dmesg_restrict=1

命名空间

至于为什么内核日志不像其他内核区域那样有名称间距,我认为答案是"it's difficult"。在这个 2012 Stepping closer to practical containers: "syslog" namespaces LWN post. I can't see any reference's to it getting any further than proposed patches: https://lwn.net/Articles/562389/ https://lwn.net/Articles/561271/. As you can see in this recent netfilter patch 中,有比你想知道的更多的细节,他们有一个变通方法允许容器名称 space 中的规则使用全局日志。