部署Harbor私有Docker鏡像倉庫實(shí)戰(zhàn))
1. 先把需求摸清楚私有鏡像倉庫到底要解決什么問題云主機(jī)上搭一套私有 Docker 鏡像倉庫這件事聽起來像運(yùn)維活但真正動手的人往往是開發(fā)者自己——團(tuán)隊(duì)里沒人愿意把公司內(nèi)部鏡像丟到公共倉庫也沒有預(yù)算一開始就買商業(yè)方案于是先在云上開一臺機(jī)器把鏡像倉庫跑起來邊學(xué)邊用。我這次做的就是這個(gè)在云主機(jī)的實(shí)例上用 Harbor 搭一套帶 Web 界面、帶用戶權(quán)限、帶鏡像掃描能力的私有倉庫同時(shí)把整條 login / tag / push / pull 鏈路走通。這個(gè)倉庫能解決三件具體的麻煩內(nèi)部鏡像不再散落在各人筆記本上、CI 機(jī)器不用重復(fù)從外網(wǎng)拉基礎(chǔ)鏡像、私有鏡像有獨(dú)立的訪問控制。它適合誰我的判斷是三類人剛接觸容器、想弄明白鏡像倉庫底層是怎么運(yùn)轉(zhuǎn)的初學(xué)者小團(tuán)隊(duì)里被安排“順手把倉庫搭一下”的后端或運(yùn)維以及已經(jīng)用過托管版鏡像服務(wù)、想搞清楚里面那些項(xiàng)目、成員、機(jī)器人賬號概念從哪來的人。整篇內(nèi)容我會按“選型 — 環(huán)境 — 部署 — 接入 — 踩坑 — 加固”的順序?qū)懨恳徊蕉冀o到可以直接抄的參數(shù)和命令中間穿插我自己踩過的坑你可以照著復(fù)現(xiàn)也可以只看自己卡住的那一段。需要先說明一個(gè)前提云廠商之間的差異主要在控制臺點(diǎn)選和網(wǎng)絡(luò)配置上實(shí)例內(nèi)部的路徑、端口、組件和開源版本的行為是一致的。所以下文統(tǒng)一稱呼為“云主機(jī)”“實(shí)例”具體的規(guī)格選型和安全組配置你按手頭那家云的控制臺界面去找對應(yīng)項(xiàng)就行邏輯完全對得上。1.1 四種方案擺在天平上我為什么落在 Harbor把可選項(xiàng)鋪開其實(shí)只有四條路。第一條是本地起一個(gè)registry:2容器五行命令搞定但它只有存儲和推拉能力沒有用戶體系、沒有 Web 界面、沒有項(xiàng)目隔離刪鏡像還得手動調(diào) API學(xué)兩天就摸到天花板了。第二條是直接用云廠商的托管鏡像服務(wù)省心但按量計(jì)費(fèi)、權(quán)限模型是別人設(shè)計(jì)的學(xué)不到東西也不適合“學(xué)習(xí)”這個(gè)目標(biāo)。第三條是在自己機(jī)器上用 Docker Desktop 自帶的小倉庫只能本機(jī)用沒有意義。第四條就是 Harbor它是從開源鏡像倉庫項(xiàng)目演進(jìn)出來的完整發(fā)行版帶 Nginx、Core、Registry、PostgreSQL、Redis、JobService、Trivy 一整套組件用 Compose 拉起一臺機(jī)器就能跑。我選 Harbor 的關(guān)鍵理由不是功能多而是它的組件拆分方式本身就是一堂容器架構(gòu)課。你會發(fā)現(xiàn) Harbor 把“認(rèn)證”和“存儲”分開了Core 服務(wù)負(fù)責(zé)用戶、項(xiàng)目、權(quán)限、APIRegistry 只負(fù)責(zé)最原始的 Blob 與 Manifest 存取JobService 干異步的復(fù)制、掃描、GC 這類重活Nginx 在最前面做 TLS 終止和路由。理解了這個(gè)分層你再看任何一個(gè)鏡像倉庫產(chǎn)品的文檔都能對上號。這是學(xué)習(xí)場景下最值回票價(jià)的部分。另外 Harbor 官方提供離線安裝包里面把鏡像都打包好了不用一個(gè)個(gè)去docker pull在帶寬不穩(wěn)定的環(huán)境里這一點(diǎn)非常關(guān)鍵。整個(gè)部署過程用prepare生成 compose 文件、用install.sh一鍵拉起失敗時(shí)日志位置固定排查路徑清晰很適合拿來練手。1.2 上線前必須釘死的四件事很多人部署失敗不是因?yàn)榧夹g(shù)難而是四件事沒提前定域名、證書、存儲目錄、端口。我這邊的定法是這樣的——域名用一個(gè)只在內(nèi)部解析的名字比如harbor.internal故意不用公網(wǎng)域名避免證書簽發(fā)和被掃描證書用自簽學(xué)習(xí)環(huán)境沒必要走自動簽發(fā)流程但自簽證書的 SAN 一定要寫全后面會細(xì)講存儲目錄統(tǒng)一放在獨(dú)立數(shù)據(jù)盤的/data下Harbor 的數(shù)據(jù)卷放/data/harborDocker 引擎自己的數(shù)據(jù)目錄放/data/docker這樣系統(tǒng)盤只裝系統(tǒng)重建實(shí)例時(shí)數(shù)據(jù)盤可以直接掛回來端口上HTTPS 占 443HTTP 占 8443 并且只做跳轉(zhuǎn)用。這里有個(gè)容易被忽略的細(xì)節(jié)Harbor 配置里的hostname字段不能帶端口。寫成harbor.internal:443會讓生成的證書配置、令牌服務(wù)和倉庫地址全部錯(cuò)位癥狀是網(wǎng)頁能打開但docker push報(bào) 401 或 404。這個(gè)坑我第一遍就踩了后面會再提一次。還有一件事需要提前想清楚這臺機(jī)器是“學(xué)習(xí)環(huán)境”還是“準(zhǔn)生產(chǎn)環(huán)境”。學(xué)習(xí)環(huán)境可以單機(jī)、可以自簽、可以只做冷備份一旦有同事真的往里推鏡像你就得考慮備份、磁盤配額、垃圾回收周期。我的建議是先用單機(jī)把鏈路走通把備份腳本寫好再考慮往高可用架構(gòu)演進(jìn)——單機(jī) Harbor 掛了的恢復(fù)動作其實(shí)比很多人想象的簡單只要harbor.yml、證書、數(shù)據(jù)卷和數(shù)據(jù)庫備份四樣?xùn)|西在。2. 云主機(jī)與基礎(chǔ)環(huán)境的落地準(zhǔn)備這一節(jié)全部是“動手之前的體力活”但它們決定了后面能不能順。我見過太多人在 Harbor 起不來之后去翻日志最后發(fā)現(xiàn)是內(nèi)存不夠被 OOM Killer 殺掉了 Core 服務(wù)或者磁盤寫滿導(dǎo)致 PostgreSQL 拒絕寫入。所以環(huán)境和規(guī)格這部分值得單獨(dú)花時(shí)間。2.1 實(shí)例規(guī)格怎么估從內(nèi)存和磁盤兩頭算先說內(nèi)存。Harbor 默認(rèn)組件跑起來Nginx 大約 30MB、Core 約 100MB、Registry 約 60MB、PostgreSQL 約 100MB、Redis 約 20MB、JobService 約 80MB、Trivy 適配器再加一點(diǎn)空載合計(jì)在 400MB 到 600MB 之間。聽起來 2GB 就夠但實(shí)際推拉鏡像時(shí) Nginx 和 Registry 會有緩沖區(qū)開銷JobService 做掃描時(shí)內(nèi)存能沖到 500MB 以上加上 Docker 引擎自身、系統(tǒng)進(jìn)程和頁緩存4GB 是安全帶。我用的就是 2 核 4GB跑起來比較從容如果你還要在這臺機(jī)器上跑 CI runner直接上 4 核 8GB。磁盤要算兩筆賬。第一筆是鏡像本體假設(shè)你打算放 100 個(gè)鏡像標(biāo)簽平均壓縮后 300MBHarbor 按層存儲且層可共享實(shí)際占用約為總量的 60%也就是 100 × 300MB × 0.6 ≈ 18GB。第二筆是回收余量垃圾回收不會立刻釋放空間而且回收前要把倉庫切成只讀中間可能堆積一批待清理對象建議預(yù)留 1.5 倍。18GB × 1.5 ≈ 27GB再加上日志、掃描數(shù)據(jù)庫、系統(tǒng)獨(dú)立數(shù)據(jù)盤開 100GB 是很舒服的配置學(xué)習(xí)場景下 40GB 也能跑但要盯著用。系統(tǒng)盤 40GB 足夠但請務(wù)必確認(rèn) Harbor 的數(shù)據(jù)目錄在數(shù)據(jù)盤上。默認(rèn)安裝會把data_volume放在安裝目錄下的data文件夾里如果安裝目錄在系統(tǒng)盤鏡像很快就吃掉系統(tǒng)盤而系統(tǒng)盤寫滿會導(dǎo)致 SSH 都登不上這是最難受的一種故障。項(xiàng)目學(xué)習(xí)/單機(jī)小團(tuán)隊(duì)使用說明vCPU24編譯、掃描占 CPU內(nèi)存4GB8GB低于 2GB 易被 OOM系統(tǒng)盤40GB40GB僅裝系統(tǒng)與鏡像緩存數(shù)據(jù)盤100GB200GB 起存 Harbor 數(shù)據(jù)與 Docker 數(shù)據(jù)帶寬按量 5Mbps固定帶寬拉基礎(chǔ)鏡像時(shí)體現(xiàn)明顯帶寬這塊補(bǔ)充一句學(xué)習(xí)環(huán)境用按量帶寬、用完就停是最省的做法但要注意鏡像拉取那一瞬間的峰值如果帶寬只有 1Mbps拉幾百兆的基礎(chǔ)鏡像會非常痛苦。我的做法是第一次把常用基礎(chǔ)鏡像拉完并推到私有倉庫后面所有構(gòu)建都走內(nèi)網(wǎng)拉取外網(wǎng)帶寬就只在少數(shù)時(shí)候打滿。2.2 安全組與端口規(guī)劃清單安全組是云上最容易配錯(cuò)的一層。原則很簡單只開必須的其余全關(guān)。我這次開放的端口如下表注意 2375 這個(gè) Docker 遠(yuǎn)程端口絕對不要對公網(wǎng)開放一旦暴露對方可以直接在你的機(jī)器上起容器做任何事這是常見的高危配置。端口協(xié)議來源用途22TCP你的固定出口 IP運(yùn)維登錄443TCP內(nèi)網(wǎng)網(wǎng)段Harbor HTTPS8443TCP內(nèi)網(wǎng)網(wǎng)段Harbor HTTP 跳轉(zhuǎn)80TCP不開由 443 承接2375/2376TCP不開Docker 遠(yuǎn)程 API5432/6379TCP不開數(shù)據(jù)庫與緩存僅容器內(nèi)如果機(jī)器需要被其他網(wǎng)絡(luò)的同事訪問把 443 的來源改成對應(yīng)網(wǎng)段不要圖省事寫0.0.0.0/0。另外很多云廠商還提供“安全組規(guī)則命中日志”配完跑一次docker login去日志里確認(rèn)命中的是你預(yù)期的規(guī)則比盲猜快得多。2.3 數(shù)據(jù)盤掛載與目錄體系拿到新實(shí)例第一件事是看盤。lsblk確認(rèn)數(shù)據(jù)盤設(shè)備名通常系統(tǒng)盤是vda數(shù)據(jù)盤是vdb但不同鏡像不一樣穩(wěn)妥做法是看容量和掛載點(diǎn)。lsblk # 假設(shè)輸出中 vdb 沒有掛載點(diǎn)容量符合你買的數(shù)據(jù)盤 mkfs.ext4 /dev/vdb mkdir -p /data blkid /dev/vdb # 記下輸出的 UUID然后寫/etc/fstab。這里一定要用 UUID 而不是/dev/vdb因?yàn)樵O(shè)備名在重啟、擴(kuò)容、換盤后可能變化用設(shè)備名會導(dǎo)致開機(jī)掛載失敗進(jìn)而進(jìn)不了系統(tǒng)。寫入一行UUID你的UUID值 /data ext4 defaults,nofail 0 2nofail這個(gè)選項(xiàng)很關(guān)鍵它保證盤掛不上時(shí)系統(tǒng)仍能啟動方便你遠(yuǎn)程修復(fù)否則一個(gè) fstab 寫錯(cuò)就得走控制臺的 VNC。寫完執(zhí)行mount -a驗(yàn)證再df -h看一眼。接著建目錄體系我的劃分是這樣/data/harbor放 Harbor 數(shù)據(jù)卷/data/harbor-certs放證書也可以放/data/harbor/certs/data/docker給 Docker 引擎當(dāng)># 以某主流發(fā)行版為例先裝依賴 sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg # 添加軟件源并安裝源地址請?zhí)鎿Q為你環(huán)境可用的地址 sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin裝完必須驗(yàn)證三件事docker version能看到 Client 和 Server 兩段、docker compose version有輸出、systemctl is-enabled docker返回enabled。第三點(diǎn)經(jīng)常被忽略機(jī)器重啟后 Docker 沒自啟Harbor 自然也就沒起來第二天上班發(fā)現(xiàn)倉庫掛了。Compose 這里有個(gè)版本差異要提醒Harbor 的不同版本對docker composev2 插件和docker-composev1 獨(dú)立二進(jìn)制的探測邏輯不同新版安裝腳本優(yōu)先用 v2。如果你機(jī)器上兩個(gè)都裝了偶爾會出現(xiàn)腳本調(diào)錯(cuò)的情況癥狀是install.sh報(bào)命令不存在但手動docker compose ps又是好的。最省事的做法是只保留一個(gè)我一般只留 v2 插件版。配置 Docker 守護(hù)進(jìn)程這一步先做一半把>{ data-root: /data/docker, log-driver: json-file, log-opts: { max-size: 50m, max-file: 3 }, live-restore: true }寫到/etc/docker/daemon.jsonsystemctl daemon-reload systemctl restart docker。日志輪轉(zhuǎn)這兩個(gè)參數(shù)千萬不要省容器日志默認(rèn)不限制大小某天一個(gè)異常容器反復(fù)報(bào)錯(cuò)幾十 GB 日志能把盤寫滿這個(gè)場景我遇到過兩次都是深夜。3. Harbor 部署的完整實(shí)操鏈路環(huán)境就緒進(jìn)入正題。這一節(jié)我會把配置文件逐項(xiàng)拆開講因?yàn)?Harbor 的配置文件里幾乎每一個(gè)改動都對應(yīng)一個(gè)具體的后果抄作業(yè)可以但知道為什么抄更重要。3.1 離線包獲取與解壓從官方 release 頁面下載離線安裝包文件名形如harbor-offline-installer-v2.10.x.tgz注意選 offline 而不是 online離線包里面已經(jīng)包含所有鏡像 tar 包。下載后校驗(yàn)一下哈希然后在數(shù)據(jù)盤解壓cd /data wget 離線安裝包地址/harbor-offline-installer-v2.10.2.tgz tar -xzf harbor-offline-installer-v2.10.2.tgz cd harbor ls -l目錄里應(yīng)該有harbor.yml.tmpl、install.sh、prepare、harbor.v2.10.2.tar.gz。第一件事是把模板復(fù)制成正式配置cp harbor.yml.tmpl harbor.yml。不要直接改 tmpl因?yàn)樯墪r(shí)新包會覆蓋模板你的配置就丟了而且排查問題時(shí)也分不清是默認(rèn)值還是你的改動。3.2 harbor.yml 逐項(xiàng)拆解與參數(shù)計(jì)算配置文件不長但每一段都有坑。我按順序講關(guān)鍵項(xiàng)。hostname: harbor.internal http: port: 8443 https: port: 443 certificate: /data/harbor-certs/harbor.crt private_key: /data/harbor-certs/harbor.key harbor_admin_password: 換成你自己的強(qiáng)密碼 data_volume: /data/harbor database: password: 換成獨(dú)立強(qiáng)密碼 max_idle_conns: 50 max_open_conns: 100 jobservice: max_job_workers: 10 trivy: ignore_unfixed: true skip_update: true insecure: false log: level: info local: rotate_count: 50 rotate_size: 200M location: /data/harbor/loghostname前面說過純域名不帶端口而且要和證書的 CN/SAN 完全一致否則客戶端會報(bào)證書不匹配。http.port我設(shè)成 8443是給那些還在用明文訪問的老腳本留一條跳轉(zhuǎn)路實(shí)際使用時(shí) Nginx 會把 HTTP 請求 301 到 HTTPS。https.port用 443這樣客戶端docker push的時(shí)候不用特殊寫端口省掉很多麻煩。data_volume指向數(shù)據(jù)盤這一行決定了幾十 GB 鏡像落在哪。database.password單獨(dú)設(shè)不要和harbor_admin_password一樣雖然學(xué)習(xí)環(huán)境無所謂但養(yǎng)成習(xí)慣很重要——數(shù)據(jù)庫密碼泄露意味著整庫憑據(jù)握在別人手里而管理員密碼泄露最多是倉庫被改。trivy.skip_update: true在離線或帶寬有限的環(huán)境下很有用否則 Trivy 每次啟動都去拉漏洞庫拉不動就一直重試日志刷屏。jobservice.max_job_workers控制異步任務(wù)并發(fā)10 是個(gè)平衡值調(diào)到 30 以上在小機(jī)器上會把 CPU 打滿導(dǎo)致 Web 界面卡頓。log.local的輪轉(zhuǎn)參數(shù)是保命的rotate_count: 50配合rotate_size: 200M理論最大 10GB實(shí)際很少到但比不限制安全得多。如果你不用漏洞掃描可以在安裝時(shí)加--without-trivy能省 300MB 左右內(nèi)存和一點(diǎn)磁盤。學(xué)習(xí)階段我建議保留掃一次看看報(bào)告長什么樣挺有意思。3.3 自簽證書生成SAN 是重點(diǎn)自簽證書最容易出問題的地方不是加密強(qiáng)度而是Subject Alternative Name?,F(xiàn)在所有主流 TLS 實(shí)現(xiàn)都只看 SAN不看 CN只寫 CN 的證書在瀏覽器和 Docker 客戶端里會被判定為無效。生成命令mkdir -p /data/harbor-certs cd /data/harbor-certs openssl req -x509 -nodes -newkey rsa:4096 -days 3650 \ -keyout harbor.key \ -out harbor.crt \ -subj /OInternal/OULearning/CNharbor.internal \ -addext subjectAltNameDNS:harbor.internal,IP:10.0.0.12這里幾個(gè)參數(shù)值得說-days 3650是十年學(xué)習(xí)環(huán)境夠用生產(chǎn)環(huán)境建議 90 到 365 天并配自動續(xù)期-addext需要 OpenSSL 1.1.1 以上如果你的系統(tǒng)版本老得改用寫配置文件的方式SAN 里同時(shí)寫 DNS 和 IP是因?yàn)槲遗紶枙?IP 直連調(diào)試不寫 IP 的話那條路就報(bào)證書錯(cuò)誤。生成完檢查一下openssl x509 -in harbor.crt -noout -text | grep -A2 Subject Alternative Name確認(rèn)輸出里有你寫的兩個(gè)值。這一步花十秒鐘能省掉后面半小時(shí)的x509: certificate signed by unknown authority排查。順手把證書文件的權(quán)限收緊chmod 600 harbor.key。私鑰文件在容器掛載時(shí)會被讀取權(quán)限太松雖然不直接報(bào)錯(cuò)但不符合最小暴露原則。3.4 執(zhí)行 install.sh 與組件狀態(tài)驗(yàn)證配置寫完執(zhí)行cd /data/harbor ./install.sh --with-trivy腳本會依次做幾件事檢查 Docker 和 Compose 可用性、調(diào)用prepare渲染出docker-compose.yml、加載離線鏡像包、拉起全部容器、等待健康檢查。整個(gè)過程兩三分鐘中途輸出會滾得很快注意看有沒有紅色的 ERROR。安裝成功的標(biāo)志是最后一屏打印出訪問地址和默認(rèn)賬號提示。然后驗(yàn)證docker compose -f docker-compose.yml ps你應(yīng)該看到harbor-core、harbor-db、harbor-jobservice、harbor-log、harbor-portal、nginx、redis、registry、registryctl、trivy-adapter全部是running或healthy。healthy狀態(tài)是核心服務(wù)的健康檢查探針剛起來時(shí)可能是starting等 30 秒再看。如果某個(gè)容器反復(fù)重啟先看日志docker logs --tail 100 harbor-core。日志目錄在容器內(nèi)是/var/log/harbor宿主機(jī)上通過harbor-log容器集中收集位置是data_volume下的log目錄。這個(gè)設(shè)計(jì)挺巧妙所有組件日志統(tǒng)一落盤排查時(shí)不用一個(gè)個(gè)進(jìn)容器。常用運(yùn)維命令我記成三行出問題先敲這三條docker compose -f docker-compose.yml ps # 看狀態(tài) docker compose -f docker-compose.yml logs -f core # 看核心服務(wù)日志 tail -f /data/harbor/log/core/core.log # 看落盤日志3.5 首次登錄與基礎(chǔ)初始化動作瀏覽器打開https://harbor.internal自簽證書會有安全警告點(diǎn)繼續(xù)訪問即可這也是為什么自簽只適合內(nèi)部環(huán)境。用默認(rèn)管理員賬號登錄密碼必須是Harbor12345這是官方默認(rèn)值第一次登錄會強(qiáng)制要求改密碼。新密碼的規(guī)則是長度至少 8 位、包含大小寫字母和數(shù)字比如Lx2024Harbor。改完密碼做幾個(gè)初始化動作新建一個(gè)項(xiàng)目比如dev勾選私有這樣匿名用戶拉不了。默認(rèn)的library項(xiàng)目是公開的很多人第一次push就推到這里結(jié)果所有人都能拉很不合適。在系統(tǒng)管理 - 用戶管理里建幾個(gè)普通用戶分配給不同同事。進(jìn)項(xiàng)目dev在成員里加成員角色選開發(fā)者可推可拉或訪客只拉。Harbor 的角色模型是三層系統(tǒng)管理員、項(xiàng)目成員、匿名用戶權(quán)限從項(xiàng)目設(shè)置里繼承。在配置管理里把項(xiàng)目創(chuàng)建權(quán)限關(guān)掉避免任意用戶隨便建項(xiàng)目把磁盤塞滿。這些動作不復(fù)雜但做了之后整個(gè)倉庫才像個(gè)“能給別人用”的東西而不只是你自己玩的沙盒。4. 客戶端接入從 login 到 push 的完整閉環(huán)服務(wù)端起得來只算完成一半。真正讓人卡住的往往是客戶端這一側(cè)證書信任、域名解析、守護(hù)進(jìn)程配置隨便一個(gè)不對docker push就報(bào)一堆看不懂的錯(cuò)。這一節(jié)我把客戶端要改的東西全部列清楚。4.1 Docker 守護(hù)進(jìn)程的配置與證書信任客戶端有兩條路走要么讓 Docker 信任你的自簽證書要么把倉庫標(biāo)記為“非安全倉庫”。生產(chǎn)環(huán)境必須走前者學(xué)習(xí)環(huán)境圖省事可以用后者但要知道它的含義——insecure-registries會讓 Docker 在推拉時(shí)跳過 TLS 校驗(yàn)中間人只要在你和倉庫之間就能看到鏡像內(nèi)容。先講后者快{ data-root: /data/docker, insecure-registries: [harbor.internal:443], insecure-registries: [harbor.internal], log-driver: json-file, log-opts: { max-size: 50m, max-file: 3 } }注意上面這段我故意寫了兩遍同名 key只是想說明格式實(shí)際只能留一個(gè)正確寫法是數(shù)組里放harbor.internal不帶端口。如果倉庫啟用了 HTTPS 且用 443insecure-registries里寫主機(jī)名即可如果是自定義端口才需要帶端口。再講正規(guī)做法讓客戶端信任證書sudo mkdir -p /etc/docker/certs.d/harbor.internal sudo cp harbor.crt /etc/docker/certs.d/harbor.internal/ca.crt sudo systemctl restart docker目錄名必須是hostname:port格式如果端口是 443 可以省略端口部分但有些版本比較嚴(yán)格我建議統(tǒng)一寫成harbor.internal:443和harbor.internal各建一個(gè)目錄兩邊都放上證書避免猜版本行為。文件名同樣是固定的自簽根證書叫ca.crt客戶端證書叫client.cert客戶端私鑰叫client.key名字錯(cuò)了不生效且不報(bào)錯(cuò)只是繼續(xù)報(bào)證書不受信任非常迷惑。改完 daemon.json 一定要systemctl daemon-reload再重啟直接 restart 有時(shí)不讀新配置。驗(yàn)證方式是docker info看輸出里Insecure Registries一欄有沒有你的域名。4.2 域名解析的三種兜底方式harbor.internal這類內(nèi)部域名在公網(wǎng) DNS 上是不存在的??蛻舳藞?bào)dial tcp: lookup harbor.internal: no such host就是解析沒通。三種解法從輕到重第一種改 hosts。每臺客戶端機(jī)器加一行10.0.0.12 harbor.internal最簡單機(jī)器多了難維護(hù)學(xué)習(xí)環(huán)境夠用。第二種內(nèi)網(wǎng) DNS 加一條 A 記錄。如果你們本來就有內(nèi)網(wǎng) DNS加一條記錄所有機(jī)器自動生效這是最干凈的方式。第三種用云廠商的私有 DNS 服務(wù)。在 VPC 內(nèi)建私有解析區(qū)把域名指向內(nèi)網(wǎng) IP新機(jī)器加入 VPC 就自動生效。這個(gè)適合有一定規(guī)模的團(tuán)隊(duì)配置略復(fù)雜。不管用哪種配完在客戶端執(zhí)行g(shù)etent hosts harbor.internal或nslookup確認(rèn)能拿到正確的內(nèi)網(wǎng) IP不要用ping判斷ICMP 可能被安全組攔掉但 TCP 443 是通的。4.3 項(xiàng)目、用戶與機(jī)器人賬號的權(quán)限模型Harbor 的權(quán)限模型剛接觸會覺得繞講清楚其實(shí)就三層。項(xiàng)目是最小隔離單位鏡像地址的格式是hostname/項(xiàng)目名/鏡像名:tag那個(gè)中間段就是項(xiàng)目。成員是用戶和項(xiàng)目的綁定關(guān)系同一個(gè)用戶在不同項(xiàng)目可以有不同角色。機(jī)器人賬號是給自動化系統(tǒng)用的憑據(jù)名字自動帶robot$前綴可以精確限定到某個(gè)項(xiàng)目、某類操作。為什么要有機(jī)器人賬號因?yàn)?CI 系統(tǒng)里存明文密碼風(fēng)險(xiǎn)太大機(jī)器人賬號的特點(diǎn)是只能操作指定項(xiàng)目、可以設(shè)置過期時(shí)間、權(quán)限只給推或只給拉。實(shí)踐中的配法是CI 用一個(gè)只有推送權(quán)限的機(jī)器人賬號部署節(jié)點(diǎn)用一個(gè)只有拉取權(quán)限的機(jī)器人賬號人類用戶用自己賬號三條線分離出事能定位。創(chuàng)建路徑是進(jìn)入項(xiàng)目 → 機(jī)器人賬戶 → 新建 → 勾選權(quán)限推送、拉取、刪除等→ 設(shè)置過期天數(shù) → 保存。生成的令牌只顯示一次關(guān)掉頁面就再也看不到必須當(dāng)場復(fù)制到 CI 的密鑰管理里。這個(gè)設(shè)計(jì)我第一次用的時(shí)候差點(diǎn)吃虧復(fù)制晚了只能刪了重建。4.4 一次完整的推送與拉取演練鏈路走一遍心里才踏實(shí)。假設(shè)本地有個(gè)鏡像叫myapp:1.0要推到dev項(xiàng)目。# 1. 登錄回車后輸入密碼或令牌 docker login harbor.internal -u robot\$devci # 2. 打標(biāo)簽格式必須是 主機(jī)/項(xiàng)目/鏡像:標(biāo)簽 docker tag myapp:1.0 harbor.internal/dev/myapp:1.0 # 3. 推送 docker push harbor.internal/dev/myapp:1.0 # 4. 換臺機(jī)器拉取 docker pull harbor.internal/dev/myapp:1.0幾個(gè)細(xì)節(jié)值得強(qiáng)調(diào)。第一docker login的-u參數(shù)里如果有$在命令行里要轉(zhuǎn)義或加單引號否則 shell 會當(dāng)成變量展開結(jié)果是用戶名變成空字符串報(bào)unauthorized。第二鏡像地址的斜杠結(jié)構(gòu)是強(qiáng)制的harbor.internal/myapp:1.0這種少一段的寫法會被 Docker 解釋成 Docker Hub 上的倉庫推不上去。第三標(biāo)簽里的 tag 不要用latest做唯一版本Harbor 支持不可變標(biāo)簽規(guī)則可以在項(xiàng)目配置里開啟防止同名標(biāo)簽被覆蓋導(dǎo)致線上拉到的鏡像悄悄變了。推上去之后回 Web 界面看dev項(xiàng)目里應(yīng)該出現(xiàn)myapp倉庫點(diǎn)進(jìn)去能看到標(biāo)簽、大小、拉取次數(shù)、漏洞掃描結(jié)果。第一次看到掃描報(bào)告可能會有不少條目別慌大部分是基礎(chǔ)鏡像里的歷史組件勾選“僅顯示可修復(fù)”會清爽很多。4.5 配額、垃圾回收與容量治理這一小節(jié)是很多人忽略、但遲早會撞上的問題。刪除鏡像不等于釋放磁盤。Harbor 的存儲是分層共享的刪掉一個(gè) tag 只是刪了引用底層的 Blob 還在必須跑垃圾回收才會真正釋放。流程是這樣的先在配置管理里把倉庫設(shè)為只讀Repository read only打開然后在系統(tǒng)管理 - 垃圾回收里新建一次回收任務(wù)勾選“刪除未被引用的 Blob”等它跑完再把只讀關(guān)掉。為什么必須只讀因?yàn)?GC 在掃描時(shí)如果有新鏡像正在推送可能把正在寫的層判定為無引用而刪掉后果是鏡像損壞。這個(gè)順序我吃過一次教訓(xùn)當(dāng)時(shí)沒開只讀回收完有個(gè)鏡像拉下來manifest unknown只能重新推一遍。配額可以按項(xiàng)目設(shè)在項(xiàng)目配置里填存儲上限比如 20GB超了之后推送會直接報(bào)denied: requested access to the resource is denied這個(gè)報(bào)錯(cuò)信息起得不太好配額和權(quán)限共用同一個(gè)錯(cuò)誤碼排查時(shí)容易走偏。配額的好處是防止某個(gè)人把整盤塞滿導(dǎo)致所有人都用不了。容量監(jiān)控靠兩件事一看df -h /data的剩余空間二看 Harbor 首頁的存儲統(tǒng)計(jì)。我在機(jī)器上加了一條定時(shí)任務(wù)磁盤超過 80% 就發(fā)通知這個(gè)簡單動作能避免絕大多數(shù)“倉庫突然掛了”的事故。5. 踩坑實(shí)錄與排查速查表這一節(jié)是我認(rèn)為整篇最有價(jià)值的部分。前面所有步驟理論上都能一次成功但實(shí)際動手時(shí)總有各種偏差下面這些錯(cuò)我基本都親手撞過一遍。5.1 證書與協(xié)議類報(bào)錯(cuò)最常見的兩條。第一條是http: server gave HTTP response to HTTPS client意思是客戶端用 HTTPS 去連服務(wù)端返回的是 HTTP。原因通常是 Harbor 只啟了 HTTP 沒啟 HTTPS而你客戶端按默認(rèn) HTTPS 發(fā)起。解決辦法要么給 Harbor 配上證書要么把insecure-registries加上。不要嘗試把倉庫地址改成http://前綴Docker 不認(rèn)這個(gè)寫法docker push http://xxx會直接把地址當(dāng)成鏡像名的一部分。第二條是x509: certificate signed by unknown authority。這說明證書鏈沒被信任檢查三件事/etc/docker/certs.d/hostname/ca.crt是否存在且內(nèi)容正確、目錄名里的主機(jī)名和端口是否完全匹配、改完有沒有重啟 Docker。還有一種隱藏情況你配的證書 SAN 里只有harbor.internal但客戶端用的是 IP 訪問那自然不匹配補(bǔ)齊 SAN 或者改了訪問方式。第三條是瀏覽器能打開但 curl 報(bào)證書錯(cuò)誤這多半是瀏覽器點(diǎn)了“繼續(xù)訪問”的例外而命令行沒有這個(gè)例外機(jī)制本質(zhì)上還是證書沒進(jìn)信任鏈。5.2 認(rèn)證與權(quán)限類報(bào)錯(cuò)unauthorized: authentication required就是沒登錄或者登錄失效。Docker 的憑據(jù)存在~/.docker/config.json有時(shí)候換了賬號但憑據(jù)還是舊的docker logout再login能解決大半。另一個(gè)高頻原因是密碼里帶特殊字符在命令行里被 shell 吃掉了建議用--password-stdin從標(biāo)準(zhǔn)輸入傳echo 你的密碼 | docker login harbor.internal -u youruser --password-stdindenied: requested access to the resource is denied有兩個(gè)完全不同的原因一是項(xiàng)目不存在或者你沒有該項(xiàng)目權(quán)限二是鏡像地址的項(xiàng)目段寫錯(cuò)了。第二種特別隱蔽因?yàn)閳?bào)錯(cuò)一樣。我當(dāng)時(shí)推一個(gè)鏡像報(bào)這個(gè)錯(cuò)折騰半天最后發(fā)現(xiàn)項(xiàng)目名叫dev我寫成了developHarbor 不會自動建項(xiàng)目就直接拒了。還有一種情況是用了機(jī)器人賬號但權(quán)限沒勾全比如只給了拉取沒給推送推的時(shí)候報(bào)同樣的錯(cuò)。排查方式是去 Web 界面看該賬號的權(quán)限列表一目了然。5.3 存儲與性能類報(bào)錯(cuò)no space left on device是硬故障處理順序是先df -h確認(rèn)是哪個(gè)分區(qū)滿再看/data/docker和/data/harbor占用清掉無用鏡像和容器跑一次 GC。預(yù)防手段前面說過日志輪轉(zhuǎn)和磁盤告警。context deadline exceeded出現(xiàn)在推送大鏡像時(shí)通常是網(wǎng)絡(luò)抖動或者 Nginx 緩沖區(qū)配置問題。Harbor 的 Nginx 模板里對 registry 路徑已經(jīng)關(guān)掉了proxy_buffering一般不會有問題但如果中間又套了一層反向代理那層代理的client_max_body_size和超時(shí)時(shí)間就要單獨(dú)調(diào)大。默認(rèn)client_max_body_size 1m是 Nginx 的出廠值它會把大層上傳直接掐斷癥狀是推送到一半失敗、日志里是 413。還有一個(gè)性能問題值得提Harbor 的數(shù)據(jù)庫在小機(jī)器上并發(fā)高了會慢表現(xiàn)是 Web 界面點(diǎn)擊后轉(zhuǎn)圈很久。這時(shí)候看max_open_conns和機(jī)器內(nèi)存往往內(nèi)存吃緊導(dǎo)致頁緩存不足PostgreSQL 頻繁讀盤。加內(nèi)存或者把max_job_workers調(diào)小都能緩解。5.4 一頁紙速查表現(xiàn)象最可能原因處理動作server gave HTTP response to HTTPS client客戶端用 HTTPS 連 HTTP 服務(wù)配證書或加 insecure-registriesx509: certificate signed by unknown authority證書未進(jìn)信任鏈或 SAN 不匹配放 ca.crt 到 certs.d 并核對 SANlookup harbor.internal: no such host域名無法解析改 hosts 或配內(nèi)網(wǎng) DNSunauthorized: authentication required未登錄或憑據(jù)過期重新 login用 stdin 傳密碼denied: requested access to the resource項(xiàng)目名錯(cuò)、無權(quán)限、超配額核對地址、成員角色、項(xiàng)目配額no space left on device磁盤滿清空間后執(zhí)行 GC413 請求實(shí)體過大前置代理體積限制調(diào)大 client_max_body_size頁面 502后端服務(wù)未就緒或崩潰看 core 與 nginx 日志、看內(nèi)存推送成功但列表看不到推到了別的項(xiàng)目或標(biāo)簽錯(cuò)看 Web 界面全量搜索鏡像名刪除鏡像后空間沒釋放未執(zhí)行垃圾回收開只讀 → 跑 GC → 關(guān)只讀6. 學(xué)習(xí)環(huán)境之外的加固思路與備份恢復(fù)單機(jī)能跑之后接下來做什么取決于這套東西會不會真的承載業(yè)務(wù)。如果只是學(xué)習(xí)這一節(jié)可以挑著看如果已經(jīng)有同事在往里推鏡像了下面幾件事越早做越省心。6.1 證書續(xù)期與自動簽發(fā)自簽證書十年的有效期在內(nèi)部環(huán)境夠用但有一個(gè)隱患換機(jī)器、改 IP、加域名的時(shí)候要重新簽發(fā)而所有客戶端都得同步更新。機(jī)器一多這件事會變成負(fù)擔(dān)。更穩(wěn)的做法是走自動簽發(fā)用一個(gè)能訪問的 ACME 服務(wù)把 80 端口暴露給驗(yàn)證流程讓工具自動簽發(fā)和續(xù)期證書放固定路徑Harbor 配置里指向它續(xù)期后重啟 Nginx 容器即可生效。如果不想引入外部依賴也可以自建一個(gè)內(nèi)部簽發(fā)機(jī)構(gòu)用它簽服務(wù)端證書然后把根證書分發(fā)到所有客戶端的certs.d目錄。好處是內(nèi)部證書鏈條可控?fù)Q服務(wù)端證書不用動客戶端。不管哪種方式都要有一個(gè)到期提醒。我見過最尷尬的情況是某天早上所有人都推不了鏡像查了半天是證書過期而前一天還沒有任何異常。加一條定時(shí)檢查證書剩余天數(shù)的腳本少于 30 天就發(fā)通知成本極低。6.2 備份與恢復(fù)演練Harbor 的備份只需要四樣?xùn)|西harbor.yml、證書目錄、數(shù)據(jù)卷主要是 registry 的存儲目錄、數(shù)據(jù)庫導(dǎo)出。數(shù)據(jù)庫導(dǎo)出用容器里的 pg_dumpdocker exec harbor-db pg_dump -U postgres -d registry /data/backup/registry_$(date %F).sql數(shù)據(jù)卷可以直接用tar打包但要注意體積幾十 GB 的鏡像打包會很慢。更聰明的方式是只備份元數(shù)據(jù)和配置鏡像本身在極端情況下可以重新推送——如果你有 CI重新構(gòu)建推一遍就行。所以我的策略是數(shù)據(jù)庫每天導(dǎo)數(shù)據(jù)卷每周打一次配置文件隨改隨備份?;謴?fù)流程也要演練一次不然真出事手忙腳亂。大致步驟是新機(jī)器裝 Docker、恢復(fù)配置文件與證書、把數(shù)據(jù)卷掛回去、導(dǎo)入數(shù)據(jù)庫、執(zhí)行./install.sh。整個(gè)過程我實(shí)際跑過一次從零到服務(wù)可用大概二十分鐘前提是備份文件都在。6.3 反向代理與端口收斂Harbor 自帶 Nginx已經(jīng)夠用但有時(shí)候你需要在一個(gè)公網(wǎng)入口后面掛多個(gè)服務(wù)那就得在前面再加一層反向代理。這時(shí)候要注意幾個(gè)配置proxy_set_header Host $host必須帶上否則 Core 服務(wù)生成的跳轉(zhuǎn)地址會錯(cuò)client_max_body_size要調(diào)到至少 1Gproxy_request_buffering off讓 Docker 的分塊上傳直通后端避免大鏡像先落盤再轉(zhuǎn)發(fā)。端口收斂的意思是對外只開 443把 8443 關(guān)掉。Harbor 的 HTTP 端口本來是跳轉(zhuǎn)用的但它在某些配置下會直接暴露 API如果安全組沒管好就是個(gè)意外入口。收斂之后客戶端行為也更統(tǒng)一。6.4 資源監(jiān)控與告警的最小配置不需要上完整監(jiān)控體系先把三個(gè)指標(biāo)盯住就夠磁盤使用率、容器重啟次數(shù)、接口響應(yīng)時(shí)間。磁盤用系統(tǒng)自帶的定時(shí)任務(wù)加腳本容器重啟次數(shù)用docker inspect里的RestartCount定期采樣響應(yīng)時(shí)間用一次curl -o /dev/null -s -w %{time_total}記日志。再進(jìn)一步可以給 Harbor 自身的/api/v2.0/health接口做探活返回 200 說明核心服務(wù)正常。這個(gè)接口不需要認(rèn)證適合放在外部撥測里。我配了一個(gè)每五分鐘跑一次的腳本連續(xù)三次失敗就告警比等用戶反饋快得多。7. 一些踩過之后的個(gè)人體會整套東西跑通之后我最大的感受是私有鏡像倉庫的難點(diǎn)根本不在“裝起來”裝起來只要半小時(shí)真正的功夫在“讓它穩(wěn)定地被別人用”。我第一次部署完興奮了半天結(jié)果第二天同事來推鏡像一個(gè)證書問題卡了兩小時(shí)因?yàn)樗麢C(jī)器上的certs.d目錄名帶了端口我沒有。這類問題的共同點(diǎn)是配置項(xiàng)的名字看起來都差不多但差一個(gè)字就不生效而且不生效的時(shí)候往往不報(bào)錯(cuò)只是行為不對。第二個(gè)體會是關(guān)于“學(xué)習(xí)環(huán)境”的邊界。一開始我什么都圖省事自簽證書、單機(jī)、不開備份覺得反正壞了重裝。但當(dāng)我真的往里推了幾十個(gè)鏡像之后心態(tài)就變了重裝意味著重新推一遍代價(jià)不小。所以哪怕是學(xué)習(xí)環(huán)境我也建議從第一天就把data_volume和數(shù)據(jù)盤規(guī)劃對把備份腳本寫好這兩件事的成本很低收益是把“隨便折騰”變成“折騰不心疼”。第三個(gè)是給準(zhǔn)備復(fù)現(xiàn)這套流程的人一個(gè)提醒遇到報(bào)錯(cuò)先看日志不要猜。Harbor 的日志集中在data_volume/log下core、jobservice、registry 各有文件Nginx 的在nginx目錄里。我踩過的坑里有一半是因?yàn)槲腋鶕?jù)錯(cuò)誤信息想象了一個(gè)原因結(jié)果改了半天配置實(shí)際問題在另一層。養(yǎng)成先tail日志再動手的習(xí)慣能省掉大量時(shí)間。最后分享一個(gè)我常用的排查套路當(dāng)docker push失敗時(shí)先用curl -k https://harbor.internal/api/v2.0/systeminfo確認(rèn)服務(wù)端到底通不通再docker login確認(rèn)認(rèn)證最后才看推送。三步走下來問題落在哪一層非常明確比一上來就翻 Docker 日志高效得多。