Docker 异常总结

提起容器,你们可能首先想到的是 Docker,Docker 已经当之无愧的成为容器界巨头。若是你使用 Kubernetes 做为私有云的解决方案,Docker 也是首选的容器解决方案。虽然 Docker 很优秀,但 Docker 并非完美的,甚至存在不少问题。下面介绍咱们下在生产环境中遇到的关于 Docker 的一些问题及排查过程。避免你们再踩坑。node

异常一

docker ps 无响应, Node 节点表现为 NotReady。git

运行信息

$ docker -v
$ Docker version 17.03.2-ce, build f5ec1e2

$ docker-containerd -v
$ containerd version 0.2.3 commit:4ab9917febca54791c5f071a9d1f404867857fcc

$ docker-runc -v
$ runc version 1.0.0-rc2
$ commit: 54296cf40ad8143b62dbcaa1d90e520a2136ddfe
$ spec: 1.0.0-rc2-dev

启用 Docker Debug 模式

有两种方法能够启用调试。 建议的方法是在 daemon.json 文件中将 debug 设置为 true。 此方法适用于每一个 Docker 平台。github

1.编辑 daemon.json 文件,该文件一般位于 /etc/docker/ 中。 若是该文件尚不存在,您可能须要建立该文件。 2.增长如下设置docker

{
  "debug": true
}

3.向守护程序发送 HUP 信号以使其从新加载其配置。json

sudo kill -SIGHUP $(pidof dockerd)

能够看到 Docker debug 级别的日志:api

Dec 14 20:04:45 dockerd[7926]: time="2018-12-14T20:04:45.788669544+08:00" level=debug msg="Calling GET /v1.27/containers/json?all=1&filters=%7B%22label%22%3A%7B%22io.kubernetes.docker.type%3Dpodsandbox%22%3Atrue%7D%7D&limit=0"
Dec 14 20:04:45 dockerd[7926]: time="2018-12-14T20:04:45.790628950+08:00" level=debug msg="Calling GET /v1.27/containers/json?all=1&filters=%7B%22label%22%3A%7B%22io.kubernetes.docker.type%3Dcontainer%22%3Atrue%7D%7D&limit=0"
Dec 14 20:04:46 dockerd[7926]: time="2018-12-14T20:04:46.792531056+08:00" level=debug msg="Calling GET /v1.27/containers/json?all=1&filters=%7B%22label%22%3A%7B%22io.kubernetes.docker.type%3Dpodsandbox%22%3Atrue%7D%7D&limit=0"
Dec 14 20:04:46 dockerd[7926]: time="2018-12-14T20:04:46.794433693+08:00" level=debug msg="Calling GET /v1.27/containers/json?all=1&filters=%7B%22label%22%3A%7B%22io.kubernetes.docker.type%3Dcontainer%22%3Atrue%7D%7D&limit=0"
Dec 14 20:04:47 dockerd[7926]: time="2018-12-14T20:04:47.097363259+08:00" level=debug msg="Calling GET /v1.27/containers/json?filters=%7B%22label%22%3A%7B%22io.kubernetes.docker.type%3Dpodsandbox%22%3Atrue%7D%7D&limit=0"
Dec 14 20:04:47 dockerd[7926]: time="2018-12-14T20:04:47.098448324+08:00" level=debug msg="Calling GET /v1.27/containers/json?all=1&filters=%7B%22label%22%3A%7B%22io.kubernetes.docker.type%3Dcontainer%22%3Atrue%7D%2C%22status%22%3A%7B%22running%22%3Atrue%7D%7D&limit=0"
Dec 14 20:04:47 dockerd[7926]:

dockerd一直在请求 list containers 接口,可是没有响应。安全

打印堆栈信息

$ kill -SIGUSR1 $(pidof dockerd)

生成的调试信息能够在如下目录找到:bash

...goroutine stacks written to /var/run/docker/goroutine-stacks-2018-12-02T193336z.log
...daemon datastructure dump written to /var/run/docker/daemon-data-2018-12-02T193336z.log

查看 goroutine-stacks-2018-12-02T193336z.log 文件内容,app

goroutine 248 [running]:
github.com/docker/docker/pkg/signal.DumpStacks(0x18fe090, 0xf, 0x0, 0x0, 0x0, 0x0)
        /root/rpmbuild/BUILD/docker-ce/.gopath/src/github.com/docker/docker/pkg/signal/trap.go:82 +0xfc
github.com/docker/docker/daemon.(*Daemon).setupDumpStackTrap.func1(0xc421462de0, 0x18fe090, 0xf, 0xc4203c8200)
        /root/rpmbuild/BUILD/docker-ce/.gopath/src/github.com/docker/docker/daemon/debugtrap_unix.go:19 +0xcb
created by github.com/docker/docker/daemon.(*Daemon).setupDumpStackTrap
        /root/rpmbuild/BUILD/docker-ce/.gopath/src/github.com/docker/docker/daemon/debugtrap_unix.go:32 +0x10a

goroutine 1 [chan receive, 91274 minutes]:
main.(*DaemonCli).start(0xc42048a840, 0x0, 0x190f560, 0x17, 0xc420488400, 0xc42046c820, 0xc420257320, 0x0, 0x0)
        /root/rpmbuild/BUILD/docker-ce/.gopath/src/github.com/docker/docker/cmd/dockerd/daemon.go:326 +0x183e
main.runDaemon(0x0, 0x190f560, 0x17, 0xc420488400, 0xc42046c820, 0xc420257320, 0x10, 0x0)
        /root/rpmbuild/BUILD/docker-ce/.gopath/src/github.com/docker/docker/cmd/dockerd/docker.go:86 +0xb2
main.newDaemonCommand.func1(0xc42041f200, 0xc42045df00, 0x0, 0x10, 0x0, 0x0)
        /root/rpmbuild/BUILD/docker-ce/.gopath/src/github.com/docker/docker/cmd/dockerd/docker.go:42 +0x71
github.com/docker/docker/vendor/github.com/spf13/cobra.(*Command).execute(0xc42041f200, 0xc42000c130, 0x10, 0x11, 0xc42041f200, 0xc42000c130)
        /root/rpmbuild/BUILD/docker-ce/.gopath/src/github.com/docker/docker/vendor/github.com/spf13/cobra/command.go:646 +0x26d
github.com/docker/docker/vendor/github.com/spf13/cobra.(*Command).ExecuteC(0xc42041f200, 0x16fc5e0, 0xc42046c801, 0xc420484810)
        /root/rpmbuild/BUILD/docker-ce/.gopath/src/github.com/docker/docker/vendor/github.com/spf13/cobra/command.go:742 +0x377
github.com/docker/docker/vendor/github.com/spf13/cobra.(*Command).Execute(0xc42041f200, 0xc420484810, 0xc420084058)
        /root/rpmbuild/BUILD/docker-ce/.gopath/src/github.com/docker/docker/vendor/github.com/spf13/cobra/command.go:695 +0x2b
main.main()
        /root/rpmbuild/BUILD/docker-ce/.gopath/src/github.com/docker/docker/cmd/dockerd/docker.go:106 +0xe2

goroutine 17 [syscall, 91275 minutes, locked to thread]:

...

至此,咱们能够肯定,containerd 无响应致使的 docker ps 无响应,在堆栈中咱们也能够看到调用 containerd 无响应是由于加了lock.ui

查看dmesg

经过 dmesg 查看系统异常信息,发现 cgroup 报 OOM 溢出错误。

[7993043.926831] Memory cgroup out of memory: Kill process 20357 (runc:[2:INIT]) score 970 or sacrifice child

查看了大部分机器的 dmesg 信息,发现都有 OOM 这个错误,至此咱们怀疑是因为某个容器 OOM 致使的 containerd 无响应.

模拟OOM

既然怀疑是容器 OOM 异常致使的 containerd 无响应,那咱们干脆本身创造现场模拟一下。

首选咱们建立一个 OOM 的部署,经过 nodeSelector 让这个部署调度到指定 Node。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    wayne-app: oomtest
    wayne-ns: test
    app: oomtest
  name: oomtest
spec:
  selector:
    matchLabels:
      app: oomtest
  template:
    metadata:
      labels:
        wayne-app: oomtest
        wayne-ns: test
        app: oomtest
    spec:
      nodeSelector:
        kubernetes.io/hostname: test-001
      containers:
        - resources:
            limits:
              memory: 0.2Gi
              cpu: '0.2'
            requests:
              memory: 0.2Gi
              cpu: '0.1'
          args:
            - '-m'
            - '10'
            - '--vm-bytes'
            - 128M
            - '--timeout'
            - 60s
            - '--vm-keep'
          image: progrium/stress
          name: stress

发现过了一会 test-001 这台 Node 出现了 docker ps 无响应的状况,查看 dmesg 以及 containerd 的堆栈信息,发现和以前的 Node 出现的异常一致。至此,基本能够肯定是某个容器 OOM 致使的 containerd hung 住。

缘由分析

经过查找社区 Issues 及相关 PR,最后发现根本缘由是 runc 的bug。

使用 runc startrunc run 启动容器时,stub process(runc [2:INIT])打开一个 fifo 进行写入。 它的父 runc 进程 将打开相同的 fifo 阅读。 经过这种方式,它们能够同步。

若是 stub process 在错误的时间退出,那么父 runc 进程 会永远被阻塞。

当两个 runc 操做相互竞争时会发生这种状况:runc run / startrunc delete。 它也可能因为其余缘由而发生, 例如 内核的 OOM killer 能够选择杀死 stub process。

解决方案:

经过解决 exec fifo 竞争来解决这个问题。 若是 在咱们打开 fifo 以前 stub process 退出,那么返回一个错误。

小结

containerd 官方已经在 v1.0.2 版本合并了这个修改。所以这个问题能够经过升级 Docker 版本解决。咱们目前已经将部分机器升级到 Docker 18.06。 已升级的机器暂时未发现相似问题。

相关issues: https://github.com/containerd/containerd/issues/1882 https://github.com/containerd/containerd/pull/2048 https://github.com/opencontainers/runc/pull/1698

异常二

Docker 在 Centos 系统下以 direct-lvm 模式运行, 没法启动

Error starting daemon: error initializing graphdriver: devicemapper: Non existing device docker-thinpool
Dec 14 03:21:03 two-slave-41-135 systemd: docker.service: main process exited, code=exited, status=1/FAILURE
Dec 14 03:21:03 two-slave-41-135 systemd: Failed to start Docker Application Container Engine.
Dec 14 03:21:03 two-slave-41-135 systemd: Dependency failed for kubernetes Kubelet.
Dec 14 03:21:03 two-slave-41-135 systemd: Job kubelet.service/start failed with result 'dependency'.

根本缘由

这个问题发生在使用 devicemapper 存储驱动时Docker试图重用以前使用 LVM thin pool。例如,尝试更改节点上的 Docker 的数据目录时会发生此问题。因为安全措施旨在防止 Docker 因配置问题而意外使用和覆盖 LVM thin pool 中的数据,所以会发生此错误。

解决方案

要解决阻止Docker启动的问题,必须删除并从新建立逻辑卷,以便Docker将它们视为新的thin pool。

警告:这些命令将清除Docker数据目录中的全部现有镜像和卷。 请在执行这些步骤以前备份全部重要数据。

1.中止 Docker

sudo systemctl stop docker.service

2.删除 Dodcker 目录

sudo rm -rf /var/lib/docker

3.删除已经建立的 thin pool 逻辑卷

$ sudo lvremove docker/thinpool
Do you really want to remove active logical volume docker/thinpool? [y/n]: y
  Logical volume "thinpool" successfully removed

4.建立新的逻辑卷

lvcreate -L 500g --thin docker/thinpool --poolmetadatasize 256m

根据实际磁盘大小调整 thinpool 和 metadata 大小

Docker自动direct-lvm模式配置

若是您想要让Docker自动为您配置direct-lvm模式,请继续执行如下步骤。

1.编辑/etc/docker/daemon.json文件以将dm.directlvm_device_force = valuefalse更改成true。 例如:

{
  "storage-driver": "devicemapper",
  "storage-opts": [
    "dm.directlvm_device_force=true"
  ]
}

2.除了删除逻辑卷以外,还要删除docker卷组:

$ sudo vgremove docker

3.启动Dokcer

sudo systemctl start docker.service

总结

Docker 虽然是目前最经常使用的容器解决方案,但它仍旧有不少不足。

  • Docker 的隔离性比较弱,混布容易致使业务互相影响,可能由于一个服务有问题就会影响其余服务甚至影响整个集群。
  • Docker 本身存在一些 bug, 因为历史缘由,不少 bug 没法彻底归因于内核或者 Docker,须要 Docker 和内核配合修复。

因此若是你使用 Docker 做为容器的解决方案,推荐使用较新稳定版,毕竟新版已经修复了不少 bug,不必本身再踩一遍坑。较新版 Kubernetes 也已经完整支持新版 Docker,具体能够参考https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.12.md#external-dependencies

相关文章
相关标签/搜索