同任何相對復雜的軟體專案一樣,Docker有很多細節問題和缺點,如果想要讓用戶體驗盡可能地流暢,知道這些很重要,
本章講述的一些技巧將會向讀者展示其中更為重要的一些部分,而且會介紹如何使用一些第三方構建的外部工具來解決自身問題,不妨把它看作一個Docker工具箱,
6.1 保持陣型
如果你跟我們一樣(并且有心關注本書),那么你對Docker的依賴將會與日俱增,這也就意味著會在選定的宿主機上啟動大量容器,然后下載更多的鏡像,
隨著時間的流逝,Docker將會消耗越來越多的資源,一些容器和卷的清理需要擺上日程,我們將會展示怎么做以及為什么這樣做,我們也會介紹一些用來保持Docker環境干凈整潔的可視化工具,以便讓不喜歡敲命令列的用戶可以從中解脫出來,
正在運行的容器都很好,但是用戶很快會發現自己想要的不僅僅是在前臺啟動一個單條命令,我們會一起來看看如何在不“殺死”該容器的前提下退出一個正在運行的容器,還會看看如何在一個正在運行的容器內執行命令,
技巧41 運行Docker時不加sudo
Docker守護行程以root用戶身份在機器的后臺運行,這給了它莫大的權力,同時它又是對你,即終端用戶開放的,需要使用sudo是一個結果,但是這樣做不太方便,而且也會造成一些第三方Docker工具無法使用,
問題
想要無須sudo便可以執行docker命令,
解決方案
官方解決方案是把自己加到docker組,Docker通過一個用戶組圍繞著Docker Unix域套接字來管理權限,為安全起見,發行版默認不會將用戶加到該用戶組里,因為這樣做會開放系統完整的root訪問權限,
把自己加到該用戶組后,用戶便能以自己的身份使用docker命令:
$ sudo addgroup -a username docker
重啟Docker然后完全注銷并再次登錄,或者更簡單點,重啟機器,現在執行Docker命令時不用再留意鍵入sudo或設定別名了,
討論
對于本書后面部分用到的一系列工具來說,這是一項極其重要的技巧,一般來說,任何想要和Docker通信的物件(無須在容器里啟動)都需要能夠訪問Docker套接字,這需要使用sudo或者使用本技巧里提到的設定,技巧76里引入的Docker Compose是Docker公司的官方工具,也是這類工具的一個示例,
技巧42 清理容器
Docker新手經常抱怨的一點便是,在短時間內,用戶可能在系統上殘留許多不同狀態的容器,而且沒有一個標準工具通過命令列管理這些容器,
問題
想要清理系統上的殘留容器,
解決方案
設定一個別名來執行清理舊容器的命令,這里最簡單的辦法是洗掉所有容器,顯然,這是一個有風險的方案,只應在確定這是預期行為的時候使用,下列命令將會洗掉宿主機上的所有容器,
$ docker ps -a -q | \ ?--- 獲取所有容器ID的串列,包括正在運行的以及已停止的,然后將它們傳給……
xargs --no-run-if-empty docker rm -f ?--- ……docker rm -f命令,被傳入的任意容器將會被洗掉,即使它們還處于運行狀態
簡單介紹一下xargs命令,它會獲取輸入的每一行內容,并將它們全部作為引數傳遞給后續命令,為了防止報錯,我們這里傳入了一個額外引數--no-run-if-empty,這可以避免在前面的命令完全沒有輸出的情況下執行該命令,
如果有正在運行的容器想要保留,但是又想洗掉所有已經退出的容器,那么不妨過濾一下docker ps命令回傳的條目:
docker ps -a -q --filter status=exited | \ ?--- --filter標志會告知docker ps命令想要回傳的容器,在這種情況下限制成狀態為已經退出的那些容器,也可以選擇處于正在運行中或者正在重啟狀態的容器
xargs --no-run-if-empty docker rm ?--- 這次不用再強行洗掉容器,因為根據給定的過濾引數,它們本身就不應該處于運行狀態
事實上,刪掉所有已停止的容器是一個很常見的用例,為此Docker專門添加了一條命令:docker container prune,然而,這條命令僅限于該用例,要進行任何更復雜的操作,仍然需要回過頭來參考本技巧里介紹的命令,
作為更高級用例的示范,下列命令將會列出所有回傳非零錯誤碼的容器,如果系統上有許多容器,用戶想要自動檢查并洗掉那些例外退出的任意容器,就可能需要這樣做:
comm -3 \ ?--- 執行comm命令來比較兩個檔案內容的差異,加上-3 引數將不會顯示同時出現在兩個檔案里的行內容(這些容器的退出碼都是0),然后輸出其他不同的部分
<(docker ps -a -q --filter=status=exited | sort) \ ?--- 找出退出的容器 ID,給它們排序,然后以檔案形式傳給comm
<(docker ps -a -q --filter=exited=0 | sort) | \ ?--- 找出退出碼為0的容器,給它們排序,然后以檔案形式傳給comm
xargs --no-run-if-empty docker inspect > error_containers ?--- 對非0退出碼(comm命令管道的輸出)的容器執行docker inspect,并將輸出結果保存到error_containers檔案中
提示
也許你還沒看到過這種用法,bash里的<(command)語法被稱為行程替換,它允許把一個命令的輸出結果作為檔案,傳給其他命令,這在無法使用管道輸出的時候非常有用,
上述示例相對比較復雜,但是它展示了將不同的工具命令組合在一起的威力,它會輸出所有已停止的容器的ID,然后挑出那些非0退出碼的容器(即那些以例外方式退出的容器),如果讀者還在努力理解這個用法,不妨先單獨執行每條命令,然后理解它們的含義,這樣有助于了解整個程序,
像這樣的命令可以用來在生產環境里采集容器資訊,用戶可能想要對它做些調整,改為執行一個cron定時任務來清除正常退出的容器,
將單行代碼包裝成命令
可以給命令設定別名,以便在登錄到宿主機后更容易操作,為了達成這一點,需要在~/.bashrc檔案里添加如下代碼:
alias dockernuke='docker ps -a -q | \
xargs --no-run-if-empty docker rm -f'
然后,在下一次登錄時,從命令列執行dockernuke,將洗掉在系統上找到的任何Docker容器,
我們發現這樣位元組省的時間是相當可觀的,但是要小心!這種方式同樣也非常容易誤刪生產環境的容器,我們可以證明,即使足夠小心,不去洗掉正在運行的容器,仍然可能會誤刪那些沒有運行但仍然有用的純資料容器,
討論
本書介紹到的許多技巧的最終目的都是創建容器,尤其是在技巧76介紹到的Docker Compose以及有關編排的章節里——畢竟,編排都是關于如何管理多個容器的,用戶也許會發現這里討論到的命令用于清理機器(本地或遠程)很有價值,在完成每個技巧后可以獲得一個全新的環境,
技巧43 清理卷
盡管卷是Docker提供的一個強大功能,與之伴隨而來的也有一些顯著的運維缺陷,由于卷可以在不同的容器之間共享,因此在掛載它們的容器被洗掉時無法清空這些卷,試想一下圖6-1中描述的場景,

圖6-1 當容器被洗掉時/var/db下會發生什么
“簡單!”你可能會這樣想,“在最后一個參考的容器被洗掉時把卷刪掉不就行了!”事實上,Docker可以采取這種手段,這也是垃圾回收式編程語言從記憶體中洗掉物件時所采用的方法:當沒有其他物件參考它時,它便可以被洗掉,
但是Docker認為這可能會讓人們不小心丟失重要的資料,而且最好把是否在洗掉容器的時候洗掉卷的決定權交給用戶,這樣做帶來的一個不幸的副作用便是,默認情況下,卷會一直保留在Docker守護行程所在的宿主機磁盤上,直到它們被手動洗掉,
如果這些卷填滿了資料,磁盤可能會被裝滿,因此最好關注一下管理這些孤立卷的方法,
問題
掛載到宿主機上的孤立Docker卷用掉了大量的磁盤空間,
解決方案
在呼叫docker rm命令時加上-v標志,或者如果忘記了,使用docker volumes子命令來銷毀它們,
在圖6-1描述的場景中,如果在呼叫docker rm時總是加上-v標志可以確保/var/db最后被洗掉掉,-v標志會將那些沒有被其他容器掛載的關聯卷一一洗掉,幸好,Docker很聰明,它知道是否有其他容器掛載該卷,因此不會出現什么意外尷尬的情形,
最簡單的方式莫過于養成在洗掉容器時加上-v標志這樣的好習慣,這樣可以保留對容器是否洗掉卷的控制權,而這種做法的問題在于用戶可能不想每次都洗掉卷,如果用戶正在寫入大量資料到這些卷,極有可能不希望丟失這些資料,此外,如果養成了這樣的習慣,很有可能就會變成自動的了,而用戶將會在洗掉某些重要東西之后才反應過來,但為時已晚,
在這類情況下,用戶可以使用一個經過許多人抱怨并且涌現出眾多第三方解決方案之后添加到Docker的命令:docker volume prune,這條命令將會洗掉所有未使用的卷:
$ docker volume ls ?--- 執行命令列出Docker所知的卷
DRIVER VOLUME NAME
local 80a40d34a2322f505d67472f8301c16dc75f4209b231bb08faa8ae48f
? 36c033f ?---
local b40a19d89fe89f60d30b3324a6ea423796828a1ec5b613693a740b33
? 77fd6a7b
local bceef6294fb5b62c9453fcbba4b7100fc4a0c918d11d580f362b09eb
? 58503014 ?--- 宿主機上存在的卷,無論是否在使用
$ docker volume prune ?--- 執行命令洗掉未使用的卷
WARNING! This will remove all volumes not used by at least one container.
Are you sure you want to continue? [y/N] y ?--- 確認洗掉卷
Deleted Volumes:
80a40d34a2322f505d67472f8301c16dc75f4209b231bb08faa8ae48f36c033f ?---
b40a19d89fe89f60d30b3324a6ea423796828a1ec5b613693a740b3377fd6a7b ?--- 已經被洗掉的卷
Total reclaimed space: 230.7MB
如果想要跳過提示確認步驟,也許可以用一個自動化腳本,在執行docker volume prune時帶上-f選項來跳過這一步,
提示
如果想要恢復一個未被洗掉但是已經不再被任何容器參考的卷里的資料,可以使用docker volume inspect來找出卷所在的目錄(像是/var/lib/docker/volumes下),隨后可以用root用戶的身份瀏覽,
討論
洗掉卷可能不是需要經常執行的操作,因為容器里的大檔案通常是從宿主機掛載的,并不會存放在Docker資料目錄里,但是值得大約每周清理一次,避免它們堆積,尤其是當你使用技巧37里的資料容器時,
技巧44 無須停止容器,從容器里解綁
使用Docker時,你常常會發現自己打開了一個互動式shell,但是一旦退出shell,容器便會被終止,因為它是容器的主行程,幸運的是,有辦法可以做到和一個容器解綁(而且,如果愿意,還可以用dockerattach命令再連到容器里)
問題
想要退出一個容器的互動會話,同時不停掉它,
解決方案
使用Docker內置的按鍵組合從容器里退出,Docker很有建設性地實作了一個不太可能被其他應用使用也不太可能被意外按到的按鍵組合,
假設我們執行docker run -t -i -p 9005:80 ubuntu /bin/bash命令啟動了一個容器,然后用apt-get安裝了一個Nginx Web服務器,我們想通過一個快捷的到localhost:9005的curl命令來測驗該Web服務器能否在宿主機上被訪問到,
先按組合鍵Ctrl+P然后再按組合鍵Ctrl+Q,注意,不是3個鍵一起按!
注意
如果運行容器時帶上了–rm標志,那么在解綁后一旦容器被終止仍然會被洗掉,無論是命令執行完畢還是手動把它停掉,
討論
如技巧2所述,如果我們之前已經啟動了一個容器,卻忘了在后臺啟動,本技巧會很有用,如果想檢查容器的運行情況或提供一些輸入,它還允許用戶和容器自由地系結和解綁,
技巧45 使用Portainer管理Docker守護行程
在演示Docker時,很難表現出容器和鏡像之間的差異——從終端里的輸出看不出來,此外,如果想要從多個容器里殺掉并洗掉一個特定的容器,Docker命令列工具對于這種場景也不太友好,創建一個即點即用的工具來管理宿主機上的鏡像和容器可以解決這個問題,
問題
想要不通過命令列管理宿主機上的容器和鏡像,
解決方案
試試Portainer,這是一款由Docker核心貢獻者之一開發的工具,Portainer的前身是DockerUI,由于沒有先決條件,可以直接跳到執行步驟:
$ docker run -d -p 9000:9000 \
-v /var/run/docker.sock:/var/run/docker.sock \
portainer/portainer -H unix:///var/run/docker.sock
執行上述命令將會在后臺啟動一個portainer容器,如果現在訪問 http://localhost:9000 ,可以在看板上看到機器上運行的Docker的簡要資訊,
容器管理功能可能是這里面最有用的部分之一 ——轉到“Containers”頁面,我們會看到正在運行的容器串列(包括portainer容器本身),還提供選項可以展示所有容器,在這里,你可以對容器執行批量操作(如殺掉它們),或者點擊一個容器的名字,深入了解該容器的詳細資訊,而且可以執行該容器相關的一些單個操作,例如,可以看到洗掉一個正在運行的容器的選項,
“Images”頁面看起來和“Containers”頁面非常相似,并且還允許選擇多個鏡像然后執行一些批量操作,點擊鏡像的ID會提供一些有趣的選項,比如基于該鏡像創建一個容器以及給鏡像打標簽等,
記住,Portainer可能會落后于Docker官方提供的功能——如果想要使用最新最強大的功能,那么可能不得不選擇命令列,
討論
Portainer是Docker眾多的圖形工具里的其中一款,也是這里面最受歡迎的,擁有眾多功能并且持續迭代的工具之一,舉個例子,你可以使用它來管理遠程機器,也許會是技巧32里在這些機器上啟動容器之后用到,
技巧46 生成Docker鏡像的依賴圖
Docker的檔案分層系統是一個非常強大的理念,它可以節省空間,而且可以讓軟體的構建變得更快,但是一旦啟用了大量的鏡像,便很難搞清楚鏡像之間是如何關聯的,docker images -a命令會回傳系統上所有鏡像層的串列,但是對于理解它們之間的關聯關系而言,這不是一個友好的方式——使用Graphviz可以更方便地通過創建一個鏡像樹并做成鏡像的形式來可視化鏡像之間的關系,
這也展示了Docker在把復雜的任務變得簡單方面的強大實力,在宿主機上安裝所有的組件來生產鏡像時,老的方式可能會包含一長串容易出錯的步驟,但是對Docker來說,這就變成了一條相對失敗較少的可移植命令,
問題
想要以樹的形式將存放在宿主機上的鏡像可視化,
解決方案
使用一個我們之前創建的鏡像(基于CenturyLink Labs的一個鏡像)配合這項功能輸出一個PNG圖片或者獲取一個Web視圖,此鏡像包含了一些使用Graphviz生成PNG圖片檔案的腳本,
本技巧使用的Docker鏡像放在dockerinpractice/docker-image-graph,時間長了該鏡像可能會過期然后停止作業,可以通過執行代碼清單6-1中的命令確保生成最新的鏡像,
代碼清單6-1 構建一個最新的docker-image-graph鏡像(可選)
$ git clone https://github.com/docker-in-practice/docker-image-graph
$ cd docker-image-graph
$ docker build -t dockerinpractice/docker-image-graph
在run命令里需要做的就是掛載Docker服務器套接字,然后一切便準備就緒,如代碼清單6-2所示,
代碼清單6-2 生成一個鏡像的層樹
$ docker run --rm \ ?--- 在生成鏡像之后洗掉容器
-v /var/run/docker.sock:/var/run/docker.sock \ ?--- 掛載 Docker 服務器的Unix 域套接字,以便可以在容器里訪問Docker服務器,如果已經更改了Docker守護行程的默認配置,這將不會奏效
dockerinpractice/docker-image-graph > docker_images.png ?--- 指定一個鏡像然后生成一個PNG圖片作為制品
圖6-2以PNG圖片形式展示了一臺機器的鏡像樹,從這張圖片可以看出,node和golang:1.3鏡像擁有一個共同的根節點,然后golang:runtime只和golang:1.3共享全域的根節點,類似地,mesosphere鏡像和ubuntu-upstart鏡像也是基于同一個根節點構建的,
讀者可能會好奇這棵樹上的全域根節點是什么,它是一個叫作scratch的偽鏡像,實際上大小為0位元組,
討論
在構建更多的Docker鏡像時,也許作為第9章里持續交付的一部分,跟蹤一個鏡像的歷史以及它所基于的內容可能會很麻煩,如果試圖通過共享更多層精簡鏡像大小的方式來加快交付速度,這一點尤為重要,定期拉取所有鏡像并生成圖譜是一個追蹤的好辦法,

圖6-2 一棵鏡像樹
技巧47 直接行動:在容器上執行命令
在Docker早期,許多用戶會在他們的鏡像里添加SSH服務,這樣一來便可以從外部通過shell來訪問它們,Docker不主張這樣做,它認為這相當于把容器當成一臺虛擬機(而我們知道,容器并不是虛擬機),并且這給本不應該需要它的系統帶來了額外的行程開銷,很多人對此持反對意見的原因在于,一旦容器啟動了,沒有一個簡便的辦法進到容器里面,結果便是,Docker引入了exec命令,它是一個更優雅地解決干涉和檢索啟動后的容器內部問題的解決方案,我們這里也將討論此命令,
問題
想要在一個正在運行的容器里執行一些命令,
解決方案
使用dockerexec命令,
下列命令會在后臺(帶上-d標志)啟動一個容器,然后告訴它一直休眠(不做任何事情),我們把這條命令命名為sleeper,
docker run -d --name sleeper debian sleep infinity
現在已經啟動了一個容器,可以用Docker的exec命令對它執行一些操作,該命令可以看成有3種基本模式,如表6-1所示,
表6-1 Docker exec 模式

我們先介紹一下基本模式,下列命令在sleeper容器內部執行了一個echo命令,
$ docker exec sleeper echo "hello host from container"
hello host from container
注意,該命令的結構和dockerrun命令非常相似,但是把鏡像ID替換成一個正在運行的容器的ID,echo命令指代的是容器里面的echo二進制檔案,而非容器外部的,
守護行程模式會在后臺執行命令,用戶無法在終端看到輸出結果,這可能適用于一些常規的清理任務,在這些任務中,你希望敲完即走,如清理日志檔案,
$ docker exec -d sleeper \ ?--- 執行命令時加上-d標志即可在后臺以守護行程的形式運行,類似dcoker run
find / -ctime 7 -name '*log' -exec rm {} \; ?--- 洗掉所有在最近7天沒有做過更改并且以log結尾的檔案
$ ?--- 無論需要多長時間完成這一操作,該命令都會立即回傳
最后,我們來試試互動模式,這種模式允許用戶在容器里執行任何想要執行的命令,要啟用這一功能,通常需要指定用來在運行時互動的shell,在如下代碼里便是bash:
$ docker exec -i -t sleeper /bin/bash
root@d46dc042480f:/#
-i和-t引數同我們所熟悉的dockerrun做著相同的事情——它們會讓命令成為可互動的,然后設定一個TTY設備,以便shell可以正常作業,在執行該命令后,用戶便拿到了一個在容器里運行的命令提示符,
討論
當出現問題或者想要弄清楚容器在做什么時,跳到容器里是必不可少的除錯步驟,往往不太可能使用技巧44里提到的系結和解綁方法,因為容器內的行程通常運行在前臺,無法訪問shell提示符,由于exec允許用戶指定想要運行的二進制檔案,這便不再是問題……只要容器檔案系統上實際存在那份想要運行的二進制檔案即可,
特別的是,如果你使用技巧58創建一個帶有單個二進制檔案的容器,那么將無法啟動shell,在這種情況下,可能想堅持采用技巧57作為允許exec執行的低開銷辦法,
技巧48 你在容器里嗎
在創建容器時,通常會把運行邏輯放到一個shell腳本里,很少會嘗試直接在Dockerfile里撰寫腳本,又或者,你可能在容器運行時用到了各種腳本,無論哪種方式,這些執行的任務通常都需要經過仔細定制,以便能夠運行在容器里,并且運行在一臺“常規”機器上可能會搞破壞,在這種情況下,設定一些安全防護,防止在容器外部意外執行是很有用的,
問題
用戶代碼需要知道是否是在一個Docker容器里操作,
解決方案
檢查/.dockerenv檔案是否存在,如果存在,那么很可能在一個Docker容器里,
注意,這并不是100%確定的——如果任何人或任何事物把/.dockerenv檔案刪掉,這個檢查就會給出誤導性的結果,這些情況不太可能發生,但是最壞的情況便是用戶得到錯誤的診斷結果而沒有不良影響,用戶會認為自己不在Docker容器里,并且在最壞的情況下不會運行潛在的破壞性代碼,
一個更現實的情況是,在較新的Docker版本里(或者使用的是實作這一行為之前的版本)已經更改或洗掉了這種未記錄的Docker行為,
這些代碼可能是啟動bash腳本的一部分,如代碼清單6-3所示,其后是剩余的啟動腳本代碼,
代碼清單6-3 如果在容器外運行,如下shell腳本會運行失敗
#!/bin/bash
if ! [ -f /.dockerenv ]
then
echo 'Not in a Docker container, exiting.'
exit 1
fi
當然,如有需要,可以使用相反的邏輯來確認自己是不是運行在容器外面,如代碼清單6-4所示,
代碼清單6-4 如果在容器里運行,如下shell腳本會運行失敗
#!/bin/bash
if [ -f /.dockerenv ]
then
echo 'In a Docker container, exiting.'
exit 1
fi
上述示例使用bash命令來確認檔案是否存在,但是絕大多數編程語言有自己的辦法來確認容器(或宿主機)檔案系統里是否存在某些檔案,
討論
用戶可能想知道這種情況多久出現一次,作為一個時常討論的話題,它經常出現在Docker論壇里,關于這是否是一個有效的用例,又或者是應用程式設計方面存在其他問題,這塊仍然存在爭議,
撇開這些爭議不提,用戶很容易陷入需要根據自己是否在Docker容器里來切換代碼路徑的情況,我們經歷過的一個這樣的例子便是使用Makefile來構建一個容器,
6.2 小結
- 用戶可以配置自己的機器,讓自己可以不帶sudo運行Docker,
- 使用內置的Docker命令來清理未使用的容器和卷,
- 以一種全新的方式使用外部工具來公開容器的相關資訊,
- docker exec命令是進入一個正在運行的容器內部的正確途徑——抵制安裝SSH,
本文摘自《Docker實踐(第2版)》

- 深入淺出Docker原始碼分析,暢銷容器與容器云實踐教程升級版
- 基于Docker1.13,114個實戰技巧
- 解決Docker的應用問題,并提供源代碼
1.暢銷Docker容器實踐教程升級版,撰寫時參考的Docker版本是Docker 1.13;
2.114個實戰技巧為讀者提供解決方案以及一些細節和技巧方面的實踐經驗;
3.提供配套源代碼下載,
本書詳細介紹了一些堅實可靠的、經過檢驗的Docker技術,如替換虛擬機(VM)、啟用微服務架構、高效網路建模、離線生產和建立容器驅動的持續交付程序等,讓開發人員能夠按照手冊風格的“問題-解決方案-討論”模式探索真實案例,并學習如何將這些經驗應用到自己的開發專案中,
本書由淺入深地講解了Docker的相關內容,涵蓋從開發環境到DevOps流水線,再一路到生產環境的整個落地程序以及相關的實用技巧,書中介紹Docker的核心概念和架構,以及將Docker和開發環境有機、高效地結合起來的方法,包括背Docker用作輕量級虛擬機、構建容器、宿主機編排、配置管理、精簡鏡像等,不僅如此,本書還通過“問題-解決方案-討論”的形式,將Docker如何融入DevOps流水線、如何在生產環境落地等一系列難題拆解成114個相關的實用技巧,為讀者提供解決方案以及一些細節和技巧方面的實踐經驗,閱讀本書,讀者學到的不只是Docker,還包括持續集成、持續交付、構建和鏡像管理、容器編排等相關領域的一線生產經驗,本書撰寫時一些案例參考的Docker版本是Docker 1.13,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/259280.html
標籤:其他
下一篇:SVG中顯示一個表格
