提起容器,大家可能首先想到的是 Docker,Docker 已經當之無愧的成為容器界巨頭。如果你使用 Kubernetes 作為私有云的解決方案,Docker 也是首選的容器解決方案。雖然 Docker 很優秀,但 Docker 並不是完美的,甚至存在很多問題。下面介紹我們下在生產環境中遇到的關於 Docker 的一些問題及排查過程。避免大家再踩坑。
異常一
docker ps 無響應, Node 節點表現為 NotReady。
運行信息
$ 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 平臺。
1.編輯 daemon.json 文件,該文件通常位於 /etc/docker/ 中。 如果該文件尚不存在,您可能需要創建該文件。 2.增加以下設置
{
"debug": true
}
3.向守護程序發送 HUP 信號以使其重新加載其配置。
sudo kill -SIGHUP $(pidof dockerd)
可以看到 Docker debug 級別的日誌:
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)
生成的調試信息可以在以下目錄找到:
...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 文件內容,
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.
查看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 start 或 runc run 啟動容器時,stub process(runc [2:INIT])打開一個 fifo 進行寫入。 它的父 runc 進程 將打開相同的 fifo 閱讀。 通過這種方式,它們可以同步。
如果 stub process 在錯誤的時間退出,那麼父 runc 進程 會永遠被阻塞。
當兩個 runc 操作相互競爭時會發生這種情況:runc run / start 和 runc delete。 它也可能由於其他原因而發生, 例如 內核的 OOM killer 可以選擇殺死 stub process。
解決方案:
通過解決 exec fifo 競爭來解決這個問題。 如果 在我們打開 fifo 之前 stub process 退出,那麼返回一個錯誤。
小結
containerd 官方已經在 v1.0.2 版本合併了這個修改。因此這個問題可以通過升級 Docker 版本解決。我們目前已經將部分機器升級到 Docker 18.06。 已升級的機器暫時未發現類似問題。
異常二
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 = value從false更改為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,沒必要自己再踩一遍坑。
閱讀更多 劉弋 的文章