Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Device Plugin開発入門

Device Plugin開発入門

36631e35786e16256423db3c9de70a78?s=128

Daisuke Takahashi

November 30, 2020
Tweet

Transcript

  1. Device Plugin開発入門 Daisuke Takahashi 2020-11-30 Kubernetes Meetup Tokyo #36

  2. 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
  3. はじめに • このスライドは https://bit.ly/k8sjp-device ◦ 文字ばっかりなので、手元でも開くことをお勧めします • コード類は https://github.com/shield-9/k8s-coral-accelerators ◦

    社内のレポジトリから雑にコピペしたので、おかしな部分を見つけたら直します • 細部の実装は、各スライド内のリンクからチェックしてみてください 3
  4. Device Plugins ⇒ 色々なハードウェアリソースをKubeletに見せるための仕組み 4 Source: https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/

  5. Device Plugins ⇒ 色々なハードウェアリソースをKubeletに見せるための仕組み 5 Source: https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/ Felica + おうちKubernetesクラスタ

  6. 使い方 - 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
  7. 使い方 - 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. 今日の話 8 使いたいハードウェアの Device Pluginがない! このDevice Pluginに、 欲しい機能がない! 対応を検討させていただきます (「やる」とは言ってない

    ) Device Vendor
  9. 今日の話 9 使いたいハードウェアの Device Pluginがない! このDevice Pluginに、 欲しい機能がない! 対応を検討させていただきます (「やる」とは言ってない

    ) Device Vendor こんなとき... ① Kubernetes (コンテナ) を使わない ② そのデバイスを使わない ③ デバイスが必要な処理だけKubernetesの外でやる ④ 自力でDevice Pluginを書く こんなとき... ① Kubernetes (コンテナ) を使わない ② そのデバイスを使わない ③ デバイスが必要な処理だけKubernetesの外でやる ④ 自力でDevice Pluginを書く ※①~③が有効な場合もあります
  10. 今回自作するDevice Plugin • 対象: Coral Edge TPU Accelerator ◦ インターフェイス:

    PCIe Gen2 x1 ◦ Google TPUのエッジ推論版 ◦ 組み込みチップなどもラインナップにあり、ドキュメントも ある程度存在 10
  11. 実物の様子 • M.2 B+M key用を購入 ◦ PCIe Gen3 x16スロットに4枚搭載 ▪

    マザボのPCIe Bifurcation対応が必須 ▪ 今回はx4x4x4x4で設定 (レーンの無駄遣い) ◦ 【余談】lspci -nnk でデバイス名が出ないときは update-pciids を。DBに追加しときました • 冷却には要注意 ◦ 動作制限や強制停止あり ◦ モニタリング可能: https://coral.ai/docs/pcie-parameters/ 11
  12. デバイスの仕様 • 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
  13. 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
  14. 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
  15. 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など を考慮したい場合 (任意)
  16. 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
  17. 【実装】起動・終了 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
  18. 【実装】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
  19. 【実装】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
  20. 【実装】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
  21. 【実装】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
  22. 【実装】デバイスの検出 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
  23. 【実装】デバイス一覧の通知 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
  24. 【実装】デバイスの割り当て 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
  25. 動かしてみる 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
  26. 完成! 27

  27. 諸注意 • 今回はLinux系のOSに限定した話 ◦ WindowsとかmacOSとかは割と事情が異なる • 同じデバイスでも、ロットによって細かい仕様が変わりうる ◦ 初回利用時の強制ファームウェア更新で ベンダーIDすら変わることも…

    (実話) ◦ 遅くとも本番投入前には、ベンダーへ仕様確認を! • デバイスの処理時間は必ずしも一定ではない ◦ リセットや初期化などは、データシートに最大何ミリ秒か書いてあることも ◦ 守らないと、「たまに動かない」システムが出来上がる … 28
  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
  29. まとめ • Device Pluginの活用で、Kubernetes上で出来ることが一気に広がる ◦ Lチカ (?)、ドローン操縦、高度なネットワーキング、 HPC、センサー (Edge)、etc… •

    ハードウェア仕様を把握できれば、簡単に自作が可能 ◦ ドキュメントがなくても、挙動を観察すればどうにかなることもある ▪ 実際、Felicaリーダーのときは、先人達が見つけた隠しコマンドに助けられた • ぜひ皆さんのニーズに合ったデバイスを動かしてみてください 30