はじめに
こんにちは。オートモーティブ事業本部の藤田泰介と吉江智弘です。
DeNA TechCon 2020
にて発表する予定だった内容をBlog記事としてお届けします。
今回は「IoTと業務システムをつなぐgRPC/RESTサービスの開発と運用」と題してお送りします。
Centralサービスの概要と全体のアーキテクチャ(前半)
目次(前半)
- タクシー配車サービスMOVの紹介
- MOV Centralサービスの紹介
- Centralサービスの特徴
- Centralサービスが扱うデータ
- Centralサービスのアーキテクチャ
タクシー配車サービスMOVの紹介
サービスインしてから間もなく2年
MOVとは
- 乗りたいときに、スマホひとつで簡単にタクシーが呼べるサービス
- ITのチカラでタクシー利用を進化させる
- 提供エリア
- 神奈川 東京 大阪 京都 兵庫など
- 車両台数
- 数万台規模に拡大中
MOVはアプリ配車だけじゃない
- 後部座席タブレット
- MOV CALL
- 決済機
MOVの全体像
MOVのリアルタイム車両情報を電話配車に活かして事業者様のタクシー配車を最大化したい
そんな中、生まれたMOVの新サービスがCentralサービス
MOV Centralサービスの紹介
MOVの新サービス、Centralサービス
- タクシー事業者様の無線配車システムとMOVアプリ配車サービスを連携させる
- 事業者様は既設無線配車システムと同等の機能をMOV乗務員タブレットで利用可能にする
- MOVの車両ステータス情報を事業者様配車システムから利用できるようにする
Centralサービスのイメージ
従来の無線配車システムはMOVと連携することにより電話配車に専念する
Centralサービスの位置づけ
Centralサービスの導入メリット
- MOV・無線配車システムそれぞれで管理していた車両ステータス情報を一元化できる
- 無線が廃止され車内の操作機器が1つにまとまるので乗務員様の車内業務がシンプルになる
- 1つの端末で配車室からの配車受付、アプリからの配車受付が可能になる
- システム導入コストの削減とアプリ配車による売上アップを見込める
Centralサービスの目指すもの
アプリ配車と電話配車を連携してタクシー事業者様の配車効率を最大化する
Centralサービスの開発
- 2019年4月から開発開始
- 2019年11月に試験稼働(2020/2から本稼働)
- チーム構成
- PdM、デザイナ
- アプリエンジニア
- サーバエンジニア
- サーバエンジニアは4名
- Kubernetes未経験者のみ
- gRPCを使ったAPI開発も全員未経験
- うち2名は1,2年前までアプリエンジニア
Centralサービスの特徴
- タクシー事業者様の電話配車システムは事業者ごとに仕様が違う
- 開発しているシステムベンダーもバラバラ
- 我々が全事業者様の配車システムの仕様を把握することは不可能に近い
- いかに汎用的な仕様に落とすかが重要
- 独自仕様に影響を受けないサービスを作る
- 独自機能を作り込まない汎用的な仕様に倒す
- APIが仕様を決めるのではなくAPIに渡ってくるデータが仕様を決める
- データによって乗務員端末と配車システムが仕様に沿った動きをする
- Centralサービスはデータが主役
少人数チームで開発スピードを落とさないための戦略
Centralサービスが扱うデータ
- 大きく分けて3種類
- マスタデータ
- メッセージ
- 車両ステータス
データの流れ
マスタデータとは
- MOVと電話配車システムで共通に使うデータ
- 営業所マスタ
- 車両マスタ
- 乗務員マスタ
- Centralサービス独自のデータ
- 車両状態
- 定型メッセージ
- 地区/待機場所
マスタデータの特性
- MOVと共通のデータはMOVのマスタ管理システム(通称タクモニ)から連携される
- 乗務員ログイン中のデータ更新はできない
- データ更新頻度は少ないが連携漏れに注意が必要
メッセージとは
- 配車室からの指示や端末の状態変更依頼をやりとりするデータ
- 配車室から乗務員に対する指示
- 配車指示
- 通話依頼
- 業務メッセージ
- 乗務員から配車室への指示応答や依頼
- 配車指示への応答
- 状態変更依頼(休憩など)
- 待機の申請
- APIを個別に開発するのではなくメッセージという汎用的なデータをつかってやりとりする
メッセージの特性
- リアルタイム性が重要
- 取りこぼしのないデータ配信はさらに重要
車両ステータスとは
- 車両状態を示す情報
- 位置情報
- タクシーメーターの状態
- メーター以外の状態(休憩、開局、閉局 etc)
- MOVにおける車両ステータス
- 数秒に1回のペースでサーバに送信する
- アプリ配車の抽選などに使用する
車両ステータスの特性
- 車両ステータスはMOVの根幹を支える重要な情報
- データのリアルタイム性が重要
- 位置情報は個人情報になり得る場合があるので取り扱いに注意が必要
Centralサービスのアーキテクチャ
技術要素
- gRPC
- Google Cloud Endpoints for gRPC
- Google Kubernetes Engine(GKE)
- Redis Streams
- Firestore
- 開発言語はGo 1.14
データ特性ごとにサービスを分割する
- マスタデータ
- マスタ管理サービス
- メッセージ
- メッセージングサービス
- 車両ステータス
- 車両モニタリングサービス
マスタ管理サービスアーキテクチャ
- マスタデータはFirestoreに保存する
- データのCRUD操作用にREST APIを提供する
- MOVタクモニがデータを追加更新、乗務員端末と配車システムがデータを参照する
- Extensible Server Proxy(ESP)がREST APIをgRPC APIに変換する
メッセージングサービスアーキテクチャ
- 電話配車システムと乗務員端末間のメッセージのやりとりを仲介する
- データのリアルタイム性が重要なためRedis Streamsを使ってメッセージの配信をする
車両モニタリングサービスアーキテクチャ
- リアルタイム配信用データと近傍検索データを管理する
- Redis Streamsを使って車両ステータスをリアルタイムに電話配車システムに配信する
- 迎車地に近い車両を検索する(近傍検索)ためのデータはMemorystoreに保存する
Centralサービスを支える技術と展望(後半)
目次(後半)
- 前半のおさらい
- Centralサービスを支える技術
- Centralサービスのこれまでとこれから
- まとめ
前半のおさらい
- Centralサービスに求められる特性
- 遅延や取りこぼしのないデータ配信
- 様々なクライアントに対応できる柔軟性
- 少人数での開発を効率的に進める工夫
Centralサービスを支える技術
遅延の少ないデータ配信
車両ステータスは数秒に1回のペースでAWS IoTを通して配信される
- サービス間の効率的な通信とは?
- 一般的なJson/HTTPなどのプロトコルではオーバヘッドが無視できない
- オーバヘッドを最小化するには、常時接続型のサービスが望ましい
- gRPC/WebSocket/MQTT/TCPなどを検討
CentralサービスではgRPCをメインプロトコルとして採用
- gRPCのメリット
- HTTP/2によるパフォーマンス面のメリット
- 複数のプログラミング言語のサポート
- IDLによるスキーマ共有やコード生成のエコシステム
まとめ
- 遅延の少ないデータ配信の実現
- gRPCを利用して遅延の少ないデータ配信を実現している
取りこぼしのないデータ配信
- 車両ステータスとメッセージではデータの特性が異なる
- 車両ステータスはデータの鮮度が重要
- メッセージはデータをロストしないことが重要
- サービス間のメッセージ管理
- スケーリングやサービスの拡張性は確保したい
- サービス間のメッセージングは非同期が望ましい
- メッセージの管理にはメッセージブローカーを採用する
- メッセージブローカーの選択肢
Redis Streams
-
Redis 5.0で追加されたデータ型
-
Apache Kafkaによく似たメッセージ処理のための機能を提供
-
特徴
- データを時系列でもつ追記型のデータ構造
- データを取得しても過去のデータを再取得可能
- コンシューマグループの概念により読み出しの分散が可能
- XACKコマンドでメッセージの購読をマークできる
CentralサービスではRedis Streamsをメッセージブローカーとして採用
まとめ
- 取りこぼしのないデータ配信の実現
- Redis Streamsを採用しメッセージの取りこぼしを防いでいる
様々なクライアントへの対応
Protocol Buffersの活用
- Protocol Buffers (protobuf)
- Google製の構造化データのシリアライズフォーマット
- IDLで記述したプロトコル定義ファイルからシリアライズ/デシリアライズ コードを生成
- 様々なプラットフォーム / 言語で利用できる
- C++, C#, Dart, Go, Java, Python …
- protocol buffer compiler (protoc)
- .protoファイルを読み込みコード生成を行うコンパイラ
- 各言語向けソースコードをprotocコマンドを利用して生成する
- Protocol Buffersを活用し様々なクライアントに対応する
- IDLによるスキーマ共有
- 様々なプラットフォーム/言語に対応したコード生成
Cloud Endpointsの活用
- gRPCサービスならではの課題
- 認証はどのように提供する?
- クライアントとのドキュメント共有方法
- 常時接続が必要のないクライアント
- マスタデータのみを扱うクライアントがgRPCコードを追加実装するのはコスト
- Cloud Endpointsでできること
- gRPCに対応したAPI管理サービス
- HTTP/JSONリクエストをgRPCに変換
- 認証機能の提供
- ドキュメントの生成/共有 (後述)
- gRPCに対応したAPI管理サービス
- Cloud Endpointsを利用した認証
- Cloud EndpointsはJWTを用いたいくつかの認証方法をサポートしている
- トークンの検証はExtensibleServiceProxy(ESP)が行う
- endpointsの構成yamlファイルに認証条件を記載するだけで利用できる
- JSON/HTTP <-> gRPCの変換
- Cloud Endpointsはプロトコルのコード変換をサポートしている
- コード変換はExtensibleServiceProxy(ESP)が行う
- 利用方法もシンプル
- .protoファイルにアノテーションを追加する
- endpointsの構成yamlにマッピングを記載する
Centralサービスの認証認可
- 認証
- 配車システムや乗務員端末、連携するMOVシステム向けの認証
- 乗務員端末は紛失や盗難に備えたリスク管理が必要
- クライアント毎に異なる認証方法を提供する
- 認可
- 配車センター同士やドライバー同士で動態情報やメッセージが共有されてはいけない
- 適切な権限管理が必要
配車システムや社内システム向けの認証
- 認証
- Cloud Endpointsの認証機能を利用
- Google ID Tokenとサービスアカウントを利用してユーザーアカウントを認証
- 認可
- 認可部分は個別に実装する必要がある
- JWTからアカウント情報を参照して権限をチェックするInterceptorを実装
- トークンの発行者によってチェック内容が切り替える
乗務員端末の認証
- 配車システムやMOVシステム向けと同様の仕組みは利用できない
- MOVで管理する乗務員端末の数は数千台
- 端末1台毎に紛失 / 盗難のリスク管理が必要
- 独自認証局を構築し権限管理を行う
独自認証局を利用した認証
- 独自認証局を利用した認証
- 端末のキッティング時に認証局がクライアント証明書を発行
- 端末はクライアント証明書を利用して各サービス向けのアクセストークン発行が行える
- Centralサービスはアクセストークンの内容から車両を識別し権限チェックを行う
- リスク管理
- 認証局はクライアント証明書の発行や管理を行う
- 端末と認証局とのコネクションは1本となるよう制限
- 問題が発生した場合は証明書を無効にすることでリスク管理が行える
- クライアント証明書の発行フロー
- MOVの車両登録APIを実行し車両トークンを受け取る
- 認証局に証明書発行依頼
- 認証局はトークンを検証し証明書を発行する
- アクセストークンの発行フロー
- 認証局にトークン発行依頼
- 認証局はクライアント証明書を検証
- 発行されたトークンを利用してCentralサービスにアクセス
まとめ
- 様々なクライアントへの対応
- ProtocolBuffersで様々なクライアント実装に対応している
- CloudEndpointsでJSON/HTTPとgRPCの選択肢を用意している
- クライアントに応じた認証方法を提供している
- CloudEndpointsによる認証
- 独自認証局による認証
少人数での開発を効率的に進める
Infrastructure as Code
- インフラ管理のコストは最小化したい
- インフラ構成をチーム全員が把握している
- 誰でも環境を立ち上げられる
- いつでも素早く環境を用意できる
- Deployment Manager
- GCPが提供するInfrastructure as Code(IaC)ツール
- 特徴
- AWSでいうCloudFormation的なサービス
- PythonやJinja2でテンプレートの仕組み
- 状態ファイルを管理しなくてOK
- Deployment Managerを利用したインフラ設定の自動化
- base : ネットワークやファイアウォールの設定を管理
- service : GKEクラスタやNodePoolなどのサービス設定を管理
- iam : IAMやユーザーアカウント周りの設定を管理
- Deployment Managerを利用したインフラ設定の自動化
- コンテキスト毎のコンフィグをマージして最終的な構成情報を生成する
クライアントとの情報共有
- クライアントとの情報共有は時間をとられやすい部分
- APIドキュメントの作成や共有のコスト
- protoスキーマの共有や生成物の管理
- APIドキュメントの共有
- Cloud Endpoints Portalを利用したドキュメントの生成
- protoファイル情報からドキュメントを生成できる
- Json/HTTPに対応するAPIはOpenAPI形式でも出力可能
- Cloud Endpoints Portalを利用したドキュメントの生成
- protoスキーマの共有や生成物の管理
- Protocol Buffersを運用する際の課題
- protoファイルのバージョン管理の問題
- コンパイル場所やコンパイラバージョンの統一
- コンパイル後のソースコードの配布
- Protocol Buffersを運用する際の課題
- protoスキーマの共有や生成物の管理
- .protoファイルの管理やバージョニングはCIで自動化する
- .protoファイルを管理するリポジトリ
- .protoファイル自体のバージョン管理
- 更新があると各言語向けのビルドジョブを実施
- .protoファイルを管理するリポジトリ
- コンパイル後の生成物を管理するリポジトリ
- protoファイルのビルド + 配布方法を管理
- .protoファイルの管理やバージョニングはCIで自動化する
- protocプラグインの活用
- protocプラグインを利用したコード生成で開発を効率化する
- protocは各言語向けコードを生成するだけのツールではない
- プラグインによる拡張が可能な設計
- protocプラグインを利用したコード生成で開発を効率化する
- 既存のprotocプラグインを利用して使いやすいコードを生成する
- 自分たちのプロジェクトにあったprotocプラグインを自作して利用する
まとめ
- 少人数での開発を効率的に進める工夫
- 少人数だからこそ積極的に自動化していく
- 属人化を防ぎたい部分
- 繰り返しコミュニケーションが必要な部分
- 既存の便利な仕組みを積極的に取り入れる
- protobufのコード生成周辺は便利な仕組みが揃っている
- 小さな部分でも積み重ねて効率化していく
- 少人数だからこそ積極的に自動化していく
Centralサービスのこれまでとこれから
これまでの振り返り
- gRPCのタイムアウトやスケールの問題を解決できた
- インフラ構築やデプロイ自動化が進んでよかった
- 新しい技術を使う機会が多くて開発が楽しい
- 検証のフローやログの整備が後回しになってしまった
これまで解決できたこと
gRPCのロードバランシング
- gRPCのロードバランシングの問題
- スケールアウトしても負荷分散しない
- Proxy Model
- HTTP/2対応のリバースプロキシを配置して負荷分散をする
- L4 ProxyではgRPCのリクエスト分散が行えないので注意する
- Balancing-aware Clinet
- クライアントサイドで接続先サーバの一覧を把握しリクエスト時に負荷分散する
- 高パフォーマンスだが, バランシングポリシーの分散など考慮事項は多くなる
- External LoadBalancing Service
- 上記のハイブリットパターン
- 外部LBサーバに接続先サーバを問い合わせてからリクエストして負荷分散
- GKE + gRPC構成のProxy Modelロードバランシング
- HTTP(S) ロードバランサ
- GKE IngressControlerが作成するL7LB(HTTP/2対応)
- GCPサービスとの連携が可能 : ManagedSSL / CloudArmor / CloudCDN
- Centralサービスでは主にこちらを採用
- ロードバランサのヘルスチェックに少し工夫が必要なので注意
/
へのGETリクエストもしくはreadnessProbeをhttpGetで作成する必要がある- gRPCサービスの場合はバックエンドをHTTP/HTTP2両受けできるようにすることで対応可能
- HTTP(S) ロードバランサ
- GKE + gRPC構成のProxy Modelロードバランシング
- TCP/UDPロードバランサ
- type: LoadBalancer のServiceが作成するL4LB
- バランシングするには別途EnvoyやIngressControler等のL7プロキシが必要
- コネクションを1本に絞りたいなど、特定のユースケースではこちらを採用
- TCP/UDPロードバランサ
- gRPCのロードバランシングの問題
- Podがスケールアウトしても負荷分散しない場合があるので注意する
- ロードバランシングは自分たちの目的にあったものを選択する
- 常時接続型サービスとはいえ、切断を許容できるようにするのも重要
- 定期的にサーバ側でコネクションをリセットしてリバランスを行う
- クライアント側は突然の切断や定期的なリセットに対応できるようにしておく
- gRPCサービスのコネクションの維持の問題
- grpc-goではKeepaliveはデフォルトで無効なので注意する
- Client-side Keepaliveの設定が必要
- サーバーはgraceful shutdownできるようにしておくことも重要
- 実際は間に入っているLBやプロキシの設定にも影響を受ける
- 設定の際は実際にやり取りされるフレームを確認しながら設定を行うとよい◎
- Goの場合は GODEBUG=‘http2debug=2’; を有効にするとframe dumpを出力できる
- パケットキャプチャツールを使うことも有効
今後の取り組み
- 自動化の取り組みは今後も取り組んでいく
- デプロイの選択肢を増やす(Blue/Green, Canary)
- k8sと親和性の高いCDツールの導入
- 運用を効率化するための仕組みの導入
- 緊急度は低いが重要度は高いタスク
- 本番の動態データの再現
- 運用の実態に合わせたログ, 監視ツールの整備
まとめ
- 配車アプリ以外のサービスもいろいろ作っています!
- 新しい技術も積極的に採用しながらアップデートしています!
- サービスやデータの特性を考えながら技術選定をおこなっています
- 開発を効率化するための取り組みも大事にしています
- 統合後もDeNAと連携しながら更にスピードアップしていきます!
最後まで読んでいただき、ありがとうございます!
この記事をシェアしていただける方はこちらからお願いします。