Cygwin環境でdockerコマンドを使えるようにするまでの奮闘記
追記
(2017/11/06)
どうやらttyが無くてdocker run -it
できない問題は、mintty特有のものでした。他のターミナルエミュレータを使えば問題なく動きます。パスの置き換えだけすればよかったんですね。
忙しい人のためのまとめ
ラッパースクリプトを書きました。 https://github.com/makiuchi-d/docker-in-cygwin
このスクリプトをPATHの通った場所に置くと、Cygwin環境でもDocker Toolboxのdocker
コマンドが使えるようになります。
なぜ必要だったのか
仕事でWindowsを使わざるをえないとき、Cygwinでもないと呼吸が苦しい困った病気を患っているのですが、ある日DockerをCygwinの中で使おうと思い立って試したら、なんか使えないんです。
$ docker ps
An error occurred trying to connect: Get http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.23/containers/json: open //./pipe/docker_engine: The system cannot find the file specified.
Docker ToolboxにはデフォルトのターミナルとしてDocker Quickstart Termnal (GitBash)が付属しています。これを使えば確かに普通にDockerが使えます。でも、このターミナル、コピペ(特にコピー)が辛いんです。タイトルバー右クリック→編集→範囲指定、マウスで範囲指定後エンター。
普段Cygwinのminttyで生活している僕にとって、これは大変な苦痛でした。minttyでは最初から画面をマウスで範囲指定でき、即コピーされます。
Cygwinでdocker
コマンドを使うという試みは、既ににチャレンジしている方もいるにはいるのですが、決して使い勝手が良い感じではありません。
仕方がないのであれやこれや苦悩しながら調べて試してなんとかした記録をここに書き残しておきます。
環境について
Windows7上でDocker Toolboxを利用しています。
WindowsでDockerを使うには他にもDocker for Windowsがありますが、これはWindows10 Professionalでないと使えません。もしWindows10環境だったとしてもDocker for Windowsを使うにはHyper-Vを有効にせねばならず、そうするとVT-xが無効になってしまうので、Virtualboxで64bitOSが使えなくなります。さすがにそれは困ることが多すぎるため、どのみちDocker for Windowsは選択肢に入りませんでした。
dockerコマンドを使えるようにする方法
方法1: 環境変数を整える (失敗)
Cygwin環境に足りないものはなにか。もしかして環境変数?と当たりをつけてQuickstart Terminalで調べてみると、案の定それっぽいものが定義されていますね。
$ env | grep DOCKER
DOCKER_HOST=tcp://192.168.99.100:2376
DOCKER_MACHINE_NAME=default
DOCKER_TSL_VERIFY=1
DOCKER_TOOLBOX_INSTALL_PATH=C:\Program Files\Docker ToolBox
DOCKER_CERT_PATH=C:\Users\makiuchi-d\.docker\machine\machines\default
あとでドキュメントを調べたら、ちゃんと書いてありました。
$ docker-machine env
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="C:\Users\makiuchi-d\.docker\machine\machines\default"
export DOCKER_MACHINE_NAME="default"
# Run this command to configure your shell:
# eval $("C:\Program Files\Docker Toolbox\docker-machine.exe" env)
この通りに環境変数をセットしてあげたらちゃんと動きました!(ようにみえました)
$ docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://cloud.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/engine/userguide/
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c8cb2172076c hello-world "/hello" 8 minutes ago Exited (0) 11 seconds ago serene_hypatia
ところが、インタラクティブなプロセスを動かそうとするとうごきません。
$ docker run -it ubuntu
cannot enable tty mode on non tty input
そうです。ttyの仕組みが違うのでつながりませんでした。先人のブログではwinptyで乗り切っていましたが、コマンド同士をパイプでつなぐことができず難儀しているようです。
このままでは使い勝手が悪すぎます。別の方法を考えましょう。
方法2: docker-machineコマンド経由で頑張る (失敗)
そもそもDocker Toolboxは、VirtualboxでLinuxのVM(Docker Machine)を動かして、その中でDockerが動いています。そのDocker Machineにアクセスするコマンドとしてdocker-machine
が用意されています。docker-machine ssh
でDocker Machineにログインしてdocker
コマンドを叩くこともできますし、普通のSSH同様、リモートからコマンドを実行することもできます。
docker-machine ssh default docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c8cb2172076c hello-world "/hello" 27 minutes ago Exited (0) 27 minutes ago serene_hypatia
しかしdocker run -it
しようとすると、先ほどと変わりません。
docker-machine ssh default docker run -it ubuntu
cannot enable tty mode on non tty input
exit status 1
それでも、この方法にはまだ先がありました。いろいろググってまわって、–native-sshオプションにたどり着きました。
docker-machine --native-ssh ssh default docker run -it ubuntu
root@663f507b9f9e:/#
今度はちゃんとコンテナが起動して、プロンプトも表示されました。試しにls
などを叩いてみてもちゃんと動いています。
ただ、この方法で起動したコンテナを少し使ってみるとおかしなことに気づきます。入力したコマンドが余分にエコーされたり、なによりTAB補完がうまく表示されなかったりします。ssh
でインタラクティブなコマンドを指定して実行した時によくあるやつです。
OpenSSHのssh
コマンドには-t
オプション(Force pseudo-tty allocation)があり、リモートで実行するコマンドがインタラクティブなものであっても対応できるのですが、docker-machine ssh
では-t
オプションのようなものはありません。
さらに、この方法では出力の改行コードがCRLFになってしまうため、LFのみの改行をを期待しているxargs
コマンド等との相性が最悪です。もちろんこれはdos2unix
コマンドをかませば回避できなくもないのですが……。
やはりこの方法もいまいちです。別の方法を考えましょう。
方法3: Docker MachineにSSHする(たぶん成功)
ちょっと立ち返って考えてみましょう。-t
オプションがあれば良いのなら、普通にCygwinのssh
コマンドでDocker MachineにSSHすればよいのです。
Docker MachineのIPアドレスはdocker-machine ip
で、鍵の場所はdocker-machine inspect
で取ってこれます。ユーザ名はdocker
固定です。ログインに必要な情報は全て集まりました。
SSHKEY=$(docker-machine inspect | grep -Po '"SSHKeyPath":\s*"\K[^\"]*(?=",)' | sed -E 's|^(\w+):\\\\|/cygdrive/\L\1/|;s|\\\\|/|g')
ssh -t -i $SSHKEY docker@$(docker-machine ip) docker run -it ubuntu
root@e7d3c9cfc870:/#
やりました!ちゃんとTAB補完も効いて快適に使えます。
ただし、常に-t
をつけると改行がCRLFになってしまい、xargs
が使えなくなってしまいます。なので必要なとき、つまり-t
や--tty
が付いているdocker run
やdocker exec
の時だけssh -t
しないといけません。ここは渡されたコマンドパラメータを解析して乗り切ることにします。
また、終了時に Connection to 192.168.99.100 closed.
が出てしまうのが気になるので、-q
オプションも指定しましょう。
ようやく、快適にdocker
コマンドを呼び出せるようになりました。ここから実用的にするためにもうひと頑張りしてみましょう。
さらに使いやすくするために
ファイルパスの指定をどうにかする
docker build
するときや、docker run
でホストのディレクトリをVolumeマウントするときにパスを指定しますが、Cygwin環境のパスをそのまま指定しても、当然Docker Machineの中には存在しないのでうまく動きません。
一方、Quickstart Terminalではこれらのパス指定がうまく動いていますが、どういう仕組みでしょうか。
前述のとおり、Docker Toolboxでは、VirtualBox上のVMの中でdockerをうごかしています。インストール時に作られたVMには、ホストWindowsのC:\Users
が/c/Users
として共有フォルダ登録されています。そしてQuickstart Terminalでの自分のHOMEは/c/Users/{UserName}
であり、WindowsでのC:\Users\{UserName}
になっています。
さらにQuickstart Terminalでtype docker
すると次のように出てきます。
$ type docker
docker is a function
docker ()
{
MSYS_NO_PATHCONV=1 docker.exe "$@"
}
なにやら環境変数を追加してdocker.exe
を起動しています。これがあることで、引数のパスをWindows形式(C:\Users\...
)に変換ぜず、/c/Users/...
のままコマンドに渡しているのです。こうして渡された/c/Users/...
のパスはDocker Machineの中に共有フォルダとして存在しているので、期待通りに動くわけです。
ここでひとつ罠があります。/c/Users
の外のパスを引数に渡してしまうと、Docker Machineの中には存在しないため動きません。もし動かしたいなら手作業でVMの共有フォルダに追加してあげる必要があります。
さて、仕組みがわかったのでCygwinでもどうにかしてみましょう。
単純な話、C:\Users\...
を/c/Users/...
に変換してあげればよいのです。cygpath
コマンドを使えばWindowsのドライブレター付きのフルパスが得られますし、それをsedで置換してあげればほしいパスが得られます。
# pathname in docker machine
function dmpath ()
{
cygpath -am "$1" | sed -E 's|^(\w*):|/\L\1|'
}
あとはどのパラメータが変換すべきパス名かですが、これは頑張ってパラメータ解析すべきでしょうか。とりあえず主要なものを実装してみたところ、期待通りに動いています。
コマンドを呼ぶたび1秒くらい待たされるのをどうにかする
docker
コマンドを叩くたびにdocker-machine inspect
、docker-machine ip
を叩いてSSH接続情報を拾っていましたが、この部分はとても遅いです。私の環境では毎回合計1秒弱かかっていました。毎回こんなに待たされていてはたまりません。よく考えてみたら、IPアドレスもdocker-machine inspect
で拾えるため、1回だけで済ませられますね。これで多少改善しました。
さらに、Docker MachineのIPアドレスや使う鍵なんてそうそう変えるものでもないので、いっそ固定値にしてしまうのも手です。そうすればQuickstart Terminalとほとんど変わらない速度になります。
Quickstart Terminalでdocker buildしたイメージとハッシュ値が違う
この違いはファイルのパーミッションの違いによるものでした。DockerfileでCOPYやADDしたファイルのパーミッションは、Quickstart Terminalでは0755
になるのに対し、この方法だと0777
になります。このためイメージ(レイヤー)のハッシュ値が異なります。
そもそもWindowsを使っている時点でUnix風のパーミッションを期待できないため、ここは基本的に気にしてはダメです。Dockerコンテナ内でパーミッションを気にしないといけない場合は、明示的にRUN chmod
するほうがポータビリティのためにもよさそうです。
また、レイヤーのハッシュ値が変わるということはQuickstart TerminalでビルドしたキャッシュがCygwinからは使えないことになりますが、まあこれは混ぜなければ問題ありません。
まとめ
こうして苦難を乗り越え、快適なdocker環境を手に入れました。
最後に言いたいことは、めんどくさいのでDockerを使うならホストもLinuxにしましょう。
以上です。