Preface
最近嘗試要再 GKE 上面開啟 GPU 的服務,然後實際上卻遇到了一些問題,因此用本篇文章來記錄遇到問題並且遇到問題的解決思路。
一開始有個需求時,很直覺的再 Google 上面直接用 GKE GPU
進行搜尋,的確也可以找到不少文章在講 GPU
與 Kubernetes
相關。
第一個搜尋結果則是 Google 官方的文章 Kubernetes Engine, GPUs
直接整理該文章中的重點流程.
- 從 GCP 的管理面板中去取得 GPU 資源
- 創造 GPU Node Pool 供 GKE ㄐ
- 透過
kubernetes DaemonSet
的方式安裝NVIDIA Drivers
到所有 GPU Node 上 - 接下來創造 Pod 即可正常使用
NVIDIA
GPU 資源
Install
前面兩個步驟基本上不會有什麼問題,透過網頁介面點選或是參考網頁中的指令去創造即可順利完成。
比較困擾的則是第三個步驟,根據該篇文章顯示我們只需要部署一個 yaml
檔案即可完全 NVIDIA Drives
到所有 GPU 節點上
1 | kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/container-engine-accelerators/stable/nvidia-driver-installer/cos/DaemonSet-preloaded.yaml |
但是我在另外一個 kubernetes
的文章中 Kubernetes scheduling-gpus
若要再 GKE
上面安裝 GPU 資源的話,則需要根據情況部署下列 yaml
檔案。
1 | On your 1.9 cluster, you can use the following commands to install the NVIDIA drivers and device plugin: |
首先,第一篇文章表明只需要透過 DaemonSet
的方式去安裝 NVIDIA Drivers
,而第二篇文章則需要部署兩個 DaemonSet
,分別來處理 NVIDIA Drivers
以及 Device Plugin
.
此外,針對第一個 NVIDIA Drivers
也提供不同作業系統的檔案(cos/ubuntu),且就我的認知上 Device Plugin
本來就需要跑一個 DaemonSet
來監聽 Kubernetes 的事件來處理各式各樣的 Device 操作。
在百思不得其解的情況下,乾脆直接找到相關的 Git 專案來看看,有沒有更清楚的解釋,畢竟我們都知道文件更新的速度比不上程式碼修改的速度。
最後找到了這個 Git 專案 GoogleCloudPlatform/container-engine-accelerators
這個專案的文件直接解決了我關於 Device Plugin
的疑惑
In GKE, from 1.9 onwards, this DaemonSet is automatically deployed as an addon. Note that DaemonSet pods are only scheduled on
nodes with accelerators attached, they are not scheduled on nodes that don’t have any accelerators attached.
根據這份文件敘述,在 GKE (1.9版本之後),會自動部署 Daemonset
到集群上面的 GPU Node 來處理 Device Plugin
相關的事宜。
由於我使用的 GKE
集群版本是 1.9 之後,所以只需要自己手動部署 DaemonSet
來安裝 NVIDIA Drivers
到所有的 GPU 節點上。
結合了上述兩者個文件,可以根據版本以及GPU 節點上面的作業系統選擇需要使用的 Yaml
.
在 Github 的專案內,使用了 Brnach
的方式來切換不同的 kubernetes
版本,所以這邊就根據各自的需求來選擇。
想要獲得如官方範例的 raw.githubusercontent.com
的連結只要到該 Github 的檔案中點選右上的 Raw
按鈕則會獲得一個關於該檔案的存取連結。
譬如1
kubectl create -f https://raw.githubusercontent.com/GoogleCloudPlatform/container-engine-accelerators/k8s-1.9/DaemonSet.yaml
由於該 yaml
使用的 namespace
是在 kube-system
, 所以若要觀察其結果別忘記切換對應的 namespace
來觀察。
這邊只要該 Pod
的狀態變成 Running
則代表該 NVIDIA Driver
已經安裝完畢了,就可以開始部署自己的 Pod 來使用這些 GPU 資源了。
題外話:
為什麼安裝一個 NVIDIA Driver
完成會是 Running
而不是其他的 Completed
等看起來結束的資訊?
這邊主要受限於目前 Kubernetes
資源的使用方法與 NVIDIA Driver
的安裝使用情境。
首先,我們希望 NVIDIA Driver
能夠安裝到每一台擁有 GPU 的節點上,所以這邊我們會希望使用 DaemonSet
來幫你部署對應的 Pod 到 GPU 節點。
然而 NVIDIA Driver
的安裝其實本身就是一個會結束的應用程式,如果正常使用上,當相關資源都安裝完畢,該 Pod 則會結束 並且將狀態切換成 Succeed
.
此時 DaemonSet
則會為了確保其選擇的 Pod
是一個類似 Daemon
的程式,必須一直存活,則又會重新將該 Pod 叫起來繼續重新安裝。
如此則會發生一個反覆執行的無窮迴圈,為了解決這個問題,不少人在提出有沒有類似 DaemonJob
的資源可以使用?
希望可以在每個符合的節點上都運行一個Job
就好,不需要一直反覆執行,
可以在下列的連結看到相關的討論串.
1.CronJob DaemonSet (previously ScheduledJob)
2.[DaemonSet] Considering run-once DaemonSet (Job DaemonSet)
3.Run Once DaemonSet on Kubernetes
在這個問題被正式解決前,有不少 Workarouund
可以處理這個情境,最常用的採用 Init-Container
的方式來完成。
藉由 Init-Container
來執行主要的安裝流程,然後再 Contianer
裡面使用則是只用了一個低資源沒做事情單純無窮迴圈卡住的應用程序。
當 Pod
開始運行的時候,先執行 Init-Contaienr
來進行真正的任務來安裝相關資源,接下來切換到 Contaienr
進行一個發呆的應用程式
確保 Pod
的狀態會維持在 Running
而不會被 Daemonset
給重啟。
這也是為什麼我們只要看到 Running
就可以視為 NVIDIA Driver
已經安裝完畢了。
另外,其實 Flannel
(CNI) 也是使用這種方式來安裝相關的 CNI 資源(config/bianry) 到每台機器上。
回到正題,按照第一篇文章提到的說明文件,我們簡單部署一個 Pod
的應用程式來使用 GPU 資源
1 | apiVersion: v1 |
看似簡單合理結果結果最後運行起來遇到問題,Pod 內的 GPU 應用程式在運行時會運行錯誤,錯誤訊息類似1
"libcuda.so.1 file" is not found.
這看起來就是找不到 CUDA
相關的函式庫所以沒有辦法正常的啟用 GPU 應用程式。
這時候再重新檢視了一下該份 Google 文件,有個段落在講述 CUDA
的函式庫
1 | CUDA® is NVIDIA's parallel computing platform and programming model for GPUs. The NVIDIA device drivers you install in your cluster include the CUDA libraries. |
首先,你安裝 NVIDIA Driver
的同時, 也會一起安裝 CUDA
的函式庫
再來 CUDA 相關的函式庫與使用工具都可以在 Container
內的 /usr/local/nvidia/lib64
以及 /usr/local/nvidia/bin
找到與使用。
最後你必須要設定你應用程式的 LD_LIBRARY_PATH
來確保你的應用程式在尋找連結庫的時候不要忘記去找 /usr/local/nvidia/lib64
.
有了上述這三個敘述,我重新檢視了一次自己運行的 GPU 應用程式。
首先, LD_LIBRARY_PATH
有正確的設定,但是我在系統上卻看不到任何 /usr/local/nvidia/
任何檔案,連整個資料夾都沒有,也完全找不到任何 libcuda.so.1
的檔案。
所以我開始懷疑 NVIDIA Driver
到底有沒有正確的安裝成功?
我們重新看一次該安裝的 yaml
檔案,如下
1 | apiVersion: apps/v1 |
我們可以觀察到下列幾個重點
- Init-Container 的名稱是
nvidia-driver-installer
- 該
DaemonSet
將節點上的/home/kubernetes/bin/nvidia
透過HostPath
方式掛載到Init-Container
內的/usr/local/nvidia
.
接下來我們使用下列指令去觀察該 Init-Container nvidia-driver-installer
的安裝訊息。1
kubectl logs -n kube-system -l name=nvidia-driver-installer -c nvidia-driver-installer
其輸出的資訊如下,而且 nvidia-smi
的資訊的確也有抓到 Tesla K80
這張卡,看起來一切都正常。
1 | + COS_DOWNLOAD_GCS=https://storage.googleapis.com/cos-tools |
不過至少我們知道了,該 nvidia-driver-installer
會將相關的資源都安裝到 /usr/local/nvidia
而該路徑則會對應到該 GPU 節點上面的 /home/kubernetes/bin/nvidia
資料夾。
這樣想了一下,我覺得我們的應用程式在使用的時候,應該要手動的將相關資源掛載進來,所以整個 yaml
檔案修改如下
1 | apiVersion: v1 |
我們手動將 GPU 節點上面的檔案掛載到容器內,其對應的路徑如下
/home/kubernetes/bin/nvidia/bin
-> /usr/local/nvidia/lib64
/home/kubernetes/bin/nvidia/lib64
-> /usr/local/bin/nvidia
接下來到我們的應用程式內就可以正確地找到了相關的檔案,如 libcuda.so.1
以及相關的測試工具。
這時候執行一下 nvidia-smi
卻發現不能執行,會直接得到下列的錯誤
“Failed to initialize NVML: Unknown Error”
嘗試 Google 這類型的錯誤都沒有辦法找到答案來處理這個問題,最後決定還是自己來研究一下哪裡出錯了
這邊使用的工具是 strace
,能夠顯示出該應用程式運行過程中呼叫到的所有 system call
,是個非常強大好用的除錯工具。
跑了一下 strace
馬上就發現事情的不對勁,由該輸出結果可以看到類似下列的錯誤訊息(原諒我沒有完整 log)
read(/dev/nvidiactl) no permission
這邊可以看到 nvidia-smi
想要嘗試讀取 /dev/nvidiactl
結果卻沒有權限,這怎像都覺得一定是容器權限不夠,所以補上 Privileged
試試看。
就發現一切都順利了, nvidia-smi
也不會噴錯,而應用程式也能夠順利運行了。
附上最後的 yaml
檔案如下
1 | apiVersion: v1 |
Summary
總結一下上述的所有歷程
- 如果今天 GKE 的版本是 1.9 之後,我們只需要運行一個
DaemonSet
去安裝NVIDIA Driver
即可,Device Plugin
會自己被運行 - CUDA 相關的資源都需要自己從 GPU 節點上掛載到所有運行的
Pod
中 - 記得設定
privileged=true
到所有使用 GPU 的節點上。
個人資訊
我目前於 Hiskio 平台上面有開設 Kubernetes 相關課程,歡迎有興趣的人參考並分享,裡面有我從底層到實戰中對於 Kubernetes 的各種想法
組合包
https://hiskio.com/packages/7ey2vdnyN
疑難雜症除錯篇
https://hiskio.com/courses/440/about?promo_code=LG28Q5G
單堂(CI/CD)
https://hiskio.com/courses/385?promo_code=13K49YE&p=blog1
基礎概念
https://hiskio.com/courses/349?promo_code=13LY5RE
另外,歡迎按讚加入我個人的粉絲專頁,裡面會定期分享各式各樣的文章,有的是翻譯文章,也有部分是原創文章,主要會聚焦於 CNCF 領域
https://www.facebook.com/technologynoteniu
如果有使用 Telegram 的也可以訂閱下列頻道來,裡面我會定期推播通知各類文章
https://t.me/technologynote
你的捐款將給予我文章成長的動力