DockerコンテナからUSBデバイスを利用する（MacOS）

## はじめに

Dockerゲスト(コンテナ)OSにLinuxを利用したい場合、DockerホストOSはLinuxでなければならない。その為、Docker for MacではmacOS上に仮想マシンを用意し、その上に軽量のLinuxOSを動作させる。

しかしデフォルトで用意されているハイパーバイザ(HyperKit)はホストOS(macOS/Windows)で認識しているUSBデバイスを認識させることができない。Dockerはこのハイパーバイザを変更することができるため、ホストOSのUSBデバイスを認識できるハイパーバイザであるVirtualBoxを利用してDockerコンテナにUSBデバイスを認識させる。

## 環境

以下の環境で検証した。

```shell
% vboxmanage --version
6.0.8r130520

% docker --version
Docker version 18.09.2, build 6247962

% docker-machine --version
docker-machine version 0.16.1, build cce350d7
```

## 概要

以下の手順でDockerコンテナにUSB機器を認識させる。

1. VirtualBox+拡張パックインストール
2. DockerホストOS用仮想マシン(DockerVM)作成
3. DockerVMにUSBを認識させる為の設定追加
4. DockerホストOSでUSBを認識していることを確認
5. DockerコンテナOSで認識できていることを確認

## 1. VirtualBox+拡張パックインストール
以下のURLにアクセスし、最新のバイナリを取得、インストールする。

[Downloads – Oracle VM VirtualBox](https://www.virtualbox.org/wiki/Downloads)

2019-06-12現在の最新は以下が最新。

- VirtualBox 6.0.8 platform packages
- VirtualBox 6.0.8 Oracle VM VirtualBox Extension Pack

## 2. DockerホストOS用仮想マシン(DockerVM)作成

DockerホストOS用仮想マシンをdocker-machineコマンドで作成する。

```shell
% docker-machine create -d virtualbox docker-host-default
```

## 3. DockerVMにUSBを認識させる為の設定追加

このセクションでは、手順を汎用的かつ簡略化するために、設定の1つである「USB Device Filter」に条件が空のフィルターを登録している。
このフィルターを登録することで、DockerホストOS側で全てのUSB機器を認識できる。

但し、使用しているPCで複数のUSBデバイスを利用している場合、USB機器を抜き差ししたタイミングなどでDockerホストOSに権限を奪われてしまうため、意図せずホストOS側でUSB機器が利用できなくなる。よって、環境に応じてフィルタは適切に設定することを推奨する。

フィルタに条件に設定する場合は、USB機器の情報(例えばVendorIdやProductId)を ```% vboxmanage list usbhost``` で確認する。macOSであれば ```% system_profiler SPUSBDataType``` でも確認できる。

### vboxmanageコマンド (CLI) で設定

```shell
% docker-machine stop docker-host-default
Stopping "docker-host-default"...
Machine "docker-host-default" was stopped.

% vboxmanage modifyvm docker-host-default --usb on 
% vboxmanage modifyvm docker-host-default --usbehci on
% vboxmanage usbfilter add 0 --target docker-host-default --name 'All Devices'

% docker-machine start docker-host-default
```

### VirtualBox Manager (GUI) で設定

1. docker-machineコマンドでVMを停止する。
2. VM(docker-host-default) > Settings > Ports > USB の設定画面で以下のチェックボックスを有効にする。
    - Enable USB Controller
    - USB 2.0 (EHCI) Controller
3. 同画面の「USB Device Filters」の右の新規作成アイコンを押下し、条件が空のフィルターを作成する。
4. フィルタが作成されたら、当該デバイスフィルタをダブルクリックし、名前を「All Devices」に変更する。
5. docker-machineコマンドでVMを起動する。

## 4. DockerホストOSでUSBを認識していることを確認

```shell
% docker-machine ssh docker-host-default
   ( '>')
  /) TC (/   Core is distributed with ABSOLUTELY NO WARRANTY.
 (/-_--_-/)           www.tinycorelinux.net

docker@docker-host-default:~$ udevadm monitor
monitor will print the received events for:
UDEV - the event which udev sends out after rule processing
KERNEL - the kernel uevent

※機器をPCに接続すると以下のようなメッセージが出力されることを確認する。
※例として接続したデバイスはUSBシリアル通信機器。

…
KERNEL[4332.894758] add      /devices/pci0000:00/0000:00:06.0/usb2/2-1/2-1:1.0/ttyUSB0 (usb-serial)
…
UDEV  [4332.897138] add      /devices/pci0000:00/0000:00:06.0/usb2/2-1/2-1:1.0/ttyUSB0 (usb-serial)
…
KERNEL[4332.910588] bind     /devices/pci0000:00/0000:00:06.0/usb2/2-1/2-1:1.0/ttyUSB0 (usb-serial)
…
UDEV  [4332.914260] bind     /devices/pci0000:00/0000:00:06.0/usb2/2-1/2-1:1.0/ttyUSB0 (usb-serial)
…

^C (確認できたらCtrl+Cでモニタリング終了)
```

接続が確認できたら、対応するデバイスファイルが存在することも確認し、ログアウトする。
複数のUSBシリアルデバイスを接続している場合は、ttyUSBXのXの部分の数字が異なる場合があるため注意する。

```shell
docker@docker-host-default:~$ ls -l /dev/ttyUSB0
crw-rw----    1 root     staff     188,   0 Jun 12 06:32 /dev/ttyUSB0

docker@docker-host-default:~$ exit
logout
%
```

## 5. DockerコンテナOSで認識できていることを確認

まずはじめに、dockerコマンドが先ほど作成したDockerVMを利用できるようにするために環境設定を行う。

DockerイメージやコンテナはDockerVM毎に別々に管理されているため、
このコマンドを忘れるとデフォルトのHyperkitを利用したDockerコンテナを起動することになるので注意する。

```shell
% eval $(docker-machine env docker-host-default)
```

続いて、DockerコンテナOSで確認するために適当なLinuxOSイメージを取得する。

```shell
% docker pull debian:9.9-slim
9.9-slim: Pulling from library/debian
fc7181108d40: Pull complete
Digest: sha256:9490c476443a3869e39c2897fa66c91daf5dcbbfca53c976dac7bbdc45775b28
Status: Downloaded newer image for debian:9.9-slim
```

USB機器を接続し、コンテナを起動する。

その際 ``` --device ``` でDockerホストOSが認識しているデバイスファイルをDockerコンテナOS上で読み書き可能にする。

```shell
% docker run --rm --device /dev/ttyUSB0 -it debian:9.9-slim /bin/bash
root@2a8988dafd77:/# ls -l /dev/ttyUSB0
crw-rw---- 1 root staff 188, 0 Jun 12 05:46 /dev/ttyUSB0
root@2a8988dafd77:/# exit
exit
```

USB機器を接続していない場合は以下のようにエラーが出力される。

```shell
% docker run --rm --device /dev/ttyUSB0 -it debian:9.9-slim /bin/bash
docker: Error response from daemon: linux runtime spec devices: error gathering device information while adding custom device "/dev/ttyUSB0": no such file or directory.
ERRO[0000] error waiting for container: context canceled
```

## おわりに
USB Device Fileterの設定を検証している際、ホストOS側で認識していたUSB機器を抜き差しすることでホストOS側から機器が利用できなくなったり、フィルタの設定を変更しても、DockerホストOSが起動しているVMを再起動しないとそれが反映されなかったりと、設定の影響を確認するのに手間取った。

また記事に直接関係ないが、DockerホストOSでシステムの情報を確認しようとすると、OSがTinycorelinuxだったり、基本的なコマンドがGNU版ではなく[BusyBox](https://ja.wikipedia.org/wiki/BusyBox)だったりして、少し戸惑った。BusyBoxは調べてみると歴史は古いようで、Linuxの奥深さに触れるいい機会になった。

macOSでしか検証していないけど、おそらくWindowsでも同じ手順で認識させることができそう。

## 参考
- [docker-machineでホスト（MacOS）のUSBデバイスをDockerコンテナにマウントする - Qiita](https://qiita.com/tomitz/items/cb35e0a76eee1d3fd8e9)