ぽよメモ

ファッション情報学徒の備忘録.

Pythonの新しいCLIアプリケーションフレームワークuroborosを公開した

はじめに

Pythonの全てのCLIアプリケーションフレームワークに習熟してるわけではないです.
自分が必要だと思ったものから優先的に実装しています.こうした方がいいとかあれば,リポジトリのIssueにお願いします.

背景

2019年現在,ソフトウェア系の研究をする人が避けて通れない言語がPythonと言っても過言では無くなってきました(炎上).実験でスクリプトを回すとき,パラメータ変更のために毎回ソースコードを書き換えるなんていう真似をしている人はおそらく居ないと思いますが,皆様どのようにCLIアプリケーションを構築されていますでしょうか.
Pythonの有名なCLIアプリケーションフレームワークはいくつかあります.僕の個人的な感想を併記します.

  • Click
    • ○:関数を簡単にコマンドにできるのは便利
    • ×:デコレータ地獄になっているコードを読み解きたいという気持ちには全くならない
  • Cliff
    • ○:OpenStackのサポートがあって将来的なサポートも期待できそう
    • ×:setuptoolsの力を借りないとサブコマンドを使えなくて嬉しくない
  • Python Fire
    • ○:Googleのサポートがあるので将来的なサポートも期待できそう
    • ×:これ使いやすいですか?
  • Cement
    • ×:本家ドキュメントのGetting startedを読んでこんなに使い方が分からないとは思わなかった
  • Plac
    • ×:もはやPythonを書いていない

正直どれもこれも学習コストが高いか,可読性を犠牲にしており,それなら自分でargparseのラッパーを書いた方がええわ!となりました.世間のPythonistaたちはどうしてるんでしょうか…
そもそもPythonargparseは非常に高機能で,CLIアプリケーション構築に必要な機能はほとんど網羅しているかと思います.インタラクティブシェル的な流行りの機能は難しいですが,そんな要件,普通に存在しますか?僕は幸い出会ったことがありません.というわけで,僕自身の機能に対するニーズはargparseで十分に満たされており,後はどれだけ快適に書けるか,というところだけです.長くなりましたがこういう経緯で新しく実装することにしました.

方針

  • 学習コストをできるだけ低く抑えること
  • argparseの使用感を損なわずにコマンドを構造的に書けるようにすること
  • 標準的な機能を利用する上では追加の依存を必要としないこと
  • Python3.5以上のみをサポートすること
    • もうこの世にPython2を使っているのはgcloudコマンドしか居ないため

気持ちだけGoのCLIアプリフレームワークspf13/cobraを参考にしました.

uroboros

GitHubで公開しています.現在v0.1.0です.

github.com

名前の由来

f:id:pudding_info:20190713220911p:plain
無限や不老不死の象徴として描かれる自分の尻尾を食べる蛇、ウロボロスの輪のイラスト from いらすとや

ヘビっぽい単語で使えそうなのを探していたとき,ouroborosウロボロス)は使われていましたがWikipediaを見るとuroborosでも良さそうだったのでこれにしました.
ちなみにウロボロスは自分の尾を飲み込む蛇で終わり始まりが無いこと,不老不死,循環性,無限性の象徴ですが,このフレームワークにおいてサブコマンドは再帰的に構築され,ループしているとエラーを吐くので全然名前を表していないなと作ってから思いました.

インストール

pipでインストールするだけです.今のところサードパーティーの依存パッケージはありません.

$ pip install uroboros

使い方

覚える必要があるのはargparseの使い方と,uroboros.Commanduroboros.Optionだけです.しかも大抵の要件ではuroboros.Commandだけでも十分でしょう.uroborosではuroboros.Commandを継承して作成したコマンドクラスをノードとするN分木を作るようなイメージでCLIツールを構築していきます.

# main.py
from uroboros import Command
from uroboros import ExitStatus

class RootCommand(Command):
    """アプリケーションのルートコマンド"""
    # アプリケーションのルートコマンド.
    name = 'sample'
    # アプリケーションの説明
    long_description = 'This is a sample command using uroboros'

    version = "v0.0.0"

    def build_option(self, parser):
        """
        引数の追加.
        argparse.ArgumentParserのインスタンスが引数として渡される.
        """
        parser.add_argument('--version',
                            action='store_true',
                            default=False,
                            help='Print version')
        # 追加した後にparserを返す
        return parser

    def run(self, args):
        """
        このコマンドが指定されたときに実行する内容.
        argsはargparse.Namespaceオブジェクトで引数をパースした結果.
        """
        if args.version:
            print("{name} v{version}".format(
                name=self.name, version=self.version))
        else:
            # このコマンドのヘルプテキストを出力するヘルパー関数
            self.print_help()
        # 返り値はExit Statusになる.
        # `uroboros.ExitStatus`を使っても良いし,単にintでも良い.
        return ExitStatus.SUCCESS


root = RootCommand()

if __name__ == "__main__":
    # 実行時は単にそのコマンドのexecuteを呼ぶだけ
    exit_code = root.execute()
    exit(exit_code)

これが基本的なコマンド一つの実装になります.ポイントは

  • argparse.ArgumentParserインスタンスが渡されるので自由にオプションを追加する
  • 実行時には引数がargparseによってパースされ,argparse.Namespaceインスタンスが渡されるので,自由にオプションを読み取って実行する
  • 返り値はそのまま終了ステータスになるので,intまたはuroboros.ExitStatus*1を返す

です.試しにヘルプを表示するとこうなります.

$ python main.py -h
usage: sample [-h] [--version]

This is a sample command using uroboros

optional arguments:
  -h, --help  show this help message and exit
  --version   Print version
# ちゃんとバージョン表示できる
$ python main.py --version
sample v0.0.0

ではサブコマンドを追加してみます.

# main.py
from uroboros import Command
from uroboros import ExitStatus

class RootCommand(Command):
    ...

class HelloCommand(Command):
    # サブコマンド名
    name = 'hello'
    # サブコマンド一覧を表示したときの短いメッセージ
    short_description = 'Hello world!'
    long_description = 'Print "Hello world!" to stdout'

    def run(self, args):
        print(self.short_description)
        return ExitStatus.SUCCESS

root = RootCommand()
root.add_command(HelloCommand())

...

これでhelloサブコマンドが使えるようになります.

# short descriptionはここで表示される
$ python main.py -h 
usage: sample [-h] [--version] {hello} ...

This is a sample command using uroboros

optional arguments:
  -h, --help  show this help message and exit
  --version   Print version

Sub commands:
  {hello}
    hello     Hello world!
# long descriptionはこっちで表示される
$ python main.py hello -h
usage: sample hello [-h]

Print "Hello world!" to stdout

optional arguments:
  -h, --help  show this help message and exit
# Hello worldしてみる
$ python main.py hello
Hello world!

更に複数のモジュールから複雑なコマンドを生成するサンプルはこちらにあります. これをargparseで実装する場合,(色々と省略すると)以下の様になります.

import argparse

def print_version(parser, args):
    if args.version:
        print("sample v0.0.0")
    else:
        parser.print_help()

def hello(parser, args):
    print("Hello world!")

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--version',
                        action='store_true',
                        default=False,
                        help='Print version')
    parser.set_defaults(func=print_version)
    sub_parser = parser.add_subparsers()
    hello_parser = sub_parser.add_parser('hello')
    hello_parser.set_defaults(func=hello)

    args = parser.parse_args()
    args.func(parser, args)

if __name__ == '__main__':
    main()

細かい動作が違いますが概ねこういう感じです.規模が大きくなってきたときにどうなるかは考えるまでも無いでしょう.更にサブコマンドの下にサブコマンドを…等をし始めると,あっという間にスパゲティコードの誕生です.
uroborosでは内部でadd_subparser()を使っており,実際の挙動としては上記のような挙動を全てラップしているような感じになります.

オプション引数の共有

いくつかのサブコマンドで同じオプション引数を使い回したいという需要はあると考えています*2.これを容易に実現する仕組みがuroboros.Optionです.コードの全体像はリポジトリのexampleに任せ,要点だけ説明します.このexampleでは指定されたディレクトリ内のファイルを一覧表示するfilesコマンド, ディレクトリを一覧表示するdirsコマンドを実装しています.
まずuroboros.Optionを継承したオプションクラスを作成します. uroboros.Commandと同様にbuild_optionメソッドで引数を追加できます.

from pathlib import Path
from uroboros import Option, Command

class CommonOption(Option):

    def build_option(self, parser):
        # 共通するオプションを追加する
        parser.add_argument('path', type=Path, help="Path to show")
        parser.add_argument('-a', '--absolute', default=False,
                            action='store_true',
                            help='Show absolute path')
        return parser

class RootCommand(Command):
    ...

# 共通するオプションをクラス変数`options`に設定する
class DirsCommand(Command):
    name = 'dirs'
    options = [CommonOption()]
    ...

class FilesCommand(Command):
    name = 'files'
    options = [CommonOption()]
    ...

root = RootCommand()
root.add_command(DirsCommand())
root.add_command(FilesCommand())

...

これだけでdirsサブコマンドとfilesサブコマンドでpathという位置引数と--absoluteというオプション引数が使えるようになります.
このOptionの良いところは単にくくりだして記述量を減らせるだけでなく,値のバリデーションをまとめることが出来る点にあります.

...

class CommonOption(Option):

    def build_option(self, parser):
        ...

    # 実行時にCommandの`run`より先に呼ばれる
    def validate(self, args):
        # 返すのはExceptionのリスト.バリデーション違反がない場合,空のリストを返す.
        errors = []
        # 指定したパスが存在しない場合エラーにする
        if not args.path.exists():
            errors.append(Exception("'{}' does not exists.".format(args.path)))
        return errors

...

これで例えば存在しないパスを指定すると,filesサブコマンドでもdirsサブコマンドでも共通して存在しないパスを弾くことが出来ます.

$ python main.py files /does/not/exists
'/does/not/exists' does not exists.
$ python main.py dirs /does/not/exists
'/does/not/exists' does not exists.

注意点

良い意味でも悪い意味でのargparseのラッパーに過ぎないため,コマンドライン引数のパースは完全にargparse任せになっています.このため,現状ではサブコマンド以下の引数の名前と,親コマンドで使用している引数の名前がコンフリクトすると最後に設定したオプションで上書きされてしまいます.例えば

$ python main.py root -d fuga command1 -d poyo

のような仕様にした場合,最終的には後者の-d poyoで上書きされてしまいます.これは

  1. 親コマンドの引数のパースをする
  2. サブコマンド以下の引数だけ抽出
  3. サブコマンドの引数のパースをする

の順で解決することでうまく行くはずですが,引数へのアクセシビリティが下がるため避けています.
つまり,今はargs.hogeargs.fugaでアクセスできるコマンドライン変数に対しサブコマンドごとに名前空間を区切ると,args.subcommand.hogeみたいなアクセスの仕方になってしまい,親コマンドからの相対的な位置に依存することになり,せっかく全てのコマンドが個別でexecute()できる仕様が崩壊してしまいます.現状のコマンドの引数のみにフォーカスして渡すことも出来ますが,上位コマンドのオプション引数を解決できなくなってしまいます.
これについては今後の展望で述べるHook機能を使うことで一部解消できるはずです.各コマンドのオプションは各コマンドごとにパースし,必要な処理はHookで行うことで,最終的に実行されるCommandのrunメソッドでは常にグローバルな名前空間にアクセスする必要がなくなる(親コマンドのオプション引数はその親コマンドのHookで解消する)ためです.が,逆に上位コマンドのオプションにはアクセスできなくなるため,上手い仕組みが必要だと考えています.

今後の展望

  • 各コマンドにHookを用意する
    • 例えばルートコマンドにHookを仕掛けてアプリケーション全体でのloggerの設定をするなどに使う
  • バリデーションエラーハンドラーのサポート
    • 現状ではエラーが発生したらそのままlogger.error()して終了してしまう.
    • バリデーションからの回復を可能にするなどがあると便利そう.
  • add_commandで複数のCommandインスタンスを受け取れるようにする
    • すぐできるが需要があるのか分からない
  • オプションをファイルから読み取り/ファイルへ保存
    • json以外の形式のサポートはサードパーティーの依存パッケージを追加することになるので,やるとしてもオプション扱い.
    • あまり上手い形式を思いついていない

他にも要望や改善等あれば是非お願いします.

まとめ

argparseについての知識を活かしたまま,よりCLIアプリケーションを構築しやすくするためのフレームワークとしてuroborosを作成・公開しました.
これにより,サブコマンドの数の増加・そのネスト数の増加に伴って複雑化しがちなCLIアプリケーションを比較的簡単に構築することができるようになりました.

何かフィードバック等あれば遠慮無く頂けると幸いです.よろしくお願いします.

*1:IntEnumで定義されているので実質intです

*2:少なくとも僕にはありました

英文を一文ずつに分割してGoogle翻訳へ投げるAlfred Workflowを作った

これまでに作ったAlfred Workflow

poyo.hatenablog.jp

poyo.hatenablog.jp

これは何?

一文ごとに分解して翻訳にかけることで見通しが良くなり,論文を読む速度が上がるWorkflow.

f:id:pudding_info:20190606222320g:plain

サンプルとして使用したのは,機械翻訳ネットワークTransformerを提案した論文*1

論文PDFの問題

全てがそうではありませんが,論文PDFはたまにそのままコピーすると「見かけ上の改行位置がそのまま反映された文章」としてコピーされてしまうことがあります*2.例えば上の例で用いた論文も,Abstractをそのままコピペすると見た目上の改行位置のままコピペされてしまいました.

f:id:pudding_info:20190606223240p:plain
そのままコピペした場合

この場合,文章の途中で改行されてしまうことが多いため,翻訳もめちゃくちゃになってしまいます.

環境

  • macOS 10.14.5
  • Alfred 4.0.1
  • Go 1.12.5

今回もGoを使っているので,使用に当たって特に準備するものはありません.今回に限っては特にGoである必要もありませんでしたが,楽だったので採用しています.
Alfredの最新版4.0系にも対応しています.3系でも動作を確認しています.

sentence-splitter

github.com

準備

最新版は以下からダウンロードできます.

Release v0.3.0 · pddg/go-alfred-sentence-splitter · GitHub

AlfredとPowerpack*3が有効になっていれば,ダウンロードしたファイルを開くだけでインストールするウィンドウが出てくるはずです.
使用するためにはインストール後,ショートカットキーを設定する必要があります.

f:id:pudding_info:20190606224842p:plain

使い方

  1. 翻訳にかけたい文章を選択する
  2. ショートカットを押す
  3. ブラウザが開く

アプリケーションを問わず動作するため,ショートカットキーのコンフリクトにはお気を付けください.
これは逆に言うとブラウザ,PDFビューワ,エディタ等何にでも使えます.man コマンドのhelpでも使えます.

f:id:pudding_info:20190606225356g:plain

注意点

一文ごとに分解する機能は単なる正規表現マッチとIFの羅列

ソースコードを見れば分かりますが,単なる正規表現で一文を区切っています.さすがにピリオドの位置で分割は頭が悪すぎるので,ピリオドの次に来る文字が英語の大文字であれば改行,というようなことをしています.

Hoge fuga piyo
poyo. Poe poe.

↓

Hoge fuga piyo poyo.

Poe poe.

そのため,1.234のような浮動小数点数のピリオドは改行としては認識されません.逆に言うと,文頭が数字であれば改行としては認識されません.

Hoge fuga 1.234 piyo
poyo. 1 poe poe.

↓

Hoge fuga 1.234 piyo poyo. 1 poe poe.

ピリオドが無い場合は文字列分解できない.

例えば論文中でも,箇条書き等で文末にピリオドが無い場合,文章の終わりを認識できないため,改行されません.

Hoge fuga piyo poyo
Poe poe

↓

Hoge fuga piyo poyo Poe poe

意図的に混入されているハイフンを消してしまう

論文ではよく,改行位置に長い英単語が来た場合,その単語の途中にハイフンを挟んで改行することがあります.この場合に正しく英単語を解釈できるようにするため,改行直前のハイフンを除去して後続の単語と結合するという処理を挟んでいます.これは便利な機能ではありますが,意図的にハイフンを加えられている場合でも削除されてしまうことに注意が必要です.

Hoge fuga piyo-
poyo. Poe poe.

↓

Hoge fuga piyopoyo.

Poe poe.

たまにうまく動かない

ショートカットを押しても,うまくコピーした内容が伝播されず,エラーが出る場合があります.その場合,以下の様な通知が出ますので,選択し直す等でリトライしてみてください.

f:id:pudding_info:20190606230548p:plain

まとめ

もうかれこれ2年近く使っていて知り合いも便利に使ってくれているので,ちょっと整理してgo moduleに対応して供養しておくことにしました.
もう少し良い文章の分割アルゴリズム等があれば,教えて頂けると嬉しいです.いつでもcontributionお待ちしています.

*1:Vaswani, A., Brain, G., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., … Polosukhin, I. (n.d.). Attention Is All You Need. Retrieved from https://arxiv.org/pdf/1706.03762v5.pdf

*2:これはビューワ等に依存するのかもしれません.よくわかりませんが,手元では発生する場合,発生しない場合があるようでした

*3:Alfred Workflowを使用可能にするための課金パッケージ

弱小大学の研究室における計算機環境の理想と現実

はじめに

これはあくあたん工房GWアドベントカレンダー1日目の記事です.

しがないM2が悲惨なラボ計算機環境をどうにかしたいとあがいている様子です.過度な期待はしないでください.
なおこれは,かなり恵まれた環境で,かなり恵まれた学生が,さらに高望みしているだけの記事です.未だにPCの起動ディスクがHDDだとか,メモリが4GBしかないとか,そういう世界の話はしません.タスクとしては主に軽い深層学習がメインで,MPIを使ってマルチノードで大規模演算!みたいなことはしていません.

理想

プログラムを書いたら,

f:id:pudding_info:20190425174850p:plain

ワンクリックで,

f:id:pudding_info:20190425175603p:plain

いいかんじに強いマシンと沢山のGPUで超高速に計算をして,

f:id:pudding_info:20190425175629p:plain

クラウドにデータをバックアップして,

f:id:pudding_info:20190425180030p:plain

AIに論文を書いて欲しい!!!!!!!!!頼む!!!!!!!!

f:id:pudding_info:20190425180200p:plain

はい.最後の一つはともかく,研究者にとって

  • 計算機上で行う計算自体はあまり意味がない(場合による)
  • できればその部分で時間を取られたくない
  • 抽象化された計算機が自分のプログラムを勝手にいいかんじにしてほしい

というのは共通の悩み・願望だと思っています.書いたプログラムをシュッとシームレスに動かす,それだけで良いのですが,現実はなかなかに非常だったりします.そもそも物理マシンがある時点で管理しないといけないですしね.

現実

多くの研究室では,予算の都合上一人一台高スペックなワークステーションを割り当てるのは難しいのではないでしょうか?せいぜい研究室に数台,それなりのスペックのものを用意して共通で使用することが多いのでは.弊研究室もその分に漏れず,それなりのワークステーションをサーバとしてみんながsshで接続し,計算を回しています*1
ユーザこそ後述するLDAPにとって統一された管理を実現していましたが,各マシンの内部の管理は各代の有識者が思い思いに環境を作っており,完全にカオス.CUDAのバージョンもNVIDIA DriverのバージョンもPythonのバージョンも違う,誰もノウハウを残していないので思い思いに計算を回している,逆に謎の遺産によってなぜか動いている,誰がどのマシンを使っているか分からない…etc トラブルが起こったときに場当たり的に解決している状況が続いていては,再発して当たり前です.いちいちその復旧に追われ,研究がままならなくなっては本末転倒…

他にもハードの問題としてそもそもマシンが古くてよく壊れる,起動しなくなる,動いても遅い,などなど…問題は山積みです.

f:id:pudding_info:20190426002727p:plain

これはお金のない研究室の都合なのですが,

  • 一度に買える計算機の台数が少ない
  • しかし数が足りないために毎年購入

スペック,CPU・GPUアーキテクチャが全く異なるPCが何台も存在

  • 年によって使える予算に差
  • 潤沢な年だけマシンのスペックが上がる

特定のマシンに計算が集中

など,管理の問題だけでなく予算の都合による構造的な問題も存在します*2

加えて研究室のWebサイトやメールなど研究に直接は関与しないサービスを動かすマシンも存在します.この管理も教員や学生がやっており,時間が無駄に……

これらを踏まえ,まずはこれまでの研究室の計算機環境を見ていきます.

今までの環境

f:id:pudding_info:20190422012417p:plain
以前のサーバ構成

M1からこの研究室に来たのですが,既に環境としてはそれなりに整っているという印象でした.もちろん多数の不安定要素がありましたが,日常のオペレーションについては問題なく,みんなそれなりにやっているという感じでした.しかし,

  • どう考えても古すぎるラボの中心となるMac mini
    • メールシステムの認証が要件を満たしていないのかGmailから認証できない
    • 再起動の度に設定が吹き飛んで初期化されるApache
    • もはや誰もわからないOpenDirectory
    • ずっとPHPのバージョン警告が出ているWordPress
  • 使いもしないのにWindowsが入っている計算サーバたち
    • システムSSDの空き容量が20GB程度しかない
  • ディスプレイにつなぎもしないのにDesktop OSが入っている計算サーバたち
    • 昔は繋いでいたらしいが,サーバとして使っているのにChromeとかLibre Officeとか入ってて邪魔
  • 完全に粗大ゴミと化している古い計算マシンたち
  • 初期構築以降の環境構築方法を誰も残していないので思い思いにインストールされているCUDAとNVIDIA Driver
  • 怪しすぎるセキュリティ

などなど,課題は山積みでした.前から早くこのMac miniから乗り換えたいよねという話はしていたのですが,やはりなかなか難しく,腰が重かったことは言うまでもないでしょう.しかし僕ももうM2になってしまう,出来る人間が出来るうちにやらないと手遅れになる,という危機感から,春先より気合いを入れて移行を始めました.

新しい環境への移行にあたって

完璧を目指すことは最初から諦めていました.そんなにスキルレベル高くないです.しかし,自分は卒業してしまう身なのでどうにかノウハウだけは残す必要があります.オレオレシェルスクリプトを残すよりはという気持ちで,今回はAnsibleを利用しました.また,

  • 完全自動化は諦める
  • ぶっ壊れない環境を作るのでは無く,ぶっ壊れた環境を躊躇無く消し飛ばしてできるだけ早く復旧できるようにする
  • 全て自力で解決しようとしない

のようなポリシーでPlaybookを組み,無理なところはおとなしく手動オペレーションでいいやという気持ちになることで,自分の中のハードルを下げています.

結果から言うと

  • OSのインストール・IPアドレスの固定までは手動(逆に言うとそれ以降は全て自動化)
  • 計算用マシンについては途中トラブってもOSのインストール含め概ね30分〜1時間以内に復旧できる
  • 金でなんとかなるものはなんとかした(NASを買った)

という感じです.かなり頑張った.

新しい環境

f:id:pudding_info:20190425173601p:plain
新しいサーバ構成

まず,ストレージ周りを全てNASに押し付けて解決を図りました.自分で頑張ってRAID組んで壊れたら復旧させて…とか面倒臭いので,ここはお金で解決*3

これまでのMac Miniの代わりに,計算以外のタスクを担うサーバとして富士通PRIMERGY TX1320M3を購入してもらいました.4コアXeon,16GBメモリ,1TB HDD×2(RAID1)でまぁ困らないくらいのスペックかなと*4.これに

など,その他いろいろ細かい設定をするRoleを書いて実行しています.当然OpenLDAPPostfixDovecotも触ったことないので一から調べました.未だにOpenLDAPはよくわからない.続いて計算機も

という感じで整備.まだSingularityの導入で変わるかも知れませんが,基本的にはユーザの善意を信じて大きくパーミッションを与え,Dockerでの実行をサポートしています*5nvidia-docker2は容易にCUDAのバージョンを切り替えることが出来るので,他人とバージョンが違っても安心なのがとても良いですね.Dockerの学習コストは少し高いですが,クライアントマシンとサーバマシンで環境を統一してデバッグできるので,メリットは十分大きいと考えています.
ワンクリックでサーバ上で実行!とはいきませんが,

  1. ローカルで開発
  2. ローカルでDockerfileを書いて実行を試す
  3. サーバ上でイメージをビルド
  4. コンテナを作ってデータセットをマウントし実行

で,少なくとも環境構築の手間はかなり省かれたかなと.

監視

業務用のサーバでもないのに,監視なんて必要なのか?という話なのですが,あった方がいいと個人的には思っています.ディスクの空き容量,CPU・GPU・メモリの使用状況等,いちいち自分で確認しに行くよりもWebUIから確認,自動でアラートするなどやっておいて損することは無いと思います.余っていた古いXeonマシンがあったのでこいつでいいやと突っ込むことに.監視ソリューションのOSSとしては

他に一部無料枠として

等がありますが,

  • 僕自身に運用経験があった*6
  • AlertManagerの通知先としてSlackがサポートされていた*7
  • GrafanaのUIが使いやすい

等の理由から,Prometheus + Grafana + Alert ManagerをDockerを用いてデプロイしています(全てAnsibleでプロビジョニング).各サーバに

  • Node Exporter:ホストの様々なメトリックを取得
  • cAdvisor:Dockerコンテナに関するメトリクスの取得
  • DCGM Exporter:GPUがある場合に導入.GPUに関するメトリクスが取れる

等を入れ,監視サーバから叩いています.各ホストに関するメトリクスを簡単にまとめてダッシュボードを作成.以下の様な感じで提供しています.どのサーバがどういったスペックなのかもここで確認できる様になっています.

f:id:pudding_info:20190426223650p:plain
Grafanaのダッシュボード(自作)

アラートも設定しており,例えば10分以上ダウンしていればSlackに通知が来ます.

f:id:pudding_info:20190426223852p:plain
Alert ManagerからSlackへのアラート

アラートはもっと拡充していきたいなと考えていますが,優先度が低いのでまだ後回しになっています.誰か手伝って.

情報共有・記録

これはステマですが,弊研究室はWikiシステムとしてesa.ioを導入しました.

esa.io

そもそもサークルでアカデミックプラン*8を知って使い始めたのですが,使いやすい,デザインが見やすい等かなり良いです.これまではMDWiki.jsを使用していたのですが,

  • Gitでcommit・pushしないといけない
  • PushしたときにWeb上に自動で反映させる独自hookが大抵上手く動かない
  • 上記2点も相まって誰も使ってくれない
  • これ本体に認証システムがないのでApacheで特定のパス以下にLDAPを用いたベーシック認証を加えていたが,Nginxだと自前ビルドが必要で面倒臭い.

などの問題があったため,思い切って先生に提案しました.目に見えて利用率は上がりました.みんなもっと気軽に自分の知見書いて欲しいですね.

他にやりたいこと

使用中であることの明示

現在どのマシンをどのくらい使うか,他の作業との共存の可否,使い終わったか等をSlackで自己申告する様にしています.あんまりいけてないので,Alert Managerでイイカンジにならないか考えているのですが,

  • Dockerを使うと基本的にプロセスはrootで動く
    • 使用ユーザの特定が難しい
  • Dockerコンテナ名は指定しない限りランダムになる
    • コンテナ名で通知しても分からない
  • 「使用している」の基準の設け方が難しい

等があり,自動化出来ていません.誰か上手いやり方を知っている人は教えて頂きたい.

自動ジョブ実行システム

現状,何か計算を実行するのはユーザの手動に頼っています.そのため,

Aさん「今日一日使います」
Bさん「あ,じゃあそのあと使わせてください(あ,じゃあ明日やるかぁ…)」

みたいな場合があり得ます.空くのは分かっていても,わざわざ終わるかどうかを監視して終わったらすぐ実行するなんて面倒なこと,締め切り間際でもしない限りやりませんよね?これを

Aさん「今ジョブ投入しました.今日中に終わると思います」
Bさん「その次投入してありまーす(明日の朝には終わってるかな?)」

みたいにできると,日中にひたすら結果待ちをする時間を省けて幸せになれるんじゃ無いかと思っています.ただ,こういうのやったことないのでどうやれば良いのか全く分かっていません.どうやるんだ…?

今後の課題

Ansibleをちゃんと使える後継者を育成することが一番大変そうです.とは言ってもそんなに難しいことはしていないので,ドキュメントをある程度残していくだけで十分(向こう数年くらいは)運用できると思っています.

理想に近づくために

共用計算マシンの整備

f:id:pudding_info:20190426232756p:plain

まずお金が足りません.重い計算を行うのに,スペックは圧倒的に足りていません.もっとスペックで殴れれば色んなことを試せるんですが,GPUメモリが全然足りていません.これについてはそもそも研究室単位で頑張ることが間違いだと思っていて,学部・大学単位で設備を揃える必要があると思っています.生物系だとたとえば質量分析器や電子顕微鏡等,高価な機材はその学部単位で所有し,各研究室が共同で使用していました.情報工学でもそろそろまともな共有マシン群が欲しい……せめてTesla K80とかが数枚載ったマシンが数台あると大分変わると思うんですが……(チラッチラッ

大学側での提供サービスの拡充

研究室Web・メール等は大学側で吸収してくれないかな……と思っています.どちらも別に研究室単位で管理する必要ないですよね?よくガバガバセキュリティでスパムの踏み台にされてる話を聞きますよ?
SMTPはリレーサーバが(おそらくスパム対策で)提供されているのですが,そもそも自分でPostfixの運用をしたくない……
更にLDAPも,大学側がActive Directoryとかで提供してくれると楽になるのにとつらい気持ちになっています.うちではWindowsマシンが一台もない,Mac等は個人が使うのでID統一の必要が無いという条件があるのでOpenLDAPで事足りていますが,Windowsを使う研究室では無理でしょう.あくまで自分の研究室が恵まれているために可能な構成だと思います.

クラウド利用は?

f:id:pudding_info:20190426232605p:plain

Google Colaboratoryは神.ただし軽いタスクに限る.
大学の予算の使い方の仕様上,従量課金制というのがとてつもなく相性が悪いです.産総研のABCIみたいな一定ポイント買い切り制ならまだ良いかも知れませんが,そもそもラボ内のマシンで四苦八苦している人がクラウドインスタンスでどうのこうのなんて,理解出来ると思いますか?
無料でトライアンドエラーできるのがオンプレの良いところなのですが,これにお金がかかるとなって,結局「みんな怖がって使わない→クラウドインスタンスがエリクサー化」するのも嫌です.やっぱり学内共用計算マシンと,その使用方法講座みたいなのを実施するのがベストなんじゃないかなぁと.

まとめ

  • 大学の計算機環境は様々な事情でレガシーの塊のところも多く,管理者がどんどん変わっていくのでカオス
  • 弊研究室では古い環境を一新してAnsibleで全て整備した
  • 今後求められるスペックに一研究室で対応するのは大変,大学・学部での共用計算資源で大規模な計算もできるようにしてほしい

お金が欲しいですね.
他の研究室でのプラクティスとか全然聞かないんですけど,みんな一体どうしてるんでしょう?実はみんなOpenLDAPもADも完全に理解してて,分かってないのは僕だけとか?もうみんなKubernetes機械学習基盤組んでて,ラボメンみんなマニフェスト書けるとか?こういうの入れると良いよとか,こういうフローオススメだよとか,無限に募集しています.よろしくお願いします.

では,明日は弊サー期待の新入生が書いてくれるっぽいので楽しみにしています.ヨロシクネー!

*1:なお,弊研究室は一人一台MacBookが支給され,これをクライアントとして用いています

*2:買えているだけマシという話もある

*3:なお,ここでQNAPを選択したのは完全に失敗でした.NAS上のホームディレクトリとNFSで配信する他のサーバで利用するためのホームディレクトリを一致させると,AFP等でNASにログインしたとき,ホームディレクトリのパーミッションが777に書き換えられ,公開鍵でのsshがPermissionのエラーで弾かれるようになります. https://forum.qnap.com/viewtopic.php?t=123842

*4:SSD 1TB×2はオプションが無かった

*5:sudoできるユーザは限られています.dockerグループにデフォルトで所属させ,sudo無しでの実行をサポートしているだけ

*6:poyo.hatenablog.jp

*7:弊研究室は全てSlack経由で連絡を取り合っています

*8:docs.esa.io