くろたんく雑記帳

日常とか、わんちゃんとか、機械学習とか、競プロとか、

MENU

【M1】devcontainerを使った、研究コード開発環境の構築

はじめに

これまで、Pythonをベースとした研究コードを作成する際に、pyenvディレクトリごとにPythonのバージョンや必要なmoduleをinstallして作っていましたが、 共有計算機や別環境で計算を行うことも多く、pyenvがupdateされていて、いい感じに必要なライブラリなどが用意できてないとpyenv install x.x.xといった単順なコマンドがコケることもあり、この際再現性などの観点からもDockerで研究コードを開発するようにしようと思ったのがきっかけです。 (ちなみに、M1のMackBook Proのまっさらな状況で、Homebrewでbrew install pyenvでインストールしてPATH等設定したら挙動することは確認できてますが今回はDockerで環境を作ることに集中します。)

前提

  1. MacのM1チップ用のDocker Desktopを利用
  2. 他の多くの計算環境でも使えるようにデフォルトのARM64イメージではなく、x86-64(AMD64)のイメージで行う
  3. Visual Studio Code](https://code.visualstudio.com/)のdevcontainerを使う(完成物と開発過程のDockerイメージを基本同一で開発したい)

特に最後のdevcontainerの話は、開発物にはformatterやnotebookのモジュールはいらないけれど、開発時にはそのようなモジュールがあると嬉しいみたいなことがあったり、 最近Visual Studio CodeのupdateでRemote - SSHでもdevcontainerを使えるようになったので、ある程度開発が進んでマシーンスペックを上げて共有計算機やクラウドなどで並列計算させたりしつつリモート環境下での開発などの需要のときに、わざわざリモート側で開発物用の環境を作る必要がないということが大きいです。(これまでは、リモートでpyenvやvenvなどを使ってDockerfileのバージョンに合わせ、requirements.txtを使ってほぼ同じ環境っぽい状態にするといった工程が挟まっていたが、それがなくなった。)

code.visualstudio.com

【注】他の共有計算機環境があるならそこにRemote −SSH + devcontainerで開発すればいいのでは?って言うこともありますが、本題は新しいMacのM1チップでどこまでできるのかということを意図しています。共有計算機ないけど、M1 MacBook ProがあってDockerでゴニョゴニョしたいという状況が1番優先される前提です。今後利便性も含め、x86-64環境でビルドしたDocker imageをつかってM1のDockerでコンテナ作成、実行などができるかの検証も今後する予定です。

M1という障壁

その他の記事でも言われていますが、M1でなければそんな面倒でもないですが、ローカルの環境がM1のMacBook Proだとチップの違いによりARM64でビルドされていないPythonモジュールはpip installできません。Dockerでもそれは同じで、Dockerで作られるimageがARM64だと同様の現象が起きます。 有名どころのモジュールは対応してくれていますがそんなたくさんないのでM1 用のDoker Desktopはマルチプラットフォームでimageが作成可能なため、AMD64でimageを作ることで、前述の問題を回避します。さらにそのビルドしたimageはx86-64の環境でそのまま使えるということにも繋がります。

対応策

最終的な形はこちらのGitHub レポジトリを見てもらえば良いと思いますが、要所をまとめます。

Dockerfile

  • 適当にお好みのPython公式のimageを引っ張ってくる。マイナーバージョンまで指定すればビルドタイミングによるバージョン変更など起きない。
  • requirements.txtに研究コードに必要なモジュールをバージョンを指定しておく。
  • WORKDIRはdocker-compose.ymlのservicesに合わせる 。アプリケーション開発だとappとすることが多いので一旦appとしているが適宜変えて良い。
FROM python:3.7.9

COPY ./requirements.txt /requirements.txt
RUN pip install -r /requirements.txt

WORKDIR /app

まぁシンプルな感じ

docker-compose.yml

Dockerコマンドが開発ディレクトリをマウントしたり、imageのidを参照するなどすると面倒だし、コマンドが長くなるので、docker-composeを使う。そのため、その設定ファイルであるdocker-compose.ymlが必要

  • 最大のポイントはここで platform: linux/amd64としてプラットフォームを指定し、x86-64(AMD64)のimageが作られるようにする。
  • あとは開発ディレクトリをマウントしておく(.とすることで、docker-compose.ymlがある場所をマウントするということになる。)
version: "3.8"
services:
  app:
    build: .
    platform: linux/amd64
    volumes:
      - ./:/app

とりあえずテスト

まずはDockerfileをビルドする。

docker-compose build

とりあえずビルドが終わったら、所定のバージョンがインストールされているか、ご所望のモジュールがインストールされているかなど確認してみる

docker-compose run app python -V
# Python 3.7.9 (Dockerfileで指定したversion)
 docker-compose run app pip list

# Package         Version
# --------------- --------
# click           7.1.2
# numpy           1.21.5
# pandas          1.3.5
# Pillow          8.4.0
# pip             21.0.1
# python-dateutil 2.8.2
# pytz            2021.3
# QEPPI           0.1.11
# rdkit-pypi      2021.9.3
# setuptools      53.0.0
# six             1.16.0
# wheel           0.36.2

コンテナの中に遊んでもいい。

docker-compose exec app bash

とすれば、コンテナの中に入れるのであとは適宜必要に応じて確認などする。

pythonモジュールの作成

なんか適当に開発モジュールを適当に作る。要するにxxxx.py。
今回テスト的に、py4chemoinformaticsを参考にRDKitのMolToGridImageでsildenafilとvardenafilの構造式をプロットしてくれるコードを書きました。(MolToGridImage.py)

github.com

あと、今回の本題とは、ずれますがargparserとしてclickを採用しました。シンプルで結構便利です。

click.palletsprojects.com

モジュールを動かしてみる

docker-compose run app python -m rdkit_MolToGridImage --output ./data --name test --align True

# 上記はargをあえて書いているが、defaultを指定しておけば、
docker-compose run app python -m rdkit_MolToGridImage
でも実行可能

f:id:black_tank_top:20211224140651p:plain
出力結果

単にDockerでPython環境を固定して動かすだけであれば、これで十分だが、Visual Studio Codeのdevcontainerを使って開発したいので、こちらを準備する。

devcontainer

Visual Studio Codeを開いて、左下の><となっているところをクリックして、Add Development Container Configuration Filesっていうのをクリックして、From docker-composeというのを選ぶと、.devcontainerディレクトリができて、devcontainer.jsonとdocker-compose.ymlが生成される。

devcontainer.json

基本自動生成されたままでよいが、使用するPythonは、devcontainerのpythonを指定。/usr/local/bin/pythonで問題ないはず。 今回は、devcontainer上だけで使いたいフォーマッターなどのモジュールをinstallしたり、Visual Studio CodeのExtensionを毎回設定するのは面倒なので、devcontainerを使うときに最初から設定されるようにする。

"settings", "extensions", "postCreateCommand"らへんがそんなところ。

docker-compose.yml

自動生成されたままでOK

詳細はGitHubを参照してもらえばと思います。

devcontainerの起動

上記がおわったら、早速起動してみましょう。左下の><となっているところをクリックして、Open Fold in Containerをクリックすれば、devcontainerがビルドされます。コンテナ作成と追加のモジュールのインストール程度の時間なので、1-2分くらいです。そうすると以下のようになります。

f:id:black_tank_top:20211224142757p:plain
devcontainerが起動したVisual Studio Codeの画面

あとは、よしなにこちらで開発をすすめていけばOKです。

Jupyter Notebookについて

Visual Studio Code.ipynbファイルを読みこんでノートブックとして動かすことが可能で固定された環境でとても便利ですが、上記の経緯で進めた結果、Notebookの実行でM1固有の問題に直面し、Cellを実行すると、

Failed to start the Kernel. 
qemu: uncaught target signal 6 (Aborted) - core dumped. 
View Jupyter log for further details.

となってしまって、実行できませんでした。いろいろ調べましたが現状Jupyter NotebookをVisual Studio Codeで動かすことは、まだ難しそうです。

おわりに

今回は、

  1. MacのM1チップ用のDocker Desktopを利用して、M1のMacでも特定のPython環境にお好みのモジュールをinstallし研究コードを開発する環境構築方法を示しました。
  2. また、docker-composeを使うことで長くなりがちのdockerコマンドを簡略化しました。
  3. docker-composeコマンドを使って、実際のpythonモジュールを動かす方法示しました。
  4. Visual Studio Codeのdevcontainerを使って、完成品の環境と同一のimageを使った状態で開発をすすめる方法を示しました。

残念ながら、Jupyter NotebookをVisual Studio Codeで動かすことができなかったです。今後のupdateを期待したいところです。