結論先講:CRC 單機跑完整 CI/CD,
disk-size建議 160GB。60GB 會在你只是開幾個 debug pod 的時候就撞上 DiskPressure,把正在滾動更新的 Pod 卡住五分鐘。以下是這個數字怎麼算出來的。
今日目的:先算清楚終局的資源需求,再決定
disk-size——因為這是官方支援的調整路徑裡,唯一一項事後改不了的設定。本篇會用這台機器的實測數字,示範沒算清楚的代價。
先備知識:本篇銜接 Day 1〈主題宣告〉,讀過該篇即可上手。
CRC(Red Hat OpenShift Local,前身 CodeReady Containers)的安裝流程官網已寫得很清楚:下載、crc setup、crc start,這篇不重複。這篇要講的是安裝之前必須想清楚的一件事:disk-size 一旦設定,官方支援的調整方式就是 crc config set disk-size 之後跑 crc delete --force,把整台 VM 砍掉重建,先前部署的 namespace、服務與 Tekton 執行紀錄全部歸零。記憶體和 CPU 隨時能用 crc stop/crc start 調整生效,磁碟走的是這條路。(有沒有繞過重建的路子,後面〈邊界條件與擴充機制〉會誠實交代查到的線索跟沒查證的部分。)
這台機器的磁碟配了 60GB,而這個數字當初沒有經過推導。後面會攤開它是怎麼來的,以及後來付出的代價:為了替這篇文章撈即時數據,撰寫過程中就觸發了一次磁碟用量觸頂,節點被標上 DiskPressure taint,一次進行中的滾動更新裡新 Pod 被 taint 卡住、無法排入節點長達 4 分 23 秒,舊 Pod 因此多存活了將近五分鐘。完整過程留在「磁碟:我配少了」一節。
crc setup 會自動檢查並啟用)在執行 crc setup 之後,務必在啟動叢集前完成本篇的資源參數規劃。
宿主機規格(供讀者自行評估是否有餘裕比照配置):
| 項目 | 規格 |
|---|---|
| CPU | Intel Core i9-13900H(14 核/20 邏輯處理器) |
| 記憶體 | 63.7GB |
| 系統磁碟(C 槽) | 總容量 952.8GB,可用 590.5GB |
| 作業系統 | Windows 11 Pro |
在決定資源配置之前,要先掌握整個叢集未來會承載的元件全貌——完整的五階段建置順序已於 Day 1 說明。這裡直接給答案:常駐服務會持續累加(Gitea、Nexus 先上,Tekton 隨後長駐,ArgoCD/Kyverno 最終也是常駐元件),資源配置要以累加後的終局為準,不能只看初期單一元件的需求。

下面這張表是「累加到終局之後,各元件實際吃多少」的閒置量測結果。量測方式先說明:這台機器上 oc adm top pod/oc adm top node 一律回傳 error: Metrics API not available——查證後發現 CRC 預設不隨附 openshift-monitoring 底下的 metrics-server(namespace 存在但零 Pod),也就是 Metrics API 從一開始就沒有被啟用,不是叢集異常。crc config 裡有一個 enable-cluster-monitoring 開關(這台機器目前沒設,維持預設 false),理論上打開後會連帶啟用 openshift-monitoring 全套元件;會不會因此讓 oc adm top 一併可用沒有在這台機器上實測——打開這個開關等於在這篇正在量測的基準環境裡多裝一套 Prometheus/Alertmanager,會動到本節要量的資源數字本身,所以沒有為了這篇文章去試。改用 oc debug node/crc -- chroot /host crictl stats -a 搭配 crictl ps -a(用 CONTAINER ID 把兩者的輸出接起來,取得每個容器所屬的 namespace)拿到等效數字(純看磁碟容量的話還有更省事的路,見〈邊界條件與擴充機制〉一節)。
| 元件 | 記憶體(閒置) | CPU(閒置,約當 vCPU) | 磁碟 | 何時進場 |
|---|---|---|---|---|
| OpenShift 本體(其餘所有系統 namespace 合計) | 11.45 GB | ≈0.52 vCPU | 主要是容器映像,見下節 | Day 2(本篇) |
| Gitea | 606 MB | ≈0.15 vCPU* | PVC 宣告 64Gi(3×PostgreSQL 10Gi+共用儲存 10Gi+3×Valkey 8Gi) | Day 8 |
| Nexus(含 proxy cache) | 1,421 MB | <0.01 vCPU | PVC 宣告 20Gi,實測 blob store 僅 1.7GB | Day 10 |
| Tekton controller(openshift-pipelines) | 352 MB | ≈0.02 vCPU | 無獨立 PVC | Day 13 |
| ArgoCD(openshift-gitops) | 1,196 MB | ≈0.02 vCPU | 無獨立 PVC | Day 28 |
| Kyverno(4 controllers) | 304 MB | ≈0.05 vCPU | 無獨立 PVC | Day 29 |
| PipelineRun 尖峰額外佔用 | 未實測 | 未實測 | 未實測 | Day 18 起 |
| 閒置合計(不含 PipelineRun 尖峰) | ≈15.3 GB | ≈0.76 vCPU | 45.44GB/63.82GB(71%,GC 之後的觀測值,見下節) | — |
* Gitea 的 CPU 數字量測當下叢集才剛啟動十餘分鐘,三節點 PostgreSQL HA 仍在做啟動後同步,數字略高於長時間穩定運行後的真實閒置值;記憶體數字不受此影響,可視為穩定值。這裡也先說在前面:Gitea 這組 3×PostgreSQL HA+3 節點 Valkey cluster 的規格,是照著多節點正式環境的習慣配置,在單機 CRC 上本身就偏重——64Gi 的 PVC 宣告有沒有必要縮減成單副本,是比「disk-size 該配多少」更前面一步的架構選擇,留到 Day 8 實際佈署 Gitea 時再處理。
「PipelineRun 尖峰額外佔用」這一行留白:量測當下 ci namespace 裡沒有正在執行的 PipelineRun,閒置記憶體實測為 0。真正的尖峰要在觸發一次完整 PipelineRun 的同時另開視窗每 10 秒抓一次上述 crictl 替代法才量得到,留到 Day 18 起實際跑過 Pipeline 再補。
閒置狀態下容器層級的記憶體加總約 15.3GB,這台機器 Allocatable 記憶體約 23.55GiB,餘裕約 8.25GiB 留給 PipelineRun 尖峰與系統緩衝——這句話講完,後面「12/24/60」這組數字才是從實際負載推導出來的,不是憑空宣稱。(crc status 顯示的 RAM Usage 會比這個容器加總數字略高一些,落差來自 VM 本身的系統開銷,屬預期範圍。)
這張表最該注意的不是任何單一元件,是第一行:什麼都還沒裝,OpenShift 本體就吃掉 11.45GB,佔閒置總量的四分之三。 所有之後要裝的東西——Gitea、Nexus、Tekton、ArgoCD、Kyverno 全部加總——不到 4GB(實際 3.88GB)。在單機上評估 CRC 時,這是最容易低估的一項:以為在為自己的服務配資源,實際上絕大部分是在為平台本身付費。
這台機器目前實際跑的配置(crc config view):
- cpus : 12
- memory : 24576 # 24GB
- disk-size : 60 # 60GB
- pull-secret-file : C:\Users\<user>\pull-secret.json
這組數字不是預設值(預設為 4 vCPU / 10752MB / 31GB)。節點實際回報的可分配資源:
| 資源 | Capacity | Allocatable | 備註 |
|---|---|---|---|
| CPU | 12 | 11800m | kubelet 保留 200m |
| 記憶體(GiB) | ~24.03 | ~23.55 | kubelet 保留約 480MB |
| 磁碟(ephemeral-storage) | 63.82GB | ~57.07GB | kubelet 保留約 6.75GB 作為系統與驅逐緩衝 |
這裡的「驅逐緩衝」不是字面上圈一塊系統保留區:查 oc get --raw /api/v1/nodes/crc/proxy/configz 可以看到 systemReserved.ephemeral-storage 實際只有 350Mi,6.75GB 的落差主要來自 nodefs.available<10% 這道驅逐門檻——Allocatable 的算法是容量扣掉 systemReserved、再扣掉驅逐門檻對應的容量(10%×63.82GB≈6.38GB+350Mi≈6.75GB,數字對得上)。這點在後面反推更大 disk-size 時有影響:imagefs.available<15% 這道實際觸發 DiskPressure 的門檻,算的是磁碟實際位元組用量,不是這裡的 Allocatable 記帳,兩者是不同的機制。(以下 GB 指十進位、GiB 指二進位,兩者偶爾混用,差異在四捨五入誤差內,不影響結論。)
調整記憶體與 CPU 設定:
crc config set memory 24576
crc config set cpus 12
記憶體與 CPU 可透過 crc stop / crc start 重啟生效,但虛擬磁碟空間的變動需要特別處理,見下一節。
實際上 60GB 只是預設值 31GB 的兩倍左右——一個看起來夠用的整數,是預設值撐不住壓力之後隨手調高的結果,沒有經過進一步推導。如果目的只是重現這條 CI 流水線本身的運作,60GB 是夠用的;但後續為了替這篇文章撈即時數據,另外開了幾個除錯 Pod,DiskPressure 就在這時候被觸發了。
以下是回頭替資源表撈即時數據時的當下輸出。
為了取得 oc adm top 失效後的替代數據,連續開了幾個 oc debug node/crc 除錯 Pod 跑 journalctl/crictl stats/crictl ps/du。這幾個除錯 Pod 本身會拉映像、佔用暫存空間,疊加在原本就逼近門檻的磁碟上。事發前的真實用量必然已超過 54.25GB(85%),因為 imagefs.available<15% 這道硬門檻不到這個水位不會觸發(這台機器的 imagefs 與 nodefs 同一個分割區,下段會確認);後文提到的 45.44GB/63.82GB(71%)是事件發生之後才量到的。kubelet 的 eviction manager 在 10:19:58 記下 attempting to reclaim resourceName="ephemeral-storage",接著在一秒內清掉 30 個沒有 Pod 在用的舊 Operator catalog/release 映像,日誌記下的合計是 17.8GB(這個數字後面會證明比實際釋放的位元組數高,見〈磁碟實際被誰吃掉〉),隨即回報 able to reduce resource pressure without evicting pods。
但 DiskPressure 這個節點條件本身,還是在幾秒後的 10:20:03 被記錄下來,節點隨即被打上:
Taints: node.kubernetes.io/disk-pressure:NoSchedule
oc get events -A 記下兩個除錯 Pod(crc-debug-rvzw6、crc-debug-9496h)被判定 Evicted,理由是 The node had condition: [DiskPressure]。這裡有個地方一開始沒看懂:kubelet 前一刻才回報 able to reduce resource pressure without evicting pods,怎麼緊接著又有兩個 Pod 被判 Evicted?答案是這兩個除錯 Pod 根本沒有真正跑起來——DiskPressure 條件成立期間,kubelet 在 admission 階段就把新 Pod 擋下來,狀態同樣寫成 Failed/Evicted/The node had condition: [DiskPressure],跟真的把一個已經在跑的 Pod 趕下線用的是同一組欄位,但成因不同:一個是進不來,一個是被踢出去。映像 GC 保住的是原本就在跑的工作負載,付出代價的是這兩個一直沒被排進來的除錯 Pod。同一時間 ci namespace 裡正在滾動更新的 Tekton EventListener 也受到影響:新版本的 Pod(el-frontend-nx-mono-ci-77689f446-zpm5z)在 10:20:46 就已建立,但 taint 是 NoSchedule,在 10:25:09 taint 解除之前應該是卡在排不進節點的狀態,不是排上了卻拉不動映像——taint 一解除就立刻進入 Pulling image,時間點也對得上這個解讀,前後卡了 4 分 23 秒;舊版本的 Pod 因為新 Pod 遲遲無法就緒,跟著多活了將近 5 分鐘才被關閉。
taint 在 10:25:09 自行清除(DiskPressure 條件轉為 False,reason: KubeletHasNoDiskPressure),前後約 5 分鐘。這不是巧合,這台節點的 eviction-pressure-transition-period 設定就是 5m0s:條件一旦成立,至少要維持滿 5 分鐘的冷卻期才會解除,用意是避免用量在門檻邊緣來回抖動時 taint 頻繁地打上又拿掉。這次事件沒有造成資料損失,但可以看出 60GB 的餘裕薄到一輪除錯/量測作業就能推過門檻,而且一旦跨過,接下來 5 分鐘內任何新排程或滾動更新的工作負載都會被卡住。
叢集沒有另外用 KubeletConfig CR 調整驅逐門檻(oc get kubeletconfig -A 回傳 No resources found),套用的是 OpenShift 內建的 hard eviction 預設值:imagefs.available<15%、nodefs.available<10%(外加對應的 inodes 門檻)。這組值不是命令列 flag 設定的,而是由 MachineConfig 產生、寫進 /etc/kubernetes/kubelet.conf 的 KubeletConfiguration;要看叢集實際生效的門檻,得另外查 configz 端點,不能只憑檔案內容判斷,排查時容易在這一步走錯。這台機器的 nodefs 和 imagefs 也確認是同一顆:df -h 顯示 /sysroot、/var/lib/containers、/var/lib/kubelet 全部落在同一個 /dev/sda4 分割區上,門檻沒有拆成兩本帳,60GB 就是唯一的預算。
比「60GB 不夠」更值得記下來的是:磁碟壓力是一個事後去看就看不到的事件。查完之後 crc status 顯示磁碟用量已退回 45.44GB/63.82GB(71%),taint 也拿掉了,看起來一切正常;證據只留在 kubelet 日誌與 oc get events 裡,而 OpenShift 事件的預設保留期通常只有一小時左右,晚一步查就什麼都看不到。如果這五分鐘裡剛好有 Webhook 進來觸發 CI,流水線不會失敗、不會告警,只是卡住排程,事後查看叢集完全健康。這個模式在後面幾天還會反覆出現:系統事後回報的狀態,跟事發當下的狀態是兩回事。這也代表單看「目前 71%」不足以判斷磁碟餘裕還剩多少,真正該盯的是這道 GC 有沒有被反覆觸發。
更準確地說,這台機器很可能長期跑在 85% 門檻邊緣:每次用量逼近門檻,kubelet 就自動清掉一批不在用的映像,把水位壓回 70% 上下,crc status 也就跟著顯示一切正常。不只是「事件過後看不到」,連平常看到的水位讀數本身,都已經是被自動回收機制修飾過的結果,不是原始用量。
這個推測後來多了一筆數據可以對照:為了撰寫 Day 3 又做了一輪完整的 crc stop → crc start——VM 重啟後 OLM 會重新拉取 Catalog Image,理論上正是水位容易被推高的時間點。重啟完成後量到的讀數是 45.75GB/63.82GB(72%),跟這次事件後的基準幾乎持平,這一輪沒有再次觸發 DiskPressure。單一次沒觸發不代表門檻邊緣的推測錯了,反而剛好對上「回收機制把水位穩定壓回 70% 上下」這個說法本身——讀數持平,看不出來的正是它離門檻還有多近。
(順帶一提:crc config 裡設定的 disk-size 60 是宣告值,節點實際回報的 ephemeral-storage 容量是 62,323,692Ki,換算成十進位 GB 正好是 crc status 顯示的 63.82GB——這是正常的單位換算與虛擬磁碟格式開銷,不是設定被打折。)
oc get pvc -A 列出的宣告容量加總起來高達 107Gi,遠超過 60GB 物理磁碟:
gitea data-gitea-postgresql-ha-postgresql-0/1/2 10Gi × 3
gitea gitea-shared-storage 10Gi
gitea valkey-data-gitea-valkey-cluster-0/1/2 8Gi × 3
nexus data-nexus-nexus3-0 20Gi
openshift-image-registry crc-image-registry-storage 20Gi
openshift-pipelines postgredb-tekton-results-postgres-0 1Gi
ci pipeline-source-pvc 2Gi
這組數字能在 60GB 磁碟上跑起來,是因為 CRC 內建的儲存驅動(kubevirt.io.hostpath-provisioner,thin-provisioned)不會預先配置,PVC 的容量宣告只是上限承諾,不是實際佔用。直接在節點上(/var/lib/csi-hostpath-data/)對每個 PVC 的資料夾做 du -sh,11 個綁定中的 PVC 實際落地只有約 4.5GB:Nexus 的 data-nexus-nexus3-0 佔 1.7GB(與 blob store 內容一致),Gitea 三個 PostgreSQL 實例加總不到 500MB。
真正吃掉磁碟的是容器映像:節點上快取的映像檔加總 30.1GB(du -x -sh /var/lib/containers 實測,GC 之後的穩態值——這裡刻意標清楚量測方法,因為 crictl images 加總跟 du 量到的實際落地可以差到四成,見下段〈事發峰值〉;這個路徑底下嚴格來說也含容器讀寫層與 CRI-O 的 runtime 中繼資料,不是純映像,但這兩者跟映像本身一起隨映像拉取/清除增減,實務上歸在同一列),是單一最大的消耗來源;事發前的實際用量明顯更高,反推的估計區間留到下段再細算。這個階段的叢集還沒真正跑過幾輪 Pipeline 建置,proxy cache 還沒機會養大,OCP release image、Operator catalog index(redhat-operator-index、community-operator-index 等,單一動輒 1GB 以上)與各元件容器映像才是現階段的大宗。
同一次盤點還挖到一個看不見的角落:/var/lib/csi-hostpath-data/ 底下有一個目錄(對應 pvc-d2114ecf-4b31-454e-b1d7-b2c19fb87a79,1.3GB)已經找不到對應的 PV/PVC 物件——oc get pv/oc get pvc 都查不到它。原因是這個 StorageClass 的 reclaimPolicy 是 Retain:PVC 被刪除後,Kubernetes 物件消失了,但節點磁碟上的資料夾原封不動留著,而且不會出現在任何 oc get 輸出裡。這 1.3GB 目前只是零頭,但它會隨著 PVC 建立又刪除的次數持續累積,是這套磁碟帳本裡唯一「官方查詢工具完全看不到」的一項。
trivy-db 每日鏡射與 SBOM 產出並沒有各自佔一塊獨立空間:CronJob 是用 oras 把資料推進 Nexus 的 blob store,因此已經算在上面 Nexus 的 1.7GB 裡,不需要另外列一行。
上面這幾項加總還缺一塊:RHCOS 系統本身、/var/log、kubelet/CRI-O 的執行期中繼資料,一直沒有單獨量過。第一次嘗試直接量 du -sh /var/lib/containers/storage/overlay,結果是 112GB——比整顆 60GB 磁碟還大。原因是 overlay 檔案系統底下每個容器的 merged/diff/work 目錄本身也是掛載點,du 會沿著這些掛載點繼續往下算,同一批檔案因此被重複計算。加上 -x(不跨越檔案系統邊界)重新量,/var/lib/containers 的實際落地是 28GiB(換算十進位即前段與推算表用的 30.1GB,同一個數字);同樣方式量 /sysroot/ostree(RHCOS 部署樹實際掛載的路徑,/ostree 只是指向它的連結)合計 43GiB,跟 df -h /sysroot 顯示的已用量完全一致——這台節點幾乎所有磁碟內容都收在這棵樹底下,/var 也是從這裡掛出來的。43GiB 扣掉容器的 28GiB,剩下 15GiB。其中約 4.6GiB 是 /var/lib/csi-hostpath-data/ 底下的 PVC 落地資料——這部分前面已經量過,下面表格也另外列了一行,這裡不重複計入。扣掉 PVC 之後剩下約 10.5GiB 才是真正沒被列過的部分,分布在 /var/log(5.3GiB,最大宗)、其他 /var/lib 下的 kubelet/CRI-O 狀態與 CNI 資料(2.4GiB)、RHCOS 部署樹本身的 /usr//etc 檔案(2.7GiB),以及 ostree repo 物件(86MiB,可忽略)。換算成十進位 GB,這一塊合計約 11.3GB。journalctl --disk-usage 顯示 journal 本身只佔 1.0GB,/var/log 裡真正的大頭是 CRI-O 寫出的 pod 日誌與稽核紀錄;journald.conf 裡 SystemMaxUse 等上限設定維持預設未開啟,這部分跟孤兒 PVC 目錄一樣,沒有硬性上限、會持續累加。
建議值怎麼來的?用這台機器實測到的數字往前推:
| 項目 | 現況(實測) | 30 天後的合理預期 |
|---|---|---|
| 容器映像(OCP release、Operator catalog、各元件) | 30.1GB(du -x -sh /var/lib/containers 實測,GC 後穩態值) |
42–51GB(工作集口徑:穩態外推 33–38GB+下段〈事發峰值〉反推出的工作集差額 8.8–12.6GB——GC 只會把水位壓回穩態,尖峰才是真正會頂到門檻的量,磁碟規劃不能只算穩態) |
| PVC 實際落地(不含 Nexus,10 個綁定 PVC,thin-provisioned,非宣告值 107Gi) | 2.8GB | 6–10GB(Gitea/Tekton Results 的 PostgreSQL 資料隨紀錄增加) |
孤兒 PVC 目錄(reclaimPolicy: Retain,刪除後不清理,oc get pvc 看不到) |
1.3GB | 沒有回收機制就會持續累加,無法預測上限 |
| Nexus blob store(含 trivy-db、SBOM,皆經 oras 推入同一個 blob store) | 1.7GB | 15–25GB(Day 10 之後才開始密集代理 npm/docker 套件) |
| RHCOS 系統本身+容器 runtime 中繼資料+未列出的系統 namespace | 11.3GB(另一次量測所得,見上段) | 13–20GB(/var/log 沒有固定上限,估計偏保守) |
| 合計 | 約 47.2GB | 約 76–106GB |
現況這幾項加總約 47.2GB,比 crc status 事發當日實測的 45.44GB 高出 1.76GB——這裡要老實說明:五項裡只有 PVC/孤兒目錄/Nexus 三項是事發當天的讀數,容器映像跟 RHCOS 系統這兩列都是後來另一次量測所得,時間點跟另外三項不同,這中間這台叢集又經過至少一次 crc stop/crc start(見前段的重啟紀錄),映像量比事發當天略高並不意外。這個落差可以用「兩列數字時間點較晚、映像有成長」來解釋,量級對得上,但不是逐位元組重建同一瞬間的用量——這張表本質上是「目前最佳估計的穩態組成」,不是事發當下的精確快照。孤兒 PVC 目錄在兩欄的處理並不對稱:現況欄的 1.3GB 有算進合計,30 天欄因為標成「無法預測上限」而沒有數字可加,76–106GB 這個區間因此沒有計入孤兒目錄的持續累積,實際成長只會更高。
事發峰值沒辦法直接量到,只能反推,而且基準要落在 crc status 實測的 45.44GB 上。驅逐門檻 imagefs.available<15% 給出峰值下限:至少要到 54.25GB(85%)才會觸發——這個下限是門檻本身的硬性數學結果,不是估計值。
上限原本用「這次回收的 17.8GB 加回 45.44GB」算成 63.24GB,但這個 17.8GB 站不住腳。它是 kubelet GC 日誌加總 30 個被刪映像的 CRI 回報大小,而 CRI 回報的映像大小是「該映像所有 layer 的總和」,不會扣掉跟其他映像共用的 base layer——這台叢集現在的映像組合就能直接驗證這個落差:crictl images -v 把 SIZE 欄加總是 39.62GiB,但 du -x -sh /var/lib/containers 量到的實際落地只有 28GiB,差了 41%。共用層被重複計算是真實存在的效應,不是猜測。拿這個比例(28/39.62≈0.71)回頭校正 17.8GB,算出 12.6GB。這個數字該當上限看,不是點估計:當初被刪的 30 個映像是舊版 Operator catalog/release,跟仍在使用的新版本之間 layer 重疊度通常比「今天全部映像互相對照」的一般情況更高——舊版 catalog 換到新版,多半只是換掉頂層的 index 內容,底下一大串 base layer 原封不動。重疊度越高,實際釋放的比例只會比 0.71 更低,所以 12.6GB 是這個校正法能給出的上限,真實值大機率比它更小——這個校正用的是今天的映像組合去推算,不是事發當下那 30 個映像的直接量測。
於是上限跟著往下修:45.44GB+12.6GB≈58.0GB。峰值因此被夾在一個窗口裡:54.25GB 到 58.0GB,約 4GB 寬,佔實體容量的 85% 到 91%。這個窗口是兩條獨立路徑夾出來的,不是同一條式子換個寫法——下限來自驅逐門檻本身(跟任何量測數字無關的硬性結果),上限來自映像回收量的共用層校正估計(今天實測到的重複計算比例)。換算成相對於 45.44GB 的回收量,就是 8.8GB(54.25-45.44)到 12.6GB 這個區間——這正是前面推算表拿去做 30 天工作集外推的那組差額。這個窗口也跟前面「這台機器很可能長期跑在 85% 門檻邊緣」的推測對得上:窗口下緣正好就是 85% 門檻本身。
這個窗口大部分落在 nodefs.available<10%(57.44GB)這道門檻以下,只有上緣 58.0GB 略微超過,所以最簡單的解釋是這次事件多半沒摸到 nodefs 門檻。就算摸到了也不構成矛盾:查了 kubelet eviction manager 的原始碼,synchronize() 每輪只挑優先權最高的門檻做節點層級回收,回收成功就直接回傳、印出同一句 able to reduce resource pressure without evicting pods,不會為每個同時被跨過的門檻各自留一筆記錄——這台機器的 nodefs 跟 imagefs 又是同一顆分割區,兩個訊號本來就會一起被同一次回收解掉,沒有 nodefs 驅逐紀錄,既不能證明「沒摸到」,也不構成矛盾。
往前推 30 天後的用量時,起點該用這個反推出的工作集區間,不是 GC 後才量到的穩態低點。
這顆磁碟的實際容量是 63.82GB,85% 門檻落在 54.25GB,60GB 這個配置從一開始就沒有留下緩衝空間。反推更大 disk-size 時,除數該直接用 0.85,不能拿 Allocatable/Capacity 的比例來算——imagefs.available<15% 這道門檻算的是磁碟實際位元組用量,Allocatable 只是 kubelet 給排程用的記帳數字,兩者不是同一件事(見前面〈資源配置心法〉的說明)。用上面表格重算過的 30 天預期用量 76–106GB 反推:磁碟總量至少要到 89–125GB(同樣沒有計入孤兒 PVC 目錄的持續累積,實際門檻只會更早到)。
100GB 在這個範圍完全不夠用;150GB 是這條算式的下限,85% 門檻下可用 127.5GB,對上預期區間上緣 125GB 只剩 2.5GB,相當緊繃。宿主機還有 590GB 可用空間、沒有容量壓力的前提下,160GB 才是比較站得住腳的起手式:85% 門檻下可用 136GB,對上同一組上緣 125GB,留得出 11GB 的緩衝。
crc config set memory 24576 # stop/start 重啟生效
crc config set disk-size 160 # 官方支援路徑:必須重建虛擬機
crc delete --force # ⚠️ 清空既有虛擬機資料
crc start
crc config set disk-size 不會直接變更已建立的 VM 虛擬磁碟,官方支援的生效機制需透過 crc delete --force 重建 instance,先前部署的 namespace、服務與 Tekton 執行紀錄都會隨之清理。
這是不是唯一的路,查了 crc-org 的 GitHub issue #3496(Windows 11+Hyper-V 案例)之後,可以把不確定的範圍縮小一點:回報者照著 crc config set disk-size 512 走過 stop/cleanup/setup/start 整輪流程,虛擬磁碟大小依然沒變;最後是 crc stop 之後直接在 Hyper-V Manager 手動改虛擬磁碟大小繞過去的,改完 crc status 讀到了新容量(Disk Usage: 19.77GB of 549.2GB)。這個數字不是憑空信任——crc status 的容量欄位是 CRC 透過 SSH 在客體裡對 /sysroot 跑 df -B1 拿到的(同一個 repo 的 issue #1640 除錯輸出可以看到這條指令),也就是說 549.2GB 是 RHCOS 客體檔案系統自己回報的容量,代表分割區確實跟著 VHDX 一起長大了,這點不算意外,RHCOS 開機本來就會做 root 分割區成長。真正還沒被驗證的只剩一件事:這個 workaround 有沒有保留原本叢集的狀態——那則 issue 沒有人跑過 crc delete,但也沒人明確交代改完之後原本的 namespace 還在不在。這台機器已經跑了 30 天份的紀錄,不想為了驗證這一點在既有環境上冒險,這條路我沒有實測,這裡只誠實記下查到的線索,供讀者自行評估、自行承擔風險。
實測提醒(OpenShift 4.21 / K8s 1.34):早期版本常見的做法是
oc get --raw /api/v1/nodes/crc/proxy/stats/summary搭配 Python 解析 kubelet 統計來看磁碟容量。這條路其實還是通的:直接對這台叢集下這條指令,回傳的是完整 JSON,node.fs底下的capacityBytes/usedBytes/availableBytes就是精確到位元組的磁碟數字,比前面資源表用的crictl stats/crictl ps組合更直接。前面繞去用crictl湊數字,原因是oc adm top依賴的 Metrics API 沒裝,跟stats/summary這條路能不能走是兩件不相干的事——如果只是要拿磁碟容量,stats/summary反而更省事。這裡要老實接一句:前面〈現場示範〉那次觸發 DiskPressure,起因就是連續開了好幾個oc debug除錯 Pod 去跑crictl替代法;而現在回頭查證才發現,當初以為走不通、才需要crictl這條替代路的stats/summary,其實一直是通的。換句話說,把磁碟推上 85%~91% 這個窗口的那幾個除錯 Pod,是為了一個當時誤判成失效、其實根本用不著開額外 Pod 就能查到的方案而開的——一個原本不必要的量測動作,就足以把 60GB 磁碟推到門檻邊緣,這件事本身比「開了幾個 debug pod 結果爆了」更能說明 60GB 這個配置有多薄:oc get --raw /api/v1/nodes/crc/proxy/stats/summary # 找 .node.fs.capacityBytes / .usedBytes / .availableBytes再加上下面三條,是實測可行的查法組合:
# 1. 檢查節點是否被標上 disk-pressure taint oc describe node crc | grep -A 2 "Taints:" # 2. 查看叢集磁碟實際用量(CRC 直接提供,最快) crc status # 3. 從節點物件讀取 ephemeral-storage 容量與 DiskPressure 狀態 oc describe node crc | grep -iE "ephemeral-storage|DiskPressure"若需要更貼近節點檔案系統的
df輸出,可用oc debug node/crc -- chroot /host df -h /sysroot(會啟動一個 debug pod,速度較慢,用完記得清掉;如前面示範,磁碟已經吃緊時,這個指令本身也可能成為壓垮駱駝的那根稻草)。
🪟 Windows/CRC 限定:
disk-size變更的官方支援路徑需要crc delete --force重建整台 VM,這是 CRC 單機模擬叢集的架構限制;雲端受管 K8s 叢集的儲存擴充通常是線上操作,不需要銷毀整個節點重建。Hyper-V 上是否存在不砍重建的繞法,見上段的查證與未測試揭露。
CPU 與記憶體的具體數字另有一段配置歷史:CRC 曾出現 CI 異常,當時懷疑是資源不足所致,因而調高了配置;事後排查顯示,異常的實際成因可能與 CNI 狀態或已結束 PipelineRun 遺留的孤兒 Pod 有關(見 Day 17),而非單純的資源不足。配置調高後未再調降。這是一次「先用資源餘裕排除嫌疑,再回頭定位真正根因」的典型除錯順序。
跟磁碟不同,這個誤判沒有留下長期代價:CPU 與記憶體隨時能用 crc config set 加 crc stop/crc start 調回合理值,不需要重建 CRC。
160GB 才是比較合理的起手式,但這台機器已經跑了 30 天份的紀錄,crc delete --force 會連同 Day 3 到 Day 30 所有實測環境一起銷毀。即使現在主機有 590GB 空間、也已經算出該設多少,這個決定依然動不了。
若不想 crc delete 的苟活方案:
PipelineRun/TaskRun 物件與對應的 Pod、Log 會一直留著;用 tkn pipelinerun delete 或設定 Tekton 的 pruner 定期清除,能省下持續累積的容量。crictl rmi --prune 是否有東西可清,尤其是被多個 tag 指向、但已經沒有 Pod 在用的舊映像。完成 CRC 部署前,建議確認以下關鍵組態:
disk-size 一次配置足夠容量(建議 160GB;150GB 是算式能推出的下限、緩衝不多,100GB 在 30 天工作集口徑的預期用量下完全不夠)。crc config set memory/cpus 依終局的常駐服務加總(見資源表)加上合理尖峰餘裕調整。crc config set pull-secret-file 寫入(命令列參數帶入的版本在下次啟動後會消失,見 Day 12)。crc start 後,先用 oc get --raw /api/v1/nodes/crc/proxy/stats/summary(或 crc status)記錄一次基準磁碟用量,方便日後比對成長速度;沒有 Metrics API 想連容器層級一起記,才需要本篇的 crictl 替代法。完成這些基礎組態後,CRC 單機叢集即可具備承載 CI/CD 負載的能力——前提是磁碟這項一次配到位。CPU 與記憶體配錯了還有回頭路,磁碟配錯了就要背著這個決定走完剩下的天數,這也是這篇撞見的 DiskPressure 事件想留下的教訓。隨後便可進入 Windows 環境下的維運維護階段,處理行程控制、CoreDNS 網路解析與 PowerShell 自動化相容性等議題。