White Box技術部

WEB開発のあれこれ(と何か)

【GCP】Datalabをチームで使うための導入手順

職場で

「他のアナリストと分析結果を共有しやすくして欲しい。というか共有のJupyter環境を用意して欲しい」

という話があったのですが、環境の制約で、すぐに共有のJupyterを用意することはできなそうだったので、Google Cloud Datalabを使ってもらうことにしました。

今回はその環境作りと使い方の話になります。


Google Cloud Datalab

f:id:seri_wb:20180620183106p:plain:w200

Datalabは、簡単に言うとGCP機械学習するのに適したJupyter Notebookを使えるサービスです。サービスといってもDatalab自体の利用には料金が発生しないので、GCEの機械学習テンプレートみたいなものでしょうか。

Datalabのインスタンスも一つを共有するのではなく、利用するユーザ分作成が必要なのが特徴です。

アイコンはここから入手しました。

Datalab構築手順

Datalabの構築は以下の公式ドキュメントを元に、一括で行いました。

1. Datalabを利用するユーザのメールアドレスを取得する

インスタンス生成時に利用するので、利用ユーザのGCPアカウントのメールアドレスを把握しておきます。

2. インスタンス名を決める

次に各ユーザのDatalabが動作する(GCEの)インスタンス名を決めます。
インスタンスがどのユーザのものかわかれば良いので、「prd-datalab-名字」で作成することにしました。

3. インスタンスを作成する

インスタンス作成の手順は、以下の通りです。

  • Google Cloud Shellを起動
  • プロジェクトがDatalabのインスタンスを作成する場所になっているかを確認し、なっていなければ変更
gcloud config set core/project プロジェクトID
  • ゾーンを設定
gcloud config set compute/zone asia-northeast1-a
datalab create --for-user 作成するユーザのメールアドレス インスタンス名

注意点

初回のdatalab createはCloud Source Repositoryにdatalab-notebooksリポジトリを作成するので、オーナー権限のあるユーザが実施する必要があります。もしくは事前にdatalab-notebooksリポジトリを手動作成しておけば大丈夫のようです。

4. 対象ユーザのIAMに以下の権限を付与する

Datalabを利用するユーザに、以下の権限を付与します。

f:id:seri_wb:20180620175544p:plain f:id:seri_wb:20180620175554p:plain

ドキュメントにはroles/iam.serviceAccountActorが必要とありますが、サービスアカウントユーザの役割を参照すると、以下のような記述があるため、これで動作します。

ユーザーに対してcompute.instanceAdmin役割をiam.serviceAccountUser役割と一緒に付与すると、そのユーザーはサービス アカウントを使用する Compute Engine インスタンスを作成および管理できるようになります。

5. (すぐに利用しないのであれば)インスタンスを停止する

Datalabのサービス起動後であれば、自動タイムアウトがありますが、createしただけでは動作していないため、作成したインスタンスは停止しておきます。

datalab stop インスタンス名

利用手順

Datalabはユーザ毎にインスタンスが必要なため、個別にインスタンスを作成しています。

接続方法

GCPのコンソールからGoogle Cloud Shellを起動します。

f:id:seri_wb:20180620175117p:plain

プロジェクトがDatalabのインスタンスを作成する場所になっているかを確認し、なっていなければ変更してください。

  • 変更する場合は、以下のように入力する
gcloud config set core/project プロジェクト名

あとは以下の起動コマンドを実行すると起動します。

datalab connect インスタンス名

初回実行時は、ホストの登録と鍵登録の問い合わせがあるので、適切に許可してください(こだわりがなければノンパスでOK)。

後はGoogle Cloud Shellの右上にあるウェブでプレビューを使い、ポートを8081に変更して起動するとDatalabが利用できます。

f:id:seri_wb:20180620175231p:plain

*左側のメニューを表示していると、ウェブでプレビューボタンが隠れていることがあるので、その場合はメニューを閉じてください

停止方法

Datalabの停止はいくつかの手順があり、いずれかで実施してください。

  • Datalab(Jupyter)の右上のメニューからVMをストップする
  • Google Cloud Shellから停止コマンドを入力する
datalab stop インスタンス名

Datalabを使う

Jupyterと同様、以下のような流れで利用することができます。

  1. Notebookを作成後
  2. コードブロックを追加し
  3. コードを書いて
  4. 実行して
  5. 結果を確認

f:id:seri_wb:20180620183341p:plain:w500

Notebookの共有

作成したNotebookを他のメンバーと共有したい場合は、以下の手順でリポジトリにコミットします。

git管理されるのは/datalab/notebooks配下なので、共有するNotebookを作成する場所に気をつけてください。

共有を受ける側のメンバーは、ungitを使い、fetch + mergeで自身のgitリポジトリを更新することで、他のメンバーが作成したNotebookを取得することができます。

f:id:seri_wb:20180620191843p:plain:w500

グラフタイトルの日本語化

デフォルトのままだとグラフタイトルに日本語が使えないので、日本語フォントをなんらかの方法で設定してください。

設定はNotebookを利用して、
例えば以下のようにリポジトリ配下に置いて利用したり

import matplotlib.pyplot as plt
import matplotlib as mpl
import matplotlib.font_manager as fm
import sys
import urllib.request

reload(sys)
sys.setdefaultencoding('utf-8')

urllib.request.urlretrieve("https://github.com/byrongibson/fonts/blob/master/backup/truetype.original/takao-gothic/TakaoPGothic.ttf?raw=true", 'TakaoPGothic.ttf')

prop = fm.FontProperties(fname='./TakaoPGothic.ttf')

plt.plot([1,23,2,4])
plt.ylabel('some numbers')
plt.title('日本語', fontproperties=prop)
plt.show()

Pythonのフォントディレクトリに配置して利用したりすると、日本語で表示することができます。

  • 対象のフォントを.fontディレクトリに配置(実施後はDatalab再起動)
!mkdir -p ~/.fonts/
!cp TakaoPGothic.ttf ~/.fonts/
!fc-cache -fv
!rm -rf ~/.cache/matplotlib/
  • グラフの確認
import matplotlib.pyplot as plt
import matplotlib as mpl
import matplotlib.font_manager as fm

font = {"family": "TakaoPGothic"}
mpl.rc('font', **font)

plt.plot([1,23,2,4])
plt.ylabel('some numbers')
plt.title(u'日本語')
plt.show()

f:id:seri_wb:20180802172300p:plain:w400

Rを使うには

Rを使う方法もありそうなのですが、本家にプルリクが出ているので、これがマージされるのを待てるのであれば待ったほうがいいかもしれません。

ちなみに

別のユーザのインスタンスを起動させようとすると、以下のようなエラーメッセージが表示されます。

$ datalab connect 別のユーザ用のインスタンス
The specified Datalab instance was created for 本来のユーザのメールアドレス, but you are attempting to connect to it as 自分のメールアドレス.
Datalab instances are single-user environments, and trying to share one is not supported.
To override this behavior, re-run the command with the --no-user-checking flag.

つまり、Datalab起動時に--no-user-checkingフラグを付けると、一つのインスタンスをみんなで触ることもできるようです。

実際できました

ただ、編集がコンフリクトすることも考えると、やはり個別にインスタンスを用意するのが良いと思います。

【TypeScript】DangerのTSLintプラグイン紹介と導入の補足

Rubyの方のDangerTSLintに対応させたかったので、ESLintのDangerプラグインforkをforkして、DangerのTSLintプラグインを作りました。

danger-tslintの作成経緯

最初はDanger-jsの方のTSLintプラグインを導入したのですが、Dangerでいいなぁと思っていたソースコード中へのコメント追加ができないようだったので、導入は見送って自分で作ることにしました。

せっかく自作するので、自分が必要なオプションはふんだんに盛り込んであります!(๑•̀ㅂ•́)و✧

danger-tslintでできること

README.mdに記載してあるように、以下が実施できます。

  • tslintのインストール先(実行パス)の指定
  • tslintで使用するコンフィグファイルの指定
  • tslint対象とするファイルの指定
  • tslint対象外とするファイルの指定
  • tslintを実行するTypeScriptのプロジェクトディレクトリの指定
  • tslint対象を新規ファイルと更新ファイルに絞るかどうかの指定
    • 絞った場合にtslint対象とするファイルの形式
  • tslintの実行

※オプションの詳細などはこちらも参考にしてください。

danger-tslint利用手順

まずはプロジェクトにGemfileを作成し、以下の内容を記載します。

source 'https://rubygems.org'

gem 'danger'
gem 'danger-tslint', :git => 'https://github.com/seriwb/danger-tslint.git', :branch => "master"

danger-tslintの利用は、

  1. Gitリポジトリのメインの構成がTypeScriptプロジェクトで、
  2. CIの実施タイミング(bundle exec danger)の前に、npm iyarnによりtslintがインストールされており、
  3. tslintのチェック対象をプラグインの設定で絞らない

という条件であれば、Dangerfileにtslint.lintを記載するだけで十分です。

  • Dangerfile
tslint.lint

そしてJenkinsなどのCIサーバ上で、対象プロジェクトのテスト時に、以下の処理が実施されるようにしてください。

npm i
bundle install
bundle exec danger

danger-tslintのオプション解説

ここからは、Spring Bootで構成されたリポジトリのサブディレクトリが、TypeScriptのプロジェクトとなっている場合、

つまり、プロジェクトルート直下のsrcディレクトリはJavaなどの言語用、サブディレクトリ(ここではclient)配下のsrcディレクトリがTypeScript用となる以下のような構成を例にとってオプションの解説をしていきます。

.
├── client
│   └── src
└── src

Dangerfileは、.gitディレクトリのあるプロジェクトルート直下に置く必要があるので、TypeScriptプロジェクトのパスと、tslintの実行パスを以下のように指定する必要があります。

tslint.executable_path = 'client/node_modules/.bin/tslint'
tslint.project_directory = 'client'
tslint.lint

こうすることでproject_directoryのパス配下にtsconfig.jsonやtslint.jsonがあれば、それらが読み込まれた状態でtslintが実行されます。

filteringオプション利用時の注意

filteringオプションを利用すると、tslint対象がプルリクでの対象ファイルに限定されます。

しかし、このオプションを利用した場合、tsconfig.jsonのincludeに当てはまらないTypeScriptのファイルがプルリクで追加されると、 tslintがXXXXX.ts is not included in project.のようなエラーを出すため、以下のようにincludeのファイルとマッチするようにfile_regexを指定する必要があります。 (target_filesはtsconfig.jsonでincludeが指定されていれば、無くても大丈夫なはずです)

tslint.filtering = true
tslint.executable_path = 'client/node_modules/.bin/tslint'
tslint.project_directory = 'client'
tslint.target_files = 'client/src/**/*.{ts,tsx}'
tslint.file_regex = /client\/src\/.*\.tsx?$/
tslint.lint

target_filesにはシェルにおける文字一致のパターンを、file_regexにはRubyにおける正規表現のパターンを記載する必要があり、これらはマッチ内容が一致していると概ね良いと思います。

ちなみにコンフィグファイルは、プロジェクトディレクトリ配下に配置してあるのであれば、プロジェクトディレクトリだけの指定で大丈夫です。

むしろ指定すると私のプロジェクト構成では動かなかったような・・・

ちょっとクセのあるfilteringオプションですが、filteringオプションを有効にしなくても、Dangerのgithub.dismiss_out_of_range_messagesオプションを使えば、プルリク外のファイルにコメントは付かないので、用途によって使い分けてみてください。

初めてまともにRubyのコードを書いてみましたが、なんとか動いて良かったです。

テストは書いてないし、gemに登録もしていないのですが、自プロジェクトではそれっぽく動作していたので、DangerでTSLintを使う際にご利用ください。

Kotlinを使ったWEBアプリケーション開発の始め方

先日、とらのあな主催のKotlin勉強会で、Kotlinを使ったWEBアプリケーション開発の始め方という題で発表してきました。

ちょっと時間が押していたので発表は巻きになってしまったのですが、Kotlinで開発を始める後押しができていたら幸いです。

といっても、Java資産周りの技術解説いりますよね、本当に始めるなら

Kotlinのリフレクション(protected/privateメソッド呼び出し)

protectedメソッドへのアクセス

Kotlinにおいてprotectedのアクセス修飾子は、Javaと異なり同一パッケージからのアクセスを許容しません。 そのため、テストコードなどでprotectedのメソッドを実行したい場合は、リフレクションを使う必要があります。

継承してテストするという方法もありそうですが、今までしなかった発想なのでどうなんですかね。

リフレクションによる実行

例えばTestDataRepositoryにcreateSampleDataというLongのパラメータを1つ取り、SampleDataクラスを返却するprotectedのメソッドあるとした場合、以下が対象のメソッドを実行するコードになっています。

var testSuite = TestDataRepository()

val testMethod = testSuite::class.memberFunctions.find { it.name == "createSampleData" }
val actual = testMethod?.let {
    it.isAccessible = true
    it.call(testSuite, 100L)
}

コード解説

  1. まずは実行対象のメソッドがあるクラスのKClass(testSuite::class)から、
    KFunctionとして対象メソッドを取得(.memberFunctions.find { it.name == "createSampleData" })します
  2. KFunctionのインスタンスが確保できていれば(testMethod?.)、
    呼び出し結果をactualに保持するようにします(val actual = testMethod?.let {
  3. 実行するためにアクセスを許可し(it.isAccessible = true)、
    実行するインスタンスとメソッドに与えるパラメータ(100L)を指定して実行します(it.call(testSuite, 100L)

以上になります。

protectedのテストが面倒になった?

Javaの場合はprotectedメソッドであれば同一パッケージからすんなり呼べたので、テストが楽でよかったのですが、Kotlinの場合はちょっと工夫がいりそうです。 もうちょっと簡単な方法もありそうなのですが(PowerMockのWhiteboxクラス使うとか)、おいおい調べてみないといけないなという感じです。