標準入出力を利用するコマンドを外部からネットワーク経由で実行したいことありませんか?
コンテナを使っているとまれにありますよね。例えばデータサイエンティストが実装したシンプルな Python スクリプトとか。
socat を使って TCP ソケット経由で外部からコマンドを実行できるようにしてみました。
課題
例えば 1マイクロ秒の誤差もないすごい date コマンドを持っているコンテナがあるとします。このコンテナは docker run date:latest を実行すると日時を標準出力に表示します。
この date コマンドをホスト側から実行するのは容易ですが、別のコンテナから実行したくても直接コマンドを実行する方法が見つかりませんでした。
これを実現するために ssh や Docker on Docker で date コンテナを実行する方法もありますが、呼出元のコンテナに Docker をインストールしたり docker.sock を共有したり事前準備がやや面倒です。
この記事では上記のような「別コンテナのコマンドを実行して標準出力を取得したい」ケースを socat を使って解決します。
具体的な例
前の記事でヘッドレス Chrome を使用して HTML を標準出力にダンプするコンテナを作成しました。このコンテナを実行すると対象のウェブサイトの HTML が出力されます。
$ docker pull grohiro/headless-chrome
$ docker run grohiro/headless-chrome http://www.google.com/
# => (HTMLが出力される)
コンテナの内部では gooogle-chrome コマンドを実行して HTML をダンプしています。このコンテナを socat を使って TCP ポート 9000 経由で外部から実行できるようにします。
コンテナイメージの作成
まずは上記の Chrome コンテナに socat を追加したイメージを作成します。そしてヘッドレス Chrome を起動するラッパースクリプトも追加します。
Dockerfile
FROM grohiro/headless-chrome
USER root
RUN apt-get install -y -qq socat
COPY ./chrome.sh /
ENTRYPOINT ["socat", "tcp4-listen:9000,fork,reuseaddr", "system:/chrome.sh"]
chrome.sh
# !/bin/sh
read URL
google-chrome --headless --no-sandbox --dump-dom --start-maximized --disable-gpu "$URL"
ファイルを作成したら Docker イメージをビルドします。名称は headless-chrome-socat です。
$ docker build -t headless-chrome-socat .
作成したコンテナを実行すると TCP ポート 9000 が socat にバインドされて LISTEN 状態になります。このポートに送信したデータは chrome.sh の標準入力に渡されます。
chrome.sh は標準入力の文字列を Chrome の引数として実行します。Chrome は指定された URL を読み込み HTML を標準出力にダンプします。
socat はこの標準出力をリクエスト元のコンテナにレスポンスとして返します。
実行元のコンテナはソケットの入力を読み込むことで Chrome がダンプした HTML を取得することができます。
動作確認
まずはホスト側から手動で URL を入力して HTML が出力されるか確認します。
以下のコマンドで作成したコンテナのポート 9000 を 127.0.0.1:9000 にマッピングして起動します。
$ docker run -p 9000:9000 headless-chrome-socat
ポート 9000 に接続して URL を送信してみましょう。
$ nc 127.0.0.1 9000
http://www.google.com
# => (HTMLが出力される)
入力した http://www.google.com が chrome.sh 経由でヘッドレス Chrome に渡されて HTML が出力されました。
docker-compose
ここまでできたら docker-compose.yml を定義します。
docker-compose.yml
services:
crawler:
# コマンド実行元のコンテナ
image: php:7.5-slim
chrome:
# ヘッドレス Chrome コンテナ
image: headless-chrome-socat
この docker-compose.yml で crawler コンテナの中から tcp://chrome:9000 に URL を送信すると Chrome が実行されて HTML が取得できるようになりました。
例えば crawler コンテナ内の PHP からは fsockopen() を使ってデータを読み書きできます。
$response = "";
$fp = fsockopen('chrome', '9000');
if ($fp) {
fwrite($fp, "https://www.google.com\n");
while (!@feof($fp)) {
$response .= fgets($fp, 4096);
}
}

