Jupyter NotebookからKubernetesを操作する

最近流行りのJupyter Notebook( http://jupyter.org/ )でkubectlコマンドを呼び出すことで、生きている手順書が作れるのでは？と思ったので、試してみることにしました。

このドキュメントではKubernetesクラスタの中にJupyter Notebookをデプロイし、そこから自身がデプロイされている、Kubernetesクラスタを操作する方法を紹介します。

# 前提条件
- minkube
- kubectl

はインストール済みとします。

# minikubeによるKubernetesクラスタの作成

```
$ minikube start
Starting local Kubernetes v1.10.0 cluster...
Starting VM...
Getting VM IP address...
Moving files into cluster...
Setting up certs...
Connecting to cluster...
Setting up kubeconfig...
Starting cluster components...
Kubectl is now configured to use the cluster.
Loading cached images from config file.
```

# python kernelをつかってみる

まずはシンプルな普通の機能を試してみます。

## Jupyter Notebookのデプロイ

まずは公式で用意しているminimal-notebookのコンテナイメージを使ってみることにします。

```jupyter.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jupyter-notebook
  labels:
    app: jupyter-notebook
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jupyter-notebook
  template:
    metadata:
      labels:
        app: jupyter-notebook
    spec:
      containers:
      - name: minimal-notebook
        image: jupyter/minimal-notebook:latest
        ports:
        - containerPort: 8888
        command: ["start-notebook.sh"]
        args: ["--NotebookApp.token=''"]
---
kind: Service
apiVersion: v1
metadata:
  name: jupyter-notebook
spec:
  type: NodePort
  selector:
    app: jupyter-notebook
  ports:
  - protocol: TCP
    nodePort: 30040
    port: 8888
    targetPort: 8888
```

```
$ kubectl apply -f jupyter.yaml
```

## デプロイしたサービスの確認

```
$ kubectl get svc
NAME               TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
jupyter-notebook   NodePort    10.100.161.148   <none>        8888:30040/TCP   5m
kubernetes         ClusterIP   10.96.0.1        <none>        443/TCP          25d
```

## ブラウザからアクセス

まずminikubeのIPを確認します。

```
$ minikube ip
192.168.99.100
```

サービスのNodePortは30040なのでこの場合は下記のアドレスをブラウザで開きます。

http://192.168.99.100:30040

## notebookの作成
WebUIからPython 3の形式のNotebookを作成します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/100375/a032d4c4-f2e9-559d-eda0-9cd280c7ebb3.png)
## 試しに書いてみる

Jupyter NotebookはドキュメントをCellという単位で記述します。CellにはMarkDownのモードとコードのモードなどがあります。
コードのモードの時に普通にプログラムを書くとPythonとして解釈され、`!`を前置するとシェルスクリプトとして解釈されます。

`Shift+Enter`を押すことでそのコードを実行することができます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/100375/b22e2ef9-0afc-949b-22e9-b44d72569eb8.png)


## kubectlを呼び出す

`!`を前置することでシェルスクリプトが動くので`wget`コマンドで`kubectl`のバイナリをダウンロードして、実行してみます。

実行したコマンドは下記です。

```
!wget https://storage.googleapis.com/kubernetes-release/release/v1.12.0/bin/linux/amd64/kubectl
!chmod 755 kubectl
!./kubectl get pod
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/100375/48a27bab-9326-dc97-b1c1-7ee899af3aad.png)


権限は無いが、一応動いていることがわかります。
このシェルスクリプトはKubernetesにデプロイされたPodの中で動作しており、権限としてはデフォルトで作成される`default`という名前のServiceAccountの権限で動作します。（これらは環境変数によって設定されています。気になる方は`!env`をNotebookで実行するとわかります）

## 権限を付与する

defaultのServiceAccountに権限を与えます。下記コマンドはviewという標準で用意されているRoleをdefaultというServiceAccountに紐づけています。

```
$ kubectl create rolebinding default-sa --clusterrole=view --serviceaccount=default:default
rolebinding.rbac.authorization.k8s.io/default-sa created
```

再度実行すると、実行できました！
これがやりたかったことです！

![image.png](https://qiita-image-store.s3.amazonaws.com/0/100375/4aa3ce17-6b29-7443-19b9-853155b860e4.png)

## 一度全て削除する

次のステップに行く前に一度デプロイしたjupyter notebookを削除します。

```
$ kubectl delete -f jupyter.yaml
deployment.apps "jupyter-notebook" deleted
service "jupyter-notebook" deleted
```


# bash kernelをつかってみる
ここまでで、Jupyter Notebookで`kubectl`が実行できることがわかりましたが、実行する時に先頭に`!`をつけるのがどうにも煩わしいです。

これはPython kernelを使っているためデフォルトとしてPythonのコードとして動作しているからです。
そこで、Bash kernelを使うことでもっと素直にシェルスクリプトが実行できるようにしてみましょう。


## Dockerイメージの作成
bash kernelの入ったDockerイメージが見当たらなかったので自分で作ります。
また出力を等幅フォントにすることで、出力結果が綺麗に表示されるようにしています。

```Dockerfile
FROM jupyter/minimal-notebook

USER jovyan

# Make sure not to create a cache dir else NB_UID switching
# will hit issues.
RUN /opt/conda/bin/pip install --no-cache-dir bash_kernel
RUN /opt/conda/bin/python -m bash_kernel.install
RUN mkdir -p ~/.jupyter/custom && echo '.CodeMirror pre, .output pre { font-family: Monaco, monospace; }' > ~/.jupyter/custom/custom.css

USER root
```

minikubeのDockerを使ってビルドします。

```
$ eval $(minikube docker-env)
```

```
$ docker build -t bash-notebook:v1.0 .
Sending build context to Docker daemon  3.584kB
Step 1/6 : FROM jupyter/minimal-notebook
 ---> 400c44c4a7a7
Step 2/6 : USER jovyan
 ---> 858a23692f3d
Step 3/6 : RUN /opt/conda/bin/pip install --no-cache-dir bash_kernel
 ---> 33313772919f
Step 4/6 : RUN /opt/conda/bin/python -m bash_kernel.install
 ---> 6ce76d215977
Step 5/6 : RUN mkdir -p ~/.jupyter/custom && echo '.CodeMirror pre, .output pre { font-family: Monaco, monospace; }' > ~/.jupyter/custom/custom.css
 ---> ebbcec57eea2
Step 6/6 : USER root
 ---> feb1400abc10
Successfully built feb1400abc10
Successfully tagged bash-notebook:v1.0
```

## デプロイ

下記のようにマニフェストを変更してデプロイします。

```jupyter-bash.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jupyter-notebook
  labels:
    app: jupyter-notebook
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jupyter-notebook
  template:
    metadata:
      labels:
        app: jupyter-notebook
    spec:
      containers:
      - name: minimal-notebook
        image: bash-notebook:v1.0  # イメージ名を変更
        imagePullPolicy: IfNotPresent # minikube内のイメージを使う
        ports:
        - containerPort: 8888
        command: ["start-notebook.sh"]
        args: ["--NotebookApp.token=''"]
---
kind: Service
apiVersion: v1
metadata:
  name: jupyter-notebook
spec:
  type: NodePort
  selector:
    app: jupyter-notebook
  ports:
  - protocol: TCP
    nodePort: 30040
    port: 8888
    targetPort: 8888
```

```
 $ kubectl apply -f jupyter-bash.yaml
deployment.apps/jupyter-notebook created
service/jupyter-notebook created
```

## ブラウザからアクセス

アドレスは前と同じです。

Bashが選べるようになっています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/100375/316994d9-7b6f-3085-40af-a95683897aab.png)

## 遊んでみる

`!`を書かずにシェルスクリプトが実行できました
![image.png](https://qiita-image-store.s3.amazonaws.com/0/100375/50192c19-6ca3-b0ba-dbce-e38ba71a9eb1.png)

# 感想

kubernetesのチュートリアルなどを作るのに良さそうと感じました。

# 参考
- Kubernetes上にjupyter notebookをデプロイする
  - https://cwienczek.com/2018/05/jupyter-on-kubernetes---the-easy-way/
- jupyer notebookのbash-kernelが便利という記事
  - https://raintrees.net/news/76
- bash-kernel入りのDockerfileの作り方
  - https://github.com/jupyter/docker-stacks/issues/25#issuecomment-139444841
- jupyter notebookのフォントを等幅にする
  - http://shu87.hateblo.jp/entry/2017/08/30/003224
- minikubeでローカルのdocker imageを使う
  - https://qiita.com/ocadaruma/items/efe720e46ae7ecb9ec25