窓を作っては壊していた人のブログ

この謎のブログタイトルの由来を知るものはもういないだろう

Windows 10 HomeとDocker Desktop for Windows + Docker.DotNet でdockerを意識させないアプリケーション作り

最近自分が仕事や趣味で使うアプリケーションは大体Dockerに閉じ込めて使うことが増えてきました。 プラットフォームに依存しないCLIツールなどを提供する時には非常にDockerが便利ですね。

やりたいことを一つずつ分解していって、それぞれをDockerのImageにして、それらを組み合わせて使っているのですが、 この「組み合わせて使う」という部分がどうしても共通化出来なくて困っていました。 基本的にはShellスクリプト書いたり、Makefile書いたり…とかで対処していたのですが、Windowsに持っていってそのまま動くかと言われるとNoでした。

WSLの登場によりそれは解消されつつあるのですが、若干手間です(マウントするなりでファイル共有をすればまだマシにはなりますが)。 そもそもではありますが、今までWindowsでDockerを動かすにはHyper-V環境が必要だったりして、気軽に試せる環境でもありませんでした。

ですが最近

forest.watch.impress.co.jp

こんな記事を見て、まだ先ではあるけれども、各種必要なものをインストールさえしてもらえれば(実際ここのハードルが高いのは知っているが)Windows利用者でも気軽にDockerが試せる状態になるじゃないかと感じました。 ということで将来的にDockerに関する問題は解消されそうです。

残る抱えている課題は、「組み合わせて使う」部分です。 WindowsでShellスクリプトを使おうとするならばGitをインストールした時に付属してくるGit Bashがあったり、MakefileならMake for Windowsを使えばいいのかなと思いますが、そのために入れるのもな、という気持ちが湧いてきます。

そんな中見つけたのが

github.com

こちらのプロジェクトです。 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をアプリケーションから参照するみたいな方法が簡単に取れるのですが、 MacLinuxの場合はFull Frameworkが入っていないためそのままではパッケージング出来ません。

各csprojのTargetFrameworksから各種FullFrameworkを削除して、.NET Standardのものだけを残せばいけるのですが、今回はそれを行わずに直接ビルドしたDLLを参照するようにします。

$ dotnet publish -f netstandard2.0 $(PATH/TO/Docker.DotNet/CSPROJ/DIR)

こんな感じでDocker.DotNetをビルドして、

medium.com

の通りにHintPathを与えてDLLを参照するようにします。

SpeechRecogSample/SpeechRecogSample.csproj at c288ab22433b82f7b0cab5342b3c0d0a35f086de · yamachu/SpeechRecogSample · GitHub

  <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相当の操作

SpeechRecogSample/Program.cs at 351ca54cd6eb82003e8ff221268321494e73df45 · yamachu/SpeechRecogSample · GitHub

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 pullgit 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 に相当する操作はCreateContainerAsyncStartContainerAsyncの組み合わせで実現できそうです。

後はこれらの処理をコード中に記述すれば、コードの処理結果をそのままDockerの他のプログラムに渡して処理する、みたいなことが出来ます。

組み合わせて使ってみる

口語表現特有のフィラーを取り除くために、形態素解析を行う by yamachu · Pull Request #1 · yamachu/SpeechRecogSample · GitHub

こちらの完成品があるので、実際にWindowsで使ってみましょう。

Command PromptやPowerShellWindows Terminalなどお好きなものを開いて実行してみましょう。

f:id:yamachu_co:20200313213416p:plain
Windowsでの動作の様子

画像からは少しわかりづらいのですが、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 WindowsWindows 10がInsidersじゃなくても使えるようになってくれたり、Docker.DotNetが更新されてReleaseされることを祈るのみです……

github.com

↑よみがえれ…プロジェクト………