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

相關文章
相關標籤/搜索