Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Speaker Deck
PRO
Sign in
Sign up
for free
Device Plugin開発入門
Daisuke Takahashi
November 30, 2020
Programming
0
620
Device Plugin開発入門
Daisuke Takahashi
November 30, 2020
Tweet
Share
More Decks by Daisuke Takahashi
See All by Daisuke Takahashi
extendwings
0
1k
extendwings
0
350
Other Decks in Programming
See All in Programming
hirotokirimaru
1
410
yshrsmz
1
450
malvinstn
1
630
atskimura
0
290
line_developers_tw
0
1.2k
ken3ypa
0
160
zsmb
1
120
anchorcable
1
120
o0h
PRO
1
1.5k
osyo
1
370
ippey
0
170
mu2in
0
140
Featured
See All Featured
3n
163
22k
jnunemaker
PRO
40
4.5k
smashingmag
229
18k
bryan
100
11k
bermonpainter
342
26k
scottboms
251
11k
philhawksworth
190
17k
paulrobertlloyd
71
3.6k
sferik
609
54k
jcasabona
7
520
jponch
103
5k
jacobian
255
20k
Transcript
Device Plugin開発入門 Daisuke Takahashi 2020-11-30 Kubernetes Meetup Tokyo #36
Profile – Daisuke Takahashi • Twitter: @yaemonsan, GitHub: @shield-9 •
MSFS2020: 500 hrs (Loves B748) • Work at: CIU (CyberAgent Group Infrastructure Unit), CyberAgent, Inc. ◦ 2019年新卒入社 Infrastructure Engineer ◦ AI Div.と兼務 • In charge of: ◦ 3DCGプロダクション / ML基盤 / その他、変わり種の 物理全般 ◦ OpenStack環境 / K8s as a Serviceの開発・運用 2
はじめに • このスライドは https://bit.ly/k8sjp-device ◦ 文字ばっかりなので、手元でも開くことをお勧めします • コード類は https://github.com/shield-9/k8s-coral-accelerators ◦
社内のレポジトリから雑にコピペしたので、おかしな部分を見つけたら直します • 細部の実装は、各スライド内のリンクからチェックしてみてください 3
Device Plugins ⇒ 色々なハードウェアリソースをKubeletに見せるための仕組み 4 Source: https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/
Device Plugins ⇒ 色々なハードウェアリソースをKubeletに見せるための仕組み 5 Source: https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/ Felica + おうちKubernetesクラスタ
使い方 - NVIDIA GPUの場合 1. Node上のデバイスとしての準備 ◦ 各Nodeにデバイスドライバ (NVIDIA Driver)
などを入れる 2. デバイスを見つけて、Kubeletに認知してもらう ◦ 各NodeにDevice Pluginのコンテナを立てる kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.7.1/nvidia-device-plugin.yml ※頑張れば、privilegedなDeamonSetで完結可能 (cf. GKE) 6
使い方 - NVIDIA GPUの場合 3. Device-attachedなコンテナを作成 apiVersion: v1 kind: Pod
metadata: name: gpu-pod spec: containers: - name: cuda-container image: nvidia/cuda:9.0-devel resources: limits: nvidia.com/gpu: 2 # requesting 2 GPUs 7 Source: https://github.com/NVIDIA/k8s-device-plugin # ls /dev/nvidia* (かなり省略してます ) /dev/nvidiactl /dev/nvidia-uvm /dev/nvidia-uvm-tools /dev/nvidia0 /dev/nvidia1 作成したコンテナ内から、 GPU 2つ分のデバイスファ イルが見えている ⇒これがDevice Pluginの働き
今日の話 8 使いたいハードウェアの Device Pluginがない! このDevice Pluginに、 欲しい機能がない! 対応を検討させていただきます (「やる」とは言ってない
) Device Vendor
今日の話 9 使いたいハードウェアの Device Pluginがない! このDevice Pluginに、 欲しい機能がない! 対応を検討させていただきます (「やる」とは言ってない
) Device Vendor こんなとき... ① Kubernetes (コンテナ) を使わない ② そのデバイスを使わない ③ デバイスが必要な処理だけKubernetesの外でやる ④ 自力でDevice Pluginを書く こんなとき... ① Kubernetes (コンテナ) を使わない ② そのデバイスを使わない ③ デバイスが必要な処理だけKubernetesの外でやる ④ 自力でDevice Pluginを書く ※①~③が有効な場合もあります
今回自作するDevice Plugin • 対象: Coral Edge TPU Accelerator ◦ インターフェイス:
PCIe Gen2 x1 ◦ Google TPUのエッジ推論版 ◦ 組み込みチップなどもラインナップにあり、ドキュメントも ある程度存在 10
実物の様子 • M.2 B+M key用を購入 ◦ PCIe Gen3 x16スロットに4枚搭載 ▪
マザボのPCIe Bifurcation対応が必須 ▪ 今回はx4x4x4x4で設定 (レーンの無駄遣い) ◦ 【余談】lspci -nnk でデバイス名が出ないときは update-pciids を。DBに追加しときました • 冷却には要注意 ◦ 動作制限や強制停止あり ◦ モニタリング可能: https://coral.ai/docs/pcie-parameters/ 11
デバイスの仕様 • Apex Driver (とGasket framework) を利用して認識 ◦ Kernel 4.19で実装:
drivers/staging/gasket ▪ 一部ディストリビューションで不具合のあるモジュールが入っているので、とりあえずGoogle製のパッチ済 みモジュールで置き換え ◦ /dev/apex_X が見えていれば正常 (Xは0からの連番) • アプリケーションは libedgetpu1-std (と TensorFlow Lite) を通して利用 ◦ 実装: google-coral/libedgetpu/driver/driver_factory.cc ▪ /sys/class/apex/apex_X/ が存在すれば、/dev/apex_X にアクセス → コンテナからこれらのファイルが見えていればOK 12
Device Pluginの仕様 2つのgPRC serviceで構成 • service Registration ◦ PluginをKubeletに登録する ◦
登録したら役割終了 ◦ (Kubelet視点だとPluginwatcher) • service DevicePlugin ◦ Device Pluginの実体 ◦ 常に必要 Kubeletとの通信はUnixドメインソケットを利用 13 Source: https://github.com/kubernetes/kubernetes/issues/65772
service Registration (Pluginの登録) /var/lib/kubelet/plugins_registry/XXXX のソケットでListenする • rpc GetInfo ◦ ソケットを作ると、Kubeletが自動的に呼び出す
◦ Pluginの登録のため、以下の情報を応答 ▪ Pluginの種類とAPIバージョン (DevicePlugin or CSIPlugin) ▪ 提供するリソース名 (coral.ai/edgetpu や nvidia.com/gpu など) ▪ Pluginのエンドポイント (service DevicePluginのソケット) • /var/lib/kubelet/device-plugins/XXXX だと、Kubelet再起動時に再登録される。両 service共通でも動く • rpc NotifyRegistrationStatus ◦ Pluginの登録が完了すると、 Kubeletが成否を通知する 14
service DevicePlugin (Pluginの実体) • rpc ListAndWatch (stream) ◦ Kubeletにデバイス一覧を逐次共有 ▪
デバイスID (Plugin内で一意ならOK) ▪ Healthy or Unhealthy ▪ トポロジー情報 (任意) • rpc Allocate ◦ Podの作成途中で呼び出される ◦ デバイスファイルのマウントのため、以下 の情報を応答 ▪ ホスト上でのパス ▪ コンテナ上でのパス ▪ パーミッション (Cgroup) 15 • rpc GetDevicePluginOptions ◦ Pluginの登録時に呼び出される ◦ 以下の2つの機能を使うかを応答 • rpc PreStartContainer ◦ Device-attachedなコンテナの開始前 に呼び出される (任意) ◦ デバイスの初期化などを行う • rpc GetPreferredAllocation ◦ トポロジー情報に加えて、 NVLinkなど を考慮したい場合 (任意)
Edge TPU Device Pluginのファイル - cmd/coral_edgetpu/ - coral_edgetpu.go コマンドの起動・終了処理など -
pkg/edgetpu/coral/ - beta_plugin.go service DevicePluginの実装 - manager.go gRPCサーバーの起動やデバイスの検出など - watcher.go service Registrationの実装 16
【実装】起動・終了 cmd/coral_edgetpu/coral_edgetpu.go main() func main() { cem := edgetpumanager.NewCoralEdgeTPUManager("/dev", "coral-edgetpu.sock")
for { err := cem.Start() // Edge TPUの検出 (Plugin動作に必要な権限の確認 & 起動時点のデバイス一覧取得) time.Sleep(5 * time.Second) } go func() { // 特定のシグナルを受け取ったら、終了処理 sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT) <-sigs cem.Stop() // gRPCサーバーの停止 & 残骸ソケットファイルを掃除 }() cem.Serve(*pluginMountPath) // Device Pluginを起動 } ※起動~コンテナの起動を追っていきます。以降、終了系は割愛 ※全体的にかなり省略しているので、完全なコードはレポジトリ参照 17 Source: https://github.com/kubernetes/kubernetes/issues/65772
【実装】Pluginの構造体 pkg/edgetpu/coral/manager.go type coralEdgeTPUManager struct { devDirectory string // "/dev"
devices map[string]pluginapi.Device // デバイスの一覧 devicesMutex sync.Mutex // devices更新時の競合防止 socket string // ソケットのファイル名 stop chan bool // 終了処理用 wg sync.WaitGroup // 終了処理用 devicesUpdated chan bool // デバイス一覧の更新時用 grpcServer *grpc.Server registrationStatus chan watcherapi.RegistrationStatus // Kubeletへの登録状態 endpoint string // ソケットのパス } 18
【実装】Pluginの起動 pkg/edgetpu/coral/manager.go Serve() func (cem *coralEdgeTPUManager) Serve(pluginMountPath string) error {
// 省略: 指定パスにソケットを作ってListen // 省略: gRPCサーバーを作成 watcherapi.RegisterRegistrationServer(cem.grpcServer, &watcherServiceV1{cem: cem}) // service Registrationを登録 pluginapi.RegisterDevicePluginServer(cem.grpcServer, &pluginServiceV1Beta1{cem: cem}) // service DevicePluginを登録 cem.grpcServer.Serve(lis) // gRPCサーバーを開始 kubeletCheck := time.NewTicker(5 * time.Second) // 5秒に一度ループ waitKubelet: // KubeletがPluginを登録するまで待つ for { select { case status := <-cem.registrationStatus: // Kubeletに登録されたらループを脱出 kubeletCheck.Stop() break waitKubelet case <-kubeletCheck.C: // Kubeletへの登録待ち } } cem.devicesUpdated <- true // 起動時点でのEdge TPUの一覧をKubeletに通知 (起動後は別途) 19
【実装】Pluginの登録 pkg/edgetpu/coral/watcher.go func (s *watcherServiceV1) GetInfo(ctx context.Context, req *watcherapi.InfoRequest) (*watcherapi.PluginInfo,
error) { return &watcherapi.PluginInfo{ Type: watcherapi.DevicePlugin, Name: resourceName, Endpoint: s.cem.endpoint, SupportedVersions: []string{pluginapi.Version}, }, nil } func (s *watcherServiceV1) NotifyRegistrationStatus(ctx context.Context, status *watcherapi.RegistrationStatus) (*watcherapi.RegistrationStatusResponse, error) { if s.cem.registrationStatus != nil { s.cem.registrationStatus <- *status } if !status.PluginRegistered { glog.Error("Registration failed: ", status.Error) } return &watcherapi.RegistrationStatusResponse{}, nil } (ここはあまり面白くない…) 20
【実装】Pluginの起動後 pkg/edgetpu/coral/manager.go Serve() (Pluginの起動後…) edgeTPUCheck := time.NewTicker(edgeTPUCheckInterval) // Pluginの起動完了後は、Edge TPUの一覧を定期的に確認
defer edgeTPUCheck.Stop() for { select { case <-edgeTPUCheck.C: foundNew, err := cem.discoverEdgeTPUs() // Edge TPUの一覧を取得 healthChanged := cem.updateDevicesHealth() // Edge TPUの状態を更新 if foundNew > 0 || healthChanged { cem.devicesUpdated <- true // 一覧や状態に変更があればKubeletに通知 } } } return nil } 21
【実装】デバイスの検出 pkg/edgetpu/coral/manager.go discoverEdgeTPUs() func (cem *coralEdgeTPUManager) discoverEdgeTPUs() (int, error) {
var devices []string foundNew := 0 files, err := ioutil.ReadDir(cem.devDirectory) for _, f := range files { if apexRe.MatchString(f.Name()) && f.Mode()&os.ModeSymlink == 0 { // /dev/apex_[0-9]+の形式のファイルを探す devices = append(devices, f.Name()) // apex_[0-9]+の部分を内部IDとして利用 } } for _, dev := range devices { if _, exists := cem.devices[dev]; !exists { // 新しく発見したデバイスを一覧に加える cem.setDeviceHealth(dev, pluginapi.Unhealthy) // とりあえずUnhealthy扱いで、別途状態チェック foundNew += 1 } } return foundNew, nil } 22
【実装】デバイス一覧の通知 pkg/edgetpu/coral/beta_plugin.go func (s *pluginServiceV1Beta1) ListAndWatch(emtpy *pluginapi.Empty, stream pluginapi.DevicePlugin_ListAndWatchServer) error
{ for { select { case <-s.cem.devicesUpdated: // デバイスの一覧が更新された時に発火 resp := new(pluginapi.ListAndWatchResponse) for _, dev := range s.cem.devices { resp.Devices = append(resp.Devices, &pluginapi.Device{ID: dev.ID, Health: dev.Health}) } if err := stream.Send(resp); err != nil { // Kubeletに最新のデバイス一覧を通知 s.cem.stop <- true return err } } } } 23
【実装】デバイスの割り当て pkg/edgetpu/coral/beta_plugin.go func (s *pluginServiceV1Beta1) Allocate(ctx context.Context, requests *pluginapi.AllocateRequest) (*pluginapi.AllocateResponse,
error) { resps := new(pluginapi.AllocateResponse) for _, rqt := range requests.ContainerRequests { resp := new(pluginapi.ContainerAllocateResponse) for _, id := range rqt.DevicesIDs { resp.Devices = append(resp.Devices, &pluginapi.DeviceSpec{ HostPath: path.Join(s.cem.devDirectory, id), ContainerPath: path.Join(s.cem.devDirectory, id), // コンテナ上でもホストと同一パスにマウント Permissions: "mrw", // 権限フルオープン }) } resps.ContainerResponses = append(resps.ContainerResponses, resp) } return resps, nil } 24
動かしてみる AKE (CyberAgent社内のKaaS)でEdge TPUが動いている様子をチラ見せします vim pod.yaml kubectl exec -it coral-edgetpu-example
-- bash ls /dev/apex* cd classification/ python3 classify_image.py \ --model models/mobilenet_v2_1.0_224_inat_bird_quant.tflite \ --labels models/inat_bird_labels.txt --input images/parrot.jpg # CPU版 python3 classify_image.py \ --model models/mobilenet_v2_1.0_224_inat_bird_quant_edgetpu.tflite \ --labels models/inat_bird_labels.txt --input images/parrot.jpg # Edge TPU版 25
完成! 27
諸注意 • 今回はLinux系のOSに限定した話 ◦ WindowsとかmacOSとかは割と事情が異なる • 同じデバイスでも、ロットによって細かい仕様が変わりうる ◦ 初回利用時の強制ファームウェア更新で ベンダーIDすら変わることも…
(実話) ◦ 遅くとも本番投入前には、ベンダーへ仕様確認を! • デバイスの処理時間は必ずしも一定ではない ◦ リセットや初期化などは、データシートに最大何ミリ秒か書いてあることも ◦ 守らないと、「たまに動かない」システムが出来上がる … 28
Device Pluginのこれから@CyberAgent • ML基盤用にNVIDIA DGX A100を導入 → • Multi-instance GPU
(MIG) ◦ NVIDIA A100 GPUの新機能 ◦ 1つのGPUを最大7分割できる ◦ メモリ/キャッシュ/コアのHWレベルでのIsolation ▪ ⇔従来のvGPU (ソフトウェアによる分割) • 公式Device Pluginは対応済みだが、機能不足 ◦ 現状: あらかじめMIGの分割をする想定 ◦ 理想: リソース要求に応じて動的に再分割 ▪ 「単純な再分割はフラグメント化を招くのでは…?」など、考え ることは多い 29
まとめ • Device Pluginの活用で、Kubernetes上で出来ることが一気に広がる ◦ Lチカ (?)、ドローン操縦、高度なネットワーキング、 HPC、センサー (Edge)、etc… •
ハードウェア仕様を把握できれば、簡単に自作が可能 ◦ ドキュメントがなくても、挙動を観察すればどうにかなることもある ▪ 実際、Felicaリーダーのときは、先人達が見つけた隠しコマンドに助けられた • ぜひ皆さんのニーズに合ったデバイスを動かしてみてください 30