SlideShare a Scribd company logo
複数アプリケーションの
プロセスとログを管理する
ための新しいツールと手法
谷津 真樹 (Masaki Yatsu)
PyCon JP
Sep 18, 2018
アジェンダ
• Jaffle: 開発支援ツールの紹介
• 開発の動機
• Pythonアプリケーションと外部プロセスの起動
/自動タスク実行
• 開発時のログ管理
• Jaffle設定の運用
• Jaffleのアーキテクチャと実装
https://jaffle.readthedocs.io/
https://github.com/yatsu/jaffle
Jaffle
• 複数のPythonアプリケーションと外部プロセスをまとめて起動・終了する
• ファイルの更新を検出してタスクの自動実行を行う (例: 自動テスト)
• ログ出力を統合し、発生時刻順に表示、フィルタリング、置換を行う
• Jupyter Kernel上でPythonコードを統合動作させるフレームワーク
(最後の実装のところで説明)
Jaffleとは
背景
• プロセスやログを管理するものは、プロダクシ
ョン環境で使えるツールやクラウド上のサービ
スは充実してきているが、手元の開発環境で手
軽に使えるものがない
• 近年の傾向として
• 連携するアプリケーションやサービスの数が増加し
てきている
• 個々のアプリケーションやサービスが出力する情報
も増加してきている
Pythonアプリケーションと
外部プロセスの起動/
自動タスク実行
事例1: TornadoとReactによるWeb開発
• Tornadoで実装されたWeb APIサーバーを立ち上げる
• 2つの外部プロセスを起動する
• “yarn start” (開発用Webサーバー)
• “jest” (JavaScriptテスト)
• .pyファイルを更新時に
• Web APIサーバー再起動
• pytest実行
• ログを統合して表示
https://jaffle.readthedocs.io/en/latest/cookbook/tornado_spa.html
Tornado (Webフレームワーク) でバックエンドWeb APIを
実装し、ReactでWebフロントエンドを開発する例
事例2: pytest自動テスト
• テストファイル test_*.py が更新されたら、そ
のテストを実行する
• 実装ファイル foo/bar.py が更新されたら、それ
に関するテスト tests/foo/test_bar.py を実行する
• pytestの実行が常にスタンバイ状態で、カレン
トディレクトリ以下のモジュールだけをリロー
ドするため、高速に動作する
• pytest実行前にキャッシュクリアは設定可能
https://jaffle.readthedocs.io/en/latest/cookbook/pytest.html
事例2: pytest自動テスト / 設定ファイル
kernel "py_kernel" {}
app "watchdog" {
class = "jaffle.app.watchdog.WatchdogApp"
kernel = "py_kernel"
options {
handlers = [{
watch_path = "pytest_example"
patterns = ["*.py"]
ignore_directories = true
code_blocks = ["pytest.handle_watchdog_event({event})"]
}]
}
}
app "pytest" {
class = "jaffle.app.pytest.PyTestRunnerApp"
kernel = "py_kernel"
options {
args = ["-s", "-v", "—color=yes"]
auto_test = ["pytest_example/tests/test_*.py"]
auto_test_map {
"pytest_example/**/*.py" = "pytest_example/tests/{}/test_{}.py"
}
}
}
ファイルシステム監視App
左側のパターンにマッチしたら右側のパターンのテストを実行
例: foo/bar.py → tests/foo/test_bar.py
パターンにマッチしたテストを実行
Jupyter Kernel (Pythonインタープリタ・プロセス) を定義
変数として参照する
実行方法:
$ jaffle start jaffle.hcl
設定ファイルがjaffle.hclだけの場合は省略可能:
$ jaffle start
上のようなjaffle.hclファイルを作成する
事例3: Sphinx自動ビルド / 設定ファイル
kernel "py_kernel" {
pass_env = ["PATH"]
}
app "watchdog" {
class = "jaffle.app.watchdog.WatchdogApp"
kernel = "py_kernel"
options {
handlers = [{
patterns = ["*/docs/*.*"]
ignore_patterns = ["*/_build/*"]
ignore_directories = true
jobs = ["sphinx”, “refresh”]
}]
}
}
job "sphinx" {
command = "sphinx-build -M html docs docs/_build"
}
job ”refresh" {
command = ”osascript browser_refresh.scpt"
}
docs/ ディレクトリ以下の .rst ファ
イルを更新すると、sphinx-build が
自動的に実行され、HTMLファイル
が生成される
Job名参照
コマンド実行はJobとして定義する
環境変数PATHをJupyter Kernelに引き継ぐ
virtualenvの場合に必要
tell application "Google Chrome" to tell the active tab of its first window
reload
end tell
(macOSの場合)
事例4: Jupyter Extension開発
https://jaffle.readthedocs.io/en/latest/cookbook/jupyter_ext.html
• .pyファイルが更新されたとき、
Jupyter Notebook Serverを再起動
• .jsファイルが更新されたとき、
“jupyter nbextension install” を実行
kernel "py_kernel" {
pass_env = ["PATH"]
}
app "watchdog" {
class = "jaffle.app.watchdog.WatchdogApp"
kernel = "py_kernel"
options {
handlers = [
{
patterns = ["*.py"]
ignore_directories = true
clear_cache = ["jupyter_myext"]
code_blocks = ["notebook.handle_watchdog_event({event})"]
},
{
patterns = ["*.js"]
ignore_directories = true
jobs = ["nbext_install"]
},
]
}
}
app "notebook" {
class = "jaffle.app.tornado.TornadoBridgeApp"
kernel = "py_kernel"
options {
app_class = "notebook.notebookapp.NotebookApp"
}
start = "notebook.start()"
}
job "nbext_install" {
command = "jupyter nbextension install jupyter_myext --user --overwrite"
}
Jupyter Notebookのサーバーサイドとフロ
ントエンドJavaScriptの両方を拡張する例
Jupyter Notebook ServerをJupyter Kernelから起動する;
Jupyter NotebookはTornado (Webフレームワーク)で実装
されており、JaffleのTornado IOLoop上で動作させること
ができる
開発時のログ管理
ログ管理の問題と解決方法
• 複数のログファイルから処
理の流れを把握するのが難
しい
• ログメッセージの数が多く、
目的のメッセージを見つけ
るのが難しい
• ひとつのログメッセージに
含まれるデータが大きく、
必要な情報を取り出すのが
難しい
すべてのログを統合して表示する
フィルタリング機能を使い、
必要のないメッセージを隠す
置換機能を使い、必要な情報だけ
取り出したり
色付けによる強調表示を行う
ログの統合
• すべてのログはまとめて発生時刻順に表示される
• ロガー名(App/プロセス単位)ごとに色がアサイン
され、識別しやすくなる
ログのフィルタリング
app "pytest" {
class = "jaffle.app.pytest.PyTestRunnerApp"
kernel = "py_kernel"
logger {
suppress_regex = [
"^platform ",
"^cachedir:",
"^rootdir:",
"^plugins:",
"collecting ...",
"^collected ",
]
}
}
pytestの出力から不要な行を隠す例
大きなデータをログ出力するときの問題
$ kubectl get services kubernetes -o json
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"creationTimestamp": "2018-09-06T13:09:41Z",
"labels": {
"component": "apiserver",
"provider": "kubernetes"
},
"name": "kubernetes",
"namespace": "default",
"resourceVersion": "18",
"selfLink":
"/api/v1/namespaces/default/services/kubernetes",
"uid": "1dc6394b-b1d6-11e8-859a-080027be08a0"
},
"spec": {
"clusterIP": "10.96.0.1",
"ports": [
{
"name": "https",
"port": 443,
"protocol": "TCP",
"targetPort": 8443
}
],
"sessionAffinity": "ClientIP",
"sessionAffinityConfig": {
"clientIP": {
"timeoutSeconds": 10800
}
},
"type": "ClusterIP"
},
"status": {
"loadBalancer": {}
}
}
service:
{"apiVersion":"v1","kind":"Service","metadata":{"creationTimestamp":"
2018-09-
06T13:09:41Z","labels":{"component":"apiserver","provider":"kubernete
s"},"name":"kubernetes","namespace":"default","resourceVersion":"18",
"selfLink":"/api/v1/namespaces/default/services/kubernetes","uid":"1d
c6394b-b1d6-11e8-859a-
080027be08a0"},"spec":{"clusterIP":"10.96.0.1","ports":[{"name":"http
s","port":443,"protocol":"TCP","targetPort":8443}],"sessionAffinity":
"ClientIP","sessionAffinityConfig":{"clientIP":{"timeoutSeconds":1080
0}},"type":"ClusterIP"},"status":{"loadBalancer":{}}}
すべて出力すると1行が長くなる:
labelsを見たい人はこのように出力したい:
{"labels":{"component":"apiserver","provider":"kubernetes"}
nameとclusterIPを見たい人はこのように出力したい:
service: {"name":"kubernetes”,"clusterIP":"10.96.0.1"}
見たい情報はコンテキストに依存する
ロガーを複数定義してそれぞれに出力ルールを決めるのは大変
アプリ起動時にロガーごとにレベルをセットするのも大変
ログの置換 / 値の取り出し [1/4]
app "my_app" {
logger {
replace_regex = [
{
from = "^service: (.*)$"
to = ”service ip: ${jqf('.spec.clusterIP', '1')}"
},
]
}
}
dictのパス .spec.clusterIP から値を取り出す
IPだけ取り出せる
JSONに対するjqコマンドと同等のことができる
ログの置換 / 値の取り出し [2/4]
app "my_app" {
logger {
replace_regex = [
{
from = "^service: (.*)$"
to = "ip: ${fg('blue')}${jqf('.spec.clusterIP', '1’)}${reset()}"
},
]
}
}
さらに色をつける
使える関数
• jq_all(): dict を jq した結果のリストを返す
• エイリアス: jq()
• jq_first(): dictを jq した結果の最初のアイテムを返す
• エイリアス: jqf()
• fg(): フォアグラウンドカラーを設定する
• bg(): バックグラウンドカラーを設定する
• reset(): カラーをリセットする
ログの置換 / 値の取り出し [3/4]
単純だがデバッグ時に便利な設定
app "my_app" {
logger {
replace_regex = [
{
from = "^XXX (.*)$"
to = "${fg('red')}XXX 1${reset()}"
},
]
}
}
今デバッグしているメッセージに
印をつけておき、赤で表示
ログの置換 / 値の取り出し [4/4]
• デバッグログでは大きなdictをそのまま出力させてよい
• ログを読むツールでフィルタリングを行う
• フィルタリングの設定はコンテキストごとにもつこと
ができる
• Jaffleは複数の設定をマージすることができる
(次のJaffle設定の運用で述べる)
大きなデータをログ出力で扱う方法の提案
Jaffle設定の運用
実行時の設定変更
• jaffle.hcl内で使用する値を変数として
定義することができる
• 変数は実行時に環境変数で値をセッ
トすることができる
variable "tornado_log_level" {
default = "debug"
}
variable "disable_frontend" {
default = false
}
kernel "py_kernel" {}
app "tornado_app" {
class = "jaffle.app.tornado.TornadoBridgeApp"
kernel = "py_kernel"
start = "tornado_app.start()"
logger {
level = "${var.tornado_log_level}"
}
options {
app_class = "tornado_spa_advanced.app.ExampleApp"
}
}
process "frontend" {
command = "yarn start"
tty = true
disabled = "${var.disable_frontend}"
}
変数参照
変数参照
変数定義
$ J_VAR_tornado_log_level=info 
J_VAR_disable_frontend=true 
jaffle start
実行例:
設定ファイルを毎回書き換えるのが大変なとき
複数の設定ファイルをマージして実行
チーム開発での設定ファイル構成例
project_repo/
├─ jaffle.hcl
├─ frontend.hcl
├─ backend.hcl
├─ my-jaffle.hcl
└─ src/
ベース設定 (gitで管理)
フロントエンド開発用設定 (gitで管理)
バックエンド開発用設定 (gitで管理)
自分用設定 (.gitignoreに設定)
$ jaffle start jaffle.hcl backend.hcl my-jaffle.hcl
バックエンド開発時のJaffle起動:
ひとつの設定にマージして実行する
特にログのフィルタリング・置換を多段適用するのが便利
Jaffleのアーキテクチャと
実装
アーキテクチャ [1/2]
JaffleSessionManager
JaffleKernelManager
JaffleKernelClient
JaffleStartCommand
App
Jupyter Kernel Session
ZeroMQ Socket for logging
Python Code/Response
via ZeroMQ
Log Message
Create Kernel
Session
Jupyterライブラリ群をPython実行と通信のための
フレームワークとして使用する
Python Code
アーキテクチャ [2/2]
• 複数のPythonアプリケーションをJupyter Kernel上で動
作させる
• Kernelの管理にはJupyter NotebookのKernelManager,
ContentsManager, SessionManagerを使用する
• Kernelとの通信にはjupyter-clientライブラリを使用する
• ZeroMQを使ってログメッセージを集める
• 以下の”App”を同梱する
• WatchdogApp: ファイル更新を監視し、イベント発生時にコー
ドを実行
• PyTestRunnerApp: pytest実行
• TornadoBridgeApp: Tornadoで実装されたアプリの起動・停止制
御
App間連携
• py_kernel上で、WatchdogAppと
PyTestRunnerAppはそれぞれwatchdog,
pytestという変数にアサインされる
• App同士は互いに変数名でアクセス
することができる
• 実行すべきコードをjaffle.hclに直接記
述する(左の例ではcode_blocks)
kernel "py_kernel" {}
app "watchdog" {
class = "jaffle.app.watchdog.WatchdogApp"
kernel = "py_kernel"
options {
handlers = [{
# …
code_blocks = ["pytest.handle_watchdog_event({event})"]
}]
}
}
app "pytest" {
class = "jaffle.app.pytest.PyTestRunnerApp"
kernel = "py_kernel"
options {
# …
}
}
変数名で参照できる
watchdogが変数名になる
App間に特別なプロトコルは必要ない
引数だけ知っていればよい
コードを直接書く
jaffle.hcl
独自App定義
app "my_app" {
class = "my_module.MyApp"
kernel = "py_kernel"
options {
foo = 1
}
start = "my_app.bar()"
}
from jaffle.app.base import BaseJaffleApp, capture_method_output
from tornado import gen, ioloop
class MyApp(BaseJaffleApp):
def __init__(self, app_conf_data):
super().__init__(app_conf_data)
self.foo = self.options.get('foo')
self.log.info('foo: %d', self.foo)
@capture_method_output
def bar(self):
print('bar')
ioloop.IOLoop.current().add_callback(self.async_example)
@gen.coroutine
def async_example(self):
yield self.execute_code('{var} = 1 + 2', var='foo')
yield self.execute_command('echo hello')
yield self.execute_job('my_job')
標準出力をキャプチャ
オプション取得
非同期呼び出し
my_app = my_module.MyApp(app_conf_data)
my_app.bar()
以下のように実行される
使用例 (jaffle.hcl)
my_module.py
将来計画
ログ表示機能の拡張
• 正規表現ルールをJaffle起動中に編集する機能
• リアルタイムにプレビューする機能
• インタラクティブに作成したルールをファイルに書き
出す機能
正規表現を書くのが難しい
ありがとうございました

More Related Content

What's hot

What's hot (20)

PostgreSQL初心者がパッチを提案してからコミットされるまで(第20回PostgreSQLアンカンファレンス@オンライン 発表資料)
PostgreSQL初心者がパッチを提案してからコミットされるまで(第20回PostgreSQLアンカンファレンス@オンライン 発表資料)PostgreSQL初心者がパッチを提案してからコミットされるまで(第20回PostgreSQLアンカンファレンス@オンライン 発表資料)
PostgreSQL初心者がパッチを提案してからコミットされるまで(第20回PostgreSQLアンカンファレンス@オンライン 発表資料)
 
限界性能試験を自動化するOperatorを作ってみた(Kubernetes Novice Tokyo #14 発表資料)
限界性能試験を自動化するOperatorを作ってみた(Kubernetes Novice Tokyo #14 発表資料)限界性能試験を自動化するOperatorを作ってみた(Kubernetes Novice Tokyo #14 発表資料)
限界性能試験を自動化するOperatorを作ってみた(Kubernetes Novice Tokyo #14 発表資料)
 
Apache Spark 3.0新機能紹介 - 拡張機能やWebUI関連のアップデート(Spark Meetup Tokyo #3 Online)
Apache Spark 3.0新機能紹介 - 拡張機能やWebUI関連のアップデート(Spark Meetup Tokyo #3 Online)Apache Spark 3.0新機能紹介 - 拡張機能やWebUI関連のアップデート(Spark Meetup Tokyo #3 Online)
Apache Spark 3.0新機能紹介 - 拡張機能やWebUI関連のアップデート(Spark Meetup Tokyo #3 Online)
 
今こそ知りたいSpring Web(Spring Fest 2020講演資料)
今こそ知りたいSpring Web(Spring Fest 2020講演資料)今こそ知りたいSpring Web(Spring Fest 2020講演資料)
今こそ知りたいSpring Web(Spring Fest 2020講演資料)
 
pg_bigmで全文検索するときに気を付けたい5つのポイント(第23回PostgreSQLアンカンファレンス@オンライン 発表資料)
pg_bigmで全文検索するときに気を付けたい5つのポイント(第23回PostgreSQLアンカンファレンス@オンライン 発表資料)pg_bigmで全文検索するときに気を付けたい5つのポイント(第23回PostgreSQLアンカンファレンス@オンライン 発表資料)
pg_bigmで全文検索するときに気を付けたい5つのポイント(第23回PostgreSQLアンカンファレンス@オンライン 発表資料)
 
PostgreSQLモニタリングの基本とNTTデータが追加したモニタリング新機能(Open Source Conference 2021 Online F...
PostgreSQLモニタリングの基本とNTTデータが追加したモニタリング新機能(Open Source Conference 2021 Online F...PostgreSQLモニタリングの基本とNTTデータが追加したモニタリング新機能(Open Source Conference 2021 Online F...
PostgreSQLモニタリングの基本とNTTデータが追加したモニタリング新機能(Open Source Conference 2021 Online F...
 
PostgreSQL14の pg_stat_statements 改善(第23回PostgreSQLアンカンファレンス@オンライン 発表資料)
PostgreSQL14の pg_stat_statements 改善(第23回PostgreSQLアンカンファレンス@オンライン 発表資料)PostgreSQL14の pg_stat_statements 改善(第23回PostgreSQLアンカンファレンス@オンライン 発表資料)
PostgreSQL14の pg_stat_statements 改善(第23回PostgreSQLアンカンファレンス@オンライン 発表資料)
 
押さえておきたい、PostgreSQL 13 の新機能!!(Open Source Conference 2021 Online/Hokkaido 発表資料)
押さえておきたい、PostgreSQL 13 の新機能!!(Open Source Conference 2021 Online/Hokkaido 発表資料)押さえておきたい、PostgreSQL 13 の新機能!!(Open Source Conference 2021 Online/Hokkaido 発表資料)
押さえておきたい、PostgreSQL 13 の新機能!!(Open Source Conference 2021 Online/Hokkaido 発表資料)
 
Let's Start Contributing to OpenJDK from Today!(Oracle Groundbreakers APAC Vi...
Let's Start Contributing to OpenJDK from Today!(Oracle Groundbreakers APAC Vi...Let's Start Contributing to OpenJDK from Today!(Oracle Groundbreakers APAC Vi...
Let's Start Contributing to OpenJDK from Today!(Oracle Groundbreakers APAC Vi...
 
PostgreSQLをKubernetes上で活用するためのOperator紹介!(Cloud Native Database Meetup #3 発表資料)
PostgreSQLをKubernetes上で活用するためのOperator紹介!(Cloud Native Database Meetup #3 発表資料)PostgreSQLをKubernetes上で活用するためのOperator紹介!(Cloud Native Database Meetup #3 発表資料)
PostgreSQLをKubernetes上で活用するためのOperator紹介!(Cloud Native Database Meetup #3 発表資料)
 
VSCodeで作るPostgreSQL開発環境(第25回 PostgreSQLアンカンファレンス@オンライン 発表資料)
VSCodeで作るPostgreSQL開発環境(第25回 PostgreSQLアンカンファレンス@オンライン 発表資料)VSCodeで作るPostgreSQL開発環境(第25回 PostgreSQLアンカンファレンス@オンライン 発表資料)
VSCodeで作るPostgreSQL開発環境(第25回 PostgreSQLアンカンファレンス@オンライン 発表資料)
 
Deep Dive into the Linux Kernel - メモリ管理におけるCompaction機能について
Deep Dive into the Linux Kernel - メモリ管理におけるCompaction機能についてDeep Dive into the Linux Kernel - メモリ管理におけるCompaction機能について
Deep Dive into the Linux Kernel - メモリ管理におけるCompaction機能について
 
Javaコードが速く実⾏される秘密 - JITコンパイラ⼊⾨(JJUG CCC 2020 Fall講演資料)
Javaコードが速く実⾏される秘密 - JITコンパイラ⼊⾨(JJUG CCC 2020 Fall講演資料)Javaコードが速く実⾏される秘密 - JITコンパイラ⼊⾨(JJUG CCC 2020 Fall講演資料)
Javaコードが速く実⾏される秘密 - JITコンパイラ⼊⾨(JJUG CCC 2020 Fall講演資料)
 
PostgreSQLのgitレポジトリから見える2021年の開発状況(第30回PostgreSQLアンカンファレンス@オンライン 発表資料)
PostgreSQLのgitレポジトリから見える2021年の開発状況(第30回PostgreSQLアンカンファレンス@オンライン 発表資料)PostgreSQLのgitレポジトリから見える2021年の開発状況(第30回PostgreSQLアンカンファレンス@オンライン 発表資料)
PostgreSQLのgitレポジトリから見える2021年の開発状況(第30回PostgreSQLアンカンファレンス@オンライン 発表資料)
 
PostgreSQLレプリケーション10周年!徹底紹介!(PostgreSQL Conference Japan 2019講演資料)
PostgreSQLレプリケーション10周年!徹底紹介!(PostgreSQL Conference Japan 2019講演資料)PostgreSQLレプリケーション10周年!徹底紹介!(PostgreSQL Conference Japan 2019講演資料)
PostgreSQLレプリケーション10周年!徹底紹介!(PostgreSQL Conference Japan 2019講演資料)
 
PostgreSQL 13でのpg_stat_statementsの改善について(第12回PostgreSQLアンカンファレンス@オンライン 発表資料)
PostgreSQL 13でのpg_stat_statementsの改善について(第12回PostgreSQLアンカンファレンス@オンライン 発表資料)PostgreSQL 13でのpg_stat_statementsの改善について(第12回PostgreSQLアンカンファレンス@オンライン 発表資料)
PostgreSQL 13でのpg_stat_statementsの改善について(第12回PostgreSQLアンカンファレンス@オンライン 発表資料)
 
GraalVMの多言語実行機能が凄そうだったので試しにApache Sparkに組み込んで動かしてみたけどちょっとまだ早かったかもしれない(Open So...
GraalVMの多言語実行機能が凄そうだったので試しにApache Sparkに組み込んで動かしてみたけどちょっとまだ早かったかもしれない(Open So...GraalVMの多言語実行機能が凄そうだったので試しにApache Sparkに組み込んで動かしてみたけどちょっとまだ早かったかもしれない(Open So...
GraalVMの多言語実行機能が凄そうだったので試しにApache Sparkに組み込んで動かしてみたけどちょっとまだ早かったかもしれない(Open So...
 
0から始めるコンテナの学び方(Kubernetes Novice Tokyo #14 発表資料)
0から始めるコンテナの学び方(Kubernetes Novice Tokyo #14 発表資料)0から始めるコンテナの学び方(Kubernetes Novice Tokyo #14 発表資料)
0から始めるコンテナの学び方(Kubernetes Novice Tokyo #14 発表資料)
 
Keycloakの実際・翻訳プロジェクト紹介
Keycloakの実際・翻訳プロジェクト紹介Keycloakの実際・翻訳プロジェクト紹介
Keycloakの実際・翻訳プロジェクト紹介
 
今こそ知りたいSpring Batch(Spring Fest 2020講演資料)
今こそ知りたいSpring Batch(Spring Fest 2020講演資料)今こそ知りたいSpring Batch(Spring Fest 2020講演資料)
今こそ知りたいSpring Batch(Spring Fest 2020講演資料)
 

Similar to 複数アプリケーションの プロセスとログを管理する ための新しいツールと手法

ASP.NET シングル ページ アプリケーション (SPA) 詳説
ASP.NET シングル ページ アプリケーション (SPA) 詳説ASP.NET シングル ページ アプリケーション (SPA) 詳説
ASP.NET シングル ページ アプリケーション (SPA) 詳説
Akira Inoue
 
Cloudstack user group meeting in osaka
Cloudstack user group meeting in osakaCloudstack user group meeting in osaka
Cloudstack user group meeting in osaka
Naotaka Jay HOTTA
 
ヒカルのGo 資料 Webアプリケーションの作り方
ヒカルのGo 資料 Webアプリケーションの作り方ヒカルのGo 資料 Webアプリケーションの作り方
ヒカルのGo 資料 Webアプリケーションの作り方
Yosuke Furukawa
 
Personal Cloud Automation
Personal Cloud AutomationPersonal Cloud Automation
Personal Cloud Automation
Etsuji Nakai
 
環境構築自動化ツールのご紹介
環境構築自動化ツールのご紹介環境構築自動化ツールのご紹介
環境構築自動化ツールのご紹介
Etsuji Nakai
 

Similar to 複数アプリケーションの プロセスとログを管理する ための新しいツールと手法 (20)

HTML5&API総まくり
HTML5&API総まくりHTML5&API総まくり
HTML5&API総まくり
 
HTML5最新動向
HTML5最新動向HTML5最新動向
HTML5最新動向
 
Apache EventMesh を使ってみた
Apache EventMesh を使ってみたApache EventMesh を使ってみた
Apache EventMesh を使ってみた
 
ASP.NET シングル ページ アプリケーション (SPA) 詳説
ASP.NET シングル ページ アプリケーション (SPA) 詳説ASP.NET シングル ページ アプリケーション (SPA) 詳説
ASP.NET シングル ページ アプリケーション (SPA) 詳説
 
Cloudstack user group meeting in osaka
Cloudstack user group meeting in osakaCloudstack user group meeting in osaka
Cloudstack user group meeting in osaka
 
ヒカルのGo 資料 Webアプリケーションの作り方
ヒカルのGo 資料 Webアプリケーションの作り方ヒカルのGo 資料 Webアプリケーションの作り方
ヒカルのGo 資料 Webアプリケーションの作り方
 
10分で作る Node.js Auto Scale 環境 with CloudFormation
10分で作る Node.js Auto Scale 環境 with CloudFormation10分で作る Node.js Auto Scale 環境 with CloudFormation
10分で作る Node.js Auto Scale 環境 with CloudFormation
 
Osoljp201210 oi swift
Osoljp201210 oi swiftOsoljp201210 oi swift
Osoljp201210 oi swift
 
恐るべきApache, Web勉強会@福岡
恐るべきApache, Web勉強会@福岡恐るべきApache, Web勉強会@福岡
恐るべきApache, Web勉強会@福岡
 
Apache Auroraの始めかた
Apache Auroraの始めかたApache Auroraの始めかた
Apache Auroraの始めかた
 
成長を加速する minne の技術基盤戦略
成長を加速する minne の技術基盤戦略成長を加速する minne の技術基盤戦略
成長を加速する minne の技術基盤戦略
 
Azure で Serverless 初心者向けタッチ&トライ
Azure で Serverless 初心者向けタッチ&トライAzure で Serverless 初心者向けタッチ&トライ
Azure で Serverless 初心者向けタッチ&トライ
 
Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力
Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力
Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力
 
Personal Cloud Automation
Personal Cloud AutomationPersonal Cloud Automation
Personal Cloud Automation
 
初めての Data API CMS どうでしょう - 仙台編 -
初めての Data API   CMS どうでしょう - 仙台編 -初めての Data API   CMS どうでしょう - 仙台編 -
初めての Data API CMS どうでしょう - 仙台編 -
 
【初心者向け】API を使ってクラウドの管理を自動化しよう
【初心者向け】API を使ってクラウドの管理を自動化しよう【初心者向け】API を使ってクラウドの管理を自動化しよう
【初心者向け】API を使ってクラウドの管理を自動化しよう
 
環境構築自動化ツールのご紹介
環境構築自動化ツールのご紹介環境構築自動化ツールのご紹介
環境構築自動化ツールのご紹介
 
20160728 hyperscale #03
20160728 hyperscale #0320160728 hyperscale #03
20160728 hyperscale #03
 
Cakephp勉強会@tokyo #4
Cakephp勉強会@tokyo #4Cakephp勉強会@tokyo #4
Cakephp勉強会@tokyo #4
 
WebRTC開発者向けプラットフォーム SkyWayの裏側
WebRTC開発者向けプラットフォーム SkyWayの裏側WebRTC開発者向けプラットフォーム SkyWayの裏側
WebRTC開発者向けプラットフォーム SkyWayの裏側
 

複数アプリケーションの プロセスとログを管理する ための新しいツールと手法

  • 2. アジェンダ • Jaffle: 開発支援ツールの紹介 • 開発の動機 • Pythonアプリケーションと外部プロセスの起動 /自動タスク実行 • 開発時のログ管理 • Jaffle設定の運用 • Jaffleのアーキテクチャと実装 https://jaffle.readthedocs.io/ https://github.com/yatsu/jaffle Jaffle
  • 3. • 複数のPythonアプリケーションと外部プロセスをまとめて起動・終了する • ファイルの更新を検出してタスクの自動実行を行う (例: 自動テスト) • ログ出力を統合し、発生時刻順に表示、フィルタリング、置換を行う • Jupyter Kernel上でPythonコードを統合動作させるフレームワーク (最後の実装のところで説明) Jaffleとは
  • 4. 背景 • プロセスやログを管理するものは、プロダクシ ョン環境で使えるツールやクラウド上のサービ スは充実してきているが、手元の開発環境で手 軽に使えるものがない • 近年の傾向として • 連携するアプリケーションやサービスの数が増加し てきている • 個々のアプリケーションやサービスが出力する情報 も増加してきている
  • 6. 事例1: TornadoとReactによるWeb開発 • Tornadoで実装されたWeb APIサーバーを立ち上げる • 2つの外部プロセスを起動する • “yarn start” (開発用Webサーバー) • “jest” (JavaScriptテスト) • .pyファイルを更新時に • Web APIサーバー再起動 • pytest実行 • ログを統合して表示 https://jaffle.readthedocs.io/en/latest/cookbook/tornado_spa.html Tornado (Webフレームワーク) でバックエンドWeb APIを 実装し、ReactでWebフロントエンドを開発する例
  • 7. 事例2: pytest自動テスト • テストファイル test_*.py が更新されたら、そ のテストを実行する • 実装ファイル foo/bar.py が更新されたら、それ に関するテスト tests/foo/test_bar.py を実行する • pytestの実行が常にスタンバイ状態で、カレン トディレクトリ以下のモジュールだけをリロー ドするため、高速に動作する • pytest実行前にキャッシュクリアは設定可能 https://jaffle.readthedocs.io/en/latest/cookbook/pytest.html
  • 8. 事例2: pytest自動テスト / 設定ファイル kernel "py_kernel" {} app "watchdog" { class = "jaffle.app.watchdog.WatchdogApp" kernel = "py_kernel" options { handlers = [{ watch_path = "pytest_example" patterns = ["*.py"] ignore_directories = true code_blocks = ["pytest.handle_watchdog_event({event})"] }] } } app "pytest" { class = "jaffle.app.pytest.PyTestRunnerApp" kernel = "py_kernel" options { args = ["-s", "-v", "—color=yes"] auto_test = ["pytest_example/tests/test_*.py"] auto_test_map { "pytest_example/**/*.py" = "pytest_example/tests/{}/test_{}.py" } } } ファイルシステム監視App 左側のパターンにマッチしたら右側のパターンのテストを実行 例: foo/bar.py → tests/foo/test_bar.py パターンにマッチしたテストを実行 Jupyter Kernel (Pythonインタープリタ・プロセス) を定義 変数として参照する 実行方法: $ jaffle start jaffle.hcl 設定ファイルがjaffle.hclだけの場合は省略可能: $ jaffle start 上のようなjaffle.hclファイルを作成する
  • 9. 事例3: Sphinx自動ビルド / 設定ファイル kernel "py_kernel" { pass_env = ["PATH"] } app "watchdog" { class = "jaffle.app.watchdog.WatchdogApp" kernel = "py_kernel" options { handlers = [{ patterns = ["*/docs/*.*"] ignore_patterns = ["*/_build/*"] ignore_directories = true jobs = ["sphinx”, “refresh”] }] } } job "sphinx" { command = "sphinx-build -M html docs docs/_build" } job ”refresh" { command = ”osascript browser_refresh.scpt" } docs/ ディレクトリ以下の .rst ファ イルを更新すると、sphinx-build が 自動的に実行され、HTMLファイル が生成される Job名参照 コマンド実行はJobとして定義する 環境変数PATHをJupyter Kernelに引き継ぐ virtualenvの場合に必要 tell application "Google Chrome" to tell the active tab of its first window reload end tell (macOSの場合)
  • 10. 事例4: Jupyter Extension開発 https://jaffle.readthedocs.io/en/latest/cookbook/jupyter_ext.html • .pyファイルが更新されたとき、 Jupyter Notebook Serverを再起動 • .jsファイルが更新されたとき、 “jupyter nbextension install” を実行 kernel "py_kernel" { pass_env = ["PATH"] } app "watchdog" { class = "jaffle.app.watchdog.WatchdogApp" kernel = "py_kernel" options { handlers = [ { patterns = ["*.py"] ignore_directories = true clear_cache = ["jupyter_myext"] code_blocks = ["notebook.handle_watchdog_event({event})"] }, { patterns = ["*.js"] ignore_directories = true jobs = ["nbext_install"] }, ] } } app "notebook" { class = "jaffle.app.tornado.TornadoBridgeApp" kernel = "py_kernel" options { app_class = "notebook.notebookapp.NotebookApp" } start = "notebook.start()" } job "nbext_install" { command = "jupyter nbextension install jupyter_myext --user --overwrite" } Jupyter Notebookのサーバーサイドとフロ ントエンドJavaScriptの両方を拡張する例 Jupyter Notebook ServerをJupyter Kernelから起動する; Jupyter NotebookはTornado (Webフレームワーク)で実装 されており、JaffleのTornado IOLoop上で動作させること ができる
  • 12. ログ管理の問題と解決方法 • 複数のログファイルから処 理の流れを把握するのが難 しい • ログメッセージの数が多く、 目的のメッセージを見つけ るのが難しい • ひとつのログメッセージに 含まれるデータが大きく、 必要な情報を取り出すのが 難しい すべてのログを統合して表示する フィルタリング機能を使い、 必要のないメッセージを隠す 置換機能を使い、必要な情報だけ 取り出したり 色付けによる強調表示を行う
  • 14. ログのフィルタリング app "pytest" { class = "jaffle.app.pytest.PyTestRunnerApp" kernel = "py_kernel" logger { suppress_regex = [ "^platform ", "^cachedir:", "^rootdir:", "^plugins:", "collecting ...", "^collected ", ] } } pytestの出力から不要な行を隠す例
  • 15. 大きなデータをログ出力するときの問題 $ kubectl get services kubernetes -o json { "apiVersion": "v1", "kind": "Service", "metadata": { "creationTimestamp": "2018-09-06T13:09:41Z", "labels": { "component": "apiserver", "provider": "kubernetes" }, "name": "kubernetes", "namespace": "default", "resourceVersion": "18", "selfLink": "/api/v1/namespaces/default/services/kubernetes", "uid": "1dc6394b-b1d6-11e8-859a-080027be08a0" }, "spec": { "clusterIP": "10.96.0.1", "ports": [ { "name": "https", "port": 443, "protocol": "TCP", "targetPort": 8443 } ], "sessionAffinity": "ClientIP", "sessionAffinityConfig": { "clientIP": { "timeoutSeconds": 10800 } }, "type": "ClusterIP" }, "status": { "loadBalancer": {} } } service: {"apiVersion":"v1","kind":"Service","metadata":{"creationTimestamp":" 2018-09- 06T13:09:41Z","labels":{"component":"apiserver","provider":"kubernete s"},"name":"kubernetes","namespace":"default","resourceVersion":"18", "selfLink":"/api/v1/namespaces/default/services/kubernetes","uid":"1d c6394b-b1d6-11e8-859a- 080027be08a0"},"spec":{"clusterIP":"10.96.0.1","ports":[{"name":"http s","port":443,"protocol":"TCP","targetPort":8443}],"sessionAffinity": "ClientIP","sessionAffinityConfig":{"clientIP":{"timeoutSeconds":1080 0}},"type":"ClusterIP"},"status":{"loadBalancer":{}}} すべて出力すると1行が長くなる: labelsを見たい人はこのように出力したい: {"labels":{"component":"apiserver","provider":"kubernetes"} nameとclusterIPを見たい人はこのように出力したい: service: {"name":"kubernetes”,"clusterIP":"10.96.0.1"} 見たい情報はコンテキストに依存する ロガーを複数定義してそれぞれに出力ルールを決めるのは大変 アプリ起動時にロガーごとにレベルをセットするのも大変
  • 16. ログの置換 / 値の取り出し [1/4] app "my_app" { logger { replace_regex = [ { from = "^service: (.*)$" to = ”service ip: ${jqf('.spec.clusterIP', '1')}" }, ] } } dictのパス .spec.clusterIP から値を取り出す IPだけ取り出せる JSONに対するjqコマンドと同等のことができる
  • 17. ログの置換 / 値の取り出し [2/4] app "my_app" { logger { replace_regex = [ { from = "^service: (.*)$" to = "ip: ${fg('blue')}${jqf('.spec.clusterIP', '1’)}${reset()}" }, ] } } さらに色をつける 使える関数 • jq_all(): dict を jq した結果のリストを返す • エイリアス: jq() • jq_first(): dictを jq した結果の最初のアイテムを返す • エイリアス: jqf() • fg(): フォアグラウンドカラーを設定する • bg(): バックグラウンドカラーを設定する • reset(): カラーをリセットする
  • 18. ログの置換 / 値の取り出し [3/4] 単純だがデバッグ時に便利な設定 app "my_app" { logger { replace_regex = [ { from = "^XXX (.*)$" to = "${fg('red')}XXX 1${reset()}" }, ] } } 今デバッグしているメッセージに 印をつけておき、赤で表示
  • 19. ログの置換 / 値の取り出し [4/4] • デバッグログでは大きなdictをそのまま出力させてよい • ログを読むツールでフィルタリングを行う • フィルタリングの設定はコンテキストごとにもつこと ができる • Jaffleは複数の設定をマージすることができる (次のJaffle設定の運用で述べる) 大きなデータをログ出力で扱う方法の提案
  • 21. 実行時の設定変更 • jaffle.hcl内で使用する値を変数として 定義することができる • 変数は実行時に環境変数で値をセッ トすることができる variable "tornado_log_level" { default = "debug" } variable "disable_frontend" { default = false } kernel "py_kernel" {} app "tornado_app" { class = "jaffle.app.tornado.TornadoBridgeApp" kernel = "py_kernel" start = "tornado_app.start()" logger { level = "${var.tornado_log_level}" } options { app_class = "tornado_spa_advanced.app.ExampleApp" } } process "frontend" { command = "yarn start" tty = true disabled = "${var.disable_frontend}" } 変数参照 変数参照 変数定義 $ J_VAR_tornado_log_level=info J_VAR_disable_frontend=true jaffle start 実行例: 設定ファイルを毎回書き換えるのが大変なとき
  • 22. 複数の設定ファイルをマージして実行 チーム開発での設定ファイル構成例 project_repo/ ├─ jaffle.hcl ├─ frontend.hcl ├─ backend.hcl ├─ my-jaffle.hcl └─ src/ ベース設定 (gitで管理) フロントエンド開発用設定 (gitで管理) バックエンド開発用設定 (gitで管理) 自分用設定 (.gitignoreに設定) $ jaffle start jaffle.hcl backend.hcl my-jaffle.hcl バックエンド開発時のJaffle起動: ひとつの設定にマージして実行する 特にログのフィルタリング・置換を多段適用するのが便利
  • 24. アーキテクチャ [1/2] JaffleSessionManager JaffleKernelManager JaffleKernelClient JaffleStartCommand App Jupyter Kernel Session ZeroMQ Socket for logging Python Code/Response via ZeroMQ Log Message Create Kernel Session Jupyterライブラリ群をPython実行と通信のための フレームワークとして使用する Python Code
  • 25. アーキテクチャ [2/2] • 複数のPythonアプリケーションをJupyter Kernel上で動 作させる • Kernelの管理にはJupyter NotebookのKernelManager, ContentsManager, SessionManagerを使用する • Kernelとの通信にはjupyter-clientライブラリを使用する • ZeroMQを使ってログメッセージを集める • 以下の”App”を同梱する • WatchdogApp: ファイル更新を監視し、イベント発生時にコー ドを実行 • PyTestRunnerApp: pytest実行 • TornadoBridgeApp: Tornadoで実装されたアプリの起動・停止制 御
  • 26. App間連携 • py_kernel上で、WatchdogAppと PyTestRunnerAppはそれぞれwatchdog, pytestという変数にアサインされる • App同士は互いに変数名でアクセス することができる • 実行すべきコードをjaffle.hclに直接記 述する(左の例ではcode_blocks) kernel "py_kernel" {} app "watchdog" { class = "jaffle.app.watchdog.WatchdogApp" kernel = "py_kernel" options { handlers = [{ # … code_blocks = ["pytest.handle_watchdog_event({event})"] }] } } app "pytest" { class = "jaffle.app.pytest.PyTestRunnerApp" kernel = "py_kernel" options { # … } } 変数名で参照できる watchdogが変数名になる App間に特別なプロトコルは必要ない 引数だけ知っていればよい コードを直接書く jaffle.hcl
  • 27. 独自App定義 app "my_app" { class = "my_module.MyApp" kernel = "py_kernel" options { foo = 1 } start = "my_app.bar()" } from jaffle.app.base import BaseJaffleApp, capture_method_output from tornado import gen, ioloop class MyApp(BaseJaffleApp): def __init__(self, app_conf_data): super().__init__(app_conf_data) self.foo = self.options.get('foo') self.log.info('foo: %d', self.foo) @capture_method_output def bar(self): print('bar') ioloop.IOLoop.current().add_callback(self.async_example) @gen.coroutine def async_example(self): yield self.execute_code('{var} = 1 + 2', var='foo') yield self.execute_command('echo hello') yield self.execute_job('my_job') 標準出力をキャプチャ オプション取得 非同期呼び出し my_app = my_module.MyApp(app_conf_data) my_app.bar() 以下のように実行される 使用例 (jaffle.hcl) my_module.py
  • 29. ログ表示機能の拡張 • 正規表現ルールをJaffle起動中に編集する機能 • リアルタイムにプレビューする機能 • インタラクティブに作成したルールをファイルに書き 出す機能 正規表現を書くのが難しい