Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Cloud RunとFastAPIで、ChatBotをミニマムスタートしよう / Getting start ChatBot with FastAPI and Cloud RUN

252e6c31a6452aa80deb9ad0107975c7?s=47 attakei
August 29, 2020

Cloud RunとFastAPIで、ChatBotをミニマムスタートしよう / Getting start ChatBot with FastAPI and Cloud RUN

252e6c31a6452aa80deb9ad0107975c7?s=128

attakei

August 29, 2020
Tweet

Transcript

  1. Cloud RunとFastAPIで、ChatBotをミニマ ムスタートしよう date: 2020-08-29 author: Kazuya Takei location: PyCon

    JP 2020 links: #pyconjp_4
  2. イントロ

  3. 今回のお題 Google Cloud Run上に FastAPIをベースにしたWebアプリケーションとして Slackで簡易なをボットを提供する という話をします。

  4. 今回のお題 Google Cloud Run上に FastAPIをベースにしたWebアプリケーションとして ↑ここがメイン予定 Slackで簡易なをボットを提供する という話をします。

  5. Who am I Kazuya Takei NIJIBOX Co., Ltd Server-side engineer

    @attakei Twitter GitHub and more
  6. Who am I Pythonista Sphinx extensions sphinx-revealjs sphinxcontrib-gtagjs

  7. 免責 「ミニマムスタート」を主眼にしているため、 一部の実装などを意図的に省いています。 所属とは無関係の発表です。 個人の嗜好性を多分に含んだ内容となっています。

  8. SlackのChatOps事情

  9. よくある、SlackのChatOps Slackbot/ワークフロー サードパーティのSlackアプリ 各種カスタムIntegration

  10. Slackbot/ワークフロー Slackbot 特定のメッセージに反応するもの 反応内容を複数用意すると、ランダムで選ばれる ワークフロー 各種トリガーに、フォーム表示とメッセージ送信をす る 「チャンネルにメンバーが増えた時にメッセージ」ぐ らいの簡単なやつ向け

  11. サードパーティのSlackアプリ Appディレクトリに登録されている様々なコラボレーショ ン用アプリ

  12. 各種カスタムIntegration 混み入ったことをしたいなら カスタムBot Incoming/Outgoing Webhooks Slash Commands

  13. 各種カスタムIntegration 混み入ったことをしたいなら カスタムBot Incoming/Outgoing Webhooks Slash Commands <= 今回はこれ

  14. なぜSlash Commands? Slackをトリガーに出来る アプリケーションの実装が楽 Incoming Webhookを含んでる 単体でもいいし、カスタムBotにも組み込める = ミニマムスタートに丁度いい

  15. Slack Commands

  16. Slash Commandsって? / から始まるショートカットコマンドを定義して、 特定の アクションを実行できるようにしたもの。

  17. ビルトインのSlash Commands 普段、自分がよく使っているもの /feed : RSSフィードを使用する /remind : リマインダー機能を使用する /invite

    : チャンネルに招待する /archive : チャンネルをアーカイブする
  18. 自作Slash Commandの仕組み ※基本的なもの コマンドを実行する 指定されたURLへリクエストを行う URLは3秒以内にレスポンスを返す レスポンスを元に、メッセージとして表示する

  19. 自作Slash Commandの仕組み レスポンスボディが… 空(0byte) => 何もしない テキスト => テキストのまま表示 json

    => Webhookと同じ
  20. 自作Slash Commandの仕組み リクエストの中身(一部) 名前 中身 例 team_domain チーム名 pyconjp-fellow user_name

    ユーザー attakei text コマンドの引数 – response_url Webhook用URL –
  21. -> Next ->

  22. ローカル開発する

  23. 基本構成 Python 3.8系 Poetryでパッケージ管理 ngrokを使用

  24. ngrok NATやファイヤーウォールなどを越えて、 ローカルホスト を外部公開できるサービス。 HTTPSのURLも利用可能 ※今回の例では、最初に ngrok プロセスを用意して、 ロー カルサーバは常時固定ポートを使用します。

  25. ngrok こんな感じに動く $ ngrok http 8000 Session Status online Account

    Kazuya Takei (Plan: Free) Update update available (version 2.3.35, Ctrl-U to update) Version 2.3.35 Region United States (us) Web Interface http://127.0.0.1:4040 Forwarding http://8e1d754c956e.ngrok.io -> http://localhost:8000 Forwarding https://8e1d754c956e.ngrok.io -> http://localhost:8000
  26. ngrok 補足として… 無料プランの場合、 コマンドのたびに公開URLが変わる 同時に用意できるプロセスは1個まで 不便な場合は有料プランをどうぞ

  27. FastAPIでSlashCommand を作る

  28. FastAPIとは Python製Webアプリケーションフレー ムワーク 名前の通り「高速なAPIサーバー」 向け Starlette というASGIフレームワ ークを土台にしている 特に何も考えずに、 async/await

    構文が使える Flask や Bottle っぽい
  29. 最小の構成 ミニマムな main.py from fastapi import FastAPI app = FastAPI()

    @app.get("/") async def hello_world(): return {"msg": "Hello, World!"}
  30. 最小の構成 サーバーを起動する 動作確認 $ pip install fastapi uvicorn $ uvicorn

    main:app INFO: Uvicorn running on http://127.0.0 INFO: Started reloader process [2719205 INFO: Started server process [2719212] INFO: Waiting for application startup. INFO: Application startup complete. INFO: 127.0.0.1:46564 - "GET / HTTP/1.1 $ http localhost:8000 HTTP/1.1 200 OK content-length: 23 content-type: application/json { "msg": "Hello, World!" }
  31. 最小の構成との比較 Flask Fast API ※見た目の関係で、意図的にコードを省いています from flask import Flask app

    = Flask(__name__) @app.route('/') def hello_world(): return json.dumps({ "msg":"Hello, World!" }) from fastapi import FastAPI app = FastAPI() @app.get("/") async def hello_world(): return {"msg": "Hello, World!"}
  32. 機能サンプル ページネーション用のクエリパラメータ from fastapi import FastAPI app = FastAPI() @app.get("/items/")

    async def get_items(p: int = 1): """ ``/items/`` => ``p`` = 1 ``/items/?p=3`` => ``p`` = 3 ``/items/?p=three`` => バリデーションエラー """ return {"msg": "Hello World"}
  33. 機能サンプル $ http localhost:8000/items/ p==3 HTTP/1.1 200 OK { "msg":

    "Hello World" } $ http localhost:8000/items/ p==three HTTP/1.1 422 Unprocessable Entity { "detail": [ { "loc": [ "query", "p" ], "msg": "value is not a valid int "type": "type_error.integer" } ] }
  34. その他機能 OpenAPIの提供 Pydanticによる恩恵各種 ミドルウェア 各種セキュリティ要求機能(Basic認証,OAuth2) Dependency Injection Background Task機能 Starletteが出来ること

  35. FastAPIでChatBotローカ ル開発(1)

  36. フォルダ構成 pyproject.toml はウィザードに従い作成 poetry.lock はそこから自動生成 + chatbot.py + poetry.lock +

    pyproject.toml
  37. pyproject.toml 重要なところだけ抜粋 [tool.poetry] name = "chatbot" [tool.poetry.dependencies] python = "^3.8"

    # 本体 fastapi = "^0.60.1" # POSTリクエスト時にformdataを受け付けるのに必要 python-multipart = "^0.0.5" # ASGIサーバー uvicorn = "^0.11.7"
  38. chatbot.py ルーティング関数のデフォルト引数が Form の場合、 POST リクエストから取る from fastapi import FastAPI,

    Form app = FastAPI() @app.post("/slash-commands/hello") async def hello(text: str = Form(...), user_name: str = Form(...)): return { "text": f"Hello {user_name}, I recieved `{text}`" }
  39. 動きを確認 $ http --form localhost:8000/slash-commands/hello text=Example user_name=attakei HTTP/1.1 200 OK

    content-length: 46 content-type: application/json date: Fri, 28 Aug 2020 18:14:23 GMT server: uvicorn { "text": "Hello attakei, I recieved `Example`" }
  40. 実際に動かす 試したいワークスペースにSlashCommandを作成して コマンドとURLの組み合わせを登録すれば 準備完了です →→ デモ?

  41. FastAPIでChatBotローカ ル開発(2)

  42. よりChatOpsっぽいコマンド /dig を作ります ※ dig = DNSの問い合わせをするツール Slack上のコマンドイメージ $ dig

    example.com ;; QUESTION SECTION: ;example.com. IN A ;; ANSWER SECTION: example.com. 48418 IN A 93.184.216.34 /dig example.com
  43. (1)を拡張して作る、 /dig # import を省略 # dns-pythonを使います app = FastAPI()

    @app.post("/slash-commands/dig") async def dig(text: str = Form("")): # DNSPythonを使ってDNS情報を取得する name = dns.name.from_text(text) query = dns.message.make_query(name, dns.rdatatype.A) msg = dns.query.udp(query, "8.8.8.8") return { "text": f"Answer for `A` of `{name}`\n```{msg}```" }
  44. 試してみる $ http --form localhost:8000/slash-commands/dig text=example.com HTTP/1.1 200 OK content-length:

    207 content-type: application/json date: Fri, 28 Aug 2020 18:18:19 GMT server: uvicorn { "text": "Answer for `A` of `example.com.`\n```id 31266\nopcode QUERY\nrcode NOERROR }
  45. 試してみる Answer for `A` of `example.com.` ```id 31266 opcode QUERY

    rcode NOERROR flags QR RD RA ;QUESTION example.com. IN A ;ANSWER example.com. 20986 IN A 93.184.216.34 ;AUTHORITY ;ADDITIONAL```
  46. 3秒しか猶予がない # import を省略 app = FastAPI() @app.post("/slash-commands/dig") async def

    dig(text: str = Form("")): name = dns.name.from_text(text) query = dns.message.make_query(name, dns.rdatatype.A) # もし、ここで想定外の時間がかかったら? msg = dns.query.udp(query, "8.8.8.8") return { "text": f"Answer for `A` of `{name}`\n```{msg}```" }
  47. 3秒しか猶予がない 対策をしないといけない

  48. Background Task機能 FastAPIに標準提供されている機能。 レスポンスとは非同期に処理させたいことを、 バックグラ ウドのイベントループに引き渡すことが出来る。 これにより、 レスポンスを早期に返してメイン処理を別途 実行することが可能になる

  49. Background Task機能 # import を省略 app = FastAPI() async def

    query_dns(name: str, url: str): name = dns.name.from_text(name) query = dns.message.make_query(name, dns.rdatatype.A) await asyncio.sleep(5) # 時間のかかる仮定 msg = dns.query.udp(query, "8.8.8.8") slack = slackweb.Slack(url) slack.notify(text=f"Answer for `A` of `{name}`\n```{msg}```") @app.post("/slash-commands/dig") async def dig( bg: BackgroundTasks, text: str = Form(""), response_url: str = Form("") ): # 直接処理せずに、移譲する bg.add_task(query_dns, text, response_url) # 空のレスポンスを返せば、まずは何もしない return Response()
  50. Background Task機能 図解 asyncioのイベントループ上で処理しているだけなので、依 存関係が増えない。 (ライブラリ的にも連携サービス的に も)

  51. デモ?(擬似的なもの) …これで、真っ当にミニマムなチャットボットが出来まし た。

  52. -> Next ->

  53. Cloud Runで運用する

  54. Cloud Runとは (Cloud Runのトッ プページより) コンテナ化されたアプリケ ーションをすばやく安全に デプロイ、スケーリングで きる、フルマネージド型の コンピューティング

    プラッ トフォーム
  55. Cloud Runとは 割と気軽に コンテナWebアプリの実行を ドメイン付きで 実現するサービス

  56. 3行で説明するCloud Runの使い方 GCPの契約をして、プロジェクトで必要なサービスを 有効化する ローカルでDockerアプリを開発してから、イメージを push サービスを起動する

  57. 図解 全てGCPでやる感じだと、こう

  58. Cloud Run向けDockerfile(例) # ------------------ # Build stage # ------------------ FROM

    python:3.8-slim as buildenv RUN mkdir -p /build/chatbot COPY chatbot/* /build/chatbot/ COPY poetry.lock pyproject.toml /build/ WORKDIR /build RUN poetry build # ------------------ # Running stage # ------------------ FROM python:3.8-slim COPY --from=buildenv /build/dist/chatbot-*-py3-none-any.whl / RUN pip install /chatbot-*-py3-none-any.whl CMD ["uvicorn", "chatbot:app", "--port", "8080"]
  59. Cloud Runのメリット/デメリット メリット: マシン管理が不要になる 従量課金 HTTPS+FQDNが自動で付与される ( {指定名+ランダムな文字列}.a.run.app )

  60. Cloud Runのメリット/デメリット デメリット: サーバー初動が遅い(微妙な頻度でタイムアウトする 運用にDockerの知識が少々必要 DDoS怖い

  61. Cloud Runの未知の部分 請求対象期間は、 「リクエストを受け付けてインスタンスが起動した時刻」 から 「インスタンスが最後にリクエストの処理をした時刻」 非同期処理している間は? インスタンスはいつ停止になる?

  62. まとめ

  63. 発表振り返り FastAPIの標準機能は強力で、ChatBotの実装を機能 面・制約面に対する対応が比較的容易でした Cloud Runベースの運用も比較的容易。ただし、この 種のサービスは初動がネック

  64. ※やりきってみたい ブラウザやCLIで確認してたネット系コマンドを SlashCommandsで提供するSlack App dig whois openssl (HTTPS validation)

  65. 各種リンク集 - Slack Slack API Slash Commands https://api.slack.com/ https://api.slack.com/interactivity/slash- commands#responding_to_commands

  66. 各種リンク集 - FastAPI Fast API Starlette https://fastapi.tiangolo.com/ https://www.starlette.io

  67. 各種リンク集 - Cloud Run Cloud Run https://cloud.google.com/run?hl=ja

  68. 各種リンク集 - その他 Sphinx sphinx-revealjs https://sphinx-doc.org/ja/master/ https://sphinx-revealjs.readthedocs.io/

  69. 各種リンク集 - その他 NIJIBOX Co., Ltd https://nijibox.jp