PHP8.0 でも Jupyter の Notebook を使いたい。macOS や RaspberryPi や Windows で。
でも、いろいろインストールしてローカルを汚したくない。
そんな遊び心満載なユーザー向けなものが Dockerどこか にないものか。
TL; DR
Jupyter-PHP の PHP8 用修正パッチと macOS/Windows10/Raspbian で動く JupyterLab の Dockerfile を作ってみました。
作り方や経緯、「Jupyter?何それ。typoってない?」と言う方は TS; DR をご覧下さい。
PHP8 カーネル入り Dockerfile の URL
-
- JupyterLab/Jupyter Notebook の PHP8 カーネル入り Dockerfile(JIT 有効済み)
https://github.com/KEINOS/Jupyter-PHP8 @ GitHub
Docker 使いたくない人向け
Jupyter-PHP-installer の修正パッチ
diff.jupyter-php.patch @ GitHub
パッチを当てる例 @ Dockerfile
使い方
$ # Dockerfile 一式のクローン(ダウンロードでも可)
$ git clone https://github.com/KEINOS/Jupyter-PHP8.git
...
$ # Docker イメージのビルド
$ cd Jupyter-PHP8
$ docker build -t jupyter:local .
...(いささか時間がかかります)...
$ # Docker イメージからコンテナ(Jupyter サーバ)の起動。
$ # ローカルのポート 8001 をコンテナの 8000 ポート(Jupyter サーバのポート)につなげて起動する
$ docker run --rm -p 8001:8000 -v $(pwd)/data:/workspace jupyter:local
...(途中でアクセストークンが表示されるのでコピーしておく)
[I 12:43:22.853 LabApp] Jupyter Notebook 6.1.5 is running at:
[I 12:43:22.854 LabApp] http://44a5fb18069a:8000/?token=375a1f68224543bde87ffd8d5ce97b89ee707e3591ddcf90
[I 12:43:22.854 LabApp] or http://127.0.0.1:8000/?token=375a1f68224543bde87ffd8d5ce97b89ee707e3591ddcf90
[I 12:43:22.855 LabApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
...(アクセス・ログが表示されていく。サーバを終了するには Ctrl+C)
-
- 上記で Jupyter サーバのコンテナが起動したら、ブラウザからアクセスします。
ポートは 8001 です。8001 で都合が悪い場合は、上記の docker run で 8001 を他のポート番号にしてください。
ローカル(同一マシン)からアクセスする場合:
http://localhost:8001/?token=<アクセストークン>
同じネットワークの別のマシンからアクセスする場合
http://<ホストのIPアドレス>:8001/?token=<アクセストークン>
Intel/AMD および ARM v7l の CPU で動作確認しています。
macOS Catalina (OSX 10.15.7, MacBook Pro Early 2015, Docker version 19.03.13)
Raspbian Stretch (Debian 9, Raspberry Pi3+, Docker version 19.03.13)
Windows 10 Pro 64bit (20H2 ビルド 19042.630, Intel Pentium Silver N5000, Docker v20.10.0-rc1)
Docker のベースイメージ: keinos/php8-jit:latest Alpine ベース
PHP のバージョン: PHP8.0.0(2020/11/28 時点の master)+ JIT 有効
デフォルトで JupyterLab が起動しますが、以下のサービスもインストールされています。
jupyter core
jupyter-notebook
qtconsole
ipython
ipykernel
jupyter client
jupyter lab
nbconvert
pywidgets
nbformat
traitlets
TS; DR
Jupyter や Jupyter Notebook をご存知でしょうか。私は知りませんでした。
いや、名前や存在自体は知っていたのです。ペチペチ PHPer の私は「Python かぁ… Python かぁ…」とか言いながら、完全に理解すらしていない機械学習の記事や本を読んでいると、Jupyter やらノートブックと言う単語にチョイチョイ出くわしていたのです。それが何であるかの説明もなく、当然のごとく話しが進むのです。
しかし、どうもテキスト・エディタや IDE のように見えます。なんてっちゃって IDLE みたいなものかな?、と。Win の頃は秀丸エディタ、サクラ・エディタ、Mac に移ってからは Coda2、Atom、XCode を経て、やっと VSCode に落ち着いた自分としては新たなエディタとして使うには汎用性に欠けると思い込んでおり、避けていました。
すると、先日(2020/11/18)とあるニュースが目に飛び込んできました。
『Microsoft、「Visual Studio Code」の新しい拡張機能「Jupyter」を発表』
「Python」言語拡張と切り離して、それ以外のプログラミング言語でも利用可能に(『Microsoft、「Visual Studio Code」の新しい拡張機能「Jupyter」を発表』@ インプレス Watch より)
「ん? VSCode で Jupyter?どう言うこと?」となりました。「VSCode で秀丸エディタが使える!」と言うような意味不明なニュースに見えたからです。
そこで、偏見や食わず嫌いなのかもしれない、と思い。改めて調べてみました。
Jupyter/Jupyter Notebook とは
プログラムのソースコードに、注釈や補助情報としてコメントを入れることがあると思います。
恐れずに言うなら、Jupyter は「その逆」です。つまり、Jupyter は Markdown のドキュメントにプログラムをコメントとして埋め込んでステップ実行できるものと言えそうです。
そのファイルが「ノートブック」(正しくは「Jupyter ノートブック」)と呼ばれ、JSON 形式の「Jupyter ノートブック・フォーマット」で書かれたものです。拡張子は .ipynbで iPython NoteBook の略です。
例えば、以下のような「なんちゃって論文」を Markdown ドキュメントで書きたいとします。
# All about Hello, world
“`python3
print(“HelloWorld”)
“`
しかし、コードブロック内の print() をユーザー(読者)が実行して試せるようにしたいので、Jupyter ノートブック形式で作りたいとします。
最終的に以下のような JSON 形式になるのですが、ざっと構成だけみて下さい。「へぇ」と思う反面、手打ちで作成するには結構面倒だと思います。
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# All about Hello, world"
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"print(\"HelloWorld\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.8"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
上記をみると、Markdown 行のブロックと、コード行のブロックで分かれており、Jupyter のフォーマットでは、この各ブロックを「セル」(cell)と呼んでいます。そして最後に実行環境のメタデータが埋め込まれているのがわかると思います。シンプルですが、柔軟性があるフォーマットだと思います。
つまり、エディタを使ってドキュメントを書く時は以下を繰り返して、積み上げるイメージで作成して行きます。
-
- セルを挿入する
-
- 各々のセルに「markdown」や「code」といった属性を付ける
- セルに内容(テキスト)を記入
そして、これらのファイルのエディタやビューアとなるのが Jupyter Notebook や JupyterLab と呼ばれる Python で書かれた Web アプリです。
Jupyter Notebook は従来からあるシンプルなエディタ&ビューアーですが、ファイルとしてのJupyter Notebook なのか、アプリとしての Jupyter Notebook なのかがわかりづらいことから、「クラシック」(Classic Jupyter Notebook)とも呼ばれます。
JupyterLab は Classic Jupyter Notebook の後続の上位バージョンです。より多機能になっており、IDE に近いアプリになっています。
公式のオンラインのデモが触れるので試してみて下さい。
オンラインで JupyterLab を触ってみる | try @ Jupyter.org
オンラインで Classic Jupyter Notebook を触ってみる | try @ Jupyter.org
どちらも画面上部に「▶︎」の再生ボタンが表示されるので、押していって動きをみてください。ステップ実行的に実行されていくのがわかると思います。本記事は、これに PHP8 を動かすための記事ということになります。
Jupyter Notebook で書くと何が良いのか
何回目か分からない人工知能(AI)ブームの昨今、毎日のように機械学習の新しい論文やアイデアが発表されているそうです。そして、どの分野の論文もそうですが査読、つまり「内容の確認と検証」などを経て認知されます。
しかし、論文と同じ環境を用意したり、論文中に書かれたコードが本当に同じ結果を出すのか、写経のごとく別途コードを入力して検証するのも大変です。
Jupyter のノートブック形式であれば、「▶︎」の再生ボタンを押して行けば Markdown 中に埋め込まれた順にコードが実行されていきます。コードエディタや IDE で言うステップ実行と同じ感覚です。
つまり、論文と共に検証実証もセットで配布できる仕組みが研究者に好まれているのだと思います。
さすが Python パイセン。後輩の PHP が、止まることより動き続けることに重きを置くフラジャイル開発に始まり(嘘)、型々言うな、小さな気合と根性の積み重ねが大事なんだ、とスタミナ勝負のアジャガイル開発を続け(嘘)、問題を後任へ後任へとパスしながら前へ進むスクラム開発を経て(違)PHP が一世を風靡したのは今や昔(本当)。
体育会系の PHP と違い、教育を念頭に力を入れてきた理系の Python が、ここに来て真価が現れてきたのだと思います。
これは PHPer の私も見習わないと!、と思い Jupyter で PHP は使えないのか調べ始めました。
Jupyter のカーネルとは
木星の Jupiter でなく Jupyter なのは、元々は「Julia、Pythonと R 言語向けだったから」だそうで、それらを組み合わせて Ju-Pyt-Er つまり Jupyter なんだそうです。お洒落ですね。PHP が Programmers Hate PHP なのとは大違い(違)。
その後、「この仕組みが勉強やアイデアの共有には便利」と言うことで他の言語でも使えるようになりました。その際の仲介役、つまり Jupyter から受け取ったスクリプトを該当するプログラム言語で実行するプログラムを「カーネル」と呼ぶようになります。
つまり、PHP を Jupyter で動かすためには PHP カーネルが必要になります。
しかし、残念なことに 2020/11/19 現在、PHP カーネルは IPHP と Jupyter-PHP の2つしか存在していません。
Jupyter がサポートするカーネル一覧 | Wiki | Jupyter @ GitHub
しかも IPHP は開発は中止、Jupyter-PHP を見ろと言いつつも、肝心の Jupyter-PHP は事実上放置されています。
Jupyter-PHP を PHP8 で動かす
公式の Docker の PHP イメージ(php:8.0-rc-alpine)は RaspberryPi Zero で動かなかったり、JIT がデフォルトで有効になっていなかったり、リージョンが日本になっていなかったり、mbstring が有効になっていなかったり、と、すぐに使うには色々と面倒臭いので設定済みのイメージを使います。
keinos/php8-jit:latest @ GitHub
これをベースに Dockerfile を構築することにしました。最終的な成果物はこちらです。
https://github.com/KEINOS/Jupyter-PHP8 @ GitHub
さて、問題の PHP カーネルですがゼロから作るよりは既存のカーネルを修正して動くなら、それを使うのがベストと言うもの。IPHP と Jupyter-PHP で違いを調べてみました。
IPHP は IPython が使えれば composer だけで使えていたらしいのですが不具合や限界も多く、Jupyter-PHP は、さらに PHP エクステンションの zmq を必要としています。
しかし、理論上 Jupyter フォーマットが変わらない限り、受け取った PHP コードをパースして PHP ランタイムに渡せば良いだけです。そこで、なんとか Jupyter-PHP を使えないものか奮闘してみました。
Jupyter-PHP のソースコード を見てみると composer.json があるので、composer require でインストールできるのかと思いきや、エラーでできません。
Jupyter-PHP 本体 @ GitHub
composer.json を覗くと “require”:{“php”:”>=7.0″} と PHP のバージョンを制限していました。そこで –ignore-platform-reqs オプションを付けてみたところ、今度はエラーは出ないものの、やはり動きません。Jupyter にカーネルが現れないのです。どうやら、肝心のカーネルがしかるべきパスに設置されないようです。
つまり、Jupyter-PHP のカーネルをスコープ(許された範囲)外に設置する必要があります。しかし、composer だけでは、どこのパスに Jupyter のカーネル設置先のディレクトリがあるか分からず、検索するにしても OS にも依存するため composer.json 単体では完結できないのです。
そこで、次に README にあるようにインストーラーを実行してみましたが、やはりエラーでインストールできませんでした。
Jupyter-PHP-Installer @ GitHub
インストーラーの中身を確認してみると、カーネルのインストール先ディレクトリを検索して見つけたのち、内部で composer コマンドを呼び出して composer install していました。
- /src/Installer/Installer.php#L159-L166
この時に –ignore-platform-reqs オプションが指定されていないため「PHP8 ではバージョンが合わない」とエラーが出ていたのです。
そのため、Jupyter-PHP を PHP8 で動かすためには、主に2カ所の修正が必要です。
-
- Jupyter-PHP-Installer の composer.json にある PHP バージョン指定の削除
- Jupyter-PHP-Installer の src にあるインストール・スクリプト内に –ignore-platform-reqs を加え、依存パッケージの PHP バージョンを無視させる
そして、これらの修正を行うパッチを作成しました。Jupyter-PHP-Installer のリポジトリをクローンかダウンロードして適用します。
diff.jupyter-php.patch @ GitHub
また、JupyterLab に表示されるカーネルの選択パネル(アイコン?)の表記が「PHP」だったので、わかりやすいように「PHP8」に変更するパッチも作成しました。
Jupyter-PHP-Installer でインストール後、PHP のカーネル kernel.json を検索して、パッチをあてて下さい。
diff.kernel.patch @ GitHub
PHP に関する注意点としては、composer のバージョンと、Jupyter-PHP が依存する PECL パッケージの zmq の PHP エクステンション(PHP 拡張機能)です。
composer のバージョンを v1系にすること。最新の v2 系だと、追随していない Composer パッケージが多いため、エラーがでまくります。
Dockerfile の指定箇所 @ GitHub
zmq はソースから PECL パッケージをインストールすること。pecl install zmq で入るコンパイル済みパッケージは古すぎるため動きません。また、zmq が依存する zip の PHP エクステンションも一緒に入れておく必要があります。
Dockerfile の指定箇所 @ GitHub
あとは、JupyterLab や Classic Jupyter Notebook を動かすのに必要な Python3 と、インストールに使う pip を入れます。
-
- Python3 を Alpine Docker に入れる
Dockerfile の指定箇所 @ GitHub
pip を Alpine に入れる
Dockerfile の指定箇所 @ GitHub
Jupyter 一式をインストールする
Dockerfile の指定箇所 @ GitHub
ここまで来て、やっと出来上がったのですが、1つだけ後悔していることがあります。いつものクセで Docker のベースイメージを Alpine にしたことです。
Docker の Alpine Linux ベースのイメージは軽量であるため、複数のコンテナを起動して利用するのには向いています。つまり、1コンテナに1つの機能(サービス)を動かして使う場合です。しかし、軽量である反面、Alpine はクセが強いため、1つのコンテナにアレコレとインストールするのには向いていません。
Alpine にも apk と言うパッケージマネージャーがあるのですが、お世辞にも Ubuntu や Debian などの apt ほど素直にインストールできません。そのため、PHP やら Python やら node.js を入れてアレコレとアプリをインストールするなら、サイズは巨大になるものの Ubuntu や Debian ベースの Docker イメージの方が慣れていない人には楽だったのかもしれないと感じました。
参考文献
Installation | Jupyter lab @ readthedocs.io
jupyter-php環境を構築してみた @ Qiita
dockerでjupyter notebookが動く環境を付け加える作業 @ Qiita
Jupyter-PHP | Litipk @ GitHub
jupyter-alpine | nbgallery @ GitHub