GoogleCloudPlatformで単発処理を行うDockerコンテナ

# 概要
あるところからデータの塊を取ってきて何らかの解析にかけたい．それらを [GCP (Google Cloud Platform)](https://cloud.google.com/) で行いたいが，インスタンスの設定やファイルのダウンロード，アップロードが面倒．そんな時に使える [Dockerコンテナイメージ](http://jkawamoto.github.io/roadie-gcp/)を作った．

なお，ここでの処理は，毎日ログデータを深夜に処理するなどのバッチ処理を考える．ストリーム処理には向かない．

# Roadie-GCP
GCP でプログラムを実行する時に使える補助[コンテナ](http://jkawamoto.github.io/roadie-gcp/)．必要なファイル，処理内容，実行結果の保存先などをまとめた YAML スクリプトファイルを渡せば後はうまくやってくれる．なおベースイメージは Ubuntu である．

スクリプトファイル名が `conf.yaml` の場合，実行方法は次の通り．

```shell-session
$ docker run -i jkawamoto/roadie-gcp < conf.yml
```

なお，このコンテナは実行が終わり，結果をアップロードし終わるとインスタンスを削除する．そのため，インスタンスの作成時に `https://www.googleapis.com/auth/compute` スコープを指定する必要がある．この挙動を変える場合には `--no-shutdown` オプションを指定する．

## スクリプトファイル
スクリプトファイル `conf.yaml` は `apt`, `source`, `data`, `run`, `result`, `upload`  の６要素を定義するYAML ファイル．それぞれ，プログラム実行に必要な apt パッケージ，プログラムのソースファイル，プログラムの実行に必要なデータファイル，プログラム実行手順，実行結果の保存先，そして実行結果のファイルパターンを定義する．`script.yml` は次のような形になる．

```yaml:script.yml
apt:
- unrar
source: https://github.com/abcdefg/some-program.git
data:
- http://mmnet.iis.sinica.edu.tw/dl/wowah/wowah.rar
run:
- unrar x -r wowah.rar
- ./analyze WoWAH
result: gs://somebucket/
upload:
- "*.png"
```

このスクリプトファイルは，次のことを指示している．

1. apt-get で `unrar` パッケージをインストールする．
2. GitHub からソースコードをダウンロードする．
3. プログラムの実行に必要なファイルをウェブから取得する．
4. 次の二つのコマンドを実行する．
  - unrar で取得したファイルを解凍．
  - 自前の解析プログラムを実行する．
5. [Google Cloud Storage](https://cloud.google.com/storage/) のバケットを出力の保存先として設定する．
6. 解析プログラムが作成した *.png ファイルを (5) で設定したバケットにアップロードする．

### apt
`apt` 要素では，apt-get でインストールするパッケージのリストを指定する．特にインストールすべきパッケージがない場合は省略可．

### source
`source` 要素には，実行するプログラムのソース URL を指定する．URLは次の形式をサポートしている．

- URLが `.git` で終わっていれば，Gitリポジトリと判断してクローンする．
- URLが `dropbox://` で始まっていれば，
[Dropbox](https://www.dropbox.com/) からダウンロードする．
この URL は Dropbox のパブリックリンクの先頭を `https://` から `dropbox://` に変えたものになる．
- URLが `gs://` から始まっていれば，Cloud Storage からダウンロードする．
- それ以外では，`http://` と `https://` をサポートする．

どの場合でも，URLが `.zip`, `.tar`, `.tar.gz` で終わっていれば適切に解凍される．ダウンロードしたソースは `/data` に展開される．

### data
data 要素では，実行前に取得するファイルをリスト形式で記述する．リストの各要素は，`URL:保存先` の形をとる．
URLスキームには，`http`, `https` の他 [Google Cloud Storage](https://cloud.google.com/storage/) を表す `gs`，[Dropbox](https://www.dropbox.com)の公開リンクを表す `dropbox` が使用できる．

保存先には絶対パス，相対パスどちらも使用できる．パスが `/` で終わっている場合は，指定されたディレクトリ内にURLが示すファイル名で保存する．逆に，パスが `/` で終わっていない場合は，当該ファイル名に変更して保存する．

なお，デフォルトのディレクトリは `/data` である．

```yaml
data:
- http://sample.com/input.json
- gs://a-project/input/data:/tmp/
```

上記の例では，http://sample.com/input.json を `/data/input.json` として保存し，Cloud Storage の a-project バケットにあるオブジェクト input/data を `/tmp/data` として保存する．

最終的に保存されるファイルの拡張子が `.zip`, `.tar`, `.tar.gz` の場合，展開される．この挙動を変える場合，`--no-unzip` オプションを使用する．

### run
実行するコマンドのリスト．上から順に実行していく．

各コマンドの標準出力は記録される．先頭コマンドの標準出力は `stdout0.txt`，次のコマンドの標準出力は `stdout1.txt` というように番号付けられ，`result` で指定した場所へ保存される．標準エラー出力はコンテナの標準出力としてコンテナ外に出力される．したがって，処理結果など記録しておくべきものは標準出力に，実行時のログなどは標準エラー出力に書き出せば良い．

なお，標準出力の結果は `/tmp/` に保存されるので，他のプログラムから利用することもできる．次の例は，`/tmp/data` を引数に `run.sh` を実行し，その標準出力の結果 `/tmp/stdout0.txt` を引数に次のコマンド `plot.sh` を実行している．

```yaml
run:
- ./run.sh /tmp/data
- ./plot.sh /tmp/stdout0.txt
```

なお，apt パッケージ以外でプログラムの実行に必要なパッケージがある場合は，`run` セクションに書けば良い．

### result
`result` セクションには，Cloud Storage の URL を指定する．そこに標準出力及び `upload` セクションで指定したファイルがアップロードされる．

### upload
`upload` セクションには，Globパターンのリストを指定できる．ここで指定したパターンのいずれかにマッチしたファイルも，実行結果として `result` セクションで指定した Cloud Storage のバケットへアップロードされる．

```yaml
result: gs://a-project/result/
upload:
- "*.out"
```

上記の例では，`gs://a-project/result/` に `stdout0.txt`，`stdout1.txt` に加えて `/data/*.out` にマッチするファイルもアップロードする．

# Roadie-GCP イメージの拡張
実行するプログラムの準備や必要なライブラリのインストールは，`apt` や `data`, `run` を使えば行える．しかし，毎回同じ処理を行う場合，準備済みの Docker イメージを作成しておく方が早い．

例えば，次のような Dockerfile でイメージを作っておけば再利用しやすい．

```Dockerfile:Dockerfile
FROM jkawamoto/roadie-gcp

# NumPy, SciPy, Matplotlib のインストール
RUN apt-get update && \
    apt-get install -y python-numpy python-scipy python-matplotlib

# matplotlib の設定
RUN mkdir -p /root/.config/matplotlib && \
    echo "backend: Agg" >> /root/.config/matplotlib/matplotlibrc

# 実行ファイルをコピー
ADD some_dir /root/
```

# GCP インスタンスの作成と Roadie-GCP の実行
`gcloud` コマンドを使ってインスタンスを作成し実行する．Roadie-GCP イメージを拡張した `jkawamoto/run` というイメージが dockerhub にあるとする．

まず，script.yml を出力するスタートアップスクリプトを用意する．

```bash:startup.sh
#!/bin/bash
cd /root

cat <<EOF > script.yml
apt:
- unrar
source: https://github.com/abcdefg/some-program.git
data:
- http://mmnet.iis.sinica.edu.tw/dl/wowah/wowah.rar
run:
- unrar x -r wowah.rar
- ./analyze WoWAH
result: gs://somebucket/
upload:
- *.png
EOF

docker run -i jkawamoto/running < conf.yml
```

次に，gcloud コマンドでインスタンスを作成する．

```shell-session
$ gcloud compute --project "your_project_name" instances create "some_name" \
   　--zone us-central1-b --image coreos --metadata-from-file startup-script="./startup.sh" \
  　 --machine-type "n1-standard-1" \
  　 --scopes=https://www.googleapis.com/auth/compute,https://www.googleapis.com/auth/devstorage.read_write
```

結果を Cloud Storage にアップロードし，インスタンスを削除するために，

- https://www.googleapis.com/auth/compute
- https://www.googleapis.com/auth/devstorage.read_write

の権限が必要になる．

# おわりに
Roadie-GCP を使えば，比較的簡単にバッチ処理が行えると思う．特に処理が終わったらインスタンスを削除させるので，無駄にコストを払わなくて済む．

もっと自動化するためには，スタートアップスクリプトを自動生成したり，コンテナのログを [Cloud Logging](https://cloud.google.com/logging/docs/) へ投げることも考える必要がある．それらを代わりに行ってくれる [アプリケーション](https://github.com/jkawamoto/roadie) も作成してある．
このアプリについては別の記事にまとめようと思う．