White Box技術部

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

PythonでのAWS Lambda開発メモ(利用開始編)

Lambdaを使うことになった経緯

Lambdaの利用経験がなかった私が、Lambda利用を決めた流れは

  1. 1日1回のS3のファイルチェック処理が必要になる
  2. ファイルがなかったらSlack通知してくれればいい(簡単なスクリプトでOK
  3. 今のところ、AWS上にサーバは立ってない
  4. 調べてみたらLambdaもスケジュール実行できるらしい
  5. ならインフラコストを考えるとLambdaがいいか

でした。

Lambdaで利用する言語については、JupyterやGlueと合わせてPythonに。

LambdaやPythonの学習コストは、自分が払って展開すれば大したことにはならないだろうという見込みもあり、

「ついに私もサーバレスか・・・」

と感慨深く利用を始めました。

Lambda利用のために何をやったらいいのか?

Lambdaはマネージドサービスなので、AWS Console内で作業ができるようになっています。 エディタも用意されており、そこのテンプレートコードにあるlambda_handle関数に処理を書けば、指定のトリガーで実行される仕組みです。

言えば簡単なのですが、以下を懸念していました。

  • Pythonのライブラリ利用について
  • コード管理
  • ログ管理
  • テスト実行(テストコードではなく)
  • デプロイの自動化

なので、今回はこれらについて記事にしようと思います。


Pythonのライブラリ利用について

Lambdaを利用した際に、最初から用意されているライブラリはBoto3で、それ以外を利用したい場合は、利用するPythonのコードと一緒にzipでまとめて、アップロードする必要がありました。

今回はSlack通知の処理部分にrequestsを使いたかったので、zipでまとめてアップロードしています。

コード管理

そんな事もあって、幸か不幸かLambdaのコンソール上だけでは完結しなかったため、Lambdaプロジェクト用のGitHubリポジトリを作成し、コードは当該リポジトリ配下で記述するようにしました。

ディレクトリ構成はLambdaコンソールを参考に、リポジトリルート直下に関数名のディレクトリを作成して、そこにコードを置き、リポジトリルート直下には以下のようなシェルファイルを作成して、アップロード用のzipを作る、といった具合の構成です。

rm -rf upload.zip
pip3 install requests -t ./someLambdaFunction
cd someLambdaFunction
zip -r ../upload.zip *

しくじりポイント

zipは展開後が関数名のディレクトリになるのではなく、ソースコードの配置になっていないと駄目でした。

Lambdaコンソール上では、例えばsomeLambdaFunction/lambda_function.pyのような構成になっているので、てっきりsomeLambdaFunctionディレクトリもzipに含めるのかと思っていましたが、含めてしまうとsomeLambdaFunction/someLambdaFunction/lambda_function.pyのようになり、動作しなくなります。

環境変数の利用

WebhookのURLなどは環境変数を利用することで、コードから除去することができます。

ログ管理

ログについては標準出力に出しさえすれば、特に設定もなくCloudWatch Logsで見れました。

テスト実行(テストコードではなく)

関数のテスト実行はLambda上でテスト用のトリガーを作成して、それを実行すればできます。

処理内部で外部パラメータを利用しないのであれば、トリガーで渡すパラメータはデフォルトのままで問題ありません。

ローカルで開発・実行できるように準備もしたのですが、アクセスキーの発行を渋られて、結局Lambda上でのみ動作テストすることになってしまいました。。

デプロイの自動化

デプロイは少し曲者で、AWSのマネージドで完結させることはできなそうでした。

最初はCodeDeployでいけるかと思ったのですが、CodeDeployはデプロイされているLambdaのバージョンを切り替えることしかできないようでした。 CloudFormationを使えばいけるのかもしれませんが、ちょっとイメージしているのとは違うというか、CIと流れるように実行したかったといいますか。

lambda-uploaderを使えばできそうだったのですが、アクセスキーが・・・
AWS Lambda Pythonをlambda-uploaderでデプロイ | DevelopersIO

というわけで、これはまだ手動です。

Lambdaを使ってみて

「思っていたより簡単に利用できてびっくりした」というのが素直な感想です。

サーバの用意もいらないし、実行のコスト(金額的な意味で)もほとんどかからないしで、いいとこづくめだなと。 でもその恩恵に預かれるのはLambdaを知っている人だけなんだなぁと思うと、読みかけのサーバレス本をちゃんと読んで、もっと使いこなせるようになっておこうという気になりました。

知は力なりとは言ったものですね。

ログ出力指針の書き方

アプリケーションのログ出力指針を作ったときに、どんな事を考えていたのかを思い出しながら、 ログ出力指針について書いていきたいと思います。

ログ指針作成にあたって

そもそもなぜログが欲しいのか?何に利用するのか?

これに対する答えを置いておかないと、内容がぶれてしまうため、最初は目的を定義しました。

目的の作成

目的としてすぐ思いついたのは障害時対策のような守りの要素でしたが、 ビッグデータに取り込んで学習に使うというような攻めの要素にも使いたかったので、 目的には両方を明記しました。

目的を定めたあとは、どのタイミングでログが出力されていればいいのか、必要となる情報はなんなのか、解析しやすいフォーマットは・・・

のように思考が進んだのですが、作成した指針もどんなときにも利用できるというわけでもないだろうし、 無条件に鵜呑みにされると再度考察する機会がなくなると思い、この指針の対象とする範囲を定義しました。

適用範囲の設定

適用範囲を定めたことで、範囲外のログはどういう扱いにするか?という課題が出てきたのですが(例でいうとサーバログ)、 このときは「自分たちはここの範囲のログを整理することとして考えています。他は私たちが持ちますか?そちらで持ちますか?」というように 関連するチームに聞いて調整をしました。

もしサーバログも持つことになっていた場合は、ログ指針別で定義するのではなく、これを拡張していたと思います。

どんなときログを埋め込むのか?または埋め込まないのか?

あとはレビューでも客観的にチェックができるように、ログの埋め込むポイントを定義しました。

出力禁止項目も定義することで、出しては駄目な項目がないかを、このタイミングでチェックしたり話し合ったりしていました。

ログのフォーマットは?

フォーマットに関しては扱いやすさを考慮して設定しました。

解析時にはLTSVが便利だったので、ファイルにはLTSV。
開発中のコンソールは見れればいいということでTSVにしました。

細かい出力順などは利用するフレームワークに基本倣うようにしています。

出力項目については、出せるもの、できれば出したいものなど、とりあえず列挙し、適時選定していこうというスタイルを取りました。

ログの保持方法

ここらへんまできて、アプリケーションとしてどういうものになるのかを置かなければいけないと思っていました。 Fluentdで収集するのか、保持期間はどうするのか、アプリケーション起動にはコンテナを使うのか、起動するインフラ環境はオンプレなのかクラウドなのか、などなど。

このときはインフラ担当者と作業責務が分かれていたため、何をどっちが担当するのかを一つずつ確認しながら行う必要があり、けっこう大変でした。

そしてそろそろ面倒くさいなーと思い、まとめに入ったログ指針が以下のようなものです。


ログ出力の目的

以下の目的のため、ログ出力を行います。

  • 攻撃や事故の予兆を検知し、早期対策するため
  • 攻撃や事故の事後調査のため
  • アプリケーションの運用監査のため
  • 行動ログから、アプリケーション改善の指針を定めるため

ログ出力要件

ログ出力の要件を記載します。

本設計の範囲とするログの種類

本ログ出力指針は、アプリケーションが出力するログを対象とします。

サーバへのアクセスログは対象外としますが、アプリケーションからDBへのアクセス(CURD)は対象とします。

ログに記録するべき内容・イベント

ユーザが画面上で行うイベントと、認証・アカウント管理や重要情報へのアクセスに関するイベントを記録してください。 特に個人情報に対してのアクセスは必ず記録するようにお願いします。

またログはユーザIDやユニークIDなど、行動実施者とイベントが紐付けれるように記録してください。

認証・アカウント管理や重要情報へのアクセスに関するイベント
  • ログイン・ログアウト(失敗も含む)
  • アカウントロック
  • ユーザ登録・削除
  • パスワード変更
  • 重要情報の参照
  • 主だった操作(商品購入、送金、メール送信など)

出力禁止項目

以下の内容はログに出力しないようにしてください。

  • パスワード
  • OAuth関連のキー情報
  • 個人情報

ログ出力項目

ログには、4W1H(いつ、誰が、どこで、何を、どのように)に従って、以下の項目を出力します。

  • 処理日時
  • アクセス元情報(ユーザIDなど)
  • アクセス対象(URL、ページ名、スクリプトIDなど)
  • 操作内容(閲覧、変更、削除など)
  • 捜査対象(リソースID、カテゴリIDなど)
  • 操作結果(成功・失敗、処理件数など)

また、アプリケーションのクラッシュ情報、Stacktraceなどを出力します。

ログフォーマット

LTSVで出力します。

ただし、コンソールにはTSVで出力します。

出力項目
項目 キー オプション
ログの出力日時 date yyyy-MM-dd HH:mm:ss.SSS
スレッド thread
ログレベル level
ロガーの名前 logger
ユーザID user
セッションID session
アクセスURL uri
ログメッセージ message
サンプル
date:2018-10-20 19:09:42.216 thread:reactor-http-nio-3   level:DEBUG logger:jp.sample.api.search.repository.ItemSearchRepository message:アプリケーションで設定するログメッセージ

ログ出力先

アプリケーションのログは、/var/log/app配下にアプリケーション単位で出力します。

例えばproduct-name(アプリケーション名)の場合は、/var/log/app/product-nameに用途別でログが出力されます。

ログファイル一覧

ログファイル名 用途
application.log アプリケーションが出力するログ
application_error.log アプリケーションログのERRORレベルのみ
gc.log JavaアプリケーションのGCログ
java_error%p.log Javaアプリケーションが異常終了した場合のログ(プロセス単位)
heap/dump.log ヒープエラーのダンプ

アプリケーションログのローテーション

アプリケーションログは日単位でローテーションします。

ログの保存期間

アプリケーションサーバ上では最低1週間、最大1ヶ月保持します。 不要になったログ(過去ログ)の削除は、アプリケーション側では実施せず、サーバ側(logrotate)で実施します。

その他、CloudWatchにログを転送し、長期保存を行います。

ログレベルの利用基準

ログレベル 意味合い 用途
FATAL 回復不可能なエラー 利用しない
ERROR エラー 想定外の処理の発生(システムエラー)に関する情報を出力する。Exception情報がある場合は、Stacktraceも本レベルで出力する。
WARN 警告 システム上エラーではないが、通常発生し得ない(発生を期待していない)処理の発生を記録する。業務処理のエラーを記録する場合も、本レベルで出力する。
INFO 情報 各種イベントの発火・実行の事実を出力する(行動ログ)
DEBUG デバック用の情報 開発時に参考となる情報を出力する。パラメータ値を出力する場合は、基本的にDEBUGとする。
TRACE デバッグよりも詳細な動作トレース 利用しない
Stacktraceの出力タイミング

エラーが発生した場合のStacktraceは、発生箇所で都度出力するのではなく、 エラー処理を実際に行う終端で出力します。

参考

LTSVの活用

LTSVはTSVでもあるので、タブ区切りの場所さえわかれば、以下のようにcutでデータを取得することが出来ます。

$ tail -n 10 application.log | cut -f 5

LTSVはキーがついているので、grepで対象のキーを指定して取得することができ、

$ egrep -o "message:[^タブ文字]*" application.log

※タブ文字はCtrl-vを入力後、Tabキーを押下することで入力できます

以下のようなPerlワンライナーで意図したキーのみを抽出し、好きにフォーマットすることも出来ます。

$ cat application.log | perl -F'\t' -nale '%h=map{split/:/,$_,2}@F;print"$h{date}\t$h{thread}\t"'



終わりに

いかがでしょうか?未完成なところも目につくと思います。

ですがこういうものは作ってそのまま見られなくなるか、また日の目を浴びたときに古くなっているということでそのまま捨てられたりするものだと思うので、 完璧なものでなくても良いと個人的には思います。

ではなぜログ指針の話をしたかというと、このような指針を元に、 自分たちのチームはどう考えてシステムを作っていくのかの意識合わせを行い、観念を共有することがシステム開発では一番大事だと思っているからです。

何もなければ考えをすり合わせるのも難しいので、適時このように課題への解決案を形にしながら、チームでチームのベースを作っていくのがいいのではないでしょうか。

それに捨てられず、ずっとメンテナンスされて、新しいメンバーがチームを知るために使える資料になっている可能性もないわけではないですからね。

参考文献

  • 安全なWebアプリケーションの作り方
  • 仕事ではじめる機械学習

【kotlin】Coroutinesを使って、Spring WebFluxでJDBC処理を行う

ちょっと記事を寝かせすぎてしまったのですが、今回は1.3でKotlin本体に入ることが決まったコルーチンのお話です。


WebFluxでJDBCを使うには

以前の発表資料にも記載したのですが、

Spring Boot 2から使えるSpring WebFluxは、Spring MVCと違い、ノンブロッキングなWebフレームワークなので、 ブロッキング処理として作られているJDBCを使うには一工夫必要です。

Schedulers#elasticで対応

解決策としては、(この言い回しで合っているのかは自信がないのですが)JDBC処理をSchedulerのelasticでサブスクライブさせ、適時Workerに処理して貰うようにすることで、処理不整合を起こすことなく動かすことができます。

実際、プロダクトでWebFluxを使ったときはこの方法で実装し、アプリケーションに対しGatlingで負荷をかけても、この部分ではKOにならなかったため、期待した動作をしているようでした。

この方法の問題点

しかしこの方法で実装すると、Handlerで応答レスポンスを作るまでは、データをMonoかFluxで持ち回る必要が出てきます。

戻り値の型がMonoなどになっていると、メソッドの使い回しがしづらく、呼び出す際もブロッキングを考慮しないといけないため、そこそこの実装難易度となってしまい、結果、kotlinを使っていても理解しづらいコードになってしまいました。

これがどうにも気に入らずもやもやしていたのですが、JJUGのナイトセミナーでコルーチンの存在を知り、「コルーチンを使えば良い感じに書けるのでは?」と光明を見出し、コルーチンを使った実装を試してみました。

コルーチン

コルーチンは軽量スレッドとよく説明されますが、私はスレッド内でJavaScriptの非同期処理を実現する仕組みと理解しています。なのでブロッキング処理を非同期処理として実行するのにコルーチンが使えるのであれば、これで先ほどの問題を解決できるのではないかと考えた訳です。

その他の説明としては、ここの中断可能な計算インスタンスという説明がイメージしやすかったです
https://qiita.com/k-kagurazaka@github/items/8595ca60a5c8d31bbe37

コルーチンの導入

コルーチンはまだKotlin自体には組み込まれていないので、利用する場合はライブラリのkotlinx-coroutines-coreを追加する必要があります。

Gradleプロジェクトの場合は、以下のように追加します。

dependencies {
    compile('org.jetbrains.kotlinx:kotlinx-coroutines-core:0.22.5')
    //...
}

WebFluxのリクエストをコルーチンで処理

例えば、リクエストでIDを受け取り、それをキーにRDBからデータを取得して、必要なデータを返すAPIをWebFluxとコルーチンで実装すると、以下のようになります。

package box.white.seriwb.api

import kotlinx.coroutines.experimental.async
import kotlinx.coroutines.experimental.runBlocking
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.MediaType
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import org.springframework.web.reactive.function.server.ServerResponse.ok
import reactor.core.publisher.Mono

@Component
class KotlinApiHandler {

    // ----- コルーチンのサンプル -----
    @Autowired
    lateinit var repository: KotlinApiRepository

    fun coroutineSelect(req: ServerRequest): Mono<ServerResponse> = runBlocking {

        val id = req.pathVariable("id").toLong()

        val responseData = async {
            repository.getSimpleResponseData(id)
        }

        ok().contentType(MediaType.APPLICATION_JSON_UTF8)
                .syncBody("{\"result\":\"${responseData.await().envelope}\"}")
    }
}

HandlerのcoroutineSelect関数は、JDBC処理(ブロッキング処理)がある以上、どこかで当該処理との待ち合わせ(ブロッキング)を行わないといけないのですが、 一方でこの関数全体がノンブロッキングである必要があります。 このため、Handlerの処理全体をコルーチンの処理として扱い、呼び出し元から見れば一つの処理となるようにしてあります。

具体的には、runBlockingで囲まれた部分がコルーチンで動作するので、

  1. Handler全体をrunBlockingで囲い、
  2. ブロッキング処理となるJDBCアクセスの処理は、asyncブロック内で呼び出し、
  3. その結果を利用するときにawaitメソッドを使うようにします。

今回はasyncが1つですが、2つ以上の場合は、asyncブロック内の処理結果が実際に必要になるところでawaitをまとめて呼び出すと、asyncの処理が並列で実行されます。

次はJDBC側の処理を見てみましょう。

Suspending関数を使う関数もSuspendingに

asyncブロック内で呼び出していたJDBC処理のgetSimpleResponseData関数は、Suspending関数とするため、suspend修飾子を付与して宣言します。

package box.white.seriwb.api

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Transactional

@Repository
class KotlinApiRepository {

    @Autowired
    lateinit var dao: KotlinApiDao

    @Transactional
    suspend fun getSimpleResponseData(id: Long): SimpleResponseData {
        val sample = dao.findSample(id)

        return SimpleResponseData(sample.value)
    }
}

data class SimpleResponseData(
    val envelope: String
)

どうしてSuspendingにするかというと、この処理は実際のJDBC処理となるfindSample関数を呼び出しているのですが、 このfindSample関数がSuspending関数だからです。 更に言うとSuspending関数を呼び出すのはSuspending関数内か、asyncブロック内である必要があるからです。

しかしSuspending関数にしたとはいえ、見て貰うとわかるように、 実際のJDBC処理となるDAOのfindSample関数を呼び出しているのにも関わらず、 suspend修飾子が付いている以外は普通の関数呼び出しの記述になっています。

つまりDAOを使うRepositoryの関数内では、Suspending関数も通常の関数呼び出しのように書けるのです。

ブロッキング処理はSuspending関数で

ではブロッキングが必要となる肝心要のDAOである、findSample関数はどうなるかというと、 こちらもsuspend修飾子を付けてSuspending関数にするだけで対応が終わります。

package box.white.seriwb.api

import box.white.seriwb.api.jooq.public_.Tables.SAMPLE
import box.white.seriwb.api.jooq.public_.tables.records.SampleRecord
import org.jooq.DSLContext
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Repository

@Repository
class KotlinApiDao {

    @Autowired
    lateinit var create: DSLContext

    suspend fun findSample(id: Long): SampleRecord {
        val result = create
                .selectFrom(SAMPLE)
                .where(SAMPLE.ID.eq(id))
                .fetch()
        return result.first()
    }
}

どうでしょうか、これであればKotlinのスッキリさを残したまま、WebFluxを利用できる気がしないでしょうか。

そしてコルーチンで利用する関数はSuspending関数として定義しているので、 通常の呼び出し方ができないことをコードとしても制約を課しているので、誤って呼び出されることを防止しています。

なんて便利なんでしょうか・・・

コルーチンとWebFluxでどうなったか

さて、これまでの話でコルーチンをWebFluxで使うことの利点を感じてもらえたかと思いますが、個人的に利点を上げると

ここらへんが大きなところかと思っています。

このようにコルーチンを利用するとWebFluxの勘所であるブロッキング処理を(そこそこ)簡単に扱えるので、 ここがWebFlux導入の障害となっていたのであれば、Kotlinとコルーチンを利用してみてはどうでしょうか?

【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フラグを付けると、一つのインスタンスをみんなで触ることもできるようです。

実際できました

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