、格式化過(guò)濾與容器運(yùn)維排錯(cuò))
docker ps 這條命令大概是所有人接觸容器技術(shù)時(shí)敲得最多、也最容易被一帶而過(guò)的一條。裝完環(huán)境第一件事是敲它確認(rèn)服務(wù)有沒(méi)有正常起來(lái)容器跑不起來(lái)第一反應(yīng)還是加個(gè) -a 翻一遍歷史。它看起來(lái)簡(jiǎn)單到不需要解釋——不就是列個(gè)表嗎可真到了線上排查問(wèn)題的時(shí)候很多人盯著那張只有容器 ID、鏡像、狀態(tài)、端口四列的默認(rèn)輸出愣是看不出容器到底為什么退出、端口為什么連不上、哪個(gè)容器在偷偷吃磁盤(pán)空間。這篇內(nèi)容我把 docker ps 從參數(shù)到格式化、從過(guò)濾到排錯(cuò)完整拆一遍順帶聊聊幾個(gè)只有踩過(guò)坑才知道的細(xì)節(jié)。不管你剛裝完環(huán)境想確認(rèn)是否正常還是已經(jīng)在維護(hù)幾十個(gè)容器、需要把這條命令接進(jìn)腳本和監(jiān)控下面這些應(yīng)該都能直接用上。讀完你至少能做成兩件事看狀態(tài)列就能大致判斷容器死因用 format 和 filter 把默認(rèn)那張表改造成適合自己工作流的形態(tài)。1. 先搞清楚 docker ps 到底在列什么1.1 一個(gè)“查看”命令為什么值得單獨(dú)拆很多人對(duì) docker ps 的理解停留在“容器版的 ls”覺(jué)得它沒(méi)有任何技術(shù)含量。這個(gè)判斷在只有一兩個(gè)容器的時(shí)候成立一旦容器數(shù)量上到兩位數(shù)、或者容器生命周期短到幾秒鐘就退出這條命令的信息密度就會(huì)突然變得極其關(guān)鍵。原因在于容器本身是個(gè)黑盒進(jìn)程你沒(méi)有辦法像查普通進(jìn)程那樣直接 attach 上去看內(nèi)部狀態(tài)docker ps 輸出的那幾列實(shí)際上是守護(hù)進(jìn)程從內(nèi)部數(shù)據(jù)結(jié)構(gòu)里摘出來(lái)的、關(guān)于這個(gè)容器當(dāng)前處境的全部公開(kāi)線索。我舉個(gè)具體的例子。一個(gè)容器反復(fù)重啟你在默認(rèn)輸出里看到的是Restarting (1) 5 seconds ago。這個(gè)(1)是退出碼1意味著應(yīng)用層自己報(bào)錯(cuò)退出而不是被信號(hào)殺掉如果是(137)那是 128 加 9說(shuō)明進(jìn)程收到了強(qiáng)制終止信號(hào)常見(jiàn)于內(nèi)存超限被系統(tǒng)干掉。同樣一列文字懂和不懂的人排查方向完全相反。所以這條命令的價(jià)值不在于“能看”而在于“看得懂”它把容器的創(chuàng)建時(shí)間、運(yùn)行時(shí)長(zhǎng)、端口綁定、健康檢查結(jié)果、退出原因全部壓縮進(jìn)幾行文本里你需要的是一套解讀方法。另外一層原因是可編程性。docker ps 支持結(jié)構(gòu)化的過(guò)濾和模板輸出意味著它不只是給人看的還能直接喂給 shell 腳本、監(jiān)控采集器、告警系統(tǒng)。日常運(yùn)維里大量重復(fù)動(dòng)作——批量停掉某個(gè)鏡像的所有容器、找出所有退出碼非零的實(shí)例、清理超過(guò)一定時(shí)間的僵尸容器——底層幾乎都靠這條命令先篩出目標(biāo)集合。把它的參數(shù)吃透后面寫(xiě)腳本能省掉一大半的解析邏輯。1.2 默認(rèn)輸出那幾個(gè)字段各自意味著什么敲一次不帶任何參數(shù)的 docker ps出來(lái)的是七列容器 ID、鏡像、啟動(dòng)命令、創(chuàng)建時(shí)間、狀態(tài)、端口、名稱(chēng)。這七列不是隨便排的從左到右基本遵循“從唯一標(biāo)識(shí)到人類(lèi)可讀描述”的順序。容器 ID 是十二位短哈希足夠日常區(qū)分但真要拿去寫(xiě)進(jìn)配置文件或者做精確匹配建議用--no-trunc拿完整的六十四位短 ID 在容器數(shù)量多的時(shí)候存在極小概率的前綴碰撞雖然概率低但腳本里因?yàn)檫@種問(wèn)題排查半天不劃算。鏡像列顯示的是容器創(chuàng)建時(shí)所用的鏡像名注意這里不會(huì)告訴你鏡像后來(lái)有沒(méi)有被更新過(guò)容器一旦創(chuàng)建它的鏡像引用就固定了所以看到鏡像標(biāo)簽是 latest 也別以為它一定是最新的。啟動(dòng)命令列默認(rèn)會(huì)被截?cái)嚅L(zhǎng)命令后面跟省略號(hào)想看全得加--no-trunc。創(chuàng)建時(shí)間列是相對(duì)時(shí)間比如“5 minutes ago”這個(gè)相對(duì)時(shí)間在跨時(shí)區(qū)協(xié)作或者寫(xiě)進(jìn)日志的時(shí)候容易產(chǎn)生歧義用格式化輸出改成絕對(duì)時(shí)間更穩(wěn)妥。狀態(tài)列信息量最大它同時(shí)包含了運(yùn)行時(shí)長(zhǎng)、健康檢查結(jié)果和退出原因三部分。端口列只在容器做了端口映射時(shí)才有內(nèi)容格式是“宿主地址:宿主端口-容器端口/協(xié)議”IPv4 和 IPv6 會(huì)各占一條用冒號(hào)分隔。名稱(chēng)列是容器名不指定的話系統(tǒng)會(huì)隨機(jī)生成一個(gè)由形容詞加人名組成的組合正式環(huán)境建議自己命名否則批量操作時(shí)根本認(rèn)不出誰(shuí)是誰(shuí)。1.3 命令家族與版本兼容的那點(diǎn)事早些年這條命令只有docker ps一種寫(xiě)法后來(lái)命令行做了重組按對(duì)象類(lèi)型歸類(lèi)于是多出了docker container ls和docker container list兩個(gè)等價(jià)寫(xiě)法。三者執(zhí)行結(jié)果完全一致底層走的是同一個(gè)接口區(qū)別只在于語(yǔ)義清晰度。新寫(xiě)法在腳本里更推薦因?yàn)樗鞔_表達(dá)了“我在列容器對(duì)象”可讀性更好而docker ps勝在短日常手敲更省事。兩種寫(xiě)法混用完全沒(méi)問(wèn)題不用糾結(jié)統(tǒng)一。需要留意的是版本差異。老版本里有些選項(xiàng)的行為和新版本不完全一致比如--format支持的字段在不同大版本之間有過(guò)增補(bǔ)健康檢查相關(guān)的狀態(tài)字段是后來(lái)才加的。如果你在寫(xiě)跨環(huán)境的部署腳本最好先確認(rèn)目標(biāo)環(huán)境的版本用一個(gè)最小字段集做兼容別一上來(lái)就用最新字段否則換臺(tái)機(jī)器就報(bào)模板解析錯(cuò)誤。我一般的做法是腳本里只依賴(lài)容器 ID、名稱(chēng)、狀態(tài)、鏡像這四個(gè)最穩(wěn)定的字段需要更多信息時(shí)再單獨(dú)查一次。2. 參數(shù)逐項(xiàng)拆解哪些每天都在用哪些關(guān)鍵時(shí)刻才想起2.1 控制顯示范圍-a、-l、-n、-q 的取舍默認(rèn)行為只列正在運(yùn)行的容器這一點(diǎn)新手最容易忽略。容器退出了docker ps里就看不見(jiàn)了于是很多人第一反應(yīng)是“我的容器沒(méi)了”其實(shí)它只是停了加-a就能看到全部包括已退出、已創(chuàng)建但沒(méi)啟動(dòng)的。這個(gè)-a是使用頻率僅次于默認(rèn)命令的選項(xiàng)排查問(wèn)題的第一步永遠(yuǎn)是docker ps -a先確認(rèn)容器到底是沒(méi)創(chuàng)建、創(chuàng)建了沒(méi)起來(lái)、還是起來(lái)又掛了。-l是只顯示最近創(chuàng)建的那個(gè)容器-n后面跟數(shù)字顯示最近創(chuàng)建的若干個(gè)。這兩個(gè)選項(xiàng)在交互式操作里很順手比如你剛docker run完一個(gè)容器想確認(rèn)狀態(tài)docker ps -l比翻全表快得多。但它們按創(chuàng)建時(shí)間排序不是按啟動(dòng)時(shí)間容器重建之后順序會(huì)變腳本里依賴(lài)這個(gè)順序要小心。-q只輸出容器 ID是批量操作的標(biāo)配。配合命令替換可以直接把結(jié)果喂給其他命令比如停掉所有運(yùn)行中的容器一行就夠。這個(gè)選項(xiàng)的輸出是純凈的 ID 列表沒(méi)有表頭沒(méi)有對(duì)齊空格天然適合管道。我見(jiàn)過(guò)有人在腳本里用docker ps加一堆文本處理去提取 ID其實(shí)一個(gè)-q就解決了省下來(lái)的解析邏輯全是潛在的 bug 來(lái)源。2.2 看清全量信息--no-trunc 與 -s 的使用場(chǎng)景--no-trunc的作用是不截?cái)噍敵鲋饕绊懭腥萜?ID 變成完整哈希啟動(dòng)命令完整顯示鏡像摘要也完整展示。什么時(shí)候必須用一種是要把容器 ID 寫(xiě)進(jìn)審計(jì)記錄或者工單系統(tǒng)短 ID 不夠嚴(yán)謹(jǐn)另一種是排查啟動(dòng)命令問(wèn)題時(shí)被截?cái)嗟哪前虢孛钋∏∈顷P(guān)鍵比如一個(gè)帶了很多參數(shù)的 Java 啟動(dòng)腳本省略號(hào)后面可能藏著錯(cuò)誤的環(huán)境變量。-s會(huì)額外加兩列顯示每個(gè)容器的可寫(xiě)層大小和虛擬磁盤(pán)總大小。這個(gè)選項(xiàng)要慎用因?yàn)樗枰蚴刈o(hù)進(jìn)程查詢每個(gè)容器的存儲(chǔ)占用容器數(shù)量多的時(shí)候會(huì)明顯變慢我在幾十個(gè)容器的環(huán)境里試過(guò)加上這個(gè)參數(shù)響應(yīng)時(shí)間能從幾十毫秒漲到一兩秒。它的正確用法是定點(diǎn)排查比如磁盤(pán)告警了用它找出哪個(gè)容器的可寫(xiě)層異常膨脹而不是把它寫(xiě)進(jìn)日常巡檢腳本里天天跑。另外注意可寫(xiě)層大小只統(tǒng)計(jì)容器自己寫(xiě)入的數(shù)據(jù)掛載的卷不計(jì)算在內(nèi)所以看到數(shù)字很小但磁盤(pán)還是滿的要去查卷的占用。2.3 參數(shù)組合時(shí)的優(yōu)先級(jí)與相互影響有幾個(gè)組合需要提前說(shuō)清楚避免用的時(shí)候犯迷糊。-q和--format同時(shí)出現(xiàn)時(shí)--format優(yōu)先因?yàn)楦袷交歉鞔_的輸出意圖-q只是它的一個(gè)簡(jiǎn)化特例。-a和-f statusexited組合時(shí)過(guò)濾條件在全集上生效不用額外再加-a因?yàn)闋顟B(tài)過(guò)濾本身就隱含了“要看已退出的”但為了語(yǔ)義清楚我一般還是會(huì)把-a寫(xiě)上。-l和-n同時(shí)出現(xiàn)時(shí)以-n為準(zhǔn)。--filter多個(gè)條件之間的默認(rèn)關(guān)系是“與”也就是必須同時(shí)滿足這點(diǎn)和很多人直覺(jué)一致但涉及名稱(chēng)過(guò)濾的正則匹配時(shí)容易踩坑后面過(guò)濾那一節(jié)會(huì)細(xì)說(shuō)。還有個(gè)隱性的坑多個(gè)--filter如果用了同一個(gè)鍵、不同值比如兩個(gè)status過(guò)濾實(shí)際效果是“或”而不是報(bào)錯(cuò)這個(gè)行為在不同版本上表現(xiàn)一致但文檔里寫(xiě)得不顯眼值得記一下。3. --format 實(shí)戰(zhàn)把列表變成自己的運(yùn)維看板3.1 模板語(yǔ)法的門(mén)檻其實(shí)比想象中低--format用的是 Go 模板語(yǔ)法聽(tīng)起來(lái)嚇人實(shí)際常用的就三樣?xùn)|西字段引用、制表符、換行。字段引用寫(xiě)成點(diǎn)加字段名比如容器 ID 就是.ID名稱(chēng)是.Names狀態(tài)是.Status。要在同一行拼多個(gè)字段中間用\t分隔這是制表符的轉(zhuǎn)義寫(xiě)法能讓輸出自動(dòng)對(duì)齊成表格。最常用的寫(xiě)法是在模板前面加table關(guān)鍵字它會(huì)自動(dòng)補(bǔ)上表頭并按列對(duì)齊。不加table的話就是純文本逐行輸出適合喂給腳本。我第一次用的時(shí)候不知道有這個(gè)關(guān)鍵字手動(dòng)用空格對(duì)齊了半天結(jié)果字段長(zhǎng)度一變?nèi)珌y后來(lái)發(fā)現(xiàn)一個(gè) table 就解決了所有對(duì)齊問(wèn)題。值得強(qiáng)調(diào)的是模板里可以塞普通文本和符號(hào)比如給每行加個(gè)前綴、在字段之間加豎線做視覺(jué)分隔、甚至拼出類(lèi)似 JSON 的結(jié)構(gòu)。這讓同一條命令既能輸出給人看的報(bào)表也能輸出給程序解析的數(shù)據(jù)不用再寫(xiě)兩套邏輯。3.2 常用字段清單與拼接技巧日常用得最多的字段就那么幾個(gè)我把它們和默認(rèn)列做個(gè)對(duì)應(yīng)方便遷移習(xí)慣。.ID對(duì)應(yīng)容器 ID.Image對(duì)應(yīng)鏡像.Command對(duì)應(yīng)啟動(dòng)命令.CreatedAt對(duì)應(yīng)創(chuàng)建時(shí)間.RunningFor是運(yùn)行時(shí)長(zhǎng).Ports是端口.Names是名稱(chēng).State是簡(jiǎn)短狀態(tài)詞.Status是帶細(xì)節(jié)的完整狀態(tài)描述。區(qū)分.State和.Status很重要前者只有 running、exited 這種單詞適合做條件判斷后者帶時(shí)長(zhǎng)和退出碼適合給人看。還有幾個(gè)不那么常用但很有價(jià)值的字段。.Labels輸出容器的所有標(biāo)簽做標(biāo)簽化管理的團(tuán)隊(duì)可以用它快速核對(duì).Mounts列出掛載信息排查卷映射問(wèn)題時(shí)直接看.Networks顯示容器接入的網(wǎng)絡(luò).LocalVolumes是本地卷數(shù)量。健康檢查相關(guān)的狀態(tài)在.Status里以括號(hào)形式體現(xiàn)想要單獨(dú)取出來(lái)需要一些字符串處理模板本身不支持條件分支的復(fù)雜邏輯這時(shí)候就退回 shell 去切。拼接上我常用的一個(gè)套路是給輸出加統(tǒng)一前綴比如在每行前面加上時(shí)間戳方便和其他日志合并。模板里直接寫(xiě)死文本沒(méi)問(wèn)題但動(dòng)態(tài)時(shí)間戳得靠 shell 變量傳進(jìn)去命令替換和引號(hào)轉(zhuǎn)義要小心雙引號(hào)里套單引號(hào)、再套反引號(hào)是最容易出錯(cuò)的組合我一般先用 echo 把完整命令打出來(lái)確認(rèn)轉(zhuǎn)義正確再去執(zhí)行。3.3 面向腳本的輸出從人讀到機(jī)器讀人看的表格和機(jī)器解析的格式要分開(kāi)設(shè)計(jì)。給人看的時(shí)候列寬對(duì)齊、表頭清晰、信息密度適中給腳本用的時(shí)候字段越少越好、分隔符越明確越好、不要表頭。我的習(xí)慣是維護(hù)兩個(gè)模板一個(gè)用于日常查看包含名稱(chēng)、狀態(tài)、端口、鏡像四列另一個(gè)用于腳本只輸出 ID 和名稱(chēng)用冒號(hào)分隔方便后面用 cut 或者 read 拆。舉個(gè)實(shí)際用法批量重啟某個(gè)鏡像下的所有容器先篩出目標(biāo)再循環(huán)操作整個(gè)過(guò)程不用碰任何解析 HTML 或者 JSON 的庫(kù)。這里的關(guān)鍵是讓 docker ps 的輸出格式固定下來(lái)腳本里只依賴(lài)位置不依賴(lài)文本匹配穩(wěn)定性會(huì)高很多。如果你需要處理容器名里帶空格或者特殊字符的情況記得在 shell 循環(huán)里用 while read 而不是 forfor 會(huì)按空格拆分遇到帶空格的名稱(chēng)直接崩。4. --filter 過(guò)濾從一堆容器里精準(zhǔn)撈針4.1 常用過(guò)濾鍵與表達(dá)式寫(xiě)法過(guò)濾是這條命令里最被低估的能力。常用的過(guò)濾鍵有七八個(gè)按狀態(tài)status、按名稱(chēng)name、按鏡像ancestor、按標(biāo)簽label、按退出碼exited、按端口publish和expose、按網(wǎng)絡(luò)network、按卷volume、按創(chuàng)建時(shí)間先后before和since。寫(xiě)法統(tǒng)一是-f 鍵值等號(hào)兩邊不要加空格否則會(huì)被 shell 當(dāng)成兩個(gè)參數(shù)。名稱(chēng)過(guò)濾用的是子串匹配不是精確匹配也就是說(shuō)-f nameweb會(huì)同時(shí)命中 web、web-1、my-web-test。想要精確匹配得用正則的錨定寫(xiě)法加^和$。這一點(diǎn)在批量操作里特別危險(xiǎn)你以為只選中了一個(gè)容器實(shí)際選中了一批。我吃過(guò)一次虧清理測(cè)試容器的時(shí)候用了寬泛的名稱(chēng)匹配結(jié)果把同前綴的生產(chǎn)容器一起停了從那以后所有涉及寫(xiě)操作的過(guò)濾一律加錨定。按鏡像過(guò)濾要理解它的語(yǔ)義匹配的是容器創(chuàng)建時(shí)引用的鏡像包括該鏡像的所有歷史版本關(guān)系。用鏡像名過(guò)濾時(shí)同名不同標(biāo)簽的容器都會(huì)被選中用鏡像 ID 過(guò)濾則更精確。如果你不確定某個(gè)容器到底基于哪個(gè)鏡像ancestor配合完整 ID 最保險(xiǎn)。4.2 多條件組合的與或關(guān)系默認(rèn)多個(gè)過(guò)濾條件之間是“與”也就是取交集這個(gè)符合直覺(jué)。但有兩種情況會(huì)打破這個(gè)默認(rèn)。第一種是同一個(gè)鍵給多個(gè)值比如兩個(gè)status條件實(shí)際是“或”的關(guān)系等于列出狀態(tài) A 或者狀態(tài) B 的容器。第二種是部分版本對(duì)label鍵的處理同一個(gè)標(biāo)簽鍵給不同的值行為可能和預(yù)期有出入。穩(wěn)妥的做法是遇到多值場(chǎng)景先在測(cè)試環(huán)境驗(yàn)證一下結(jié)果集別直接寫(xiě)進(jìn)自動(dòng)化流程。組合的實(shí)用例子列出所有已退出且退出碼非零的容器用兩個(gè)條件就行statusexited加上exited1之類(lèi)別。再比如找出某個(gè)網(wǎng)絡(luò)下、某個(gè)鏡像的所有運(yùn)行中容器三個(gè)條件疊加一行命令就能把目標(biāo)鎖定到一個(gè)很小的集合后面接操作命令風(fēng)險(xiǎn)低很多。條件越多集合越小出錯(cuò)的代價(jià)也越小這是我寫(xiě)運(yùn)維腳本時(shí)的一條基本原則。還有個(gè)容易忽略的點(diǎn)過(guò)濾是作用在命令看到的全集上的-a影響的是默認(rèn)展示范圍但一旦你用了狀態(tài)過(guò)濾范圍會(huì)自動(dòng)擴(kuò)展到匹配該狀態(tài)的容器不需要額外加-a。理解這一點(diǎn)能少寫(xiě)很多冗余參數(shù)。4.3 狀態(tài)過(guò)濾與健康檢查的細(xì)節(jié)狀態(tài)過(guò)濾常用的值有 running、exited、created、paused、restarting、dead。這里要特別注意 paused 和 restarting 這兩個(gè)中間態(tài)它們既不是完全活著也不是完全死了很多監(jiān)控腳本只判斷 running結(jié)果漏掉了卡在 restarting 里的容器。我一般會(huì)單獨(dú)維護(hù)一個(gè)“異常狀態(tài)”過(guò)濾把 restarting 和 dead 都拉出來(lái)告警。健康檢查的判斷不在狀態(tài)過(guò)濾里它藏在完整狀態(tài)描述里。一個(gè)容器可能是 running 但健康檢查結(jié)果是 unhealthy這種情況在默認(rèn)視圖里只有狀態(tài)列的括號(hào)能看出來(lái)。做健康巡檢的時(shí)候不能只看statusrunning還得對(duì)輸出做二次判斷或者用格式化把完整狀態(tài)取出來(lái)后匹配 unhealthy 關(guān)鍵字。這塊是我覺(jué)得這條命令設(shè)計(jì)上稍微別扭的地方希望以后能直接支持健康狀態(tài)的過(guò)濾鍵。5. 幾個(gè)真實(shí)場(chǎng)景的排查過(guò)程5.1 容器啟動(dòng)就退出從狀態(tài)列讀出死因容器起來(lái)幾秒鐘就退出是最高頻的問(wèn)題。排查順序我固定為三步。第一步docker ps -a找到那個(gè)容器重點(diǎn)看狀態(tài)列的退出碼。退出碼 0 表示進(jìn)程正常結(jié)束多半是啟動(dòng)命令跑完就退出了比如一個(gè)前臺(tái)進(jìn)程寫(xiě)成了后臺(tái)執(zhí)行或者腳本里缺了保持前臺(tái)運(yùn)行的指令。退出碼 1 是應(yīng)用自己報(bào)錯(cuò)得去看日志。退出碼 126 和 127 分別是命令不可執(zhí)行和命令找不到通常是啟動(dòng)命令寫(xiě)錯(cuò)或者鏡像里沒(méi)有這個(gè)可執(zhí)行文件。第二步是如果退出碼是 137說(shuō)明進(jìn)程收到了強(qiáng)制終止信號(hào)最常見(jiàn)的誘因是內(nèi)存超過(guò)限制被系統(tǒng)終止也可能是有人手動(dòng)發(fā)了終止命令。這時(shí)候去看容器的資源限制配置和宿主機(jī)的內(nèi)存水位基本能定位。退出碼 143 對(duì)應(yīng)的是正常的終止信號(hào)一般是容器被優(yōu)雅停止屬于正常退出范疇。第三步才去看日志。很多人一上來(lái)就翻日志其實(shí)狀態(tài)列已經(jīng)把大方向指明了先讀退出碼能省掉大量無(wú)效翻找。這個(gè)習(xí)慣我建議所有人都養(yǎng)成尤其是在容器數(shù)量多、日志量大的環(huán)境里。5.2 端口映射異常從端口列還原綁定關(guān)系服務(wù)連不上端口問(wèn)題占很大比例。默認(rèn)輸出的端口列寫(xiě)得很緊湊格式是宿主地址冒號(hào)宿主端口箭頭指向容器端口和協(xié)議??催@一列要關(guān)注兩件事綁定地址是什么宿主端口是多少。綁定地址顯示為全零地址表示監(jiān)聽(tīng)所有網(wǎng)卡顯示為回環(huán)地址則表示只有本機(jī)能訪問(wèn)外部當(dāng)然連不上。這個(gè)區(qū)別在本地開(kāi)發(fā)時(shí)經(jīng)常被忽略容器里服務(wù)正常、本機(jī) curl 也通換個(gè)機(jī)器就是不通原因往往就在這里。另一個(gè)高頻問(wèn)題是宿主端口沖突或者沒(méi)映射。容器內(nèi)部的端口是容器自己的不映射到宿主外面根本摸不到。想確認(rèn)某個(gè)端口被誰(shuí)占了用端口過(guò)濾能快速定位到具體容器。如果發(fā)現(xiàn)同一個(gè)宿主端口出現(xiàn)多條映射記錄說(shuō)明有容器啟動(dòng)失敗或者配置寫(xiě)重復(fù)了這時(shí)候要結(jié)合狀態(tài)列看哪個(gè)是真的在運(yùn)行。5.3 批量清理前的安全確認(rèn)清理容器是危險(xiǎn)操作尤其是帶數(shù)據(jù)卷的。我的固定流程是先列后刪絕不直接刪。先用過(guò)濾把目標(biāo)集合列出來(lái)加上完整的狀態(tài)、名稱(chēng)、鏡像、創(chuàng)建時(shí)間四列人工掃一遍確認(rèn)沒(méi)有誤傷再執(zhí)行刪除。列表這一步省不得我曾經(jīng)寫(xiě)過(guò)一個(gè)自以為很穩(wěn)的過(guò)濾條件結(jié)果因?yàn)槊Q(chēng)匹配太寬泛選中了不該選的容器好在先列后刪的習(xí)慣救了我一次。對(duì)于已經(jīng)退出的容器清理前還要確認(rèn)它掛載的卷有沒(méi)有數(shù)據(jù)。容器刪了卷默認(rèn)保留但如果當(dāng)初是用自動(dòng)清理方式啟動(dòng)的卷會(huì)跟著走。這個(gè)判斷靠容器詳情里的掛載信息光看列表不夠得再查一次。批量操作里我一般會(huì)加一個(gè)確認(rèn)環(huán)節(jié)把要?jiǎng)h的容器數(shù)量打印出來(lái)數(shù)量超過(guò)預(yù)期就中止人眼過(guò)一遍再放行。6. 常見(jiàn)問(wèn)題與踩坑記錄6.1 問(wèn)題速查表現(xiàn)象可能原因排查動(dòng)作容器列表里找不到剛啟動(dòng)的容器容器已退出默認(rèn)只顯示運(yùn)行中加-a查看全部狀態(tài)顯示 Restarting 反復(fù)出現(xiàn)應(yīng)用啟動(dòng)失敗觸發(fā)重啟策略看退出碼和日志確認(rèn)重啟策略是否合理狀態(tài)顯示 Up 但服務(wù)不通端口未映射或綁定地址受限檢查端口列和綁定地址狀態(tài)含 unhealthy健康檢查探測(cè)失敗單獨(dú)查健康檢查配置和探測(cè)日志容器 ID 短哈希重復(fù)前綴碰撞容器數(shù)量大用--no-trunc取完整 ID格式化輸出報(bào)模板錯(cuò)誤字段名寫(xiě)錯(cuò)或版本不支持確認(rèn)字段拼寫(xiě)和目標(biāo)環(huán)境版本名稱(chēng)過(guò)濾選中了多余容器子串匹配未加錨定用錨定寫(xiě)法做精確匹配磁盤(pán)滿但可寫(xiě)層很小數(shù)據(jù)在掛載卷里單獨(dú)查卷占用6.2 幾條不太容易在網(wǎng)上找到的經(jīng)驗(yàn)第一條別把docker ps的調(diào)用頻率開(kāi)太高。有些監(jiān)控方案圖省事每隔一兩秒輪詢一次容器數(shù)量一多守護(hù)進(jìn)程的響應(yīng)壓力會(huì)上來(lái)極端情況下會(huì)拖慢其他操作的響應(yīng)。輪詢間隔我一般設(shè)在十秒以上真要秒級(jí)感知狀態(tài)變化應(yīng)該走事件訂閱而不是輪詢。第二條腳本里永遠(yuǎn)不要依賴(lài)默認(rèn)輸出的列順序和對(duì)齊。默認(rèn)表格是為了人眼優(yōu)化字段之間的空格數(shù)量會(huì)隨內(nèi)容長(zhǎng)度變化用文本切割去解析必然翻車(chē)。要解析就用--format固定分隔符或者干脆用-q只拿 ID。第三條涉及寫(xiě)操作的過(guò)濾條件先在末尾加一個(gè)--format把目標(biāo)打印出來(lái)跑一遍。這個(gè)習(xí)慣看起來(lái)多一步實(shí)際能擋掉絕大多數(shù)誤操作。我在好幾個(gè)環(huán)境里把這個(gè)動(dòng)作做成了肌肉記憶寧可慢十秒不要讓一次誤刪變成事故。最后分享一個(gè)我自己常用的小組合一條命令打印出所有容器的名稱(chēng)、完整狀態(tài)、鏡像和創(chuàng)建時(shí)間按創(chuàng)建時(shí)間倒序?qū)V巍拔矣浀脛偱苓^(guò)一個(gè)什么但不知道叫啥”的健忘癥。字段不多信息夠全掃一眼就能定位。容器多起來(lái)之后這種自己順手的小命令比任何復(fù)雜的監(jiān)控面板都來(lái)得快。