CrazyRyan
Synology NAS 上 GitLab/MongoDB 導致 kswapd0 暴走排查與優化紀錄

筆者近期在操作Synology NAS時,明明物理上的RAM還很多,但在撈mongoDB的時候卻卡卡的,所以有了以下這一篇的排查筆記,這次我們感謝Gemini(但中間還是有修正及補充啦 嗚嗚要失業了QAQ)

已知狀況:GitLab/MongoDB都架在docker container裡面

1. 現象與問題核心

  • 異常狀況:系統出現 kswapd0:0 程序大量進行硬碟 I/O 讀寫,導致系統卡頓。
  • 記憶體關鍵數據:
    • 實體記憶體(RAM):6.14G / 31.3G(剩餘將近 25G,非常充裕)
    • Swap 分區:5.28G / 20.8G(已被佔用超過 5G)
  • 核心矛盾:為什麼實體記憶體還空這麼多,系統卻拼命在使用 Swap?

2. 元凶程序排查方法(不限 htop 版本的一鍵指令)

因筆者 htop 版本較舊且網路限制,找不到 M_SWAP 或 NSWAP 欄位,因此改在 NAS 的 SSH 終端機(需有 root 權限)執行以下指令。

它不依賴任何軟體版本,會直接去 Linux 底層撈出目前 Swap 佔用量前 10 名的「程序名稱、PID 與完整啟動指令」:

for file in /proc/*/status; do \
  if [ -f "$file" ]; then \
    pid=$(echo "$file" | cut -d'/' -f3); \
    vmswap=$(awk '/VmSwap/{print $2}' "$file"); \
    if [ ! -z "$vmswap" ] && [ "$vmswap" -gt 0 ]; then \
      name=$(awk '/Name/{print $2}' "$file"); \
      cmdline=$(cat /proc/$pid/cmdline 2>/dev/null | tr '\0' ' '); \
      echo "$vmswap KB | PID: $pid | Name: $name | Cmd: $cmdline"; \
    fi; \
  fi; \
done | sort -n -r | head -n 10

🔍 本次排查結果:

經由上述指令,揪出卡在 Swap 區的大魔王皆為正常服務,無資安(挖礦病毒)風險:

  1. **mongod (MongoDB 資料庫)**:單一程序佔用約 290 MB Swap。
  2. **gitlab-puma-worker (GitLab 網頁核心)**:共 8 個 Worker,每個卡了 430 MB ~ 450 MB 不等的 Swap。
  3. **sidekiq (GitLab 背景非同步任務)**:佔用約 167 MB Swap。
  4. Synology 原生服務:如 syncd (Synology Drive) 等系統常駐工具也卡了少量 Swap。

3. 根本原因分析(為什麼會暴走?)

  1. 歷史殘留與 Linux 機制:過去某個時間點(如 GitLab 備份、大量 CI/CD 或 Git 讀寫),記憶體曾瞬間暴增。kswapd0 為了釋放 RAM,將當時沒活動的程序踢進了 Swap。Linux 機制規定:一旦進入 Swap,除非程序主動讀取,否則就算 RAM 空出來了,資料也不會自動搬回 RAM。
  2. MongoDB 的記憶體管理機制:預設會吃掉系統可用 RAM 的一半來做快取(Cache Layout)。當它有一段時間沒有密集的資料讀寫時,Synology NAS 的作業系統就會判定它是「冷資料」,並豪不客氣地把這 290MB 的資料全部踢進硬碟的 Swap。
  3. 群暉 DSM 預設策略太激進:Synology NAS 預設的 vm.swappiness 值為 **60**。系統為了保留極大空間給檔案快取(Cache),即使實體 RAM 還很空,也會提早把閒置程序移往 Swap。
  4. 暴走發作點:當使用者連線 GitLab 或觸發背景任務時,這些睡在硬碟 Swap 裡的程序(Puma/Sidekiq/MongoDB)突然被喚醒,kswapd0 必須瘋狂在硬碟與 RAM 之間進行「資料置換(Thrashing)」,因而導致 I/O 飆高。

4. 解決與優化行動方案

步驟一:重啟 Container 內的 GitLab 核心組件(釋放卡住的 Swap)

不用重啟整個容器,直接進入 GitLab Container 內重啟核心服務,速度快且不影響使用者:

# 進入 GitLab 容器(請將 gitlab 替換為你的容器名稱)
docker exec -it gitlab bash

# 在容器內重啟網頁 Worker 與背景任務處理器
gitlab-ctl restart puma
gitlab-ctl restart sidekiq

# 離開容器
exit

步驟二:在 NAS 宿主機清空 Swap

既然實體 RAM 還有 25G 空閒,直接在 NAS 的 SSH 終端機(需切換為 sudo -i root 權限)執行以下指令,強迫把剩餘卡在 Swap 的冷資料(如 MongoDB、Synology Drive)安全地搬回實體 RAM:

sudo swapoff -a && sudo swapon -a

(執行時 htop 的 Swp 會歸零,Mem 會稍微上升,硬碟讀寫隨之平息)

步驟三:治本(1/2)— 修改 Synology DSM 開機自動優化策略

將 NAS 宿主機的 swappiness 從 60 調低至 10,強迫系統優先使用大記憶體,非必要絕不用 Swap。

群暉網頁介面(DSM)操作步驟:

  1. 進入 「控制台」 -> 「任務排程表」。
  2. 點選 「新增」 -> 「觸發的任務」 -> 「使用者定義的指令碼」。
  3. 常規設定:
    • 任務名稱:Optimize_Swappiness
    • 使用者:務必下拉選擇 root
    • 事件:選擇 開機
    • 勾選 「已啟用」
  4. 工作設定(使用者定義的指令碼方框中貼上以下指令):
    # 強迫系統優先使用實體 RAM
    sysctl -w vm.swappiness=10
    # 開機自動清空可能殘留的 Swap
    swapoff -a && swapoon -a
    
  5. 點選 「確定」 儲存。並在列表中選取該任務,點選上方的 「執行」 讓優化立即生效。

步驟四:治本(2/2)— 修正 Container 容器層面設定

雖然宿主機調整了,但某些 Docker 容器或 Container Manager (WebUI) 的機制仍可能引發 Swap。如果想要在 Container 層面永久修正與限制,請依據你的啟動方式選擇:

方案 A:使用 Docker Compose 啟動(最推薦)

在你的 docker-compose.yml 檔案中,找到 gitlab 或 mongodb 服務,透過 sysctls 直接限制該容器的 Swap 權重。這樣就算宿主機以後不小心被改回 60,該容器依然會維持 10 的高效率運作。

version: '3.8'
services:
  gitlab:
    image: 'gitlab/gitlab-ce:latest'
    # 針對此容器調低 swappiness(優先使用實體記憶體)
    sysctls:
      - vm.swappiness=10

方案 B:使用 Docker Run 傳統指令啟動

如果你是用純指令建立容器,可以在 docker run 中加入 --memory-swappiness=10 參數:

docker run -d --name gitlab \
  --memory-swappiness=10 \
  gitlab/gitlab-ce:latest

方案 C:使用 Synology Container Manager 網頁介面(群暉專屬)

如果你是用群暉內建的套件中心(舊版叫 Docker,新版叫 Container Manager)管理的:

  1. 打開 Container Manager 點選 「容器」。
  2. 將 GitLab / MongoDB 容器先停用。
  3. 點選 「編輯」 -> 進入 「進階設定」。
  4. 檢查是否有「限制記憶體」與「Swap 限制」的選項(若有,請確保 Swap 的限制量不要設得太低或與實體記憶體落差太大,避免觸發強迫置換)。
  5. 重新啟動容器。

5. 驗證優化結果

  • 在 NAS 終端機輸入:cat /proc/sys/vm/swappiness,確認回傳值已成功變為 **10**。
  • 觀察 htop,Swp 條狀圖應維持在極低水平或歸零,且 kswapd0 不再因為正常服務喚醒而引發硬碟讀寫風暴。