リモートjupyter起動、Port forwarding、後始末をシェルスクリプトで自動化

月曜日, 2月 01, 2021

UNIX サーバー

t f B! P L

リモートサーバー上で起動したjupyterですが、ip='0.0.0.0’にしてportを公開する人はほとんどいないでしょう。

sshのPort fowardingで接続する人が大半だと思います。そのほうがセキュアだし。

コマンド1つでリモートサーバーでjupyterを起動し、ローカルのブラウザですぐ使いたい&後始末もしたい

要はローカルと同じ使用感でさくっとjupyterを使いたいというだけです。

ローカルの場合はjupyter labコマンドだけで済む話ですが、リモートの場合は完全手動だと

  1. リモートサーバーにsshしてjupyter起動
  2. 別のTerminalを開いてssh port fowarding
  3. Step 1で表示されているtoken付きURLを、ローカルのブラウザのアドレスバーに貼り付ける

という作業が必要になります。

閉じる際も、

  1. sshで起動したjupyterをCtrl-Cで停止
  2. ssh port fowardingを解除(当該portを使用しているsshのプロセスをkill)

という後始末も必要です。

めんどくさいので自動化します。

こういうのは、構成管理ツールの範疇な気もするのですが、そういったツールが必要になるのは、複数台のサーバーを同時管理するなどの業務レベルケースの話であり、個人開発レベルの自動化であれば、シェルスクリプトでさくっと書けたほうがいい気がします(手間的にも技術力的にも)。

なのでシェルスクリプト(ごくふつうのbashスクリプト)で対応します。

スクリプト

P=$1

cleanup () {
  echo "STOP jupyter server on PORT: ${P}"

  ssh yourserver "/path/to/jupyter lab stop ${P}"

  kill -9 $(lsof -t -i:${P})
}

# Ctrl-Cでmainを停止した場合の処理
trap 'cleanup' 2

main () {
  echo "START jupyter Server on PORT: ${P}"

  ssh -fNL ${P}:localhost:${P} yourserver

  ssh yourserver "/path/to/jupyter lab --no-browser --port ${P}"
}

# 実行
main

全体の実装は以上になります。

前出の手動操作は理解しているという前提で、要点を解説します。

Portは、色々変えるニーズがあると思うので、シェルスクリプトの第1引数にしています。

main処理

yourserverは、sshのconfigファイルに記載してあるリモートサーバーのホスト名です。つまり、ssh yourserverとするとssh接続ができる状態になっている必要があります。

main処理では、sshでport fowardingした後、再度sshコマンドで、リモートでコマンドを実行しています。
ここでjupyter labコマンドを実行することで、リモートサーバー上でjupyterが立ち上がります。

めんどくさいのでローカルとリモートのportは同じにしていますが、こだわりがあれば引数を増やすなりして対応してください。

ssh yourserver "command"

とすることで、リモートでコマンドを実行できます。pathが通っていないため、絶対パスでの指定が必要でした。

mainが実行されると、お馴染みのjupyter起動メッセージが出てきて

http://localhost:8888/lab?token=XXXXXXX

という出力で止まります。コントロールはリモート側に取られたままで、ローカルのシェルスクリプトに処理が戻りません。

ただし、この状態で既にjupyterは起動しているので、全く問題ありません。

上記のtoken付きURLをブラウザに貼り付けると、晴れてリモートのjupyter labをローカルブラウザから使えます。

残念ながら、このブラウザへの貼り付けだけは手動でやる必要があります。すみません。

cleanup処理

trapコマンドで第1引数にcleanup処理を仕込んでおくことで、後始末も自動化します。

trapの第2引数の2というのはシグナル番号で、2は割り込みシグナルSIGINT(Ctrl-C)になります。

(ちなみにkill -lコマンドで一覧を確認できます)

trapコマンドでは、第2引数に指定したシグナルを受け取った際、第1引数にセットしたコマンドを実行するようにしてくれます。

cleanup処理では、リモートのjupyterの停止(portを指定して停止できます)と、
port fowardingしているローカルのsshプロセスをkillしています(当該portを使用しているsshプロセスのpidを調べています)。

jupyterを停止させるには、Ctrl-Cを押す必要があります(jupyter起動時のInfoログに書いてある)。

ただ、ちょっと考えればわかりますが、ローカルのTerminalでCtrl-Cを押すと、SIGINTを受け取るのはローカルのシェルスクリプト本体であって、リモートのjupyterまでSIGINTが届きません。

ふつうはsshが落ちたり途切れたりすると、sshで起動したプロセス(子プロセス)も落ちる仕様なのですが、
上記のシェルスクリプトでは、Ctrl-Cでローカルのシェルスクリプトを終了してもリモートのjupyterは生きたままでした(ここはちょっと何でか分からない)。

なので、cleanup処理にて、jupyerの停止やport fowardingの解除が必要です。

で、最初のtrapコマンドに戻ります。

ローカルのCtrl-Cをtrapで拾ってcleanup処理を実行できるようにしておけば、Ctrl-Cを使って後始末も自動化できることになります。

QooQ