寫在前面:
由於紅帽在Linux界的影響力,相信很多朋友在測試和生產系統用的是RedHat或者CentOS系統,這次我在CentOS系統上遇到了一個很有意思的故障,通過這次故障的原因分析及解決,特意寫了這篇文章分享給大家。
我們在CentOS上部署了一套Docker系統,運行了一段時間后,突然發現所有容器運行異常,同時宿主機內核報磁盤I/O錯誤:
看到問題的第一反映是查看磁盤狀態和空間使用情況,發現系統的根目錄已經用完:
我們知道,Docker默認的存儲目錄是在/var/lib/docker/下,同時我們也知道,可以通過使用-g, --graph=”/var/lib/docker” 參數修改Docker 默認存放路徑。知道了問題后,我們可以通過掛載一個大硬盤到系統,並將Docker的目錄更改為新掛載到硬盤上:
我將Docker的存儲目錄設置到剛才新增加的/data目錄下,但是原來的鏡像和容器都找不到了,因為路徑改了。原來的鏡像是在/var/lib/docker/devicemapper/devicemapper/{data,metadata},轉移文件后繼續運行Docker服務,這樣我們就有了一個300G的大房子給Docker們用了。
大家以為事情到了這里就完結了么?其實我也想,但是我順便折騰了一下,於是又發生了接下來的事情。說我手賤也好,瞎折騰也罷,導入一堆容器鏡像和運行一堆容器后,系統又光榮告訴我所有的容器根目錄全部變成了只讀,宿主機內核同樣報磁盤I/O錯誤,一開始我以為data目錄又被寫滿了,但是用df –Th命令查看后,發現目錄還有很多空間:
但是殘酷的現實是,只用了不到一半的空間后,所有的容器就全部出現異常了,這是我祭出了經典三板斧:重啟容器,重啟Docker服務,重啟服務器。然並卵,容器還是運行異常。通過在網上爬了一堆資料,在http://jpetazzo.github.io/2014/01/29/docker-device-mapper-resize/上查到,CentOS默認用的是Device Mapper作為容器的存儲驅動的,大家可以用dockers info命令查看,Docker服務啟動時默認會在/var/lib/docker/devicemapper/devicemapper/目錄創建一個100G(由於1000和1024換算的關系,系統實際顯示的是107.4G,其他數字亦同)的data文件,然后啟動的容器的所有變更的數據全部保存到這個data文件中;也就是說當容器內產生的相關data數據超過100G后容器就再也沒有多余的空間可用,從而導致所有容器的根目錄變為只讀!同時它會限制每個容器最大為 10GB。太坑爹了有木有,給了大房子只能用100G!
為了找到根本原因,我們需要了解Device Mapper存儲驅動的原理: Device Mapper存儲驅動是以精簡配置的方式運行的,它實際上是目標塊設備的快照。
Docker啟動時會設置一個100G的sparse文件( /var/lib/docker/devicemapper/devicemapper/data,元數據為/var/lib/docker/devicemapper/devicemapper/metadata ),並將其作為Device Mapper的存儲池,而所有容器都從該存儲池中分配默認10G的存儲空間使用,如下圖所示:
當有實際讀寫后,這些存儲塊將在存儲池中被標記為已使用(或者從池中拿走)。當實際讀寫的塊容量大於池的容量時,容器的運行空間不足,所以報I/O錯誤。
Device Mapper存儲驅動非常方便,你不需要做任何安裝部署便可以使用:如創建額外的分區來存儲 Docker 容器,或者建立LVM。然而它也有兩個缺點:
• 存儲池會有一個默認 100GB 的容量,滿足不了大存儲的需求。
• 它將會被稀疏文件所支持(精簡配置,一開始基本不占用空間,只有當實際需要寫的時候才會使用磁盤的存儲塊)但性能較差。
針對這些問題,有兩個解決方案:
1. 使用更大的文件/磁盤/邏輯卷創建data文件:
2. 通過Docker啟動參數的--storage-opt選項來限制每個容器初始化的磁盤大小,如-storage-opt dm.basesize=80G 這樣每個容器啟動后,根目錄的總空間就是80G。
但是我總覺得這樣的解決方式不夠優雅,需要多步操作才能滿足需求,同時,容器的空間還是被限制的,只是限制的大小變化而已。那有沒有更好的辦法呢? 讓我們繼續來爬資料,在Docker的官方網站上:
(https://docs.docker.com/engine/reference/commandline/dockerd/)
Docker在存儲驅動方面支持 AUFS、Device Mapper、Btrfs、ZFS、 Overlay 、Overlay2等多址方式,現由於AUFS並未並入內核,目前只有Ubuntu系統上能夠使用aufs作為docker的存儲引擎,而在CentOS系統上默認使用Device Mapper,但是幸運的是,在Linux內核3.18.0以上的版本,是可以原生支持Overlay驅動方式的,Overlayfs跟AUFS很像,但是性能比AUFS好,有更好的內存利用。
Docker通過-s參數選擇存儲驅動, 通過-s=overlay,我們將存儲驅動器設置為Overlay方式,再重啟Docker應用。
大家可以看到,現在Docker已經是使用了OverlayFS(這里大家要注意,如果系統有存儲的鏡像和運行的容器,更改存儲驅動后將都不可用,請先行備份)。
通過修改為OverlayFS,Device Mapper的存儲池容量限制及單個容器運行最大空間限制統統沒有了,同時Overlay的讀寫性能也好於Device Mapper,只需通過-s=overlay一個參數即可優雅的使用更好的文件系統來運行容器。
至此,容器運行時I/O錯誤的原因已經完美解決,希望這篇文章能幫到在使用過程中遇到相同問題的朋友。