Google Cloud DatalabでGitHubレポジトリを使う

# はじめに
[Cloud Datalab](https://cloud.google.com/datalab)は簡単にGCP上でJupyter Notebook環境を構築できるサービスです。しかしGitレポジトリには同じGCPの[Cloud Source Repositories](https://cloud.google.com/source-repositories/?hl=ja)しか使えません。

この記事ではCloud DatalabでCloud Source RepositoriesのかわりにGitHubのレポジトリを使う方法を紹介します。

# 対象読者
こんな人

* Cloud Datalab使ってみたい
* Cloud DatalabでGitHubレポジトリ使いたい
* Jupyter Notebook運用したくない
* セキュリティもちゃんとしときたい

また、ある程度のJupyter Notebookの知識を前提としています。

# TL; DR
* `startup.sh`を使う
* https://github.com/nownabe/datalab-with-github の手順通りに操作すればOK

# Datalab
[Cloud Datalab](https://cloud.google.com/datalab)はGCP上でマネージドなJupyter Notebookを提供するサービスです。

Jupyterに比べてUIがちょっとイケてる感じになってたり、使ってない時間は自動でVM停止して節約してくれたり、GCPと統合されてたり、何かと便利です。マネージドかつ使い捨てできるような仕組みが整っているので運用コストがあまりないのも魅力です。

ガッツリカスタマイズには向いていません。


# GitHubを使う3つの方法
前提として、Datalabは各ユーザがそれぞれのDatalabインスタンスを持ち、Gitでコードを共有するというスタイルで利用します。そのため、全Datalabインスタンスから同じレポジトリを参照できるようにする必要があります。

GitHubのレポジトリを使う方法は3つあります。

1. Jupyter Notebook上でGitHubのレポジトリをcloneする
1. `startup.sh`を使ってDatalabをカスタマイズする
1. DatalabのDockerイメージをカスタマイズする

この記事では2番目の`startup.sh`による方法を紹介します。

1番目は最も簡単な方法です。しかし、Datalabインスタンスを立ち上げるたびにレポジトリをcloneする必要があります[^1][^2]。また、Source RepositoriesとGitHubのレポジトリが混在することになり、Git操作が複雑になったり運用でカバー的なルールが増えることが考えられます。Datalabの利用者にできるだけ負担をかけたくない、ルールを増やしたくないという理由からこの方法は却下しました。

2番目の`startup.sh`については後述します。

3番目は最も自由度が高い方法ですがヘビーです。DatalabはGCEインスタンス上のDockerコンテナとして動作します。Datalabインスタンス作成時にイメージを指定することができます。この方法では[本家のイメージ]()をカスタマイズしてDatalabを利用することができますが、本家の更新に追従する必要があり運用にコストがかかることが想定されます。Datalabを使う理由として運用コスト削減は大きいのでこの方法は却下しました。

[^1]: 1つのPersistent Diskを使い回せば不要ですが、Datalabはインスタンスごとにシングルユーザとなっていてユーザごとにインスタンスを作成するという前提なので現実的ではありません。
[^2]: Source Repositoriesのレポジトリ内にGitHubのレポジトリをcloneするという方法もありますが複雑性は排除できません。

# startup.sh
`startup.sh`はDatalabインスタンスをカスタマイズするために提供されている方法です。PyPIパッケージを追加するといった利用が想定されているようです。

[Adding Python libraries to a Cloud Datalab instance  |  Google Cloud Datalab Documentation  |  Google Cloud Platform](https://cloud.google.com/datalab/docs/how-to/adding-libraries)

Datalabインスタンス(コンテナ)内の`/content/datalab/.config/startup.sh`に任意のシェルスクリプトを作成することでDatalabインスタンス立ち上げ時に`startup.sh`が実行されます[^3]。スクリプトは次のようなコードをNotebookで実行することで作成できます。

```bash
%%bash
echo "pip install lib-name" >> /content/datalab/.config/startup.sh
```

[^3]: https://github.com/googledatalab/datalab/blob/master/containers/datalab/content/run.sh#L107-L114

# 要件・方針
以上を踏まえ、要件は次のようになります。

* Dockerイメージはカスタマイズしない
* Datalabインスタンスから扱えるGitレポジトリはGitHubのもののみとする
* プロジェクト内の全Datalabインスタンスは単一のGitHubレポジトリを使用する
* 誰がDatalabインスタンスを立ち上げても環境を再現できる

実装の方針は次のようになります。

* できるだけシンプル、安全に
* `startup.sh`を使う
* GitHubへの認証にはレポジトリのDeploy keyを使う
* Deploy keyの暗号化には[Cloud KMS](https://cloud.google.com/kms/)を使う

Deploy keyを使うのは、個人のTokenだと権限がある人であればVMに入って盗み見ることが可能だからです。

この記事では必要最小限の手順のみ紹介します。

# 構築手順
## 準備
以下の準備をしておいてください。

* GCPのプロジェクトの用意
* KMS APIの有効化
* Compute Engine APIの有効化
* `jq`コマンドのインストール

## 環境変数設定
プロジェクト名やレポジトリ名を予め環境変数に設定しておきます。ここでは次のような設定を使用します。

```bash
export PROJECT=datalab-with-github-test
export ZONE=asia-northeast1-a
export KEYRING=testkeyring
export KEYNAME=testkey
export DATALAB_NAME=testlab
export REPOSITORY=nownabe/datalab-with-github-test
```

## gcloudセットアップ
`gcloud`はインストール済み、認証済みとします。インストールがまだの方はドキュメントを参考にインストールと設定をしてください。

[Cloud SDK のインストール  |  Cloud SDK のドキュメント  |  Google Cloud Platform](https://cloud.google.com/sdk/downloads?hl=ja)

プロジェクトとゾーンを設定しておきます。

```bash
gcloud config set project ${PROJECT}
gcloud config set compute/zone ${ZONE}
```

## datalabコマンドインストール
Datalabを扱う`datalab`コマンドは別途インストールする必要があります。

```bash
gcloud components install datalab
```

## Deploy Key生成
Deploy keyを生成してGitHubのレポジトリに追加します。

```bash
ssh-keygen -t rsa -b 4096 -N '' -f id_rsa
cat id_rsa.pub
```

生成された`id_rsa.pub`をレポジトリのDeploy keyとして追加してください。**また、そのとき"Allow write access"にチェックをいれてください。**

レポジトリのSettings > Deploy keysから追加できます。

## Deploy Key暗号化
Cloud KMSを使ってDeploy keyを暗号化します。

まずはKMSのKeyRingを作成します。

```bash
gcloud kms keyrings create ${KEYRING} --location global
```

暗号化キーを作成します。今回はローテーションしないようにします。

```bash
gcloud kms keys create ${KEYNAME} --location global --keyring ${KEYRING} --purpose encryption
gcloud kms keys remove-rotation-schedule ${KEYNAME} --location global --keyring ${KEYRING}
```

また、Datalabインスタンスから復号できるようにGCE標準のサービスアカウントに復号の権限を付与します。

```bash
gcloud kms keys add-iam-policy-binding ${KEYNAME} \
  --location global \
  --keyring ${KEYRING} \
  --member "serviceAccount:$(gcloud iam service-accounts list | grep "Compute Engine default service account" | awk '{ print $NF }')" \
  --role "roles/cloudkms.cryptoKeyDecrypter"
```

Deploy keyの秘密鍵を暗号化して`id_rsa.encrypted`に保存します。

```bash
curl -s -X POST \
  "https://cloudkms.googleapis.com/v1/projects/${PROJECT}/locations/global/keyRings/${KEYRING}/cryptoKeys/${KEYNAME}:encrypt" \
  -d "{\"plaintext\":\"$(<id_rsa base64)\"}" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "Content-Type: application/json" \
  | jq ".ciphertext" \
  | tr -d '"' \
  > id_rsa.encrypted
```

`cat id_rsa.encrypted`で暗号化されたことを確認できます。

## startup.sh作成Notebookコードの生成
`startup.sh`スクリプトは若干複雑なので生成スクリプトを用意しています。

次のコマンドを実行すると`startup.sh`を生成するためのNotebook用のコードが出力されるのでコピーしてください。

```bash
curl -s https://raw.githubusercontent.com/nownabe/datalab-with-github/master/template.sh | bash
```

## Datalab作成
Datalabのインスタンスを作成します。

```bash
datalab create ${DATALAB_NAME}
```

しばらくしてインスタンスが立ち上がると自動でブラウザが開きます。開かない場合は `http://localhost:8081/` にアクセスしてください。

## startup.sh作成
Datalabにアクセスできたら`/datalab/notebooks`配下に新しいNotebookを作成してください。

先程のスクリプトを貼り付けてRunをクリックしてください。

これで`/content/datalab/.config/startup.sh`が作成されました。

今はまだSource Repositoriesの`datalab-notebooks`というレポジトリが参照されている状態ですが、この状態でインスタンスを再起動するとGitHubのレポジトリが参照されるようになります。

## NotebookのPush
再起動する前に、今の状態をSource RepositoriesのレポジトリにPushしておきます。なぜなら、新しくDatalabインスタンスを立ち上げた人が簡単に`startup.sh`を使えるようになるからです。

まず、Notebook上部のメニューからNotebook > Save and CheckpointとしてNotebookを保存します。

次にDatalab上部メニュー右側のGitアイコンからGitクライアントを開きます。

![スクリーンショット_2017-09-26_20_44_49.png](https://qiita-image-store.s3.amazonaws.com/0/43208/5021023d-ac68-4d46-c098-77dc129fce15.png)

適当なメッセージを入れてNotebookをコミットします。

![スクリーンショット_2017-09-26_20_47_27.png](https://qiita-image-store.s3.amazonaws.com/0/43208/176dc888-8e5d-52b5-4d2e-b6b0beff2410.png)

コミットしたらPushします。「master」をクリックすると「Push」ボタンが現れます。

![スクリーンショット_2017-09-26_20_49_10.png](https://qiita-image-store.s3.amazonaws.com/0/43208/444a637b-3eb6-3899-77ff-f09f80ca5a89.png)

これでPushされました。Web ConsoleのSource Repositoriesでも確認できます。

これで、あとからDatalabインスタンスを立ち上げた人も簡単に`startup.sh`を利用することができます。

## インスタンス再起動
再起動して`startup.sh`を実行します。
Datalab上部メニューの人アイコンからAbout Datalabをクリックします。

![スクリーンショット_2017-09-26_21_00_41.png](https://qiita-image-store.s3.amazonaws.com/0/43208/c9985810-da34-9e22-33d3-1c91b8959112.png)

Restart Serverというリンクをクリックします。

![スクリーンショット_2017-09-26_21_02_04.png](https://qiita-image-store.s3.amazonaws.com/0/43208/cd6aa47a-0140-ca95-a181-a581f817002c.png)


うまく再起動されない場合はVMを直接再起動してしまいましょう。

```bash
gcloud compute instances reset testlab
```

## 確認
しばらくすると、再起動されてDatalabに再びアクセスできるようになるのでブラウザで`http://localhost:8081/`を開きます。

このようにGitHubのレポジトリが参照されているはずです。

![スクリーンショット 2017-09-26 21.08.02.png](https://qiita-image-store.s3.amazonaws.com/0/43208/c5e8de84-0e4d-cafd-ffc0-1865fa1b4168.png)

先程と同じように新しいNotebookを作成してDatalabからPushするとGitHubに反映されます。

![スクリーンショット_2017-09-26_21_23_14.png](https://qiita-image-store.s3.amazonaws.com/0/43208/62aac375-3448-71b1-8a02-e7059f480e5d.png)

以上でGitHubレポジトリを使ったDatalabの構築が完了しました。

# 新しいDatalabインスタンス
新しいDatalabインスタンスから同じGitHubレポジトリを使うには最初に作成したNotebookを利用します。

確認してみましょう。

まず、最初に立ち上げたDatalabインスタンスへの接続を解除します。ProxyしているプロセスをCtrl+Cで殺してください。

新しいDatalabインスタンスを作成します。

```bash
datalab create testlab2
```

立ち上げた直後はまだSource Repositoriesのレポジトリを参照しています。初回の構築時とは異なり`startup.sh`を生成するNotebookがあります。

初回と同じようにこのNotebookを実行して`startup.sh`を生成し、インスタンスを再起動してください。

再起動が完了すれば同じGitHubレポジトリを参照できているはずです。

![スクリーンショット_2017-09-26_21_24_22.png](https://qiita-image-store.s3.amazonaws.com/0/43208/949cf383-e586-c7bb-1419-fcdd69397c75.png)

# startup.shの解説
最後に、簡単に`startup.sh`の内容を説明をします。

ざっくりと次のことを順番に行っています。

* 新しいGitをインストール
    * Datalabに標準で入っているGitクライアントが古く`core.sshCommand`を使えないため
* Deploy keyの復号
* Source Repositoriesのレポジトリを削除して、同じ場所にGitHubのレポジトリをclone
* `startup.sh`自身を削除
    * GitHubレポジトリの方で本来の使い方の`startup.sh`を管理できる

# おわりに
紹介した方法を利用すると専用のサービスアカウントを作成してさらに厳しくしたり、インスタンスごとに参照するレポジトリを変えたりということもできます。

なにかとSource Repositoriesではなく普段使っているGitサーバを使いたいことが多いと思うので、そのときは是非試してみてください。