ガラケー+格安SIM差しタブレットの2台持ちに憧れて数年、完全に時代遅れ感があった昨秋に、遂にiPad(Wi-Fi + Cellularモデル)を買ってしまった。データ通信専用の格安SIMも差した。定期的に見たいが我が家の旧式PCでは見れないWebサイトがあったので、この機会にと思って勢いで買ったのだが、購入して2ヶ月、予想通り、Webブラウザ以外ほとんど使っていない。折角なので何か他にも活用したいと思って、まず思いついたのはPythonのプログラミング環境構築だった。
外出先でプログラミングすることはまず無いのだが、筆者は、目の前にあるiPadのような高スペックのコンピューターにプログラミング環境が無いと、気になって仕方がないのである。
そして、筆者が今一番興味ある言語はPythonであり、iPadでやるならPython一択である。
少し調べた限り、今iPadでPythonプログラミングするならPythonista3の一択のようであるが、Pythonista3ではPandasを使えないようで、しかも有料だったので、今回は見送った。
仕方なく、Jupyter Notebookを使うことにした。
自宅のLANでiPadのSafariからJupyter Notebookに接続してみると、表示もコード入力も実行もできた。ソフトウェアキーボードではかなり辛く、特にTABキーが無いので補完が効かないのが致命的であるが、必要であればBluetoothキーボードを使ったりJunoという有料のアプリを使えば解決しそうなので置いておくとして、とりあえず自宅のPCに起動したJupyter Notebookに外出先からアクセスできるようにすれば、目的が達成できそうである。
筆者はプロバイダーから動的に付与される自宅のIPアドレスをDDNSに登録するようにしているので、既に外から自宅内PCへのアクセスは可能である。実際、ルーターのポートフォワーディング(静的NAT)設定をするだけで、iPadからセルラー網経由で自宅PCのJupyter NotebookへHTTP接続できた。
しかし、HTTPだとパスワードが平文で送られてしまうのが問題である。
そこで、以下の3通りの方法を考えてみた。
(1) 自宅PCにSSHサーバーを設けてSSHトンネリングでJupyter Notebookに接続する
(2) ApacheにHTTPS接続し、Reverse ProxyでJupyter Notebookに接続する
(3) Jupyter Notebookに直接HTTPS接続する
(1)は筆者が過去にそうやって接続するしかないクラウドサービスを使ったことがあるので真っ先に思い付いた方法で、PCからは難なく成功したのだが、iPadで安全にSSHトンネリングしてキープさせるには有料アプリを使うしかなさそうなので、やめた。
(2)(3)を今回やってみたので、ここに記録する。
HTTPS接続したApacheのReverse ProxyでJupyter Notebookに接続する環境の作成方法
ここでは、https://ynomura.dip.jp/desktop/ipython/でデスクトップPC(192.168.0.80)のポート8888のJupyter Notebookに接続できるようにするものとする。
■Apache(2.4)の設定
HTTPSのVirtualHostは設定済、SSL/TLSサーバ証明書もインストール済みとする。
結果として、以下の設定で成功した。
- proxy, proxy_http, proxy_wstunnelモジュールの有効化
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod proxy_wstunnel
- sites-enabled/*の<VirtualHost *:443>の所に以下のように/desktop/ipython/の設定を追加
"pXXXXXXX-mobac01.tokyo.ocn.ne.jp"というのは、筆者のiPadのFQDN(アドレス)である。これは時々変化するのだが、"-mobac01.tokyo.ocn.ne.jp"の部分は変わらないようなので、Allowの所に"-mobac01.tokyo.ocn.ne.jp"と書きたいのだが、残念ながらそれは通らないらしい。(https://httpd.apache.org/docs/2.4/ja/mod/mod_access_compat.htmlに、"Allow from apache.org"と書くと foo.apache.org にはマッチするが、 fooapache.org にはマッチしないと書かれている。マッチすると困るので当たり前であるが)<VirtualHost *:443> ... <Location /desktop/ipython/> ProxyPass http://192.168.0.80:8888/desktop/ipython/ ProxyPassReverse http://192.168.0.80:8888/desktop/ipython/ ProxyPreserveHost On Order deny,allow Deny from all Allow from 192.168. pXXXXXXX-mobac01.tokyo.ocn.ne.jp </Location> <Location /desktop/ipython/api/kernels/> ProxyPass ws://192.168.0.80:8888/desktop/ipython/api/kernels/ ProxyPassReverse ws://192.168.0.80:8888/desktop/ipython/api/kernels/ </Location> </VirtualHost>
"ProxyPreserveHosts On"としないと、Jupyter Notebookで"Blocking Cross Origin API request. Origin: https://ynomura.dip.jp, Host: 192.168.0.80:8888"というエラーになる。
■Jupyter Notebook側の設定
- リモートアクセスを可能にする設定
jupyter_notebook_config.pyにて
(2022/8更新、これではjupyter-notebook起動時に"ValueError: '' does not appear to be an IPv4 or IPv6 address"というエラーが出るようになった)c.NotebookApp.ip = '*'
とする。c.NotebookApp.ip = '0.0.0.0'
- パスワード設定
Jupyter Notebookにて
としてハッシュ化されたパスワードを得て、jupyter_notebook_config.pyにてfrom notebook.auth import passwd
passwd()
とする。c.NotebookApp.password = u'(ハッシュ化されたパスワード)'
参考: Securing a notebook server - Jupyter Notebook documentation - URL変更
jupyter_notebook_config.pyに
を追加する。c.NotebookApp.base_url = '/desktop/ipython/'
これをせず、https://ynomura.dip.jp/desktop/ipython/をhttp://192.168.0.80:8888/にリレーする方法がわからなかった。
■Jupyter NotebookをHTTPSサーバーにする設定方法
Jupyter Notebook DocumentationのUsing SSL for encrypted communicationの所に全て書いてあった。- リモートアクセスを可能にする設定
上記と同じ - パスワード設定
上記と同じ - SSL/TLSサーバ証明書と秘密鍵の設定
jupyter_notebook_config.pyに
の2行を追加する。c.NotebookApp.certfile = u'/absolute/path/to/your/certificate/mycert.pem'
c.NotebookApp.keyfile = u'/absolute/path/to/your/certificate/mykey.key' - ポート番号変更
ポート8888をインターネットに解放するのはすぐ狙われそうで抵抗があるので、変更する。ルーターのNAPT機能でポート番号を変換できれば良いのだが、筆者宅のルーターはNATしか対応していないので、Jupyter Notebookの待ち受け番号を変更する。
jupyter_notebook_config.pyに
を追加する。c.NotebookApp.port = XXXXX
- ポートの解放、NAT設定
Jupyter Notebookを起動するPCのFirewallのポートXXXXXを解放し、ポートXXXXX宛のパケットをそのPCに転送するようにルーターのNAT設定を変更する。 - URL変更
インターネットからhttps://既知のドメイン名:ポート番号/でアクセス可能だと気になるので、せめてhttps://既知のドメイン名:ポート番号/何らかのパス/としておく。
Jupyter Notebookを起動するPCでもApacheを立ち上げるなどしてhttps://未知のホスト名.既知のサーバー名:ポート番号/とする方が無難だが、動作確認した方法の1つとして控えておく。
jupyter_notebook_config.pyに
を追加する。c.NotebookApp.base_url = '/desktop/ipython/'
他に、外出先からJupyter Notebookを起動/終了する為のCGIを用意することを考えたが、当面ほとんど起動することがないので、見送った。
試しに外出先で、iPadの「ブック」(iPadOSの電子書籍リーダー)でPythonのtutorialを読みながら、Safariで開いた自宅のJupyter Notebookにコード片をコピーして実行してみるというのを、外付けキーボード無しでやってみた。何とか動かせたが、ちょっと時間が経つとカーネルとの接続が切れてしまって反応が無くなるので、頻繁にReconnectの操作をしないといけないのと、Safari上のJupyter Notebookの編集中のセルにおける選択&削除操作がうまくできない(選択してDELキーを押しても消えないなど)ことが多く、ソフトウェアキーボードのDELキーはオートリピートも効かないので、コードをペーストした後が大変だった。
Jupyter Notebookとの通信を暗号化する方法として、上記のApacheのReverse Proxyを使う方法とJupyter NotebookをHTTPSサーバーにする方法とを比べると、後者は簡単だが、ホスト名やIPアドレスによる接続元の制限がほとんどできないのがとても不満だし、やっぱり先入観としてセキュリティに不安がある。加えて、セキュリティの為に長いURLに変更しても、https://ynomura.dip.jp:XXXXX/にアクセスすると"404: Not Found"というJupyterのページが出て、Jupyterが動いているのがわかってしまうのが残念である。また、URLにポート番号が含まれると、どこかにアクセスログに残って弱点になりそうである。
対して、ApacheのReverse Proxyを使う方法は、接続元の制限がやりやすくてかなり安心できるのだが、iPad側のホスト名/IPアドレスが変わることへの対策が見つかっていないのが致命的で、実運用には至っていない。
これまで、自分で署名した「オレオレ証明書」を使って自宅にHTTPS接続していたが、上記のJupyter Notebook DocumentationのUsing Let's Encryptの所で紹介されていたので、この機会にLet's Encryptの証明書に切り替えた。
実施した手順はきちんと控えていなかったが、certbot-autoを実行すると途中で何かダウンロードしたものがチェックサムエラーになって進まなかったのでaptでcertbotをインストールした以外は、大体https://certbot.eff.org/lets-encrypt/debianother-apacheのページの通りにやって、https://www.ssllabs.com/ssltest/のテストも通った。
Webブラウザからの警告も出なくなって、快適である。
コメント