コマンド配布ツールとしてDockerは使えるか?
まずは論よりなんとやら。Docker導入済みの方は、下記のコマンドを実行してみてください。
$ docker run -i --rm greymd/cureutils cure precures
すると、下記のようにDockerhubからDockerのイメージの取得が始まり、そのイメージに含まれるcure
コマンドが実行されます。このコマンドは、以前私が自作したコマンドで、プリキュア1全員2の名前を表示できます。
Unable to find image 'greymd/cureutils:latest' locally # Dockerhubからイメージの取得が始まる
latest: Pulling from greymd/cureutils
efd26ecc9548: Pull complete
a3ed95caeb02: Pull complete
d1784d73276e: Pull complete
72e581645fc3: Pull complete
9709ddcc4d24: Pull complete
7013ecfd652c: Pull complete
66373b9c2e12: Pull complete
2a6578ab609c: Pull complete
4356bd31ea9f: Pull complete
34ea4e1d4a33: Pull complete
Digest: sha256:8ae33b900e562e9cbafb8bd6dda45b658521b9e81d999bd5bfd34e40c5a61015
Status: Downloaded newer image for greymd/cureutils:latest
キュアブラック # コマンドが実行される
キュアホワイト
シャイニールミナス
キュアブルーム
キュアイーグレット
キュアドリーム
キュアルージュ
キュアレモネード
# (中略)
# 一旦イメージを取得してしまえば、以降はスムーズにできる
$ docker run -i --rm greymd/cureutils cure precures
キュアブラック
キュアホワイト
シャイニールミナス
キュアブルーム
# (中略)
このcure
コマンドを使うためには、本来はRuby2.0.0以上を用意してgem
でインストールする必要があります。しかし上記の方法で、それらが入っていない環境でもDockerを使ってコマンドを実行することができました。
とはいえ、わざわざcure
コマンドを使うのに、毎回docker...ほにゃらら
などと打っていられないと思います。そういう時は、下記のような関数を.bashrc
に追加します。
cure () {
[ -t 0 ] && T="t" || T=""
docker run -i$T --rm greymd/cureutils cure "$@"
}
すると、まるでホスト上にcure
コマンドがインストールされたかのように、cure
コマンドを使うことができます。標準入力をフィルターできますし、オプションやコマンド引数も動作します。実際にホストに直接cure
コマンドはインストールしていないにもかかわらず、です。
$ cure echo -a -p cure_heart -q
プリキュアラブリンク!
L! O! V! E!
みなぎる愛! キュアハート!
響け愛の鼓動!ドキドキプリキュア!
愛を無くした悲しいジコチューさん、
このキュアハートがあなたのドキドキ取り戻してみせる!
あなたに届け!マイスイートハート!
$ echo キュア{ハート,ダイヤ,エース,スペード} | xargs -n 1 | cure grep
キュアハート
キュアエース
このような方法で、ホスト側からの標準入力を、コンテナ内のコマンドでフィルタリングして、ホスト側の標準出力に結果として出力できます。この方法を知った時「自作コマンド配布のためにDockerのイメージ使うのってどうなんだろう?流行る可能性はあるのだろうか?」と思ったので、以下、作り方をざっくりとまとめ、軽く考察します。
試した環境
Mac OSX Yosemite 10.10.5の端末上で実施。
zshを使って動作確認してますが、多分bashでも動きます。
$ zsh --version
zsh 5.0.6 (x86_64-apple-darwin13.3.0)
DockerはMac OSXのネイティブ版Dockerを使います。
$ docker --version
Docker version 1.12.5, build 7392c3b
作り方
動かしたいコマンドが動くDockerのイメージを作る
例えば私の場合、cure
コマンドを動かすために下記のようなDockerfileを作りました。依存するDockerイメージとしてruby:2.3.0
を指定してあります(OS自体はDebianです)。やっていることは、リポジトリからソースをクローンして、rakeでビルドし、bundleでインストールしています。この一連の作業で、cure
コマンドがPATH
が通っている/usr/local/bundle/bin/cure
に置かれました。
https://github.com/greymd/dockerfiles/blob/master/cureutils/debian/Dockerfile
また、今回の場合cure
コマンドは日本語を出力します。なので、イメージの文字エンコーディングの諸々の設定を全てja_JP.UTF-8
にしてあります。
FROM ruby:2.3.0
RUN apt update && \
apt install -y locales && \
localedef -f UTF-8 -i ja_JP ja_JP.utf8 && \
cd ~ && \
git clone https://github.com/greymd/cureutils.git && \
cd cureutils && \
gem install bundler rake && \
bundle install && \
rake build && \
find ./pkg/ -type f | head -n 1 | xargs gem install && \
cd .. && \
rm -rf cureutils
ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP.UTF-8
ENV LC_ALL ja_JP.UTF-8
Dockerfile
があるディレクトリでビルドします。
# docker build -t <Dockerhubのユーザ名>/<イメージ名> .
$ docker build -t greymd/cureutils .
DockerhubにPushする
Dockerのイメージを登録できるサービスであるDockerhubにイメージを登録します。事前にDockerhubのアカウントを作成するなどの作業が必要です。こちらの記事によくまとまっていたので、説明は割愛します。
http://qiita.com/moru3/items/32931813db81d891effb
$ docker login
$ docker push greymd/cureutils
こうして、docker pull
でイメージが取得できれば成功です。
$ docker pull greymd/cureutils
.bashrc
に追加
再掲になります。
cure () {
[ -t 0 ] && T="t" || T=""
# docker run -i$T --rm <Dockerのイメージ名> <コマンド名> "$@"
docker run -i$T --rm greymd/cureutils cure "$@"
}
[ -t 0 ] && T="t" || T=""
という箇所で、標準入力が無ければ T
という変数に"t"という文字列を入れ、標準入力があれば空文字を入れます。これでdocker run
コマンドの-t
オプションの有無を制御しています。
-t
オプションがあると、標準入力がコンテナ内のコマンドまで渡らないので、フィルタのような動きができません。そこで標準入力がある場合は-t
を無効にしています。逆に、常に-t
オプションを無効にすると、スリープをかけながら標準出力するコマンド3が正しく動かないため、基本的には有効にしています。
配布する時
人様に自作コマンドを使ってもらいたいときは、
- Docker入れて!
-
.bashrc
にmy_command () ...
って関数入れて!
ってやればできる。(2はzplugのほうが良いかもしれない。)
考察
この方法の利点
- 環境の影響を気にせずコマンドを配布できる。
- コマンドの動作が標準入出力のみで決定するようであれば、環境の差分はないハズ?
- 導入が面倒なコマンドを配布するのに向いている
- パッケージ管理システムで配布されておらず、色々入れてビルドが必要なものとか。
- 異なるバージョンのインタプリタが必要なコマンドを共存するのに役立つ。
- 例: コマンドAはRuby2.0でしか動かない。コマンドBはRuby2.1でしか動かない。じゃあ片方Dockerにして、ホストの
PATH
のruby
はRuby2.1にしよう、みたいな。
- 例: コマンドAはRuby2.0でしか動かない。コマンドBはRuby2.1でしか動かない。じゃあ片方Dockerにして、ホストの
この方法の欠点
- 標準入出力以外の要素が介在すると、コマンドが正しく動くとは限らない。(例: ディレクトリやファイル、GUI、ネットワークなど)
- ホストとコンテナ間の情報のやり取りのオーバーヘッドがあるのか、比較的動作が遅い。コマンドに巨大なデータを投げるのには向かない。(例:
grep
,awk
など)
所感
標準入出力以外のホストのリソースを簡単に使えないため、総合的には微妙な気がしました。ただ、コマンドをこんな感じでDockerのイメージと抱き合わせで配布しやすくするための、パッケージ管理ツール的なものがあれば、需要はありそうだと思いました。
おまけ(活用例)
Mac OSXだとgrep
コマンドがデフォルトではBSD版ですが、この手法を使うことでGNU版のコマンドが使えます(coreutils使えって話ですが)。
# BSD版にはない-Pオプションが動く
$ seq 10 20 | docker run -i ubuntu:16.04 grep -P '.0'
10
20