🐳

VSCode + Dockerでよりミニマルでポータブルな研究環境を

2023/07/28に公開
3

はじめに

もっとミニマルで簡単なポータブルな環境を!

自分自身の研究のための環境構築についてこれまで二本の記事を書いてきました.

これらの記事から二年ほどたち, いくつかの点において不満点が出てきました. 特に, GCPや自宅のサーバー上でリモートで作業することが多くなってきたので, よりミニマルでポータブルな環境が必要になりました.

以下では, 現時点で最小限の努力で環境を再現ができることを目標にしたDockerベースのGitHubレポジトリのテンプレートとその使い方を紹介します. このテンプレートを用いて作られた環境は, 新たなコンピュータ上で最短4ステップで環境を再現できるようになります.

  1. git clone
  2. VSCodeの"Open in Remote Containers"
  3. renv::restore()
  4. dvc pull

この環境とセットアップはこのレポジトリにテンプレートとしておいて置きます. 実際, 私自身も新しいプロジェクトを始める際にはこのテンプレートを用いて環境を作っています.

https://github.com/nicetak/dockerR/

概要

私自身が研究の際に使う言語はR, Julia, \LaTeX, と時々Pythonです. また, バージョン管理にはGitを, データ管理にはDVCを使っています. DVCによるデータ管理は以前の記事もご覧ください.

https://zenn.dev/kazuyanagimoto/articles/vscode-docker-rstudio

これらの条件とミニマルさポータブルさを満たすために私が出した結論は以下の通りです.

  1. VSCode内ですべて完結する. DevContainerを使う.
  2. Rは必須なので, rockerベースのDockerイメージ
  3. DVCはPythonのパッケージなのでPythonも必須.
  4. Juliaはオプション. プロジェクトごとに使ったり使わなかったり.
  5. \LaTeX はRのtinytexで必要かつ十分. (後述)
  6. R, Python, Julia, TinyTeXのパッケージはDocker Volumesによってホスト側にキャッシュする.
なぜRは必須か. なぜrockerベースか.

私はデータ分析を扱う分野においての論文執筆でRは必須と考えます. これは私の専門の経済学で様々な推定方法がRで提供されているからというだけではありません. 最大の理由はggplot2より美しいグラフとkableExtraほどの多機能な\LaTeXの表を作れるパッケージが他の言語では提供されていないからです. 私なりの図と表の作り方は以前の記事にまとめてあります.

https://zenn.dev/nicetak/articles/r-tips-graph-2022
https://zenn.dev/nicetak/articles/r-tips-table-2022

またなぜubuntuやイメージベースではないのかというと, LinuxへのRとRstudioのインストールがPythonやJuliaより面倒に感じるからです. Dockerfileを見ていただければお分かりかと思いますが, PythonとJuliaのインストールがかなり簡単なのに対し, RとRstudioのインストールはかなり面倒です. どうせRは必須であるので, rockerベースで作るのが最適と感じています. さらに, あくまでおまけではありますが, TinyTeXの扱いがR環境があるとやりやすいということは大きなメリットでした.

Docker 環境

https://github.com/kazuyanagimoto/dockerR/blob/main/Dockerfile

R

ホスト側にRのパッケージはキャッシュするので, rocker/rstudioで十分です. 地理情報を扱うプロジェクトの時だけ rocker/geospatial に変更しています.

パッケージ管理はrenv一択であると考えます. パッケージを追加するたびごとにいつもrenv::snapshot()で記録をrenv.lockファイルに残すことができ, 同様のパッケージを他のコンピュータで再現する際にも renv::restore() で一発です. renvを用いたパッケージ管理は以下の記事でも解説しています.

https://zenn.dev/nicetak/articles/r-tips-cleaning-2022

Python

僕はPythonはDVCの依存関係ぐらいにしか使わないことが多いので, 特にバージョンにこだわりがなく, aptでインストールできるものをインストールします. また, Pythonを分析で使わないといけない場合は (スクレイピングや自然言語処理などで) piprequirements.txt で管理します.

requirements.txt で管理する理由

2023年8月現在, Pythonの仮想環境は混迷を極めていると思います. ざっと見ただけでもvenv, anaconda, pyenv, poetry, ryeなどさまざまなツールがあって何が最適か, 長く使えるのか分かりません. そもそも, Dockerで環境を構築するのだからパッケージのバージョン (と依存性) が分かればいいので, 多機能なツールは使う必要がないと考えています. 正直, pip install -r requirements.txtpip freeze > requirements.txt で十分な気がしてなりません. ただ本格的にPythonを研究で使っているわけではないので, 誤解があればご指摘ください.

Julia

Dockerfileから明らかではあると思いますが, Juliaのバージョンを指定してインストールします. 個人的な経験ですが, Juliaはアップデートによって高速化こそされ, バグで動かなくなるという自体にはあまり遭遇していません. そのため, 基本的には最新のバージョンを指定して, プロジェクトの最中にアップデートがあれば数字を書き換えてビルドし直します.

パッケージ管理はProject.tomlを使いましょう. これはJuliaの標準的なパッケージ管理ファイルで, Juliaのパッケージを追加するたびに自動で更新されます. はじめにProject.tomlファイルをからファイルで作成したあとは, Pkg.activate()で環境をアクティベートして使ってください. またProject.tomlからパッケージをインストールする場合は, Pkg.instantiate()でインストールしてください.

その他

  • openssh-client コンテナの中から, GitHubにSSHで通信するのに必要です
  • DVCはPythonのパッケージなのでdvcコマンドをターミナルで使用できるようにパスを通しています
  • 最後にキャッシュしたパッケージの権限を変更しています. これは Docker Volumesをマウントした場合, root権限で作成されてしまい, ユーザ権限では書き込めなくなってしまうためです.
コンテナ内でGitHubにSSHする方法について

コンテナ内で作業するため, git pullgit push もコンテナ内で行いたくなります. またDVCは基本的にはコンテナの中にしか導入されていないため, コンテナ内で Git + DVC の作業を完結させたいです.
そのためにはホスト環境のSSHキーをコンテナに移す必要がありますが, これはssh-agent にキーを追加してあれば, Remote Containers の機能によって, 自動で鍵が使えます. 設定はホストOSごとに異なるので, 詳しくは公式ドキュメントの Developping inside a container の "Sharing Git credentials with your container" の節を読むことをおすすめします.

私はホストOSがWindowsのWSLであるので, ~/.bash_profile に以下を記述してます.

~/.bash_profile
# SSH-Agent
eval "$(ssh-agent -s)"
if [ -z "$SSH_AUTH_SOCK" ]; then
   # Check for a currently running instance of the agent
   RUNNING_AGENT="`ps -ax | grep 'ssh-agent -s' | grep -v grep | wc -l | tr -d '[:space:]'`"
   if [ "$RUNNING_AGENT" = "0" ]; then
        # Launch a new instance of the agent
        ssh-agent -s &> $HOME/.ssh/ssh-agent
   fi
   eval `cat $HOME/.ssh/ssh-agent`
fi
ssh-add $HOME/.ssh/id_ed25519

ちなみに, 最終行の ssh-add が公式ドキュメントと異なり, 記述する必要がありました.

docker-compose.yml

https://github.com/nicetak/dockerR/blob/main/docker-compose.yml

ポイントはcacheというDocker Volumes$HOME/.cache/にマウントしているところです. renvはデフォルトで$HOME/.cache/R/renvにキャッシュしますし, PythonやJuliaのライブラリのキャッシュもPYTHONUSERBASEJULIA_DEPOT_PATHという環境変数を指定することで$HOME/.cache/以下に保存しています. これによって一度インストールしたパッケージはホスト側にも保存されることになるため, DockerをビルドをするたびにRのパッケージをインストールし直す必要がなくなります. また, 複数のプロジェクトでDocker環境を使い分けている際に, 重複するパッケージをインストールしなくてよくなります. なお, TinyTeXのパッケージやフォントも同様にキャッシュしています.

Docker Volumesとは

Docker VolumesはDockerコンテナのデータをホスト側に保存するための仕組みです. これによって, コンテナを削除してもデータを保持することができます. なお, 初めてこのテンプレートを使う際はDocker Volumesが存在しないので, 以下のコマンドで作成する必要があります.

docker volume create cache
docker volume create TinyTeX
docker volume create fonts

なお, このコマンド叩かずにDockerをビルドすると, Docker Volumesが自動で作成されます. この場合はDocker Volumesの名前がPROJECTNAME_cacheのようになるので, 他のプロジェクトですでにインストールしたパッケージを使い回すことができません.

また, 以前の私の記事のようにインターネット上では, Docker Volumesを使わずに ~/.cache/R/renv のようにホストOSの領域に直接マウントしている例がありますが, これはソースコードの場合は良いですが, パッケージの場合はおすすめできません. MacOSやWindows (WSL2を除く) の場合ファイルシステムがLinux (Docker) と異なるため, パッケージを読んで実行する際に著しくスピードが落ちる可能性があります. Docker内で使うパッケージはDockerの領域でキャッシュしましょう.

\LaTeX 環境

Docker内で\LaTeXを使うには, apttexliveをインストールしたり, docker-compose.ymlの別サービスとして使うという事もできますが, Rにはtinytexという軽くてとても便利なパッケージがあるのでこれを利用します.[1] なお, VSCodeで\LaTeXを扱う際は, 以下の記事も参考にしていただければ幸いです.

https://zenn.dev/nicetak/articles/zotero-tex-bibtex

TinyTeXとは

TinyTeXは超軽量の\LaTeXのディストリビューションです. コンパイルの際に自動で足りないパッケージをインストールしてコンパイルしてくれるため, 事前に大量のパッケージをインストールして\LaTeX環境を構築する必要がありません. そのため, RmarkdownやQuartoでPDFをコンパイルする際にはデフォルトで使用されています. この軽量であるという特徴はDocker環境とかなり相性がよく, rocker/verseイメージでも採用されています. 今回は, このTinyTeXをVSCodeでの\LaTeXコンパイラーにしてしまおうという話です. また, この時インストールされる\LaTeXのパッケージもDocker Volumesにキャッシュされます.

TinyTeXのインストールとVSCodeでの設定

TinyTeXをインストールする際は, tinytex::install_tinytex() というRコマンドがあります. ただ, TinyTeXを用いてインストールされるパッケージはDocker Volumesにキャッシュしたいため, 以下のようにインストールフォルダを指定します.

tinytex::install_tinytex(dir = "/home/rstudio/.TinyTeX", force = TRUE)

なお, 一度Docker Volumesにインストールしてしまえば, 今後はこのコマンドを他のプロジェクトであっても実行する必要はありません.

VSCodeでTinyTeXを\LaTeXのコンパイラにするために, settings.jsonを以下のように編集します. なお, WORKSPACE_DIR/.vscode/settings.jsonに設定した場合は, このワークスペースのみで有効な設定になります. チームプロジェクトではsettings.jsonをgit-ignoreすることも多いと考えるので, テンプレートでは_settings.jsonと名前を変えてあります.

https://github.com/kazuyanagimoto/dockerR/blob/main/.vscode/_settings.json

なぜOverleafを使わないのか

Overleafはもはや\LaTeXのエディターの第一候補といっても差し支えないでしょう. 二年前の話ですが, 修士の同級生がOverleafと\LaTeXの概念の区別がついていないことにびっくりしました. しかし, 私はOverleafにいくつもの不満点があります.

  • 無料版ではOverleafはGitHubが連携できない.
  • GitHubのブランチも分けられない
  • スライドや論文の図表の見え方の修正をしたいのにいちいち, アップロードしないといけない
  • ファイル数が1プロジェクトあたり最大で2000ファイルと制限されている.
  • たまにサービスが落ちる. 締め切り前であった場合これは致命的
  • GitHub Copilotが使えないエディタは時間の無駄 (後述)

正直, TinyTeXとVSCodeのLaTeX Workshop拡張があれば環境の設定も難しくなく, コンパイルもローカルのコンピュータにもよりますが早いので, 私にとってはOverleafを使う意味はあまりありません.

VSCode Extensionについて

VSCodeのRemote Containersのための設定は以下のとおりです. ほとんど直感的ですが, Extensionに関してはいくつか説明を加えたいと思います.

https://github.com/kazuyanagimoto/dockerR/blob/main/.devcontainer/devcontainer.json

Gramarly Extension

言わずとしれた英文校正サービスのGrammarlyのVSCode Extensionです. これを入れるだけで, 無料でスペルミスから三単現のsのつけ忘れ, 冠詞の修正などをこなしてくれます. 有料版の機能も使うことが可能です. また, .tex, .Rmd, .qmdファイルにおいても使うことが可能です. 詳しくは拡張機能自体のヘルプを参照にしてほしいですが, 以下のようにconfigファイルに拡張子追加するだけです.

{
  "grammarly.files.include": ["**/README.md", "**/readme.md", "**/*.txt", "**/*.tex", "**/*.Rmd", "**/*.qmd"]
}

Edit CSV

CSVやTSVファイルなどをサクッと確認や編集したいときに大変便利な拡張機能です. これがないと, Excelのような表計算ソフトを使ったりしないとプレビューもろくにできないので, かなり重宝します. とくにリモートで作業する際には, ローカルでExcelなどでファイルを開くのはかなり面倒なので, この拡張機能は必須です.

GitHub Copilot

2023年8月現在, GitHub Copilotを使わずにコーディングするのは時間の無駄です. ゲームで言うなら舐めプです. この拡張機能は移り変わりが激しいので, 最新のドキュメントを参考に導入してみてください.

ワークフロー

実際にこのテンプレートを用いてプロジェクトを始める際の手順と作業中のワークフローを紹介します. 管理者はこのテンプレートを使ってプロジェクトを作成し, 共同作業者はそのプロジェクトをクローンして作業をすることを想定しています. 作業中は管理者も共同作業者も同じ手順で作業を進めます.

管理者

  1. DockerのVolumeを作る. (初めてこのテンプレートを使う際のみ)
docker volume create cache
docker volume create TinyTeX
docker volume create fonts
  1. GitHubでこのテンプレートから新しいレポジトリを作り, ローカルにクローンする
  2. VSCodeでこのレポジトリを開く. (Remote Containers)
  3. Rのプロジェクトを作成する. Rstudioを用いる場合, localhost:8787にアクセスし, プロジェクトを作成する
  4. renv::init() でパッケージ管理を開始する
  5. pip install dvc dvc-gdrive でDVCをインストールする. 二回目以降はpipのキャッシュがあるためこのコマンドは不要
  6. DVC環境を設定する
    • Google Drive上にフォルダを作成し, そのフォルダのIDをコピーする
    • dvc init && dvc remote add -d myremote gdrive://<Google DriveのフォルダID> を実行する
    • Google Driveのフォルダは共同作業者と共有の設定をする
  7. LaTeX用のVSCodeの設定をする
    • 初めての場合, tinytex::install_tinytex(dir = "/home/rstudio/.TinyTeX", force = TRUE) を実行して, TinyTeXをインストールする
    • .vscode/_settings.json.vscode/settings.json にコピーする
  8. Juliaの環境を設定する. Project.tomlの空ファイルを作成し, Pkg.activate()でアクティベートする

共同作業者

  1. DockerのVolumeを作る. コマンドは管理者と同様. (初めてこのテンプレートを使う際のみ)
  2. GitHubで管理者が作ったレポジトリをクローンする
  3. VSCodeでこのレポジトリを開く. (Remote Containers)
  4. Rのプロジェクトを開く
  5. renv::restore() でパッケージをインストールする.
  6. Pythonパッケージ (DVCを含む) を pip install -r requirements.txt でインストールする
  7. dvc pull でデータをダウンロードする
  8. LaTeX用のVSCodeの設定をする
    • 初めての場合, tinytex::install_tinytex(dir = "/home/rstudio/.TinyTeX", force = TRUE) を実行して, TinyTeXをインストールする
    • .vscode/_settings.json.vscode/settings.json にコピーする
  9. Juliaのパッケージをインストールする. Pkg.activate(); Pkg.instantiate()でインストールする

作業中

  1. Rのパッケージを追加する際は, install.packages("PACKAGENAME") でインストールし, renv::snapshot()で記録をrenv.lockファイルに残す
  2. Juliaのパッケージを追加する際は, Pkg.add("PACKAGENAME")で追加する. Project.tomlに自動で記録される
  3. Pythonのパッケージを追加する際は, pip install PACKAGE_NAMEで追加し, pip freeze > requirements.txt で記録する
  4. DVCでデータを追加する際は, dvc addで追加する. 基本的にはdvc add data/でディレクトリごと追加する
  5. 以上の作業が終わった上で, git add, git commit, git push する
  6. 作業が終わったら, dvc push でデータをアップロードする

まとめ

以上が私が考えるミニマルでポータブルな研究環境のテンプレートとその使い方でした. VSCodeとDockerで完結しているため, 実際にはかなり少ない手順でまったく同一の環境を他のコンピュータ上で再現することができます. また, すべてのパッケージをキャッシュするため, Dockerのビルド時間もかなり少なくなっておりメンテナンスのコストも結果下がりました. 私にとってのベストな環境が皆さんにとってもベストな環境であるとは限りませんが, この記事が皆さんの研究の一助になれば幸いです.

脚注
  1. TinyTeX自体はRがない環境でもインストールして使うことができます. 詳しくは公式ドキュメントを参考にしてください. ↩︎

GitHubで編集を提案

Discussion

KobaryuKobaryu

この記事を参考にし、DockerからRを使っています。非常に勉強になる記事をありがとうございます。

共同研究において、DVCの運用をどうされているかについて質問です。

DVCでデータを追加する際は, dvc addで追加する. 基本的にはdvc add data/でディレクトリごと追加する

と記載がありますが、2人で別々にデータ作成の作業をしている場合にはどうされていますか?

例として、下記のような作業をしているとします。

  • Aさんは a.csv を作成して dvc add data/ で登録 → git add〜pushも実施
  • Bさんは b.csv を作成して dvc add data/ で登録 → git add〜pushも実施

双方が同時進行で進めると、dvc.data が conflict を起こす可能性が考えられます。
あるいは、Aさんが先、Bさんが後の場合、Bさんが dvc pull をすると b.csv が消えてしまいます。

この問題について、柳本さんがどう対処されているか伺いたく、コメントをしました。

Kazuharu YanagimotoKazuharu Yanagimoto

私自身はあまりそういったケースにあった経験があまりないので詳しくありませんが, 基本的にはb.csvを一次避難させておいて, dvc pulldvc addするしかないように思います. 一応公式のサイトによるとgit merge driverもあるようなので, こちらを試すとよいかもしれません.
https://dvc.org/doc/user-guide/how-to/resolve-merge-conflicts

もしコンフリクトが頻繁に起きて煩雑なようでしたら,

  1. data_a/data_b/にフォルダを分けてそれぞれdata_a.dvc, data_b.dvcで管理する
  2. データフォルダのgit/dvc管理を諦める (.gitignore & Dropboxなどの従来の方法で)
    のように解決するしかないのではないでしょうか.

以前は dvc add data -R でフォルダ内のファイル一つ一つに.dvcファイルが作成できたのであまり問題はなかったのですが, DVCはその機能をやめてしまったようです...

KobaryuKobaryu

詳細にありがとうございます。やはりその選択肢しかないですよね…。
ご教示いただいたリンクも見てみます!