Windows 10 Pro上にLinuxのwebcam開発環境をDockerで構築する

　ラップトップを新調してWindows環境が手に入ったので、10年ぶりにWindowsに戻ってきました。
思えばOSX/Ubuntu時代は開発環境が対応していない問題で、組込み・FPGA・メカCADから遠ざかってしまっていたので、これから戻していければいいなと思っています。
　さて、とは言えソフトウェアを開発する環境としては慣れ親しんだLinuxを離れる気はないので、そちらもできるようにWindows上に環境を構築しました。チャレンジして初めて知ったんですけど、話題になってたWindows上でLinuxが走るというWSL（無印）とか仮想環境のHyper-Vとか、USB外部デバイスの接続に対応してないんですね。自分はロボット屋で、PCにわちゃわちゃデバイスくっつけて現実環境にサービスを具現化するのが主戦場なので、これでは使い物にならない。来年に出るというWSL2はそのへん改善されてるらしいので期待してますが、とりあえず現状でなんとかする仕組みを構築しました。[構築したプロジェクト全体](https://github.com/yoneken/Win-vagrant-Ubuntu-webcam)はGitHubに上げてあります。

# 使ったもの
- Windows 10 Pro
- Vagrant 2.2.6
- VirtualBox 6.0.14
- Ubuntu 18.04 (bento/ubuntu-18.04)
- Docker 19.03.4
- docker-compose 1.24.1
- VcXsrv 1.20.5.1

# 手順

　以下の手順ですが、基本的に偉大な「 [Windows環境でDockerコンテナにUSBウェブカメラを認識させてみる](https://www.unitrust.co.jp/7117)[^1]」のページに記載されている手順がベースとなっており、その中に私がハマった部分をチョコチョコ加筆していった構成となっています。

　**注．** 以下、黒背景のカコミ内で **>** はホストの Windows への入力で、 **$** はゲストのUbuntuへの入力を表しています。

## VagrantとVirtualBox, VcXsrvのインストール
　それぞれ公式ページから最新版（2019年10月時点）をダウンロードしてきてインストールしました。
- [Vagrant](https://www.vagrantup.com/downloads.html)
- [VirtualBox](https://www.virtualbox.org/wiki/Downloads)
- [VcXsrv](https://sourceforge.net/projects/vcxsrv/)

## Hyper-V無効化
　WindowsのHyper-Vも仮想環境を作成する機能で、実際にHyper-V上にUbuntuをインストールすることは可能なのですが、Hyper-V上の仮想マシンはUSBデバイスに対応しておらず、この方法でwebcamを認識することはできないようです。まったく同じ理由で、WSL上のUbuntuもWindows on Dockerも候補から外れました。2020年に登場するというWSL2ではそのあたり改善されるらしいので、期待したいですね。現在、Windows上でUSBデバイスを認識できる仮想マシンを作成する方法でお手軽なのはVirtualBoxを使う方法のようです。ただ、VirtualBoxを使うためにはHyper-Vの機能を切る必要があります。Windows on DockerはHyper-Vを利用するので両方使う人はいちいち切り替えがめんどくさそうですね。
　Hyper-Vの機能を切る方法は、「Windows featureのダイアログでHyper-Vのチェックボックスからチェックを外す」と色々なところに書いてあるんですが、実際やってみたところ、それだけだと何か足りなかったらしくうまくいきませんでした。最終的には、「[Windows10上のVirtualBoxがエラー吐いた時の備忘録](https://qiita.com/reef1217/items/e760b6e4078dedc0b434)のページ[^2]」に書いてあったとおり、PowerShellから管理者権限で

``` console
> Disable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-All
```

を実行した後、再起動することでようやくVirtualBoxが動作するようになりました。

　確認するためには、PowerShellとかで

``` console
> systeminfo
```

としたときに、末尾の"Hyper-V の要件"が、

``` shell-session
Hyper-V の要件:         VM モニター モード拡張機能: はい
                        ファームウェアで仮想化が有効になっています: はい
                        第 2 レベルのアドレス変換: はい
                        データ実行防止が使用できます: はい
```

とずらずら要件が出てきていればOKで、「ハイパーバイザーが検出されました」と出ている状態だとダメなようです。

## Vagrantによる仮想マシン生成
　[Vagrant boxes](https://app.vagrantup.com/boxes/)にUbuntu 18.04用のboxも色々あるんですが、 generic/ubuntu1804 だけは使っちゃダメです。更新頻度も高くダウンロード数も多いので最初は試してたんですが、こいつはaptのrepositoryがusのサーバーを向いてるので、検索はミスるわダウンロードはエラるわ散々でした。"generic"て書いてあるくせに米国人仕様だと・・・。
　次に、 ubuntu/bionic64 を試してみたんですが、こちらで使用されているカーネルにはuvcvideoのモジュールが読み込まれておらず、USBカメラを接続しても /dev/ 以下にvideoデバイスが組み込まれませんでした。結局、 [bento/ubuntu-18.04 を選択する](https://github.com/yoneken/Win-vagrant-Ubuntu-webcam/blob/master/Vagrantfile#L15)ことで解決しました。

　それでは、Vagrantfileがあるフォルダに移動して、


``` console
> vagrant up
```

で仮想マシンを作成します。
　＊ ちなみにこの仮想マシン作成なんですが、途中で *apt update & apt upgrade* をやっているため、これがエラーになるとそこでvagrant自体も終了してしまいます。こういう場合、何度か *vagrant up* することでできるようになったりします。。

　このVagrantfileには、「 [Windows環境でDockerコンテナにUSBウェブカメラを認識させてみる](https://www.unitrust.co.jp/7117)[^1]」のページを真似て、[webcamを自動で仮想マシンに接続するスクリプト](https://github.com/yoneken/Win-vagrant-Ubuntu-webcam/blob/master/Vagrantfile#L97-L105)を仕込んであります。
　このスクリプトが正常に動作するためには、VirtualBoxのフォルダにパスを通す必要があります。具体的には *C:\Program Files\Oracle\VirtualBox* にパスを通してください。

　＊ 初回up時のみ、このタイミングでは仮想マシンが作成されておらず、マシンidがなくてwebcamの接続に手動操作を必要とするので、以下のコマンドで仮想マシンを再起動してください。

``` console
> vagrant reload
```

## 仮想マシンの設定

　まずは起動した仮想マシンの中にsshで入ります。

``` console
> vagrant ssh
```

　＊ 以下で説明している手順は、今は [Vagrantfile に記述している](https://github.com/yoneken/Win-vagrant-Ubuntu-webcam/blob/master/Vagrantfile#L90-L94)ので、最初に上げたGitHubリポジトリのプロジェクトを使用する場合は必要ありません。


### ユーザーをvideoグループに追加
　デフォルトの状態では、作成された仮想マシン上でユーザー（vagrant）がvideoグループに所属しておらず、videoデバイスを開けなかったので、

``` console
$ sudo usermod -aG video vagrant
```

でグループに追加しました。
　手動でユーザーをvideoグループへ追加した直後は、一旦

``` console
$ exit
> vagrant ssh
```

して、仮想マシンに再ログインするとグループが適用されます。

### APTのリポジトリを国内サーバーにする
　また、「[Dockerのイメージビルド中でapt-getを高速化するたった1つの方法](https://genzouw.com/entry/2019/09/04/085135)」のページ[^3]を参考に、 apt のsources リポジトリを日本国内のサーバーを参照するようにして、apt 関連が高速になるように設定しました。

``` console
$ sudo sed -i 's@archive.ubuntu.com@ftp.jaist.ac.jp/pub/Linux@g' /etc/apt/sources.list
```

### docker-composeを入れなおす
　あと、なぜかprovisionでインストールしたはずのdocker-composeがSegmentation fault になるので、結局、「 [Vagrantアップデートを乗り越える](https://qiita.com/eghkanno/items/d3ee2faf7c56e081c749)」のページ[^4]を参考にdocker-composeを入れ替えました。

``` console
$ curl -L https://github.com/docker/compose/releases/download/1.24.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
```

### DISPLAY環境変数をホスト側のIPアドレスにする

``` console
export DISPLAY=10.0.2.2:0.0
```


## WindowsでXサーバーを起動
　Ubuntuの画面をWindowsに飛ばして表示するために、Windows側でXサーバーを起動しておきます。Xサーバーには今回、VcXsrvを使用しました。スタート画面等から起動してください。 ~~ほぼデフォルト設定そのままですが、
![vcxsrv.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35648/33284de0-0a8d-258e-241e-889287ff4339.png)
　起動時にこの画面で、"Disable access control"を選んでください。これをしておかないと、~~

``` console
$ Authorization required, but no authorization protocol specified
$ Error: Can't open display: 192.168.33.1:0.0
```

~~と言われてXの画面がWindows上に出てきません。
　これ、xauthをちゃんと設定すれば回避できる気がするので、詳しい人は教えてください。~~

　これ解決しました。
　ホストOSと仮想マシン上のOS間のネットワークは通常NATで構成されて、ホスト側10.0.2.2、ゲスト側10.0.2.15が割り振られてvagrant sshでもこれを使うのですが、VcXsrvは C:\Program Files\VcXsrv\X0.hosts ファイル内に記載されているホストのみX11のforwardingを許可するので、中身を

``` shell-session
localhost
inet6:localhost
10.0.2.2 # 10.0.2. でも良い
```

となるように編集してください。こうしておくとVcXsrvのセキュリティを落とさずにXの画面をWindows上で表示できます。また、この設定は、WSLなど素のsshで仮想マシン上のOSに接続する際も有効です。

## Dockerでカメラ起動

　次に、docker-compose経由でdockerコンテナを作成して起動します。

``` console
$ cd webcam_test
$ docker-compose up
```

　初回はDockerイメージの作成が入るので、起動にとても時間がかかると思いますが、2度目以降は作成済のイメージを使うので早いです。これで、カメラ画面のウィンドウが表示されたら成功です。ESCキーを押して終了してください。終了時にディレクトリ内にcap.jpgというキャプチャも作成されます。

## 終了処理

　さて、用事は済んだので、以下で、作成したdockerコンテナを削除してバーチャルマシンから抜けます。

``` console
$ docker-compose rm -f
$ exit
```

　最後に、仮想マシンをシャットダウンします。

``` console
> vagrant halt
```

　以上です。お疲れさまでした。

# 2019/12/27 追記
　なんか久しぶりにvagrant upしようとしてみたらバーチャルマシンが起動できず、systeminfo見てみたらHyper-Vハイパーバイザが起動しているみたいでした。ところが今回はDisable-WindowsOptionalFeatureしてもハイパーバイザの起動を止められず、調べてみたらWindows 10 v1903から入ったWindowsサンドボックスとやらが関係していたらしく、[こちらのページ](https://superuser.com/questions/1442766/virtualbox-fails-to-start-with-verr-nem-vm-create-failed)に書いてあるとおり、プログラムと機能>Windowsの機能の有効化または無効化からWindowsサンドボックスのチェックを外した上で、 **2回再起動する** ことでようやくハイパーバイザを止めてバーチャルマシンを起動できるようになりました。これ、1度の再起動だとHyper-Vの機能もWindowsサンドボックスの機能もチェック入っていないのにハイパーバイザが起動していて謎だったんですが、そのまま何も設定をいじらずに再度再起動したらこんどこそハイパーバイザを止められました。闇だ・・

# 参考にさせていただいたページ
[^1]: [Windows環境でDockerコンテナにUSBウェブカメラを認識させてみる](https://www.unitrust.co.jp/7117)
[^2]: [Windows10上のVirtualBoxがエラー吐いた時の備忘録](https://qiita.com/reef1217/items/e760b6e4078dedc0b434)
[^3]: [Dockerのイメージビルド中でapt-getを高速化するたった1つの方法](https://genzouw.com/entry/2019/09/04/085135)
[^4]: [Vagrantアップデートを乗り越える](https://qiita.com/eghkanno/items/d3ee2faf7c56e081c749)