The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

在 Docker 或 Kubernetes 執行 SQL Server Linux 容器應注意記憶體限制問題

前幾天一樣在客戶端進行 Kubernetes 教育訓練的時候,發現有少部分學員無法正確部署應用程式,我們的例子是一個 ASP.NET Core 網站,搭配一個 SQL Server Linux 容器,然而在練習實作 livenessProbe 的時候,卻發現有些人怎樣都無法將 SQL Server Linux 容器啟動,但是若將 livenessProbe 設定移除,服務可以啟動。若將 Pod 中容器記憶體限制移除,服務也可以順利啟動。是不是有點詭異?這篇文章我就來說說這個來龍去脈!

情境描述

首先,坐在我隔壁的學員因為無法啟動 SQL Server Linux 容器,所以我先看了一下節點的記憶體用量,結果發現以下狀況:

image

基本上他的 Node 配置了 4 GiB 記憶體,但是目前已經用了 2694 MiB 空間,佔了整體 70% 的用量,所以「理論上」記憶體還有 1.3 GiB 左右的剩餘空間才對,但一台 SQL Server Linux 容器「感覺上」只吃了 665 MiB 左右,怎麼會無法再啟動一個容器呢?

這裡的 Node 吃了 2694Mi 的原因,是因為許多 Pod 有設定 .spec.containers[0].resources.requests.memory 欄位,Pod 要求的記憶體用量會被節點優先保留下來,所以才會看到節點吃了這麼多記憶體。想要查詢 Kubernetes 節點的詳細記憶體用量配置,可以參考 Phil Huang (小飛機) 的 如何條列出單一 Namespace 內的 Kubernetes 計算資源? 文章說明。

接著我去看這個 SQL Server Linux 容器的 Logs,發現了一下訊息:

Detected 820 MB of RAM. This is an informational message; no user action is required.

我看了他的 YAML 檔,發現他對 SQL Server 的 Pod 要求了 500 MiB 記憶體用量,限制了 1 GiB 的記憶體用量 (.spec.containers[0].resources.limits.memory)。

在問題尚未得到釐清前,我只有直覺認為他給太少記憶體了,所以觸發了 Pod 的 OOM 機制,非常有可能因為 SQL Server 啟動時剛好超過記憶體限制,所以被 kubelet 砍掉了!🔥

但是學員立刻提問:「可是我的 SQL Server 沒吃這麼多 RAM 啊,你看,另一個 SQL Server 只有 664 MiB 而已耶。」

問題重現

我為了想釐清這個問題,先用 Docker Desktop 測試一下限制 SQL Server Linux 容器的記憶體用量,看看是否可以重現相同的問題。

首先,我先用以下命令啟用一個 SQL Server 容器,並限制給予 1 GiB 的記憶體限制:

docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=Pa@@w0rd1234" -p 1433:1433 --name sql1 --hostname sql1 -d -m 1073741824 mcr.microsoft.com/mssql/server:2019-latest

注意: 1073741824 = 1 * 1024 * 1024 * 1024 = 1 GiB

接著我直接可以重現問題,並發現以下訊息:

Detected 820 MB of RAM. This is an informational message; no user action is required.

Failed allocate pages: FAIL_PAGE_ALLOCATION 1

很明顯的,真的是記憶體不足造成的!

查詢根因

首先,我有查到在 Microsoft SQL Server by Microsoft | Docker Hub 頁面中有寫到以下最低記憶體需求,基本上低於 2GB 就是有問題的!

At least 2GB of RAM (3.25 GB prior to 2017-CU2). Make sure to assign enough memory to the Docker VM if you're running on Docker for Mac or Windows.

當然這是根因所在,但是這個 820 MB 是怎麼來的呢?

我後來有查到在 Configure SQL Server settings on Linux 有查到一個 Set the memory limit 段落,裡面寫到:

The memory.memorylimitmb setting controls the amount physical memory (in MB) available to SQL Server. The default is 80% of the physical memory.

此時真相大白,原來在不設定 SQL Server Linux 容器的參數的情況下,預設他就是會吃掉 80% 的實體記憶體! 👍

深入探究

以我過去的經驗得知,所有 SQL Server 實例原則上都是「有多少記憶體就會用多少記憶體」的狀況,因為 SQL Server 會自動的追求最有效率的運行方式,因此「記憶體」的配置絕對需要精打細算。

原來 SQL Server Linux 容器中也有一樣的機制,不過 SQL Server Linux 容器中的記憶體機制預設是 soft limit 而已,如果你在執行容器的時候沒有設定 hard limit 的話,他預設會把所有可用的記憶體吃光。

這個機制跑在 Kubernetes 之後,問題將會更加嚴重,因為不設定 hard limit 的結果,就是遲早被 Node 的 OOM 機制砍掉,不得不慎!

疑惑釐清

學員最後的疑惑是,雖然官方文件說要 2 GiB 記憶體,但我為什麼「有時候」還是可以啟動呢?而且沒有加上記憶體限制是可以理解,但為什麼這件事跟 livenessProbe 有關係?我「有時候」還是可以啟動的耶!

我自己在 Kubernetes 實測,在給定 1 GiB 的記憶體用量下,確實有一定的機率是可以啟動成功的 (但我不知道為什麼),難道是 containerdDocker Desktop 更省記憶體?這是相當有可能的啊!

而我們在課堂上的 livenessProbe 實作,是要登入 SQL Server 並執行 SELECT * FROM sys.databases 這個 T-SQL 語法,其 livenessProbe 設定的 command 命令如下:

livenessProbe:
  exec:
    command: ["bash","-c","/opt/mssql-tools/bin/sqlcmd -S . -U sa -P $MSSQL_SA_PASSWORD -Q 'SELECT name FROM sys.databases;' -b"]
  initialDelaySeconds: 5
  timeoutSeconds: 5
  successThreshold: 1
  failureThreshold: 3
  periodSeconds: 10

原本 SQL Server Linux 容器在捉襟見肘的記憶體之中剛好啟動,但在嘗試執行 livenessProbe 的過程把記憶體用光了,導致 SQL Server Linux 的 FAIL_PAGE_ALLOCATION 的問題發生! 😅

相關連結