Slide 1

Slide 1 text

DockerとPythonで理解する TaskQueueサーバー 2018/05/16@サポーターズColab 19:30 - 21:30 Twitter@himenoglyph

Slide 2

Slide 2 text

プレゼンター 姫野滉盛 Twitter@himenoglyph Github@Himenon 4月に退職して 5月はニート 6月からまた働きます

Slide 3

Slide 3 text

アジェンダ ● “プロセス”のライフサイクル ● コンテナ化 ● “イベント”駆動アーキテクチャ ○ Task Queueサーバーの設計と実装 ○ ライブラリやマネージドサービスの紹介 ● まとめ サンプルコード:https://github.com/Himenon/TaskQueueLearn ハッシュタグ:#spzcolab

Slide 4

Slide 4 text

目的の整理 ● アプリケーションのエンジニアは、コンテナ技術とは何かを把握した上で利用する、 どこまでが境界なのかを検討する。 ● イベント駆動型のアーキテクチャとは何なのかを知る。 ● Task Queueサーバーの実装パターンを学ぶ。 ● DockerとPythonを使って、Task Queue Serverの作り方を学ぶ。 ● OSSやマネージドサービスを確認する。

Slide 5

Slide 5 text

ミスコミュニケーション Ref: Software Design 2018年5月号 試して理解Linuxのしくみ(第1回)

Slide 6

Slide 6 text

プロセスの ライフサイクル

Slide 7

Slide 7 text

実行単位 - プロセス - Linuxカーネルからみた処理の単位 - プログラムの実行単位の一つ - ライフライクルをもつ - ジョブ - シェルからみた処理の単位 - スレッド - プロセス内の単独の実行フロー

Slide 8

Slide 8 text

ps 現在動作しているプロセス を表示するコマンド $ ps PID TTY TIME CMD 1853 pts/0 00:00:00 ps 32712 pts/0 00:00:00 bash

Slide 9

Slide 9 text

プロセスとOSの関係 関数 システムコール Ref: [試して理解] の仕組み 〜実験と図解で学ぶとハードウェアの基礎知識 図 02−01 ユーザープログラム OS外ライブラリ OSライブラリ カーネル ハードウェア ユーザーモード カーネルーモード

Slide 10

Slide 10 text

プロセスの状態遷移 プロセス生成 実行待ち状態 スリープ状態 実行状態 ゾンビ状態 プロセス終了 終了処理を呼ぶ イベント発生 イベント待ち CPU実行権を失う CPU実行権を得る Ref: [試して理解] の仕組み 〜実験と図解で学ぶとハードウェアの基礎知識 図 04−12

Slide 11

Slide 11 text

複数プロセスと論理CPU1個 コンテキストスイッチ - プロセス0からプロセス1にスイッチするときの過程 - 複数のプロセスが1つのCPUを共有するための仕組み プロセス0 プロセス1 論理CPU プロセス0 プロセス1 プロセス0 プロセス0 プロセス1 実行状態 スリープ状態 実行待ち状態 アイドル状態

Slide 12

Slide 12 text

プロセスの生成:Fork 1 親 子 fork プロセス プロセスをforkすると、 - 子プロセス(サブプロセス)が生成される - 子プロセスは、親のメモリの内容をコピー - コピーオンライト - 親と子は独立している - 通信には、プロセス間通信( IPC: InterProcess Commmunication)を利用する 例)WEBサーバーによる複数リクエストの受付 親のリソース 子のリソース

Slide 13

Slide 13 text

プロセスの生成:Fork - Cで見る 親 子 fork プロセス // Ref: リスト03-01 fork.c プログラム 説明用に諸々省きました int main(void) { pid_t ret; // ココは親プロセスしか来ない領域 ret = fork(); // 親プロセスには0, 子プロセスには1, 失敗したら-1 if (ret == -1) { // forkに失敗したときの処理 } if (ret == 0) { // 子プロセスの処理 } else { // 親プロセスの処理 } } Ref: [試して理解] Linuxのしくみ〜実験と図解で学ぶ OSとハードウェアの基礎知識

Slide 14

Slide 14 text

fork プロセス _exit() プロセスの終わり 親 子 fork プロセス wait 親 子 ゾンビ化 _exit() メモリ解放 されてない

Slide 15

Slide 15 text

Pythonで検証する 本章のサンプルコードは Learn001 Python 3.6で検証

Slide 16

Slide 16 text

htop プロセスの状態をインタラクティブに確認するコマンド topコマンドの上位版 https://hisham.hm/htop/ Ubuntu Manpage: htop

Slide 17

Slide 17 text

デバッグコード 標準パッケージを利用 - os.getpid() :現在のpidを取得 - os.getppid():親のpidを取得 右のコードの様にinfo関数を定義。 後で使います import os def info(title): template = """ | {title} | -------------------------- | module name : {name} | parent pid : {ppid} | current pid : {pid} """ print(template.format(title=title, name=__name__, ppid=os.getppid(), pid=os.getpid() ))

Slide 18

Slide 18 text

検証:forkされていることをpidで確認する // mp001.py from multiprocessing import Process def myfunc(name): info('Sub') print('Hello', name) if __name__ == '__main__': info('Main') p = Process(target=myfunc, args=('おけまる',)) p.start() p.join() 出力結果 | Main | -------------------------- | module name : __main__ | parent pid : 110 | current pid : 243 | Sub | -------------------------- | module name : __main__ | parent pid : 243 | current pid : 244 Hello おけまる

Slide 19

Slide 19 text

検証:subprocessを実行させない // mp002.py def myfunc(name): info('Sub') print('Hello', name) if __name__ == '__main__': info('Main') p = Process(target=myfunc, args=('おけまる',)) while True: sleep(1) p.start() p.join() p.start()を実行していないので、プロセスが分岐していな いことがわかります。 じれったいですね。

Slide 20

Slide 20 text

検証:ゾンビ化してることを確認する // mp003.py def myfunc(name): info('Sub') print('Hello', name) if __name__ == '__main__': info('Main') p = Process(target=myfunc, args=('おけまる',)) p.start() while True: sleep(1) p.join() p.join()の手前で止めました。サブプロセスがゾンビ化 (Z)されていることが確認できます。 グレちゃいました。

Slide 21

Slide 21 text

検証:joinで終了していることを確認する // mp004.py def myfunc(name): info('Sub') print('Hello', name) if __name__ == '__main__': info('Main') p = Process(target=myfunc, args=('おけまる',)) p.start() p.join() while True: sleep(1) p.join()を実行すると、サブプロセスが終了していることが 確認できます。 じれったいですね。

Slide 22

Slide 22 text

スレッドとは? ● プロセス内の単独実行フロー ● メモリ空間をスレッド単位で共有 ○ システムリソースの共有 ● Kernerl内部ではプロセスとスレッドはほぼ区別が無い ● コンテキストスイッチの切り替えが高速 ● スレッドセーフかどうか考慮する必要がある ○ 複数のスレッドコンテキストから呼ばれることを前提にして いないライブラリを使用するとバグの温床になる Ref: naoyaのはてなダイアリー マルチスレッドのコンテキスト切り替えに伴うコスト Ref: Wikipedia - Native POSIX Thread Library 親 子 pthread_cre ate プロセス リソース共有

Slide 23

Slide 23 text

検証:threadのpidを確認してみる1 // th001.py import threading def myfunc(name): info('Sub') print('Hello', name) while True: sleep(1) if __name__ == '__main__': info('Main') for i in range(5): t = threading.Thread(target=myfunc, args=('おはよー!',)) t.start() $ python th001.py | Main | -------------------------- | module name : __main__ | parent pid : 110 | current pid : 619 | Sub | -------------------------- | module name : __main__ | parent pid : 110 | current pid : 619 Hello おはよー! | Sub | -------------------------- | module name : __main__ | parent pid : 110 | current pid : 619 Hello おはよー! 親 Thread 0 Thread 1

Slide 24

Slide 24 text

検証:threadのpidを確認してみる2 ps auxHで確認すると、全部同じPID。ただし、psのPID はthread数を考慮したものになっている。 ※Hオプションはスレッドをプロセスの様に表示 htopコマンドで確認すると、PIDが変わっているように見 える。 TGID(Thread Group ID)は同じ → htopのPIDは信じないほうがいい(誤解を招く) Ref: Get thread pid (ミスコミュニケーションが見れます)

Slide 25

Slide 25 text

プロセスとスレッドの関係 Memory System Resource thread1() thread2() thread3() Process Process System

Slide 26

Slide 26 text

ここまでのまとめ ● プロセスの状態遷移について学んだ。 - 生成 - 実行待ち状態 - 実行状態 - スリープ - ゾンビ化 - 終了 ● Pythonでプロセスとスレッドの挙動を確認した。 もっと知りたい人は... Pythonの3系では他にも便利なパッケージ(concurrentとか)がある。 詳しく知りたい人は、公式ドキュメント: 17. 並行実行 を参照

Slide 27

Slide 27 text

参考 ウェブサイト ● Tech Village - スタックと割り込み -- プログラムが動く仕組みを知ろう ● naoyaのはてなダイアリー マルチスレッドのコンテキスト切り替えに伴うコスト ● Wikipedia - Native POSIX Thread Library ● The Python-list Archives - Get thread pid ● Python 標準ライブラリ | 17. 並行実行 書籍 ● ふつうのLinuxプログラミング 第2版 | 青木 峰郎 著 ● [試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識 | 武内覚 著 ● 入門 Python 3 | Bill Lubanovic 著、斎藤 康毅 監訳、長尾 高弘 訳 ● 新しいLinuxの教科書 | 三宅 英明、大角 祐介 著

Slide 28

Slide 28 text

コンテナとは?

Slide 29

Slide 29 text

まず、仮想化とは? コンテナ型 - Pack: ミドルウェア - Base: ホストOS ホスト型 - Pack: OS - Base: ホストOS ハイパーバイザ型 - Pack: サーバー全体 - Base: ハードウェア それぞれ、抽象化の度合い・方法が違う OS Container Runtime VM Process Process Process OS Process Process Container Container Hardware Hypervisor OS OS Hardware OS 非仮想化 コンテナ型仮想化 ホスト型仮想化 ハイパーバイザ型仮想化 非仮想化 OSより上側の仮想化 OS以下の仮想化 Ref: Dockerが注目されている理由を探る - Think IT

Slide 30

Slide 30 text

コンテナランタイム ランタイムって? コンテナの実行環境・展開・操作(作成・削除)・ パッケージングなど管理に必要な環境を提供する もの OCI (Open Container Initiative) が標準化を進めている Ref: Dockerだけじゃないコンテナ runtime徹底比較@makocchi その他のRuntime - Docker - 現在の主流 - Frakti - hypervisor経由で展開 - cri-o - Kubernetes専用 - Containerd - Docker(>=v1.11)に含まれる

Slide 31

Slide 31 text

HOST OS システムリソース コンテナを実現するためには? - namespaceによる分離 ○ uts:システム情報(uname系) ○ ipc:プロセス間通信 ○ pid:プロセスの分離、PID制御 ○ net:ネットワーク制御 ○ mnt:ファイルシステム ○ user:uid, gid - cgroups(Control Groups)による分離 ○ システムリソース(CPU/Memory/Disk/Network) uts ipc pid net mnt user Ref: Think IT - コンテナ技術の基礎知識 システムリソース uts ipc pid net mnt user コンテナ コンテナ

Slide 32

Slide 32 text

コンテナ化によって達成すること The Twelve-Factor Appに収束します。

Slide 33

Slide 33 text

“コンテナ”の検証 本章のサンプルコードは learn000 以下

Slide 34

Slide 34 text

Dockerのコンテナを”プロセス”の観点から検証 目標:コンテナは名前空間で区切られたプロセスであること確認する。 検証1 ホストからコンテナのプロセスを確認する 検証2 他のコンテナのpid空間に参加する

Slide 35

Slide 35 text

便利なコマンド

Slide 36

Slide 36 text

pgrep プロセス名からpidを検索する (ps + grepのようなコマンド) https://hisham.hm/htop/

Slide 37

Slide 37 text

pstree プロセス分岐を表示 https://hisham.hm/htop/

Slide 38

Slide 38 text

検証環境

Slide 39

Slide 39 text

Vagrantで検証する場合 ゲストOS(Ubuntu)上にDockerをインストールし、その上で検証。 macOS Docker Engine Vagrant (Ubuntu) Process Container

Slide 40

Slide 40 text

Dockerのコンテナを”プロセス”の観点から検証 目標:コンテナは名前空間で区切られたプロセスであること確認する。 検証1 ホストからコンテナのプロセスを確認する 検証2 他のコンテナのpid空間に参加する

Slide 41

Slide 41 text

検証1:ホストからコンテナのプロセスを確認する コンテナはホストOSから見て”プロセス”か、実際に確認してみる。 予め、redis-serverとpythonのループプロセスのコンテナを起動しておきます。 Docker Engine ホストOS Process Container 同格? Process

Slide 42

Slide 42 text

検証1:結果 ホストOSからpstree $(pgrep dockerd)を実行することで、 dockerdからプロセスがforkされていることが確認できます。

Slide 43

Slide 43 text

Dockerのコンテナを”プロセス”の観点から検証 目標:コンテナは名前空間で区切られたプロセスであること確認する。 検証1 ホストからコンテナのプロセスを確認する 検証2 他のコンテナのpid空間に参加する

Slide 44

Slide 44 text

同一のpid空間 検証2:他のコンテナのpid空間に参加する コンテナのPID空間に--pid引数を利用して、参加します。 Ref: https://docs.docker.com/engine/reference/run/#pid-settings---pid my-redis inspctor $ docker run --name my-redis -d redis $ docker run --pid=container:my-redis inspector bash

Slide 45

Slide 45 text

検証2:結果 inspector側からps -auxを実行し、pid=1がredis-serverであることを確認。 strace -p 1を実行することで、redis-serverのシステムコールが確認できる。

Slide 46

Slide 46 text

補足1:Docker for Macだと確認できない? (そもそもdockerdが見つからない...バグ?)

Slide 47

Slide 47 text

補足1:薄いVMがいる HyperkitというVMがmacOSとの間にいます。 https://github.com/moby/hyperkit macOS Docker Engine Hyperkit (VM) Process Container Preference > Advancedにリソースの設定がある のは、Hyperkit用です。

Slide 48

Slide 48 text

補足1:Macからコンテナのプロセスを確認する screenコマンドでHyperkitのttyにattachして、プロセスを確認することができます Ref: https://stackoverflow.com/questions/39739560/how-to-access-the-vm-created-by-dockers-hyperkit $ screen ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/tty $ pstree $(pgrep dockerd)

Slide 49

Slide 49 text

補足2:htopをhostから使う Htopコマンドをホスト側で使って、dockerdで絞り込めば、 常にプロセスを監視できるようになります。

Slide 50

Slide 50 text

まとめ - コンテナ技術は特殊な技術ではなく、 リソースの名前空間の分離 や、 システムリソースの割当 によって実現されている。 - コンテナ内でミドルウェアや依存関係が独立しているため、開発のしやすさが向上し、 本番と一致されることも可能になっている。 - ただし、コンテナはホスト OSの1プロセスとして扱われていたり、 他の名前空間に接続しようと思えばでき るので、コンテナ技術を支えている技術に対して無知ではいけない。 - また、近い内にDocker以外のランタイムが台頭してきてもおかしくないので、 その場しのぎの理解だと置いていかれる。 - 理解して使えば、従来よりも開発が楽しくなるので、もっと技術を楽しもう!

Slide 51

Slide 51 text

コンテナ系の情報収集 たくさん書いてもアレなので、公式や、一次情報に近いメディアを選定 抑えておくべき公式 - Cloud Native Computing Foundation - Kubernetes メディア - InfoQ - Containers Content On InfoQ - Publickey - 「Docker / コンテナ / 仮想化」カテゴリの記事一覧 - Think IT - 仮想化/コンテナ 記事一覧 実装やその他、基本的な情報を得るには ... - Linux Containers - Qiita - コンテナ・デザイン・パターンの論文要約 @MahoTakara ※ Linux系の知識を予めつけた上で読むと、理解が進みます。 コミュニティ(connpass / slack) - Kubernetes Meetup Tokyo - Docker Meetup Tokyo - GCPUG - Cloud Native Meetup Tokyo コンテナ技術について、相当熱いです。 常に、connpassの倍率が2〜3倍くらいあります。

Slide 52

Slide 52 text

PythonでDockerっぽいもの作りたい人向け ● Fewbytes/rubber-docker ○ 掲載されているプレゼン資料も面白いです。

Slide 53

Slide 53 text

イベント駆動 アーキテクチャ

Slide 54

Slide 54 text

イベント駆動? アーキテクチャ形式 ● タスクキュー ○ キューに登録されたタスクを逐次実行していく。 ● メッセージキュー ○ 送信側と受信側が直接通信せずに、データを伝達する。 ○ Pub/Subメッセージングモデル(出版・購読モデル) どちらも、非同期処理を実装する際に通る道。

Slide 55

Slide 55 text

非同期処理とは? 例えば、ボタンを押した時に長い処理が走っている間、次の操作ができなかったらつらい。 → 到達確認が必要なプロトコルな場合、処理している間は同期通信している。(TCP/IPのスリーハンドシェイク) ※図はイメージです

Slide 56

Slide 56 text

タスクキューとは? タスク:分散させたい処理(ジョブとも言う) キュー:待ち行列のデータ構造 task 6 task 5 task 4 task 3 task 2 task 1 Worker Worker Worker Client タスクキュー

Slide 57

Slide 57 text

どんな場合に使う? ● 非同期処理 ● 長時間掛かるような処理(サーガ) ○ メールの送信処理 ○ PDFの生成(グラフとか) ○ スクレイピング ○ Githubのリポジトリのインポート ● 分散処理 ○ 長期処理を複数のワーカーに任せる ● マイクロサービス化 ○ 疎結合化 ○ イベント駆動型アーキテクチャ ● 結果整合性が許容される場合 次のページからは実装例を紹介します。 learn002: シンプルな実装 learn003: WEBサーバーを挟んだ実装 learn004: pub/subを使った実装 余談: redashの機構

Slide 58

Slide 58 text

[learn002] シンプルなTask Queue Client Broker Worker ・タスクの登録を行う 例) - CUI - WEB Server ・仲介人 例) - Redis - RabbitMQ ・メインロジック 例) - 長期プロセス

Slide 59

Slide 59 text

[learn003] Flask + Redisで実装するタスクキュー WEB Server Worker Client Broker Store 【要件】 Clientは /task?number=[整数] へPOSTリクエストを送信し、レスポンスとして resuit_idを受け取 る。次に、Clientは受けとったresult_idを元に、/task?result_id=[受け取った値] へGETリクエ ストを送信し、結果を受け取るまで、ポーリングする。 ※ポーリング:定期的な問い合わせ 8. 結果の問い合わせ ポーリング 1. タスクの登録 2. Queueの登録 3. QueueのPolling 6. 結果の保存 7. 結果の取得 5. タスクの実行 ※矢印は問い合わせの方 向

Slide 60

Slide 60 text

[learn003] Flask + Redisで実装するタスクキュー

Slide 61

Slide 61 text

[learn003]の問題 - ポーリングし続けると、負荷が掛かるのでは? - Server側にCache-controlのmax-ageなどを設定しましょう。 - Client側は、Exponential Backoff(指数関数的後退)などを用いて、リトライ間隔を広げたり、timeoutの設定をしましょ う。 - Redisってインメモリじゃなかったっけ?落ちたら終わりでは? - Redisのスナップショット機能を使う手があります。 - 分散処理するときに重複して処理が実行されない? - 重複実行をどこで食い止めるか、は要件次第にもなりますが、キューシステム側(Broker)にトランザクションの機能が あれば、そちらで食い止める事ができます。 - タスクに識別子をもたせ、クライアント側でその識別子を確認することで防ぐことがあります。 - ただ、どうしても信頼性が重要であれば、このアーキテクチャはふさわしくありません。 まだまだ、考えることは山程あります。

Slide 62

Slide 62 text

余談:redashの構成 Server Broker Worker Scheduler Result Store Client Redash の構成がこのような形です。 flask + celeryで構成されています。 Schedulerを入れることで、 cron job(定期実行)が可能なアーキテクチャになっています。

Slide 63

Slide 63 text

[learn004] pub/sub機能を使った例 Broker Subscriber Publisher 1. Publisher(出版社)はメッセージブローカーにメッセージキューを送信 2. Subscriberは購読しているPublisherのメッセージキューを受け取る Subscriber Subscriber Publisher Publisher

Slide 64

Slide 64 text

[learn004] いつ使うの? Ref: https://cloud.google.com/pubsub

Slide 65

Slide 65 text

[learn004] Pub/Subについて ● Pub/Subはキューを分散させるのが得意 ○ 自前でチャット機能などが作れるので、試してみると面白い ■ WEBサーバーをかませる場合は、 Polling方式か、WebSocketを利用する ● Subscribeするためにマルチスレッド化する必要があるので、なかなか大変 ● 【追記】この話はマルチサーバーアーキテクチャの話です(下記リンク) ○ Subscriber側は同じロジックをためたコンテナをスケールさせてしまうと、メッセージキューが来た時 に同じ処理が走るので、異なる処理を走らせたい + スケールさせたい場合は、前段にロードバラン サーが必要(下図) Publisher Pub/Sub Subscriber (Loadbalancer) Worker Worker Ref: Amazon ElastiCache for Redis を使ったChatアプリの開発

Slide 66

Slide 66 text

考えることいっぱいですね

Slide 67

Slide 67 text

Message Queue ● Apache kafka ● RabbitMQ ● Akka ● ActiveMQ ● NServiceBus ● MassTransit Cloud ● Cloud Pub/Sub (GCP) ● AWS SQS ライブラリやクラウド系サービス Task Queue ● Celery ● rq ● Sidekiq (Ruby) Cloud ● Google App Engine ● AWS Batch(なんか違う気がする) ● Azure : ServiceBus Queue …. とか

Slide 68

Slide 68 text

なんだ、フレームワーク使えばいいじゃない そんなことはない。 ● (コンテナの)サービス間の連携は常に同一言語とは限らない ○ 言語に依存してしまうフレームワークもある ● フレームワークやサービスが隠蔽してくれているだけで、無くなったわけではないから。 ● 開発に使うプロトコルはいつも同じものとは限らない。それに (絶対)増える。 ○ RCP、AMQP、TCP/IP、UDP、gRPC …. その他もろもろ ● 現場の技術選定にも依存するので、常に自分の知ってる技術を使っているとは限らない。

Slide 69

Slide 69 text

まとめ ● イベント駆動アーキテクチャを見た ● 結果整合性を許容するようなシステム設計にすることで、プロセスを分離し、疎結 合化できることを学んだ。 ● コンテナを使うことで、設計したアーキテクチャを、そのまま実装できることを学ん だ。 ● タスクキュー・メッセージキューの実装パターンを見た。 ● また、これらを実現するためのライブラリとSaaSを確認した。

Slide 70

Slide 70 text

さらなる情報:次に何したら良いってい人向け ● Kubernetes ○ オートスケール機能や、死活監視をさせたい場合 ○ デファクトスタンダード ○ ただ、一人でやるもんじゃないって、誰かが言ってた(実際そう) ● コンテナのデザイン・パターンについて知りたい ○ Qiita - コンテナ・デザイン・パターンの論文要約 @MahoTakara ○ 実はいつの間にかやってました、みたいなことがよくある ● デプロイしたいな〜 ○ Herokuの無料枠にdockerをデプロイできるのでおすすめです

Slide 71

Slide 71 text

全体のまとめ

Slide 72

Slide 72 text

本日のまとめ ● プロセスのライフサイクルを確認した。 ● コンテナ技術の仕組みをプロセスの観点から確認した。 ● イベント駆動アーキテクチャとは何か、を見た。

Slide 73

Slide 73 text

参考図書 ● ★ エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践 ) ● ★ 実践ドメイン駆動設計 (Object Oriented SELECTION) ● Redis入門 インメモリKVSによる高速データ管理 ● RDB技術者のためのNoSQLガイド ● NOSQLの基礎知識 (ビッグデータを活かすデータベース技術 ) ● マイクロサービスアーキテクチャ Sam Newman (著), 佐藤 直生 (監修), 木下 哲也 (翻訳)

Slide 74

Slide 74 text

ありがとうございました まだ今月はニートです。