Recipeファイルを使ったSingularityコンテナのビルド

概要
======
Singularityコンテナのビルド方法は以下のようなものがあります。

1. Singularity HubからPullする
2. Docker HubからPullする
3. Sandbox環境を使いビルドする
4. 書き込み可能なイメージを使いビルドする
5. 他のイメージフォーマットから変換する(例:Squashfs → writable ext3)
6. Recipeファイルを使いビルドする

コンテナの再現性を確保すること、本番環境用のコンテナを作成することを目的とする場合には、Recipeファイルを使ったコンテナ作成方法が適しています。Recipeファイルを使う場合には、以下のようなメリットもあります。

+ ファイルや環境変数を追加しやすい
+ カスタムソフトウェアをインストールしやすい

本稿では、Recipeファイルのフォーマットを俯瞰するとともに、実際にRecipeファイルからイメージを生成する手順を説明します。詳細については[公式ページ](http://singularity.lbl.gov/quickstart)を参照ください。

予備知識
========
Recipeファイルは大別すると2つの要素から構成されます。

1. Header：ベースとなるOSを指定する部分
2. Sections (Body)：環境変数の設定やソフトウェアインストールについて記述する部分

Header
------
まず`Bootstrap:`でベースとするイメージの取得先を指定します。この記述に依り、以降に続くキーワードは変化します。`Bootstrap:`に指定できる参照先は以下のようなものがあります。

+ [shub](http://singularity.lbl.gov/build-shub)
+ [docker](http://singularity.lbl.gov/build-docker-module)
+ [localimage](http://singularity.lbl.gov/build-localimage)
+ [yum](http://singularity.lbl.gov/build-yum)
+ [debootstrap](http://singularity.lbl.gov/build-debootstrap)
+ [arch](http://singularity.lbl.gov/build-arch)
+ [busybox](http://singularity.lbl.gov/build-busybox)
+ [zypper](http://singularity.lbl.gov/build-zypper)

Singularity Hubを指定する場合は以下のようになります。`Bootstrap: shub`に`From:`が続きます。

```bash
Bootstrap: shub
From: shub://<registry>/<username>/<container-name>:<tag>@<digest>
```

`registry`、`tag`、`digest`はオプションです。`From:`の冒頭、`shub://`も省略可能のようです。
例えば、白鳥氏のOpenFOAMのイメージを取得してビルドする場合には以下のようになります。

```bash
Bootstrap: shub
From: TakahisaShiratori/openfoam:4
```

Docker Hubを指定する場合は以下のようになります。上記と同様に、`From:`が続きます。

```bash
Bootstrap: docker
From: <registry>/<namespace>/<container>:<tag>@<digest>
```

例えば、Ubuntu 16.04のイメージを取得する場合は以下のようになります。

```bash
Bootstrap: docker
From: ubuntu:16.04
```

Sections
------
Sectionsはsection(scriptlet)から構成され、`%`から始まる文字列で区切られた領域に内容が記述されます。全てオプションとして記述されるものになり、必ず記述が必要なわけではありません。
Sectionの指定には以下のようなコマンドを利用できます。

|Notation    |説明        |
|:----------:|:---------|
|%help   |Singularityコマンドにhelpオプションを指定した際に表示されるコメントを記述する。|
|%setup|コンテナにbase OSがインストールされた後にホスト側で実行する処理を記述する。|
|%files|ホストからコンテナへのファイルコピーの処理を記述する。|
|%labels|コンテナに格納するメタデータを記述する。|
|%enviroment|コンテナが実行される際に読み込まれる環境変数を記述する。(ビルド時は無効)|
|%post|ビルド時にコンテナにbase OSがインストールされた後にコンテナ側で実行する処理を記述する。(ホストからコンテナへのファイルコピーは不可)|
|%runscript|コンテナが実行された際に呼び出される処理を記述する。|
|%test|ビルドの最後の段階で行う処理、または、コンテナが実行された際に検証のために行う処理を記述する。|

以下にいくつかの注意点を列記します。

+ `%files`の処理は`%post`およびインストールの処理の後に行われるため、`%post`処理中に必要となるファイルは`%setup`に置いてコピー処理を行います。
+ `%setup`においてコンテナ内にファイルをコピーするには`$SINGULARITY_ROOTFS`を使います。この指定がない場合はホスト側での処理になってしまいます。
+ `%files`の処理は`<source> <destination>`のペアで記述されます。`<destination>`を省略した場合は、`/`直下にコピーされます。
+ ビルド時に有効にさせたい環境変数がある場合には、`%environment`ではなく`%post`に設定を記述します。
+ `%labels`に記述されたメタデータは`/.singularity.d/labels.json`に格納されます。ここの記述は、`singularity inspect`で確認できます。
+ Sectionが処理される順序は、[Build]`%setup`→`%post`→`%files`→`%test`、[Run]`%environment`→`%runscript`となると考えられます。
+ Sectionのいずれかの処理に失敗するとビルドは停止します。

Recipeファイルのフォーマット詳細については[公式ページ](http://singularity.lbl.gov/docs-recipes)を参照ください。

準備
========
Singularityのイメージファイルのデフォルトのファイルフォーマットはsquashfsです。simgイメージが取り扱えるようにパッケージをインストールします。

```bash
sudo yum install -y squashfs-tools
```

なお、Recipeファイルからのビルドにはroot権限が必要になります。

操作
========
分子動力学計算ソフトウェアLAMMPSのコンテナイメージをRecipeファイルから作成する手順を説明します。
この操作は以下の手順で行います。

1. Recipeファイルを作成する。
2. Recipeファイルを指定して`build`コマンドを実行し、コンテナイメージを作成する。

Recipeファイルの作成
--------
Recipeファイルの中では以下のような順序で操作を記述します。

1. HeaderにDocker Hubからbase OS(CentOS)を取得する記述を行います。
2. `%post`に必要パッケージのインストール、LAMMPSのビルド・インストール処理を記述します。
3. `%environment`に共有ライブラリの環境変数設定を記述します。
4. `%runscript`にコンテナ実行時の処理を記述します。

以下内容は[古家氏資料](https://www.slideshare.net/ShinnosukeFuruya/singularity-on-rescale)を参考にし、version 2.4における操作の説明に修正したものになります。

新規にRecipeファイルを作成します。

```bash
vi  Singularity
```

Header情報を記述します。

```bash
Bootstrap: docker
From: centos:latest
```

次にインストール操作を`%post`以下に記述します。

```bash
%post
    yum -y update
    yum -y install gcc gcc-gfortran gcc-c++ make wget git

    mkdir /tmp/build
    cd /tmp/build
    wget https://www.open-mpi.org/software/ompi/v1.6/downloads/openmpi-1.6.4.tar.gz
    tar xvfz openmpi-1.6.4.tar.gz
    cd openmpi-1.6.4
    ./configure --prefix=/usr/local
    make
    make install

    cd /tmp/build
    git clone -b 'patch_22Sep2017' https://github.com/lammps/lammps.git
    cd lammps/src/
    make g++_openmpi
    cp lmp_g++_openmpi /usr/local/bin/

    cd /
    mkdir /enc
    rm -rf /tmp/build
    yum clean all
```

次に、共有ライブラリの環境変数を`%enviroment`に記述します。

```bash
%environment
    export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
```

続いて、`run`実行時の処理を記述します。

```bash
%runscript
    /usr/local/bin/lmp_g++_openmpi
```

Recipeファイルを使ったイメージのビルド
-------
上述のRecipeファイルを使い、`lammps.simg`を作成します。

```bash
sudo singularity build lammps.simg Singularity
```

なお、version 2.4以前においては、`create`コマンドを使い空のイメージを作成してからOSやアプリケーションのインストールを行う手法が取られていましたが、version 2.4以降は`build`コマンドを使い、イメージ作成とインストールを同時実行することが推奨されています。version 2.4では空のイメージを作成する場合は、`image.create`コマンドで代替することになりました。

補足
========
Proxy環境下において、Recipeファイルからのイメージ生成に失敗することがあります。`http_proxy`、`https_proxy`といった環境変数の設定を行っているにも関わらずエラーが発生する場合があります。
`/usr/local/libexec/singularity/python/base.py`に以下のコマンドを追記することでこのエラーを回避できます。詳細は[リンク](https://github.com/singularityware/singularity/issues/764)を参照ください。

```py
os.environ['http_proxy'] = "http://host:port"
os.environ['https_proxy'] = "http://host:port"
```

また、Recipeファイルからのビルドの際にもProxy設定が必要になります。`%post`の最初に以下のような記述の追記します。(ディストリビューションにより記述方法は異なります。)

```bash
export http_proxy="http://host:port"
export https_proxy="http://host:port"
```