優(yōu)與安全收口)
Docker 容器與虛擬化技術(shù)這條線上的活我這幾年干得最多的不是“怎么把容器跑起來(lái)”而是“怎么讓一群人能安全地看見并管住跑起來(lái)的容器”。openEuler 部署 Docker UI 就是典型的后者機(jī)器裝好、引擎起來(lái)了不代表團(tuán)隊(duì)能用得順手命令行敲得再熟交接給同事、交給值班同學(xué)、交給不常碰服務(wù)器的人效率立刻掉一半。所以這篇我想聊的是完整的落地鏈路——從容器和虛擬化的取舍開始到 openEuler 上的引擎安裝、參數(shù)調(diào)優(yōu)再到 Docker UI 的選型、部署、入口收口和后續(xù)的日常巡檢把我踩過的坑和調(diào)過的參數(shù)都攤開講。先說(shuō)清楚這東西是什么。Docker UI 是給 Docker 引擎套的一層 Web 管理界面它本身也是一個(gè)容器通過掛載宿主機(jī)的容器運(yùn)行時(shí)接口來(lái)讀取和操作容器、鏡像、網(wǎng)絡(luò)、數(shù)據(jù)卷。它能解決三件事第一把docker ps、docker logs、docker exec這些高頻動(dòng)作變成點(diǎn)選第二讓多人協(xié)作時(shí)有賬號(hào)和權(quán)限的概念而不是所有人共用一個(gè) root shell第三把“部署一套服務(wù)”沉淀成模板或編排文件減少口口相傳。適合的讀者范圍其實(shí)挺寬剛接手一臺(tái) openEuler 服務(wù)器、想把容器管起來(lái)的新手能照著把流程走完已經(jīng)在用 Docker 但界面還停留在命令行的老手可以重點(diǎn)看參數(shù)調(diào)優(yōu)、安全收口和排查那幾節(jié)。1. 動(dòng)手之前先想清楚為什么是 openEuler 加 Docker UI1.1 容器與虛擬化不是替代關(guān)系是兩把不同型號(hào)的刀很多人一上來(lái)就問“容器能不能完全取代虛擬機(jī)”這個(gè)問題本身就問偏了。虛擬化是在硬件之上插一層 Hypervisor給每臺(tái)虛擬機(jī)切出獨(dú)立的內(nèi)核、獨(dú)立的設(shè)備視圖隔離邊界在硬件抽象層容器是復(fù)用宿主機(jī)內(nèi)核靠 namespace 做視圖隔離、靠 cgroup 做資源限制隔離邊界在操作系統(tǒng)內(nèi)核里。這個(gè)差別決定了它們?cè)趩?dòng)速度、資源密度、隔離強(qiáng)度上的全部差異也決定了你在 openEuler 上做選型時(shí)該怎么擺位置。我給一個(gè)自己常用的對(duì)照方便你直接對(duì)著業(yè)務(wù)場(chǎng)景挑對(duì)比維度虛擬機(jī)容器隔離層級(jí)硬件抽象層獨(dú)立內(nèi)核內(nèi)核級(jí)共享宿主內(nèi)核啟動(dòng)耗時(shí)十幾秒到一分鐘毫秒到幾秒資源開銷需要預(yù)留固定內(nèi)存與 vCPU按需占用超賣空間大鏡像體積GB 級(jí)別幾十 MB 到幾百 MB單機(jī)密度十幾到幾十臺(tái)幾十到上百個(gè)典型用途強(qiáng)隔離、異構(gòu)系統(tǒng)、遺留應(yīng)用微服務(wù)、持續(xù)集成、灰度發(fā)布看這張表你會(huì)發(fā)現(xiàn)一個(gè)規(guī)律需要“內(nèi)核級(jí)隔離”和“跑不同內(nèi)核版本”的場(chǎng)景虛擬機(jī)仍然是唯一答案需要“密度”和“交付速度”的場(chǎng)景容器優(yōu)勢(shì)壓倒性。我實(shí)際的做法是分層——底層用虛擬機(jī)切出資源池和故障域池子里跑 openEuler再用 Docker 在池子里切服務(wù)。UI 這一層就落在這個(gè)混合結(jié)構(gòu)的上半部分它管的是容器不管虛機(jī)所以定位一定要清楚別指望它替你把資源池也管了。提示如果你所在的環(huán)境里已經(jīng)有成熟的虛擬化平臺(tái)Docker UI 的定位應(yīng)該是“應(yīng)用側(cè)的可視化入口”不要試圖讓它承擔(dān)基礎(chǔ)設(shè)施層的編排職責(zé)職責(zé)一旦混了權(quán)限模型會(huì)變得很難收口。1.2 openEuler 作為宿主發(fā)行版特性帶來(lái)的真實(shí)影響openEuler 這幾年在服務(wù)器側(cè)的存在感越來(lái)越強(qiáng)它默認(rèn)內(nèi)核版本較新cgroup v2 支持完整安全加固策略也相對(duì)嚴(yán)格。這些特性對(duì)跑容器是好事但也帶來(lái)幾個(gè)必須在部署前確認(rèn)的點(diǎn)。第一是 cgroup 版本cgroup v1 和 v2 下容器引擎的資源限制行為不完全一樣docker info里能看到 Cgroup Driver 和 Cgroup Version裝完第一件事就該看這個(gè)。第二是 SELinux 的默認(rèn)狀態(tài)openEuler 上通常是 enforcing 或 permissive這兩個(gè)狀態(tài)對(duì)掛載宿主機(jī) socket 文件的影響完全不同后面第三節(jié)我會(huì)專門講。第三是軟件源。openEuler 有自己的軟件倉(cāng)庫(kù)體系社區(qū)也維護(hù)了一部分容器相關(guān)包但版本更新節(jié)奏和上游不完全同步。我在實(shí)際項(xiàng)目里遇到過 EPOL 倉(cāng)庫(kù)里的引擎版本落后于業(yè)務(wù)需要的 API 版本導(dǎo)致某些 compose 特性用不了。這種情況下我一般優(yōu)先選擇“官方靜態(tài)二進(jìn)制 自建 systemd 單元”的方式繞開發(fā)行版打包差異代價(jià)是要自己處理依賴和升級(jí)收益是版本完全可控、行為可預(yù)期。還有一個(gè)容易被忽略的點(diǎn)openEuler 默認(rèn)可能預(yù)裝了 podman 或 buildah 這類容器工具它們和 Docker 的命令行工具在部分路徑上會(huì)打架尤其是/usr/bin/docker這個(gè)位置。裝之前先確認(rèn)一下有沒有殘留比裝完再排查省事得多。這個(gè)我在第四節(jié)會(huì)給出檢查命令。1.3 UI 解決的是“人”的問題別把它當(dāng)技術(shù)方案我想強(qiáng)調(diào)一個(gè)觀點(diǎn)Docker UI 不提升任何技術(shù)指標(biāo)。它不會(huì)讓你的容器跑得更快不會(huì)讓鏡像變小也不會(huì)自動(dòng)幫你做高可用。它解決的是協(xié)作成本——值班同學(xué)半夜要看日志不用再找人要 SSH 權(quán)限新人接手項(xiàng)目能直觀看到有哪些容器、映射了什么端口、掛在哪個(gè)卷上交接時(shí)不用再寫“這臺(tái)機(jī)器上跑了什么”的文檔界面本身就是文檔。理解了這一點(diǎn)你在部署時(shí)的取舍就會(huì)清楚很多。既然是給人用的那么訪問入口、賬號(hào)體系、審計(jì)記錄就比“功能多不多”更重要既然是內(nèi)部工具就不要把它暴露在公網(wǎng)也不要用默認(rèn)端口和默認(rèn)密碼。我見過太多把管理面板直接映射到 0.0.0.0 上、跑了半年沒人管的例子這類資產(chǎn)一旦被掃到等于把整臺(tái)機(jī)器的 root 權(quán)限拱手讓人因?yàn)?UI 容器掛載了容器運(yùn)行時(shí)接口它天然具備宿主機(jī)的等效權(quán)限。這句話不是危言聳聽后面講 socket 掛載時(shí)我會(huì)展開。2. 部署前的環(huán)境盤點(diǎn)三個(gè)必須先確認(rèn)的硬條件2.1 系統(tǒng)版本、內(nèi)核與磁盤的規(guī)劃方法動(dòng)手前先跑幾條命令把底摸清比裝到一半發(fā)現(xiàn)問題強(qiáng)。系統(tǒng)版本用cat /etc/openEuler-release內(nèi)核用uname -r架構(gòu)用uname -m。openEuler 主流在用的是 22.03 LTS 和 24.03 LTS 兩個(gè)系列前者內(nèi)核 5.10后者更新一些兩者都完整支持 Docker 需要的 namespace 和 cgroup 特性不需要額外編譯內(nèi)核模塊。磁盤是重點(diǎn)。Docker 的默認(rèn)數(shù)據(jù)目錄在/var/lib/docker這里會(huì)同時(shí)存放鏡像層、容器可寫層、數(shù)據(jù)卷和構(gòu)建緩存。我的經(jīng)驗(yàn)值是只跑幾個(gè)輕量服務(wù)給 40GB 起步跑十幾個(gè)中等規(guī)模服務(wù)100GB 比較踏實(shí)如果涉及數(shù)據(jù)庫(kù)容器或者構(gòu)建流水線單獨(dú)掛一塊盤給/var/lib/docker是必須的。原因很直接——鏡像層是共享的刪了標(biāo)簽數(shù)據(jù)層未必馬上釋放加上日志默認(rèn)不限制大小一臺(tái)跑了三個(gè)月的機(jī)器被日志撐爆根分區(qū)是極常見的事故。這里給一個(gè)可以照著算的公式用來(lái)估日志占用上限單容器日志上限等于max-size乘以max-file。假設(shè)你設(shè)置成 50MB 和 3 個(gè)文件那么單個(gè)容器最多占 150MB機(jī)器上跑 50 個(gè)容器最壞情況就是 7.5GB。這個(gè)數(shù)字必須在磁盤規(guī)劃里預(yù)留出來(lái)而不是等它漲到把根分區(qū)吃滿再去清理。我在第六節(jié)會(huì)給出對(duì)應(yīng)的daemon.json配置。內(nèi)存方面引擎本身加 UI 的常駐開銷并不大Docker 守護(hù)進(jìn)程通常在 100MB 到 200MB 量級(jí)Portainer 這類面板容器也差不多在 100MB 上下真正吃內(nèi)存的是業(yè)務(wù)容器。所以規(guī)劃時(shí)別把 UI 當(dāng)成負(fù)擔(dān)它幾乎可以忽略不計(jì)。不過有一點(diǎn)要注意如果宿主機(jī)內(nèi)存比較緊張務(wù)必給容器設(shè)置內(nèi)存上限否則單個(gè)容器內(nèi)存泄漏會(huì)觸發(fā)宿主機(jī) OOM Killer它挑誰(shuí)下手是隨機(jī)的殺掉引擎進(jìn)程導(dǎo)致整機(jī)容器全掛的情況我遇到過不止一次。2.2 軟件源、依賴與舊組件的清理順序裝 Docker 之前有一輪清理工作順序錯(cuò)了會(huì)很麻煩。第一步查殘留rpm -qa | grep -Ei docker|podman|buildah|containerd|runc把結(jié)果看一眼。如果存在 podman 和 buildah它們和 Docker 共享runc和部分容器運(yùn)行時(shí)組件同時(shí)裝可能引發(fā)依賴沖突實(shí)踐中最省事的處理是先卸載它們裝完 Docker 需要的話再單獨(dú)裝回來(lái)。第二步處理倉(cāng)庫(kù)。openEuler 上可以走社區(qū)維護(hù)的容器相關(guān)倉(cāng)庫(kù)也可以直接配置上游的軟件源。走上游源時(shí)有個(gè)坑必須提前說(shuō)上游倉(cāng)庫(kù)路徑里通常帶$releasever變量而 openEuler 的$releasever值是22.03這種格式拼出來(lái)的路徑在上游并不存在dnf makecache會(huì)直接報(bào) 404。解決辦法是把倉(cāng)庫(kù)文件里的變量寫死成對(duì)應(yīng)的主版本號(hào)比如按兼容性填8或9或者干脆不用倉(cāng)庫(kù)走靜態(tài)二進(jìn)制。這個(gè)細(xì)節(jié)網(wǎng)上很多教程不會(huì)提但它確實(shí)是新手卡住時(shí)間最長(zhǎng)的一步。第三步是基礎(chǔ)依賴。靜態(tài)二進(jìn)制方案需要iptables、iproute、libcgroup、systemd這些openEuler 默認(rèn)基本都齊了只需要確認(rèn)iptables是 nft 還是 legacy 后端。新版 Docker 默認(rèn)走 nft 后端如果系統(tǒng)里只有 legacy 的iptables引擎啟動(dòng)后創(chuàng)建規(guī)則會(huì)失敗表現(xiàn)為容器端口映射不生效但容器本身是運(yùn)行狀態(tài)。檢查命令是iptables --version如果輸出里沒有nf_tables字樣就需要額外裝iptables-nft包讓兩個(gè)后端并存。注意清理和卸載動(dòng)作一定要在業(yè)務(wù)低峰做并要求有回滾手段。刪除包的同時(shí)如果帶走了配置文件重建所需的時(shí)間往往比預(yù)想的長(zhǎng)。2.3 網(wǎng)絡(luò)、時(shí)間與安全基線的三件小事第一件是時(shí)間同步。容器里跑的東西一旦涉及證書、令牌、日志時(shí)間戳宿主時(shí)間漂移就是災(zāi)難。timedatectl status看一眼有沒有同步源chronyc tracking看偏差。我一般要求偏差控制在秒級(jí)以內(nèi)因?yàn)?TLS 證書校驗(yàn)對(duì)時(shí)間很敏感差出幾分鐘就會(huì)報(bào)證書未生效或已過期而這個(gè)報(bào)錯(cuò)在容器里往往被翻譯成“連接失敗”排查方向會(huì)被帶偏。第二件是防火墻。openEuler 默認(rèn)可能啟用 firewalld這里有一個(gè)非常經(jīng)典的坑Docker 啟動(dòng)時(shí)會(huì)自己往 iptables 的nat表和filter表里寫規(guī)則而這些規(guī)則位于 firewalld 管理鏈的下游導(dǎo)致你通過 firewalld 關(guān)掉的端口只要被容器映射了仍然能從外部訪問。也就是說(shuō)firewall-cmd --remove-port對(duì)容器端口是無(wú)效的。正確的做法有兩種要么把容器端口綁定到127.0.0.1上只讓本機(jī)的 Nginx 轉(zhuǎn)發(fā)要么把限制規(guī)則寫進(jìn)DOCKER-USER鏈這個(gè)鏈?zhǔn)?Docker 專門留給用戶自定義規(guī)則的入口位置在容器規(guī)則之前優(yōu)先級(jí)正確。第三件是主機(jī)名和 DNS。給機(jī)器起一個(gè)能看懂的名字比如docker-ui-01后續(xù)在界面里看到節(jié)點(diǎn)名字時(shí)不會(huì)一臉懵。DNS 方面建議確認(rèn)/etc/resolv.conf里沒有指向127.0.0.53這類只有本機(jī) systemd-resolved 才認(rèn)的地址因?yàn)槿萜髂J(rèn)繼承宿主 DNS 配置而127.0.0.53在容器網(wǎng)絡(luò)命名空間里是不可達(dá)的結(jié)果就是容器能啟動(dòng)、能 ping 通 IP但域名解析全部失敗。這個(gè)問題在第六節(jié)還會(huì)詳細(xì)講。3. 引擎安裝的完整實(shí)操兩條路線與參數(shù)調(diào)優(yōu)3.1 路線選擇倉(cāng)庫(kù)安裝還是靜態(tài)二進(jìn)制我把兩條路線的差異攤開方便你按場(chǎng)景選。倉(cāng)庫(kù)安裝的優(yōu)點(diǎn)是省事dnf install一條命令依賴自動(dòng)處理升級(jí)也走dnf upgrade缺點(diǎn)是版本受限于倉(cāng)庫(kù)遇到上游打包滯后時(shí)沒轍。靜態(tài)二進(jìn)制的優(yōu)點(diǎn)是版本完全由你決定解壓即用遷移方便缺點(diǎn)是需要自己寫 systemd 單元升級(jí)要手動(dòng)替換二進(jìn)制并重啟而且出問題時(shí)沒有包管理器的完整性校驗(yàn)兜底。我的選擇邏輯是如果是個(gè)人實(shí)驗(yàn)或者小規(guī)模環(huán)境倉(cāng)庫(kù)安裝足夠如果是團(tuán)隊(duì)共用、需要長(zhǎng)期維護(hù)、對(duì)版本有明確要求走靜態(tài)二進(jìn)制更省心。下面兩條路線我都會(huì)給出關(guān)鍵步驟倉(cāng)庫(kù)那條我會(huì)寫清變量替換的坑靜態(tài)那條我會(huì)寫全單元文件。3.2 路線一配置軟件源后安裝倉(cāng)庫(kù)配置文件放在/etc/yum.repos.d/下名字隨意后綴必須是.repo。內(nèi)容的關(guān)鍵是baseurl里的版本號(hào)不能留變量gpgcheck保持開啟gpgkey指向?qū)?yīng)的密鑰地址。寫完執(zhí)行dnf clean all dnf makecache能正常拉到元數(shù)據(jù)說(shuō)明配置對(duì)了。安裝時(shí)我習(xí)慣顯式帶上--allowerasing原因是如果系統(tǒng)里還有舊的運(yùn)行時(shí)組件不加這個(gè)參數(shù)會(huì)因?yàn)槲募_突直接失敗報(bào)錯(cuò)信息里一堆file /usr/bin/xxx conflicts新手看了容易懵。帶上之后 dnf 會(huì)自動(dòng)把沖突的包換掉代價(jià)是你得確認(rèn)被換掉的包不是業(yè)務(wù)依賴。裝完之后立刻驗(yàn)證docker version看客戶端和服務(wù)端是否都返回只出來(lái)客戶端說(shuō)明服務(wù)端沒起docker info看Server Version、Storage Driver、Cgroup Version、Kernel Version四項(xiàng)這四項(xiàng)是后續(xù)所有問題的判斷依據(jù)。還需要處理用戶組。把日常運(yùn)維賬號(hào)加進(jìn)docker組避免每條命令都sudousermod -aG docker your_user newgrp docker這里必須說(shuō)一句實(shí)話加入docker組等價(jià)于拿到 root 權(quán)限因?yàn)槟憧梢灾苯觗ocker run -v /:/host把宿主機(jī)根目錄掛進(jìn)容器再改文件。這不是設(shè)計(jì)缺陷是機(jī)制使然。所以我的做法是只給確實(shí)需要的人開同時(shí)把 UI 的權(quán)限體系用起來(lái)讓大部分人通過 UI 操作而不是直接上 SSH。3.3 路線二靜態(tài)二進(jìn)制安裝與 systemd 單元下載對(duì)應(yīng)架構(gòu)的壓縮包解壓后把docker/目錄下的可執(zhí)行文件復(fù)制到/usr/bin/然后在/etc/systemd/system/docker.service寫單元文件。核心配置如下[Unit] DescriptionDocker Application Container Engine Afternetwork-online.target firewalld.service Wantsnetwork-online.target [Service] Typenotify ExecStart/usr/bin/dockerd ExecReload/bin/kill -s HUP $MAINPID TimeoutStartSec0 Restartalways RestartSec2 LimitNOFILEinfinity LimitNPROCinfinity Delegateyes KillModeprocess OOMScoreAdjust-500 [Install] WantedBymulti-user.target幾個(gè)參數(shù)值得解釋。Typenotify讓 systemd 等引擎真正就緒后才認(rèn)為啟動(dòng)完成避免服務(wù)狀態(tài)顯示 running 但實(shí)際還在初始化。LimitNOFILEinfinity是因?yàn)槿萜髅芗瘓?chǎng)景下文件句柄消耗很大默認(rèn)的 1024 遠(yuǎn)遠(yuǎn)不夠容器數(shù)一多就會(huì)出現(xiàn)too many open files。OOMScoreAdjust-500降低引擎被內(nèi)核優(yōu)先殺掉的概率這個(gè)在內(nèi)存緊張時(shí)能救命。Restartalways配合RestartSec2提供基礎(chǔ)的自動(dòng)拉起能力但注意它不是萬(wàn)能的如果引擎是因?yàn)榕渲缅e(cuò)誤反復(fù)崩潰你會(huì)看到不斷重啟的循環(huán)要結(jié)合journalctl -u docker -n 100看具體原因。寫完之后systemctl daemon-reload systemctl enable --now docker再跑一次docker info驗(yàn)證。靜態(tài)二進(jìn)制方案下 containerd 會(huì)由 dockerd 自己拉起不需要額外啟用 containerd 服務(wù)這一點(diǎn)和包管理器方案不同別多此一舉去啟兩個(gè) containerd 搶同一個(gè) socket 目錄會(huì)直接報(bào)錯(cuò)。3.4 daemon.json五個(gè)真正影響長(zhǎng)期運(yùn)行的參數(shù)配置文件放在/etc/docker/daemon.json改完必須重啟引擎systemctl reload docker只對(duì)部分參數(shù)生效圖省事用 reload 結(jié)果參數(shù)沒生效、排查半天的情況我見過。下面這份是我在多數(shù)環(huán)境里會(huì)用的基線{ data-root: /data/docker, log-driver: json-file, log-opts: { max-size: 50m, max-file: 3 }, live-restore: true, default-address-pools: [ { base: 172.30.0.0/16, size: 24 } ], exec-opts: [native.cgroupdriversystemd] }>docker volume create portainer_data docker run -d \ --name portainer \ --restartalways \ -p 127.0.0.1:9443:9443 \ -p 127.0.0.1:8000:8000 \ -v /var/run/docker.sock:/var/run/docker.sock \ -v portainer_data:/data \ portainer/portainer-ce:lts-p前面的127.0.0.1是我強(qiáng)烈建議加的。不加的話端口監(jiān)聽在0.0.0.0等于把管理面板直接暴露出去后面再加 Nginx 也擋不住——?jiǎng)e人可以直接打容器端口。加上綁定之后外部只能通過本機(jī)的轉(zhuǎn)發(fā)層訪問攻擊面立刻收窄了一大截。8000端口是邊緣計(jì)算代理用的單機(jī)環(huán)境其實(shí)用不上我保留它是因?yàn)槟承┌姹镜慕缑鏁?huì)用到這個(gè)端口做節(jié)點(diǎn)注冊(cè)去掉也能跑屬于可選項(xiàng)。9443是默認(rèn)的 HTTPS 端口新版 UI 默認(rèn)只走 HTTPS早期版本還提供過一個(gè) HTTP 端口現(xiàn)在需要顯式打開才有我傾向于不開 HTTP直接走 HTTPS 更省心。-v /var/run/docker.sock:/var/run/docker.sock這一行是整個(gè)部署里最需要理解的。它把宿主機(jī)上的容器運(yùn)行時(shí)接口以 Unix socket 文件的形式掛進(jìn)容器UI 通過這個(gè)文件發(fā) HTTP 請(qǐng)求來(lái)操作引擎。這意味著容器里的進(jìn)程實(shí)際擁有和宿主機(jī) root 同等的容器管理權(quán)限。所以這個(gè)掛載不能給任意鏡像用只能給自己審查過的 UI 鏡像用而且一旦 UI 本身被攻破宿主機(jī)也就失守了。如果你對(duì)這點(diǎn)比較介意有一個(gè)中間方案在前面加一層 socket 白名單網(wǎng)關(guān)只放行讀操作和有限的寫操作UI 連網(wǎng)關(guān)不連原始 socket。這個(gè)方案會(huì)增加一個(gè)容器和一份配置但能把“UI 被攻破等于宿主機(jī)失守”降級(jí)成“只能做允許的動(dòng)作”。規(guī)模不大時(shí)可以先不做但要清楚這個(gè)風(fēng)險(xiǎn)存在。portainer/portainer-ce:lts這個(gè)標(biāo)簽我建議在生產(chǎn)上換成具體版本號(hào)比如明確寫死到某個(gè)小版本。浮動(dòng)標(biāo)簽在升級(jí)時(shí)可能帶來(lái)非預(yù)期變更管理面組件尤其不該這樣因?yàn)榻缑娼Y(jié)構(gòu)變化會(huì)影響值班同學(xué)的操作習(xí)慣。4.3 首次初始化與安全收口容器起來(lái)之后先看日志確認(rèn)沒有報(bào)錯(cuò)docker logs portainer里如果出現(xiàn)權(quán)限相關(guān)的錯(cuò)誤基本都是 socket 掛載或 SELinux 的問題第六節(jié)有排查表。然后瀏覽器訪問https://服務(wù)器IP:9443此時(shí)是自簽證書瀏覽器會(huì)告警點(diǎn)繼續(xù)即可反正后面會(huì)用 Nginx 接管。第一件事是設(shè)置管理員密碼第二件事是選環(huán)境類型單機(jī)選 Docker 就行。初始化完成后有幾個(gè)收口動(dòng)作建議立刻做把默認(rèn)的admin用戶名改掉開啟雙因素認(rèn)證如果環(huán)境支持給團(tuán)隊(duì)成員建普通賬號(hào)而不是共用管理員把“允許外部加載應(yīng)用模板”這類用不到的功能關(guān)掉。管理面的原則是能關(guān)就關(guān)留下的每一個(gè)入口都會(huì)成為后續(xù)審計(jì)的負(fù)擔(dān)。還有一個(gè)細(xì)節(jié)初始化有個(gè)時(shí)間窗口如果容器起來(lái)后超過幾分鐘沒人訪問某些版本會(huì)要求重啟容器才能繼續(xù)初始化。這不是故障是安全設(shè)計(jì)。我遇到過同事以為部署失敗反復(fù)重建容器其實(shí)是沒在窗口內(nèi)完成初始化白折騰。4.4 用編排文件管理 UI 自身UI 自己也是容器用編排文件管它升級(jí)和遷移會(huì)舒服很多services: portainer: image: portainer/portainer-ce:2.20.3 container_name: portainer restart: always ports: - 127.0.0.1:9443:9443 volumes: - /var/run/docker.sock:/var/run/docker.sock - portainer_data:/data volumes: portainer_data: external: true注意數(shù)據(jù)卷聲明成external因?yàn)槟阋呀?jīng)用命令建過了編排文件如果自己再建一個(gè)同名卷會(huì)沖突。把文件放在/opt/stacks/portainer/這類固定路徑下和業(yè)務(wù)編排文件分開避免哪天誤刪業(yè)務(wù)目錄把管理面一起帶走。升級(jí)時(shí)改鏡像版本號(hào)docker compose up -d就完成替換回滾就把版本號(hào)改回去比記命令靠譜。5. 訪問入口收口Nginx 前置與證書配置5.1 為什么建議再套一層UI 自帶的 HTTPS 用的是容器內(nèi)置的自簽證書每次重建容器證書都可能變?yōu)g覽器告警會(huì)反復(fù)出現(xiàn)值班同學(xué)點(diǎn)“繼續(xù)訪問”點(diǎn)多了就麻木了這恰恰是安全習(xí)慣被磨掉的過程。套一層 Nginx 的好處有三個(gè)證書統(tǒng)一管理?yè)Q證書不用動(dòng)容器可以加訪問控制比如只允許內(nèi)網(wǎng)段訪問、加基礎(chǔ)認(rèn)證做二次校驗(yàn)可以做統(tǒng)一入口多個(gè)管理面板掛在不同路徑下對(duì)外只暴露一個(gè)域名和端口。這一層的定位就是轉(zhuǎn)發(fā)不做緩存不做改寫配置越簡(jiǎn)單越好因?yàn)樗撬泄芾砹髁康谋亟?jīng)之路多一條規(guī)則就多一個(gè)故障點(diǎn)。5.2 配置示例與證書生成先出證書內(nèi)網(wǎng)用自簽就夠了關(guān)鍵是把它固定下來(lái)mkdir -p /etc/nginx/certs openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ -keyout /etc/nginx/certs/docker.key \ -out /etc/nginx/certs/docker.crt \ -subj /CNdocker-ui.internal有效期給到 3650 天內(nèi)網(wǎng)自簽沒必要頻繁換但要在日歷上記一筆到期提醒我吃過證書過期導(dǎo)致整個(gè)面板打不開、排查半天以為是服務(wù)掛了的虧。如果內(nèi)網(wǎng)有統(tǒng)一的證書簽發(fā)設(shè)施優(yōu)先用統(tǒng)一簽發(fā)省掉信任導(dǎo)入的麻煩。Nginx 配置的關(guān)鍵點(diǎn)在于后端是 HTTPS需要關(guān)掉后端證書校驗(yàn)同時(shí)要把長(zhǎng)連接的升級(jí)頭帶上否則界面里某些實(shí)時(shí)刷新的功能會(huì)失效server { listen 443 ssl; server_name docker-ui.internal; ssl_certificate /etc/nginx/certs/docker.crt; ssl_certificate_key /etc/nginx/certs/docker.key; client_max_body_size 0; location / { proxy_pass https://127.0.0.1:9443; proxy_ssl_verify off; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_read_timeout 3600s; } }client_max_body_size 0是必須的因?yàn)橥ㄟ^界面上傳鏡像或?qū)刖幣盼募r(shí)請(qǐng)求體可能很大默認(rèn)的 1MB 限制會(huì)直接返回 413而界面上只會(huì)顯示一個(gè)含糊的失敗提示。proxy_read_timeout拉長(zhǎng)到一小時(shí)是因?yàn)椴榭慈罩玖鬟@類長(zhǎng)連接會(huì)被默認(rèn) 60 秒掐斷表現(xiàn)為日志看一會(huì)兒就不動(dòng)了容易誤判成服務(wù)卡死。proxy_ssl_verify off只在這個(gè)內(nèi)網(wǎng)回環(huán)場(chǎng)景下可以接受因?yàn)楹蠖司驮诒緳C(jī)不存在被中間人截獲的路徑。配完nginx -t驗(yàn)證語(yǔ)法再systemctl reload nginx。驗(yàn)證方式是訪問域名看證書是不是 Nginx 那張同時(shí)確認(rèn)界面能正常加載、日志能持續(xù)刷新。6. 踩過的坑排查思路與速查表6.1 鏡像拉不下來(lái)先分層排除再動(dòng)手這個(gè)問題我被問得最多排查順序錯(cuò)了會(huì)浪費(fèi)大量時(shí)間。正確的順序是從外到內(nèi)先在宿主機(jī)上curl -I目標(biāo)倉(cāng)庫(kù)地址確認(rèn)網(wǎng)絡(luò)可達(dá)和證書鏈正常再docker pull一個(gè)極小的公共鏡像做對(duì)照排除是單個(gè)鏡像的問題還是全局問題再看journalctl -u docker -n 200里的具體錯(cuò)誤碼。常見的三種錯(cuò)誤對(duì)應(yīng)三種原因。報(bào) TLS 握手超時(shí)多半是宿主時(shí)間偏差或者中間有設(shè)備做了證書攔截報(bào)no such host或者解析超時(shí)是 DNS 問題容器內(nèi)繼承的resolv.conf指向了本機(jī)回環(huán)地址的解析服務(wù)在容器命名空間里根本連不上解決辦法是在daemon.json里顯式指定dns字段或者把宿主機(jī)的解析配置改成容器可訪問的地址報(bào)i/o timeout但curl正常很可能是 MTU 不匹配鏈路上存在隧道或者 overlay 封裝時(shí)默認(rèn) 1500 的 MTU 會(huì)讓大包被丟棄小包正常大包卡住表現(xiàn)就是拉取進(jìn)度條走到一半停住。MTU 的處理方法是在daemon.json里加mtu: 1450然后重啟引擎并重建網(wǎng)絡(luò)。這個(gè)值不是固定的1400 到 1450 之間試判斷標(biāo)準(zhǔn)是ping -M do -s 1472這類不分片探測(cè)能不能通。我給的這個(gè)排查法雖然土但比翻日志快。6.2 端口不通與權(quán)限拒絕兩類完全不同的原因容器跑了但端口訪問不了第一步看docker port 容器名確認(rèn)映射關(guān)系第二步在宿主機(jī)curl 127.0.0.1:端口繞開防火墻測(cè)試第三步從另一臺(tái)機(jī)器測(cè)。這三步能把問題定位到“映射錯(cuò)”“本機(jī)防火墻”“鏈路防火墻”三個(gè)層面之一。如果本機(jī)通、外部不通八成是前面說(shuō)的 firewalld 與 Docker 規(guī)則順序問題。驗(yàn)證方法很直接iptables -t nat -L -n看有沒有對(duì)應(yīng)的 DNAT 規(guī)則有規(guī)則但外部不通就去DOCKER-USER鏈里找有沒有被攔截。解決辦法是把放行規(guī)則插到DOCKER-USER鏈?zhǔn)譱ptables -I DOCKER-USER -s 內(nèi)網(wǎng)段 -j ACCEPT再補(bǔ)一條默認(rèn)拒絕。注意這些規(guī)則重啟會(huì)丟要寫進(jìn)開機(jī)腳本或者用iptables-save持久化。另一類問題是 SELinux 拒絕。表現(xiàn)是容器啟動(dòng)失敗日志里有permission deniedausearch -m avc -ts recent能看到具體的拒絕記錄。UI 掛載 socket 時(shí)最容易碰到處理辦法有兩種給掛載加:z標(biāo)簽讓 SELinux 重打上下文或者針對(duì)容器相關(guān)布爾值做定向放行。我不建議直接把 SELinux 設(shè)成 disabled那等于放棄了最后一道防線而且改回來(lái)很麻煩。定向放行雖然要多花十分鐘但長(zhǎng)期看是值得的。6.3 磁盤告急與數(shù)據(jù)目錄遷移根分區(qū)被吃滿的現(xiàn)場(chǎng)通常是這樣的df -h顯示 100%du -sh /var/lib/docker/*一看overlay2占了絕大部分再進(jìn)去看是某個(gè)容器的日志文件幾個(gè) G。這時(shí)候別急著刪目錄先按前面說(shuō)的給daemon.json加日志上限并重啟引擎新日志會(huì)按新策略滾動(dòng)老的大文件再單獨(dú)清理。直接rm正在被進(jìn)程打開的文件空間不會(huì)立刻釋放因?yàn)槲募浔€在lsof | grep deleted能看到這類懸空文件。數(shù)據(jù)目錄遷移要按順序來(lái)停業(yè)務(wù)容器、停引擎、用rsync -aHAX同步、確認(rèn)新目錄權(quán)限、改daemon.json、啟引擎、驗(yàn)證docker info里的 Root Dir 已指向新路徑、再啟業(yè)務(wù)容器。中間任何一步跳過都可能出現(xiàn)“容器還在但數(shù)據(jù)沒了”的情況。我一般會(huì)在遷移前把docker ps -a和docker volume ls的輸出存一份遷移后逐項(xiàng)核對(duì)雖然土但有效。6.4 UI 打不開或報(bào) 500 的速查路徑這類問題的排查我整理成了一張表按命中率排序現(xiàn)象最可能原因驗(yàn)證方式處理頁(yè)面 500日志報(bào)連接 socket 失敗socket 掛載路徑錯(cuò)或權(quán)限不足ls -l /var/run/docker.sock確認(rèn)掛載路徑檢查 SELinux 上下文頁(yè)面能開但容器列表為空UI 連的是別的引擎或環(huán)境配錯(cuò)看 UI 里的環(huán)境配置重新指向本機(jī) socket上傳編排文件報(bào) 413轉(zhuǎn)發(fā)層請(qǐng)求體限制看 Nginx 錯(cuò)誤日志調(diào)整client_max_body_size日志界面幾秒后停止刷新轉(zhuǎn)發(fā)層讀超時(shí)觀察停止的固定間隔調(diào)大proxy_read_timeout界面操作全部超時(shí)引擎無(wú)響應(yīng)或磁盤滿docker info是否卡住先救引擎再查 UI證書告警反復(fù)出現(xiàn)用了容器內(nèi)置自簽證書看證書簽發(fā)者用 Nginx 統(tǒng)一證書6.5 三條來(lái)自現(xiàn)場(chǎng)的經(jīng)驗(yàn)第一條出問題先看引擎再看 UI。UI 只是殼它所有的能力都來(lái)自引擎接口引擎不健康的時(shí)候 UI 的表現(xiàn)五花八門容易把人引向錯(cuò)誤方向。養(yǎng)成先跑docker info的習(xí)慣能省掉一半排查時(shí)間。第二條任何變更前先留證據(jù)。docker ps -a、docker images、docker volume ls、daemon.json備份四樣?xùn)|西存一份出問題時(shí)對(duì)照著看比憑記憶靠譜得多。第三條別在生產(chǎn)上試新版本 UI。管理面的界面變更會(huì)影響操作習(xí)慣而操作習(xí)慣在應(yīng)急時(shí)就是肌肉記憶。要升級(jí)先在測(cè)試環(huán)境用一周確認(rèn)常用路徑?jīng)]變?cè)偻啤?. 長(zhǎng)期可用備份、升級(jí)與日常巡檢7.1 備份策略備份的是配置不是數(shù)據(jù)管理面的備份重點(diǎn)在于數(shù)據(jù)卷和編排文件不在鏡像。數(shù)據(jù)卷里存的是用戶、環(huán)境、模板、設(shè)置這些信息丟了就得重建一套重新配權(quán)限很煩。備份方式很直接停容器用臨時(shí)容器把卷打包成 tar 落到宿主機(jī)再傳到備份目錄最后啟容器。整個(gè)過程對(duì)業(yè)務(wù)容器沒有影響因?yàn)橥5闹皇枪芾砻?。編排文件和證書目錄要一起備份因?yàn)樗鼈兒途砝锏呐渲檬桥涮椎?。我?xí)慣把這三樣?xùn)|西卷歸檔、編排文件、證書放在同一個(gè)帶日期的目錄下恢復(fù)時(shí)整體解壓不會(huì)漏。備份頻率看團(tuán)隊(duì)規(guī)模賬號(hào)體系變化不頻繁的話一周一次加上變更前手動(dòng)一次就夠。7.2 升級(jí)與回滾的具體動(dòng)作升級(jí)前先看新版本的變更說(shuō)明里有沒有涉及認(rèn)證方式、數(shù)據(jù)結(jié)構(gòu)的調(diào)整有的話必須先備份再動(dòng)。動(dòng)作順序是改編排文件里的鏡像版本、docker compose pull、docker compose up -d、看日志確認(rèn)遷移完成、登錄驗(yàn)證核心功能。如果驗(yàn)證不通過把版本號(hào)改回舊值再up -d因?yàn)閿?shù)據(jù)卷通常做了向后兼容遷移回滾到舊版本多數(shù)情況下能正常讀取但有極小概率舊版本讀不了新結(jié)構(gòu)所以備份是回滾的前提。引擎本身的升級(jí)我建議和 UI 升級(jí)錯(cuò)開至少一周。兩個(gè)組件同時(shí)變更出問題時(shí)無(wú)法判斷是誰(shuí)引起的排查成本翻倍。7.3 巡檢清單每周花十分鐘我給自己定的巡檢項(xiàng)很樸素docker system df看鏡像和卷的占用趨勢(shì)漲得太快說(shuō)明有東西在堆積df -h看磁盤水位超過 80% 就提前處理docker ps --filter healthunhealthy看有沒有不健康的容器journalctl -u docker --since 7 days ago | grep -i error看引擎有沒有反復(fù)報(bào)錯(cuò)最后登錄 UI 掃一眼確認(rèn)賬號(hào)沒有多余的、模板沒有被亂改。這套動(dòng)作十分鐘能做完但能擋住絕大多數(shù)“突然全線崩”的場(chǎng)景因?yàn)樗写蠊收显诎l(fā)生前都會(huì)有小征兆只是沒人看。我在實(shí)際使用中最大的體會(huì)是容器這套東西的上手門檻很低但長(zhǎng)期可用性完全取決于這些瑣碎的日常動(dòng)作裝的時(shí)候花的心思只是開頭后面每周十分鐘才是真正決定它能不能撐住的部分。最后分享一個(gè)小技巧給 UI 加一個(gè)只讀賬號(hào)專門給不常操作但需要看狀態(tài)的同事用然后把這個(gè)賬號(hào)的登錄方式寫進(jìn)值班手冊(cè)第一頁(yè)。既省了開權(quán)限的溝通成本也把誤操作的可能性壓到了最低。