fav2mark アプリ開発メモ
TRANSCRIPT
Pythonでアプリ開発メモ
whoami
ko-zu ◦ Python だいたい
◦ Javascript (web/userscripts) ときどき
◦ C, Java (Android) まれに
◦ twitter @cause_less
◦ http://causeless.hatenablog.jp/
◦ http://causeless.seesaa.net/
開発史
前提
ビジネスロジック ◦ OAuth ◦ データストア
並列化 ◦ 非同期フレームワーク
◦ gevent
管理 ◦ タスク管理
◦ プロセス管理
◦ サーバー管理
動機
締め切り間際のコンテスト発見
◦ このこん http://www.conoha.jp/conocon 6日前
自分が使うもの作ろう
◦ Twitter-はてブ連携の不満部分を実装
◦ アグリゲーションっぽいことしたい
多少触ったことがあるツールで
◦ 最低限セキュアに書けるライブラリで組む
fav2mark https://fav2mark.usb0.net/
favorite: Twitterのお気に入りツイート
bookmark: はてブのブックマーク
fav → mark ◦ URLを共有できると(自分が)便利!
なんのひねりもない……
1ユーザならcronでも出来る。
貧弱なVPS(openvz)でどこまで捌けるだろうか?
環境
Python2.6+ ◦ Flask, Requests, OAuthlib, Celery, gevent …
Redis ◦ redis-py
MongoDB ◦ MongoEngine (Google AppEngine風 Mapper)
当然全てOSS ◦ MongoDBはAGPL
◦ ライブラリはAPL/BSD/MIT/Python
一日目
前提
ビジネスロジック ◦ OAuth → Requests + OAuthLib
◦ データストア → Redis + MongoEngine
並列化 ◦ 非同期フレームワーク
◦ gevent
管理 ◦ タスク管理
◦ プロセス管理
◦ サーバー管理
基本設計
1ページアプリ
◦ OAuthでTwitterアクセス権限をもらう
◦ OAuthではてブ書込権限をもらう
◦ ちょっとした同期設定
◦ Flaskフレームワークで構築
バックグラウンドタスク
◦ Twitter APIからFavoritesを取得
◦ URLを抽出してはてブ Atom APIにPost
OAuth?
エンドユーザーからWebサービスの アクセス権(認可)を貰う仕組み
◦ パスワードは渡さない(≠合鍵)
◦ ユーザーは何時でも個別に無効化出来る
ユーザー認証にも使えなくはない
◦ sign in with twitter
◦ でもopenid (=IDを証明) とは別物!
OAuth
OAuthライブラリ多すぎ ◦ python-oauth (EOL?)
◦ python-oauth2
◦ python-oauthlib
◦ OAuth認可フローはWebと密結合
=フレームワークとのバインディング多数
非標準パラメータ対応のばらつき ◦ はてなAPIのscopeなど
OAuth
危なっかしいライブラリが……
アクセストークン管理 ◦ flask-oauthはaccess tokenをsigned cookieに保存しているような?
HTTPS証明書検証 ◦ 信頼チェーンとCN/SANsチェックしている?
セキュリティ上の前提がドキュメントにない なるべく単機能で疎結合なものがほしい
requests-oauthlib
pip install requests-oauthlib
Webフレームワーク非依存
ドキュメントが比較的まとも
HTTPカスタム認証プラグインとして実装
>>> oauth = OAuth1(client_key,
client_secret=client_secret, resource_owner_key=resource_owner_key, resource_owner_secret=resource_owner_secret, verifier=verifier) >>> r = requests.post(url=access_token_url, auth=oauth)
Requests
pip install requests
多機能HTTPクライアント実装
◦ コネクションプール
◦ HTTPS証明書検証(Mozilla準拠CA内蔵) ◦ doc/en と doc/jp (古いけど)
◦ ヘッダ処理・リダイレクト処理
短縮URL展開も手軽に
>>> res = requests.get(“http://bit.ly/1eb9WfE”) >>> print res.url u'http://fav2mark.usb0.net/'
Redis
オンメモリKey-Value+ Storage ◦ セッション・キャッシュ・ロック等に利用
memcachedの置き換えが楽 ◦ key長の制限無し ◦ redis-py実装のredis API対応が素直
python-memcachedとは何だったのか……
再起動してもデータが(大抵)消えない
シングルスレッド =トランザクションが低コスト
MongoEngine
ObjectDictMapper? ◦ Django/AppEngineライクなモデル記述
まだ開発途上 (doc)
◦ ネスト要素の取扱が重い
◦ シリアライズ重い
◦ 致命的なバグ・未実装が…… #537 にハマったり
素のpymongoで十分だった気もする
ロジック実装レシピ
requests-oauthlib ◦ 認証周りはシンプルなものを ◦ セキュリティ甘いライブラリ多い
httpはpython-requestsで
ACI(D)はRedisで ◦ redis-clusterが出たら全部Redisにするかも
MongoDBでスキーマレス ◦ Python上のObjectMappingが課題
二日目
前提
ビジネスロジック ◦ OAuth ◦ データストア
並列化 ◦ 非同期フレームワーク
◦ gevent
管理 ◦ タスク管理
◦ プロセス管理
◦ サーバー管理
フロー
Twitterから favorites取得
共有済みかDBでチェック
短縮URLを展開
個別にブックマークを生成
◦ 全てI/O待ちで時間が掛かる……
◦ 並列化するしかない
並列化
WebAPI経由のサービス
DBクエリ ◦ …CPUはほとんどiowaitしている
プロセス・スレッドを増やす? ◦ メモリ不足
◦ openvz VPSのプロセス数制限
◦ CPythonのLock問題
→非同期APIを使う
非同期化手法
例)Twisted(Asyncフレームワーク doc) フローを(人力で)
「短くてブロックしないコルーチン」 に分割
コールバックで遅延評価 コンテキストはクロージャか引数で維持
コンテキストがわかりづらい 分岐の記述が煩雑 他のライブラリをどう組み込む?
gevent
pip install gevent 非同期処理「自動化」ライブラリ (doc/jp) libev+マイクロスレッド(greenlet) ◦ 単一スレッドでイベント待ち ◦ 一応Windowsでも動く
よく使うAPIのAsync版を提供 ◦ socket I/O ◦ file I/O ◦ process/signal
gevent.monkey.patch_all()
非同期化作業は面倒 ◦ ブロックする処理は決まったAPI
◦ 他の処理はほとんど一瞬
……なら手作業で遅延評価に書き直す意味は?
同期APIを非同期APIに「パッチ」 ◦ ブロックする代わりに他のタスクに コンテキストスイッチ
◦ 協調的マルチタスクをほぼ自動化
gevent 協調的マルチタスク
同期的プログラミングがそのまま使える
◦ タスク(Greenlet)は普通のPython関数
◦ 外部ライブラリもほぼ無変更で並列化
greenlet数で並列度は制御可能
◦ 純イベント駆動と違い未発火イベントの リークやソケット溢れを気にする必要なし
割り込みされない
◦ 重い処理はsleep(0)して分割
並列化レシピ
from gevent import monkey monkey.patch_all()
greenlet間の排他制御はthreadと同様
あとはタスク管理に任せる
三日目
前提
ビジネスロジック
◦ OAuth
◦ データストア
並列化
◦ 非同期フレームワーク
◦ gevent
管理
◦ タスク管理 → Celery
◦ プロセス管理 → uWSGI
◦ サーバー管理 → Ansible
Celery
pip install celery
タスクキュー・メッセージ管理
◦ 各種バックエンド対応 amqp/redis/RDB
ワーカープロセス管理 (celeryd)
◦ タスクをモジュールとしてロード・実行
◦ greenletタスクに対応
cronライクジョブ実行 (celerybeat)
Celery
マルチアプリ対応が貧弱
◦ タスクをモジュール化して同じワーカープロセスに読ませる……面倒
ログ管理が貧弱
◦ 安全にlogrotate出来ない
◦ loggingモジュール依存でutf-8が通らない
uWSGI
pip install uwsgi
アプリケーション・サーバー
◦ php-fpmみたいなもの
◦ configファイルからWebアプリを起動
◦ Webアプリ以外の外部deamonも対応
豊富(過ぎる?)アプリ管理機能
◦ プロセス数管理(thread/greenletも対応)
◦ アプリリロード、ウォームアップ etc
uWSGI
全部uwsgiでプロセス管理
◦ Flask アプリのホストとリロード
◦ Celery Worker タスクモジュールのリロード
◦ redis/mongodb 専用インスタンス
◦ 設定の一元化・アプリ別設定の簡素化
uWSGI
uwsgi本体はC
◦ ビルド・依存解決が面倒
pip (pythonのパッケージマネージャ)は ビルド時に依存Cパッケージ教えてくれない
Pythonバージョン依存
◦ 別VerのPythonでvirtualenv化できない
◦ uwsgiを複数走らせる or libpython.so を動的リンクするようビルド
環境管理
自前でプロセスの面倒を見る部分 ◦ uwsgi
◦ nginx
依存関係 ◦ パッケージ管理
◦ C/Pythonライブラリのビルド
◦ 設定ファイルの統合 etc
…少なくなったけど手作業管理するのは厳しい
Ansible
pip install ansible リモートサーバー管理ツール ◦ Chef/Vagrantと違ってデプロイ済みサーバー 管理・維持の簡略化に特化
低レベルのシスオペ向け ◦ sshでコマンドを叩く+α ◦ テスト機構は未実装
別のVPSやVagrantでテストラン
冪等性からの逸脱に寛容 ◦ Changeイベントハンドラがある ◦ 冪等性を担保しないshell command
Ansible
Inventory ◦ サーバーの静的リスト AWS等の自動追加もできなくないけど……
◦ 各サーバーの振る舞い(role)を一元管理
Playbook ◦ リモートに流し込むコマンド(task)のリスト 複雑な条件分岐は書きにくい
$ ansible –i inventory.ini all –m yum –a name=nginx $ ansible-playbook –i inventory.ini myconfig.yml
Ansible
単純な設定同期・管理は非常に楽 ◦ sshでログイン出来る環境とpython2.5+があれば 始められる
ルール変更や失敗時の追跡が難しい ◦ playbook編集時は差分をチェックして 変更前後の挙動を把握していないとハマる
ドキュメントが分散 ◦ 開発中でAPIがよく変わる ◦ 暗黙の変数やパス展開が多く分かりにくい ◦ テンプレート展開を入れ子に適用していて エスケープに困る
サーバーデプロイ
今のところ手動(VPS)
Ansible+ローカルyum repo
◦ 何故かubuntuでrpm弄ってる……
セットアップ時間の大部分が SSH設定+システムのアップデート
OSテンプレート更新進捗どうですか?@VPSの中の人
動いた!
約3日 ◦ OAuth対応が一番かかった気がする ◦ cat *.py | wc –l 2000くらい
エラー処理・一貫性維持は簡略化 ◦ Twitter APIが明確なので依存 ◦ 稀に多重POSTしてもAPI的に許容範囲
性能は未検証 ◦ APIのテストはダミー相手 ◦ 100並列 1000qps 程度を想定
ボトルネック?
MongoDB Read ◦ MongoDBレプリケーションで自動化
MongoDB write ◦ 同期済みTweetIDのwriteが増える Redisに置き換え?
or MongoDBシャーディングで対応? (ToDo)
Redis ◦ KVSとして使う限り考慮する必要無さそう Web側はビジター増えないアプリ バックエンドはユーザーIDでシャーディング可能
ボトルネック
CeleryのCPU負荷 ◦ Redis側は余裕なので分散
◦ (デ)シリアライズが重い模様 フル機能が不要であればRedisのリストで もっと軽量に書けそう
ソケット数・バッファメモリ ◦ カーネル共有(openvz)だと厳しい
◦ KVMか専用サーバーへ
CPUが律速 ◦ レイテンシは並列化で対応してCPUが安い所へ
セキュリティ
SSL ◦ Forward Secrecy取得
エンコード・正規化(XSS) ◦ 全てテンプレートエンジン利用
セキュアセッション ◦ Redisで実装 fixation対策他
CSRF ◦ flask_wtf.csrf ベース
セキュリティヘッダ ◦ X-Frame-Options 他
追加対策(ToDo) ◦ サイト識別性向上
◦ DoS耐性向上
機能追加 ToDo
もう少しまともなURLフィルタリング ◦ 現状twitter側のsensitive/withheld依存
最新の同期ツイート表示
共有ツイート数カウンタ ◦ トップにもう少しコンテンツほしい
自動デプロイ
性能テスト・モニタ自動化
まとめ
Requests-OAuthlib便利
geventで簡単並列化
Ansibleでサーバー管理
全部(?) Pythonで! (uwsgi pipで入るし……)
参考・素材・ライブラリ OAuth
◦ http://oauth.net/
◦ http://wiki.oauth.net/w/page/12238520/Logo ( CC-BY-SA3.0, Chris Messina)
Twitter API
◦ https://dev.twitter.com/
◦ https://dev.twitter.com/terms/api-terms
はてな API
◦ http://developer.hatena.ne.jp/ja/license
◦ http://developer.hatena.ne.jp/ja/documents/auth/apis/oauth
Requests (APL)
◦ http://docs.python-requests.org/en/latest/
Requests-oauthlib (MIT)
◦ https://github.com/requests/requests-oauthlib
◦ http://requests-oauthlib.readthedocs.org/en/latest/oauth1_workflow.html
MongoDB (AGPL)
◦ http://www.mongodb.org/
◦ MongoEngine (MIT) http://mongoengine.org/
Redis (BSD)
◦ http://redis.io/
◦ https://github.com/antirez/redis-io
◦ Redis-py (MIT) https://github.com/andymccurdy/redis-py
参考・素材・ライブラリ Flask (BSD)
◦ http://flask.pocoo.org/
◦ http://flask.pocoo.org/snippets/75/
◦ flask-wtf https://flask-wtf.readthedocs.org/en/latest/
Gevent (MIT) ◦ http://www.gevent.org/
◦ http://methane.github.io/gevent-tutorial-ja/
Celery (BSD) ◦ http://www.celeryproject.org/
◦ http://docs.celeryproject.org/en/latest/userguide/workers.html
uWSGI (GPLv2) ◦ https://github.com/unbit/uwsgi
◦ http://uwsgi-docs.readthedocs.org/en/latest/
◦ http://uwsgi-docs.readthedocs.org/en/latest/AttachingDaemons.html
Ansible (GPLv3) ◦ http://www.ansibleworks.com/
◦ https://github.com/ansible/ansible