この記事は創薬 Advent Calendar 2017 の15日目の記事です。
後でブログに移しますが、間に合わなかったのでgistに置いてます……
昨今、Reproducible Researchが重要視されてきており[要出典]、それに伴なって環境をまるごと他者に配布できるdockerなどに注目が集っています[要出典]。
それ以外にも、例えばハンズオンなどで予め環境を準備しておき講師と全く同じ環境を使用してもらう事でトラブルを最小限に抑えることが出来るなど、色々な場面で便利です。
dockerhubでは自分で作成したdockerのイメージをアップロードする事ができ、簡単に他者に配布する事ができます。
dockerhubにイメージを上げておくと、
$ docker run -it イメージ名 コマンド
の様に簡単にそのイメージの環境を使えるのでとても便利です。
もちろん、有名なケモインフォマティクスライブラリであるrdkitが予め準備された環境も複数アップロードされておりますが、dockerの小さいイメージを作成するにはちょっとしたコツと醜いハックが必要となるためか、現在アップロードされているrdkitを含むイメージはサイズがとても大きいです。
dockerhubにはWeb APIが用意されているのでサクっと調べる事ができます。
%matplotlib inline
import requests
from tqdm import tqdm
import pandas
import dateutil.parser
from datetime import datetime
def get_tags(repo, n=1000):
"あるイメージのタグ一覧を取得"
return requests.get(
"https://registry.hub.docker.com/v2/repositories/{}/tags/".format(repo),
params={"n": n}
).json()
def search_repo(q, n=100):
"dockerhubを検索"
return requests.get(
"https://registry.hub.docker.com/v1/search/",
params={"q": q, "n": n}
).json()
def search(q):
"dockerhubを検索して結果にtag一覧を含める"
result = search_repo(q)["results"]
for repo in tqdm(result):
repo["tags"] = get_tags(repo["name"])["results"]
return result
search_result = search("rdkit")
100%|██████████| 42/42 [00:33<00:00, 1.25it/s]
検索できたので、眺めるためにpandasに入れます。
def parse_date(d):
if d is None:
return None
return dateutil.parser.parse(d, ignoretz=True)
images = pandas.DataFrame(
[
(repo["name"], tag["name"], tag["full_size"], parse_date(tag["last_updated"]))
for repo in search_result
for tag in repo["tags"]
if repo["name"] != "philopon/py3-rdkit" # ネタバレ防止
],
columns=("name", "label", "full_size", "last_updated")
)
あまり古いものはアレなので、最近1年に絞ってサイズが小さい順にソートして見てみます。
now = datetime.now()
images[images.last_updated.map(lambda v: (now - v).days < 365)].sort_values('full_size').head()
name | label | full_size | last_updated | |
---|---|---|---|---|
51 | dsatoh/rdkit_pgsql_cartridge | latest | 244099872 | 2017-03-01 19:58:16.510782 |
0 | itservices/rdkit | latest | 285943503 | 2017-02-21 16:21:01.158248 |
33 | informaticsmatters/rdkit_debian_base | latest | 352521876 | 2017-09-21 13:45:47.317737 |
70 | spitzenidee/postgresql_rdkit | 9.6.5 | 389171712 | 2017-09-23 16:26:57.208558 |
69 | spitzenidee/postgresql_rdkit | latest | 389171712 | 2017-09-23 16:26:58.445822 |
はい。一番小さいイメージでも250MBくらい (これは圧縮済みのサイズです) であることがわかりました (これが目的に合致する様なイメージなのかは調べていないので分かりませんが……)。
dockerはあるイメージを基にして新しいイメージを作成できて便利なのですが、派生したイメージはベースイメージよりファイルサイズが大きくなってしまいます。ハンズオンなどでは回線が細い事がしばしば有るため、小さいほうが良いですね。
では、小さいイメージを作っていきましょう!
alpine-linuxはdockerでしばしば用いられるlinuxディストリビューションで、圧縮後のサイズで僅か2MBととても軽量なのが特徴です。
そこで、alpine linuxにRDKitを入れたいのですが、その場でビルドなどを行なうと容量増加に繋がる (コンパイラなどをビルド時のみに依存するパッケージを入れる必要がある、無駄なレイヤーが増えるなど) ので、まずalpine-linuxの野良パッケージを作成し、それをインストールする方針にします。
パッケージのビルドを行なうabuild
コマンドにビルドの方法を教えるためのAPKBUILD
ファイルを書きます。
# Contributor:
# Maintainer: Hirotomo Moriwaki <hirotomo.moriwaki@gmail.com>
pkgname=py3-rdkit
veryear=2017
vermonth=09
verpatch=2
eigenversion=3.3.4
pkgver=$veryear.$vermonth.$verpatch
pkgrel=0
pkgdesc="RDKit - A collection of cheminformatics and machine-learning software written in C++ and Python."
url="http://rdkit.org/"
arch="all"
license="New BSD License"
depends="py3-numpy boost-system boost-thread boost-serialization boost-python3 boost-regex"
makedepends="alpine-sdk py-numpy-dev cmake python3-dev sqlite sqlite-dev boost-dev py3-six py3-pillow"
provides="rdkit"
source="https://github.com/rdkit/rdkit/archive/Release_${veryear}_${vermonth}_$verpatch.tar.gz http://bitbucket.org/eigen/eigen/get/$eigenversion.tar.gz"
build() {
EIGENPATH=$(cd $srcdir/eigen-*; pwd)
builddir=$(cd $srcdir/rdkit-*; pwd)
cd $builddir
mkdir build
cd build
cmake ..\
-DPYTHON_LIBRARY=/usr/lib/python3.6/config-3.6m-x86_64-linux-gnu/libpython3.6m.a\
-DPYTHON_INCLUDE_DIR=/usr/include/python3.6m\
-DPYTHON_EXECUTABLE=/usr/bin/python3\
-DEIGEN3_INCLUDE_DIR=$EIGENPATH\
-DRDK_INSTALL_INTREE=OFF\
-DCMAKE_INSTALL_PREFIX=/usr
make
}
check() {
pip3 install pandas --user
RDBASE=$(cd $srcdir/rdkit-*; pwd)
cd "$RDBASE/build"
mkdir fakebin
ln -sf $(which python3) fakebin/python
make DESTDIR=fakeinstall install
PATH=`pwd`/fakebin:$PATH RDBASE=$RDBASE PYTHONPATH=`pwd`/fakeinstall/usr/lib/python3.6/site-packages LD_LIBRARY_PATH=`pwd`/fakeinstall/usr/lib ctest
}
package() {
cd $srcdir/rdkit-*/build
make DESTDIR=$pkgdir install
}
sha512sums="3d7e03eefeded09abcd398ff962de0051a40ff92722433605790c18bba616369e5fa57ad980094a61ae285790a090c4cc272e052c614839e0edef80a6c3c2b5f Release_2017_09_2.tar.gz
4077a5c3b95e3573774ccd3fe6c7233cb4b83db2358c19b43ea796925bd0201451d8632bddc5d68b1b57bbf67c5473a8908926eed065a745689a2acec9711d5c 3.3.4.tar.gz"
こんな感じです。おおよそ普通のシェルスクリプトなので特に説明が必要な部分は無いかと思われます。
更にサイズを小さくするために、コンパイルオプション-Os -s
などを付けてもみましたが、殆んど変わらなかったので外しました (ちなみに-march=nativeを付けるとテストが通らなくなりました)。
余談ですが、これを書くときの試行錯誤によって、-DRDK_INSTALL_INTREE=OFF
でテストを通すためには
python
で呼びだされるpythonが違う場合、適当なディレクトリを作ってsymlink貼ってPATHを通すなどして対応するpythonを呼ぶ様にするなどをしてやると良いという事が知見として得られました。
alpine-linuxを実機なり仮想マシンなりに入れている人は稀かと思われるので、パッケージのビルドにもdockerを使用します(ややこしい……)。
こんな感じです (署名も一緒にしてるので他の環境では動かない気がする、雰囲気で見ていただければ……):
FROM alpine
MAINTAINER HirotomoMoriwaki <hirotomo.moriwaki@gmail.com>
ENV username HirotomoMoriwaki
ENV useremail hirotomo.moriwaki@gmail.com
ENV PACKAGE_PRIVKEY=philopon-alpine-repo.rsa
RUN apk add --update alpine-sdk coreutils
RUN sed --in-place "s/export *JOBS=.*$/export JOBS=$(nproc)/" /etc/abuild.conf
RUN adduser -D $username && addgroup $username abuild && echo "$username ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
USER $username
RUN mkdir /home/$username/.abuild
ADD $PACKAGE_PRIVKEY /home/$username/.abuild
RUN sudo chown $username /home/$username/.abuild/$PACKAGER_PRIVKEY
RUN echo "PACKAGER_PRIVKEY=$PACKAGE_PRIVKEY" >> /home/$username/.abuild/abuild.conf
ADD _recipe/py3-rdkit /home/$username/py3-rdkit
RUN sudo chown $username /home/$username/py3-rdkit
WORKDIR /home/$username/py3-rdkit
RUN abuild -r
ADD update-repo.sh /home/$username
RUN sudo apk add rsync
CMD sudo /home/$username/update-repo.sh $username
ポイントはabuildはrootでは使えないので、一般ユーザー作ってそのユーザーでabuildを実行する位です。
で、無事パッケージを作成する事ができたらリポジトリを作らないとダメなのですが、サックリ使えるhttpサーバーが無かったので、github pagesを使ってみています (バイナリのリリースはgithub releaseを使った方が良いよってメールが来るけれど……)。
https://github.com/philopon/alpine-repo
これで、リポジトリのurlと公開鍵を登録すればrdkitをインストールできる所までいけました!
あとは、これをインストールするDockerfileを書けば完成です。先のリポジトリを追加してパッケージをインストールするだけなので簡単です。 Dockerhubにはgithubと連携して自動でイメージを作成してくれる機能があるのでそれを使ってあげれば良いですね。
完成品のDockerhubのページはこちらです。
CMD python3
にしてるので
$ docker run -it philopon/py3-rdkit
でpythonのreplが開きます。もちろん
$ docker run -it philopon/py3-rdkit sh
など任意のコマンドを実行できます。
さて、これでどの位のサイズになったか確認してみましょう。
images = pandas.DataFrame(
[
(repo["name"], tag["name"], tag["full_size"], parse_date(tag["last_updated"]))
for repo in search_result
for tag in repo["tags"]
],
columns=("name", "label", "full_size", "last_updated")
)
images[images.last_updated.map(lambda v: (now - v).days < 365)].sort_values('full_size').head()
name | label | full_size | last_updated | |
---|---|---|---|---|
63 | philopon/py3-rdkit | latest | 59646097 | 2017-12-05 15:45:04.634370 |
62 | philopon/py3-rdkit | 2017.09.2 | 59646255 | 2017-12-05 15:47:18.930446 |
64 | philopon/py3-rdkit | 2017.09.1 | 61058137 | 2017-12-05 15:37:26.473140 |
51 | dsatoh/rdkit_pgsql_cartridge | latest | 244099872 | 2017-03-01 19:58:16.510782 |
0 | itservices/rdkit | latest | 285943503 | 2017-02-21 16:21:01.158248 |
はい。1/4まで削減できました。よかったですね。