vagrant上のipython kernelに接続してjupyter を使う

# まえがき
サーバー上で動作するipython kernel(またはjupyter notebook)にクライアントマシン上のターミナル(またはブラウザ)から接続するためのハウツー記事です。

## 前提
* Virtualboxインストール済み
* vagrantインストール済み
* python動作可能なOSイメージ取得済み
* 仮想マシン上でpython, ipython, jupyter notebook関連インストール済み

## 環境

()内は本記事で実際に使用したOS, アプリケーション名です。

|種別      |              構成               |      ipythonに接続するとき使うもの  |  jupyter notebookに接続するとき使うもの |
|----------|------------------------------|----------------------|--------------------|
|サーバー   |  仮想マシン(Virtualbox+vagrant) |   ipython kernel   |    jupyter notebook |
|クライアント| 実マシン(Ubuntu 16.04)         |    ターミナル(Gnome Terminal)  |     ブラウザ(Firefox57.0.4 64bit)  |


## 表記の注意
サーバー上の操作とクライアント上の操作を区別しやすくするため、PS1の表記をそれぞれ

* サーバー:[vagrant]% 
* クライアント:[cliant]$ 

としました。

# ssh接続してjupyter console(ipython)を立ち上げる

`vagrant ssh`したり仮想マシンを表示したウィンドウ上でターミナル立ち上げるなどして`ipython kernel`を実行します。
`ipython kernel`は`ipython --help`に書いてあるように

```
kernel
    Start a kernel without an attached frontend.
　　フロントエンドアプリケーションなしにipythonカーネルを実行するコマンドです。
```



```shell-session
[vagrant]% ipython kernel  # ipython起動

NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work.

To exit, you will have to explicitly quit this process, by either sending
"quit" from a client, or using Ctrl-\ in UNIX-like environments.

To read more about this, see https://github.com/ipython/ipython/issues/2049


To connect another client to this kernel, use:
    --existing kernel-4232.json

```

一番下の行が重要です。「kernelに接続するには `--existing kernel-4232.json`を使って下さい」とあります。
リモート上のipython kernelに接続するには

```shell-session
[cliant]$ jupyter console --existing [kernel情報の書かれたJSONファイルパス] --ssh <ホスト名>
```
と打ち込みますが、まず、"kernel情報の書かれたJSONファイル"が必要です。









## JSONファイルの確認
`jupyter --runtime-dir`というコマンド表示されるパスにJSONファイルが作成されていることを確認して下さい。

```shell-session
[vagrant]%  jupyter --runtime-dir
/run/user/998/jupyter
```

こうするとipython kernelを立ち上げたサーバー上の"/run/user/{UID}/jupyter/ "というパスにJSONファイルが作成されます。({user uid}は特別に指定していなければ1000とか9百番台くらいの数字だと思います。)
JSONファイルにはポート番号やipアドレスなど、リモートのkernelへ接続するのに必要な情報が含まれています。

```shell-session:ipython_kernel起動した時にできるJSONのパス
[vagrant]%  ls /run/user/998/jupyter/                 
kernel-4232.json
```



```shell-session:JSONファイルの中身
[vagrant]%  cat /run/user/998/jupyter/kernel-4232.json
{
  "shell_port": 37393,
  "iopub_port": 34411,
  "stdin_port": 44867,
  "control_port": 42805,
  "hb_port": 37427,
  "ip": "127.0.0.1",
  "key": "132a7ffd-2f3f9cb12201448d4358b5df",
  "transport": "tcp",
  "signature_scheme": "hmac-sha256",
  "kernel_name": ""
}
```

なお、kernel-****.jsonの数字は起動したipython kernelのPIDが充てられます。


リモート上でqtcconsoleを立ち上げると、ホスト・リモート共用のqtconsoleが立ち上がります。
名前空間が共有されるので、意図しない変数の書き換えや操作に気をつけましょう。

```shell-session
[vagrant]% jupyter qtconsole  # qtconsole起動
```

qtconsoleを立ち上げた時はJSONファイル名は表示されずにリモート上にqtconsoleが現れます。JSONファイル名はPIDを調べる`[vagrant]% ps aux | grep ipython`か、`[vagrant]% ls /run/user/{UID}/jupyter/`などして調べましょう。
















# sshを使って仮想マシンに接続できるようにする
続いてクライアント上でリモートの仮想マシンに`ssh`コマンドでssh接続を行ったり、`scp`でセキュアコピーを行ったりする設定をします。

バーチャルマシンを動かしているディレクトリ上で、`vagrant ssh-config`コマンドを打つと、`vagrant ssh`コマンドでssh接続するための情報を見ることができます。そこで

```shell-session
[cliant]$ vagrant ssh-config --host 192.168.22.33 >> ~/.ssh/config     
```
としてconfigファイルにssh設定を保存します。
これで

```shell-session
[cliant]$ ssh 192.168.22.33
[cliant]$ vagrant ssh
```

が同じ意味になりました。


sshするときはこの情報が使われます。

```shell-session:~/.ssh/config
[cliant]$ cat ~/.ssh/config
Host 192.168.22.33
  HostName 127.0.0.1
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking xxxxxx
  PasswordAuthentication xxxxxx
  IdentityFile /home/u1and0/.vagrant.d/insecure_private_key
  IdentitiesOnly yes
  LogLevel FATAL
```

> 適当なIPアドレス192.168.22.33を与えましたが、適当なホストネーム(たとえばvagrantとする)を与えればconfigファイルの1行目が`Host vagrant`となって`$ ssh vagrant`と`$ vagrant ssh`が同じ意味になります。(むしろややこしい)
>> ややこしいけれども仮想マシンを立ち上げているディレクトリ以外の場所でも`ssh vagrant`とか`ssh 192.168.22.33`で接続できるのでメリットはあります。

configファイルに設定を保存することでsshだけでなくscpとかも使えるようになります。


```shell-session
[cliant]$ scp 192.168.22.33:/run/user/998/jupyter/kernel-3701.json ./
```

上のコマンドでは仮想マシン上のjupyterカーネルの設定ファイル`kernel-3701.json`をクライアント上の現在ディレクトリにコピーをします。










# ssh接続してjupyter console(ipythonの親戚)立ち上げる

```shell-session
[cliant]$ jupyter console --existing ./kernel-3701.json --ssh 192.168.22.33 
```

でipython shellのような見た目のjupyter consoleがクライアント上のshellで立ち上がります。

![Screenshot from 2017-12-29 22-47-35.png](https://qiita-image-store.s3.amazonaws.com/0/113494/b7797310-5096-37b2-9e32-383046205120.png)


> ipythonを呼ぼうとするとエラーが出るので、代わりにjupyter consoleを呼びましょう。

>```shell-session
[cliant]$ ipython --existing ./kernel-7149.json --ssh 192.168.22.33

> ...

> [TerminalIPythonApp] CRITICAL | Bad config encountered during initialization: 
[TerminalIPythonApp] CRITICAL | Unrecognized flag: '--existing'     
```

>> 2018年9月、anaconda環境をアップデートしたら？ssh接続のリモートホスト上でjupyter consoleが立ち上がりませんでした。代わりにipythonを起動するとipythonが立ち上がりました。アプデでjupyter consoleとipythonが統合されたのでしょうか。(未確認)








## core.dumpエラー
`~/.ipython/profile_default/startup/`スクリプトに

```python
get_ipython().magic('matplotlib inline')
get_ipython().magic('matplotlib auto')
```

といった書き込みを入れたスクリプトを入れていたので立ち上がりかけたconsoleが落ちる現象がありました。
ssh接続したシェルで`ipython`や`jupyter console`を打ち込まず、kernel-<PID>.jsonをコピーして、`jupyter console --ssh 192.168.22.33 --existing kernel-<PID>.json`を打ち込みましょう。
~~立ち上がったのを見てから`get_ipython().magic('matplotlib inline')`(マジックコマンドを使うなら`%matplotlib inline`)を入力したほうが良いのでしょう。~~








# ssh接続してjupyter qtconsole立ち上げる

```shell-session
[cliant]$ jupyter qtconsole --existing ./kernel-4640.json --ssh 192.168.22.33
```

でクライアント上でjupyter qtconsoleが立ち上がります。

![Screenshot from 2017-12-29 23-38-51.png](https://qiita-image-store.s3.amazonaws.com/0/113494/119062bc-a521-8476-eb5b-44906653fbb4.png)
ホスト上のshellに現れるメッセージ


プロットもできますし、`!whoami`と打ち込んでホスト名を聞くとちゃんとvagrant上で実行されているjupyterであることがわかります。

![Screenshot from 2017-12-29 23-34-28.png](https://qiita-image-store.s3.amazonaws.com/0/113494/e1d07757-7d71-5907-e4fc-9d25c040a38e.png)

## jupyter notebook立ち上げてssh接続する
## ホスト側の作業1

### ポートを開く
Vagrantfileを編集してjupyter notebook用のポートを開きます。

```shell-session
[cliant]$ vi Vagrantfile
```

```shell:Vagrantfile
Vagrant.configure("2") do |config|
 config.vm.network "forwarded_port", guest: 8887, host: 8887
end
```


## ゲスト側の作業1
### コンフィグファイルの作成
jupyter notebookの設定をいじるための設定ファイルを作成します。

```shell-session
[vagrant]% jupyter notebook --generate-config
```

デフォルトでは`~/.jupyter`というディレクトリ下にファイルが生成されます。






###  jupyter_notebook_config.pyの設定



```python:~/.jupyter/jupyter_notebook_config.py
c.NotebookApp.ip = '*'  # いずれのipアドレスからでも接続可能
c.NotebookApp.open_browser = False  # jupyter notebook開いたと同時にブラウザを開かない
c.NotebookApp.port = 8887  # デフォルト値は8888
```



### パスワード作成(設定しなくても開けるがセキュリティ的に危険)
jsonファイルにパスワードのキーが書き込まれる

```shell-session
[vagrant]% jupyter notebook password
Enter password:  ****
Verify password: ****
[NotebookPasswordApp] Wrote hashed password to /Users/you/.jupyter/jupyter_notebook_config.json
```





## ホスト側の作業2
ホストのブラウザのアドレス欄に[http://localhost:8887](http://localhost:8887)とうちこむとパスワード打ち込めと現れるので、さっき設定したパスワードを打ち込みます。





# まとめ
* サーバーでipython kernelなどを起動する

> `[vagrant]% ipython kernel`

* 作られたjsonファイルをクライアントにコピーする
    * scp使う、またはコピペでクライアントの適当な場所にファイル作って貼り付ける

    >  `[cliant]$ scp [ホスト名]:/run/user/[user_id]/jupyter/kernel-[kernel PID].json [コピー先] `

* ホスト上でjupyterなどを立ち上げる際にオプションでjsonファイルの指定とssh接続先ホスト名を入力する
    * `[cliant]$ jupyter console --existing ./kernel-[kernel PID].json --ssh [ホスト名]`
        * # サーバーマシン上のカーネルを使用してjupyter console(ipython的な奴)立ち上げ
    * `[cliant]$ jupyter qtconsole --existing ./kernel-[kernel PID].json --ssh [ホスト名]`
        * # サーバーマシン上のカーネルを使用してqtconsole立ち上げ
    * クライアントのブラウザのアドレス欄に`http://localhost:8887`
        * # サーバーマシン上のjupyter notebookに接続
              * `http://`は省略可能です。
              * ローカルホスト上に立ち上げたサーバーでないならば、localhostの代わりにIPアドレスを打ち込む(`http://192.168.15.10:8887`)と、実サーバーマシンに立ち上げたjupyter notebookにつなぐことができます。







# 参考
* [Cookbook: Connecting to a remote kernel via ssh](https://github.com/ipython/ipython/wiki/Cookbook:-Connecting-to-a-remote-kernel-via-ssh)
* [The Qt Console for Jupyter](https://qtconsole.readthedocs.io/en/stable/)
* [VagrantインスタンスにSSHでログインする](https://qiita.com/y_arakawa/items/7bb292c999873032e6a4)
* [Running a notebook server](https://jupyter-notebook.readthedocs.io/en/stable/public_server.html): パスワード生成
* [Jupyterをサーバ上で起動する](http://www.mwsoft.jp/programming/numpy/jupyter.html): jupyter_config.pyの設定
* [Jupyter Notebookの設定をする](http://dragstar.hatenablog.com/entry/2016/12/19/110313):jupyter_config.pyの設定
* [ポートフォワードによるネットワーク構成について](http://maku77.github.io/vagrant/port-forward.html): Vagrantfile上でVirtualboxのポート設定