最近自分が仕事や趣味で使うアプリケーションは大体Dockerに閉じ込めて使うことが増えてきました。 プラットフォームに依存しないCLIツールなどを提供する時には非常にDockerが便利ですね。
やりたいことを一つずつ分解していって、それぞれをDockerのImageにして、それらを組み合わせて使っているのですが、 この「組み合わせて使う」という部分がどうしても共通化出来なくて困っていました。 基本的にはShellスクリプト書いたり、Makefile書いたり…とかで対処していたのですが、Windowsに持っていってそのまま動くかと言われるとNoでした。
WSLの登場によりそれは解消されつつあるのですが、若干手間です(マウントするなりでファイル共有をすればまだマシにはなりますが)。 そもそもではありますが、今までWindowsでDockerを動かすにはHyper-V環境が必要だったりして、気軽に試せる環境でもありませんでした。
ですが最近
こんな記事を見て、まだ先ではあるけれども、各種必要なものをインストールさえしてもらえれば(実際ここのハードルが高いのは知っているが)Windows利用者でも気軽にDockerが試せる状態になるじゃないかと感じました。 ということで将来的にDockerに関する問題は解消されそうです。
残る抱えている課題は、「組み合わせて使う」部分です。 WindowsでShellスクリプトを使おうとするならばGitをインストールした時に付属してくるGit Bashがあったり、MakefileならMake for Windowsを使えばいいのかなと思いますが、そのために入れるのもな、という気持ちが湧いてきます。
そんな中見つけたのが
こちらのプロジェクトです。 Docker Remote APIを.NET Applicationから叩くことの出来るライブラリで、多くのアプリケーションをC# で書いている私にはピッタリのソリューションでした。 ということで、Docker.DotNetを使ってDocker Imageを使用するアプリケーションを作ってみたいと思います。
ベースにしたアプリケーションは
Azure Cognitive ServicesのSpeech to Textで書き起こしをしてみよう - Qiita
で作成したAzure Cognitive ServicesのSpeechを使用して音声の書き起こしをするアプリケーションです。
将来的にこのアプリケーションで認識した結果をAzure Cognitive Searchに投げて、どの音声でどのタイミングでこんな事を話したか、あるキーワードに引っかかる発言、その周辺の発言はどんなものか、みたいのを取ろうと思っています。 それを実現する上で、口語表現に特徴的な、例えば「あー」や「うーん」、「えっと」の様なフィラー(言いよどみ)があると検索の邪魔になったりしないか、また別途キーワード検出などをしたくなった場合頻度で言ったらおそらく高いので、意味のないフィラーがキーワードとして出てこないかなどの事が考えられました。 ということで、今回のゴールはベースのアプリケーションに対してフィラー除去機能を付与する、というものにしてみました。
フィラー除去を実現するアプリケーションの選定
フィラーの除去などを行う場合、形態素解析などを行なって、品詞が感動詞であるものを除去するという手段で解決できそうです。 であればMeCabみたいなものを使えば良さそうです。
正直なところMeCabのDotNetバインディングを使えば解決!みたいなところはあるのですが、今回はDocker.DotNetを使ってみたいという気持ちもあるので、MeCabをいい感じにDockerに閉じ込めたものを探してみましょう。
ということで見つけたものがこちらです
https://hub.docker.com/r/intimatemerger/mecab-ipadic-neologd
NEologdの辞書も同梱です便利!しかも現代語にも強い!ということで最高ですね。 ということで今回はこちらのImageを使っていきます。
Docker.DotNetを使う
ビルドして参照に加える
えっ、NuGetから落としてくればいいじゃん、とお思いのそこのあなた…
NuGet Gallery | Docker.DotNet 3.125.2
リポジトリのREADMEとバージョンが割と乖離していたり、interfaceが若干違ったりで使うのが若干面倒だったりします。 OSSとして公開されているので、自分でビルドしてそれを使う方がいいかもしれません。
Windowsマシンを利用の方はリポジトリをクローンしてきて、リポジトリルートで
$ dotnet pack
すればおそらくnupkgが生成されて、そのnupkgをアプリケーションから参照するみたいな方法が簡単に取れるのですが、 MacやLinuxの場合はFull Frameworkが入っていないためそのままではパッケージング出来ません。
各csprojのTargetFrameworks
から各種FullFrameworkを削除して、.NET Standardのものだけを残せばいけるのですが、今回はそれを行わずに直接ビルドしたDLLを参照するようにします。
$ dotnet publish -f netstandard2.0 $(PATH/TO/Docker.DotNet/CSPROJ/DIR)
こんな感じでDocker.DotNetをビルドして、
の通りにHintPathを与えてDLLを参照するようにします。
<ItemGroup> <Reference Include="Docker.DotNet"> <HintPath>Docker.DotNet\src\Docker.DotNet\bin\*\netstandard2.0\publish\Docker.DotNet.dll</HintPath> </Reference> </ItemGroup>
これで自分のアプリケーションから最新のDocker.DotNetが使えるようになりました。
dockerコマンドに対応したコードを書く
それではDocker.DotNetを使ってdockerの操作をしていきます。
今回は
- 対象のDocker Imageが存在するかの確認(docker images)
- 対象のDocker Imageをリポジトリからpullしてくる(docker pull)
- 対象のDocker Imageに引数を与え実行する(docker run)
の3つの処理で自分のやりたい事が実現できるのでやっていきます。
docker images相当の操作
using Docker.DotNet; using Docker.DotNet.Models; ... var client = new DockerClientConfiguration().CreateClient(); var images = await client.Images.ListImagesAsync(new ImagesListParameters { MatchName = "対象のImageの名前", });
今回はImageの存在の有無さえ取れればよかったので、ListImagesAsync
のパラメータでフィルタを行なって、結果の長さが0かどうかの判断で済ませてます。
docker pull相当の操作
await client.Images.CreateImageAsync(new ImagesCreateParameters { FromImage = "対象のImageの名前", Tag = "対象のタグ(今回はlatest)", // タグを指定しないと全部取ってこようとするので注意 }, null, new Progress<JSONMessage>((_) => { // ダウンロード進捗とかが落ちてくるのでハンドリングして見せると良さそう }));
CreateImage
なのか……
我々の使っているdockerコマンドって、git pull
が git fetch
+ git merge
相当って感じで、他のdockerコマンドとの組み合わせだったりするんでしょうかね。
この処理を行った後にターミナルで docker images
を叩くとちゃんとpullされていることが確認できます。
docker run相当の操作
今回実行したいコマンドは以下の通りです
$ docker run -v /Users/yamachu/tmp:/tmp intimatemerger/mecab-ipadic-neologd /usr/local/bin/mecab /tmp/input.txt -o /tmp/output.txt
これを実現するコードは
var createContainerResponse = await client.Containers.CreateContainerAsync(new CreateContainerParameters { Cmd = new[] { "/usr/local/bin/mecab", "/tmp/input.txt", "-o", "/tmp/output.txt" }, Image = "intimatemerger/mecab-ipadic-neologd", HostConfig = new HostConfig { Mounts = new[]{new Mount { Source = "/Users/yamachu/tmp", Target = "/tmp", Type = "bind" }} } }); var containerId = createContainerResponse.ID; await client.Containers.StartContainerAsync(containerId, new ContainerStartParameters()); // 終了を待ちたいので while (true) { var s = await client.Containers.InspectContainerAsync(containerId); if (!s.State.Running) { break; } await Task.Delay(200); }
docker run
に相当する操作はCreateContainerAsync
とStartContainerAsync
の組み合わせで実現できそうです。
後はこれらの処理をコード中に記述すれば、コードの処理結果をそのままDockerの他のプログラムに渡して処理する、みたいなことが出来ます。
組み合わせて使ってみる
口語表現特有のフィラーを取り除くために、形態素解析を行う by yamachu · Pull Request #1 · yamachu/SpeechRecogSample · GitHub
こちらの完成品があるので、実際にWindowsで使ってみましょう。
Command PromptやPowerShell、Windows Terminalなどお好きなものを開いて実行してみましょう。
画像からは少しわかりづらいのですが、VSCodeのTerminalに表示されている認識結果は既にフィラーが取り除かれた文章で、エディタの右側に表示されているのが元の認識結果です。 口語特有の語が取り除かれている雰囲気があります。
無事Cognitive Serviceで音声認識を行い、認識結果からフィラーを取り除くことが出来ました。 しかもWindowsでDockerを動かして、です。
終わりに
今回はファイル入出力のみを扱いましたが、標準入出力を扱いたいという場面も出てくるかと思います。
その場合は
Docker.DotNet/IExecOperations.cs at master · microsoft/Docker.DotNet · GitHub
この辺りの処理を行えば良さそうだなーという雰囲気を感じているので今度試してみようかと思います。
この様にDockerと協調するアプリケーションがC# で書けそうだな、ということがわかりました どうしてもDocker上に作ったアプリケーションを人に使わせたいけど、コマンドを打ってもらうのは…みたいな時に使えるんじゃないでしょうか。 Linuxで作ったアプリケーションをDockerに閉じ込めて、Xamarin.FormsなどのUIフレームワークでガワを作れば、クロスプラットフォームなGUIアプリケーションが更に作りやすくなるかもしれませんね(?
あとはDocker Desktop for WindowsやWindows 10がInsidersじゃなくても使えるようになってくれたり、Docker.DotNetが更新されてReleaseされることを祈るのみです……
↑よみがえれ…プロジェクト………