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

GCP活用事例と実践的Tips ~ Cloud Run + API Gateway から フロントエンドのソリューション選定まで ~

BonBon
September 30, 2021

GCP活用事例と実践的Tips ~ Cloud Run + API Gateway から フロントエンドのソリューション選定まで ~

医療スタートアップであるBonBon株式会社におけるGCPの活用事例とその知見です。
API Gateway、Cloud Run、フロントエンド(Next.js)のホスティング手段に焦点を当てて、活用ポイントと実践における注意点をまとめています。

BonBon

September 30, 2021
Tweet

More Decks by BonBon

Other Decks in Technology

Transcript

  1. Riku Ogura 2 • Software Engineer ◦ 本業: FE ~

    BE 横断のアプリケーション開発(一時は MLも) ◦ BonBon: インフラチーム所属・フロントエンド開発も兼任 • Bio ◦ 2018/04 ~ @Yahoo! JAPAN (EC) ▪ Java (SpringBoot), Node.js, React + TypeScript, Python(sklearn, keras) ▪ プライベートクラウド , マイクロサービス, DDD, gRPC, GraphQL ◦ 2021/01 ~ @freee (HR) ▪ Ruby on Rails, Golang, React + TypeScript ▪ AWS, モジュラモノリス, DDD, kubernetes (EKS) • Achievements / Activities
 ◦ ドメイン駆動設計で保守性をあげたリニューアル事例 〜 ショッピングクーポンの設計紹介 ◦ Next.js + NestJS + GraphQLで変化に追従するフロントエンドへ 〜 ショッピングクーポンの事例紹介 ◦ Next.js ドキュメント日本語訳プロジェクト | Next.js Documents Japanese Translation Project  tw: @ogugudayo
  2. API Gateway: 概要 5 • GCPのマネージドなゲートウェイサービス (2021/01 GA) ◦ 比較対象:

    Cloud Endpoints, Apigee • 特徴 ◦ OpenAPIによるルーティング記述 ◦ 認証レイヤーのサポート ▪ ユーザー認証: Custom JWT, Firebase Auth, Auth0, Google ID Tokens ▪ サービス間認証(サービスアカウントベース) ◦ レートリミット ◦ Monitoring & Tracingとの統合 • BonBonにおける導入目的 ◦ 認証レイヤーの吸収 ▪ Firebase Auth による認証を API Gateway で一挙に担う ◦ 今後増え続けるプロダクト・マイクロサービスを整理・標準化するための API 基盤 ▪ Cloud Run サービスや App Engine インスタンスの URL を知らなくてもよい世界 ◦ いざというときのモニタリングのポイントを絞るため ▪ トレースIDを自動で発行・送出してくれるため、それを伝搬させればトレーシングは充実する
  3. API Gateway: 類似ソリューションとの比較 6 API Gateway Cloud Endpoints Apigee ルーティング記述

    OpenAPI OpenAPI GUI もしくは OpenAPI コンピューティングの制約 App Engine / Cloud Run / Cloud Run for gRPC / Cloud Functions (ほぼ制約なし ?) コンピューティングとプロトコル (gRPC/OpenAPI) 次第でばらつきあり (参考) 制約なし(オンプレ環境対応) ユーザー認証 Firebase, Auth0, Okta, Google ID Token, カ スタムJWT Firebase, Auth0, Okta, Google ID Token, カスタ ムJWT 任意の OAuth2 準拠の認証方式 サービス間認証 サービスアカウントベース サービスアカウントベース ? 認可 なし なし VerifyJWTポリシーやFirestore連携などを活用 すれば可能 (?) カスタムドメイン 設定可能 (Cloud LBを利用) 設定可能 (Cloud LB 非利用) Apigee側で設定可能 CORS 現時点でサポートなし (2021 H1予定?) allowCorsを記述する サポートあり APIドキュメント生成 なし Cloud Endpoint Portal デベロッパーポータル その他 料金体系 0~200万rps/月 であれば $0.00 料金体系 0~200万rps/月 であれば $0.00 条件付きロジック、手続き型コード組み込み、収 益化機能などなど 「開発側としてはApigeeを使って頂きたいが、オーバースペックに感じる場合はそれ以外を」 @GC_OH
  4. API Gateway: BonBon における活用例 9 • 各プロダクトのAPIの前段には API Gateway がいる

    • ユーザー認証は API Gateway の Firebase Auth を利用 • 認可は専用のマイクロサービスを 作成し、API Gatewayを挟む • 認可サービスにはAPI Gateway の サービスアカウント認証を利用 • 各マイクロサービスは自らの サービスアカウントトークンを送出
  5. API Gateway: OpenAPI仕様の記述 10 OpenAPI仕様を利用してルーティングルールを記述する (ref. OpenAPI Extensions ) swagger.yaml

    swagger: '2.0' info: title: example-api x-google-backend: address: https://example-api-reufhirgr-an.a.run.app deadline: 60.0 securityDefinitions: firebase: authorizationUrl: "" flow: "implicit" type: "oauth2" x-google-issuer: "https://securetoken.google.com/example-project" x-google-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/[email protected]" x-google-audiences: "example-project" security: - firebase: []
  6. swagger: '2.0' info: title: example-api x-google-backend: address: https://example-api-reufhirgr-an.a.run.app deadline: 60.0

    securityDefinitions: firebase: authorizationUrl: "" flow: "implicit" type: "oauth2" x-google-issuer: "https://securetoken.google.com/example-project" x-google-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/[email protected]" x-google-audiences: "example-project" security: - firebase: [] API Gateway: OpenAPI仕様の記述 11 OpenAPI仕様を利用してルーティングルールを記述する (ref. OpenAPI Extensions ) swagger.yaml アップストリームのURL (トップレベルで記述した場合は全 path適用) アップストリームへのリクエストにおけるタイムアウト値 (トップレベルで記述した場合は全 path適用)
  7. API Gateway: OpenAPI仕様の記述 12 OpenAPI仕様を利用してルーティングルールを記述する (ref. OpenAPI Extensions ) swagger:

    '2.0' info: title: example-api x-google-backend: address: https://example-api-reufhirgr-an.a.run.app deadline: 60.0 securityDefinitions: firebase: authorizationUrl: "" flow: "implicit" type: "oauth2" x-google-issuer: "https://securetoken.google.com/example-project" x-google-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/[email protected]" x-google-audiences: "example-project" security: - firebase: [] swagger.yaml アップストリームのURL (トップレベルで記述した場合は全 path適用) アップストリームへのリクエストにおけるタイムアウト値 (トップレベルで記述した場合は全 path適用) 認証情報の発行者 (Firebase Authの例) APIが受け入れるオーディエンスのリスト JWTの署名検証に利用するプロバイダの公開鍵 URL
  8. API Gateway: OpenAPI仕様の記述 13 OpenAPI仕様を利用してルーティングルールを記述する (ref. OpenAPI Extensions ) paths:

    /aiseki/videos: get: security: - firebase: [] x-google-backend: address: https://aiseki-api-xds2faolwa-an.a.run.app path_translation: APPEND_PATH_TO_ADDRESS tags: - Videos operationId: GetVideos responses: '200': description: '' post: tags: - Videos swagger.yaml “/aiseki/videos” というパスを aiseki-api という Cloud Run サービスにルーティングする例
  9. paths: /aiseki/videos: get: security: - firebase: [] x-google-backend: address: https://aiseki-api-xds2faolwa-an.a.run.app

    path_translation: APPEND_PATH_TO_ADDRESS tags: - Videos operationId: GetVideos responses: '200': description: '' post: tags: - Videos API Gateway: OpenAPI仕様の記述 14 OpenAPI仕様を利用してルーティングルールを記述する (ref. OpenAPI Extensions ) swagger.yaml [GET] /aiseki/videos • APPEND_PATH_ADDRESS: パスをアップストリームに引き継ぐ ◦ https://aiseki-api-xds2faolwa-an.a.run.app/aiseki/videos • CONSTANT_ADDRESS: パスをアップストリームに引き継がない ◦ https://aiseki-api-xds2faolwa-an.a.run.app アップストリームのURL (オペレーション単位での適用 ) “/aiseki/videos” というパスを aiseki-api という Cloud Run サービスにルーティングする例
  10. API Gateway: 注意点① 15 • 注意点: OpenAPIファイルは単一ファイルである必要がある ◦ → だが

    swagger ファイルが肥大化するためプロダクトごとに分割したい ◦ → 何らかのCLIを用いて、デプロイ前に分割した swaggerを単一ファイルに統合したい • 解決策: swagger-combine を採用 ◦ すべての $ref を解決して統合してくれる ▪ 通常のOpenAPI では使えない場所でも $ref を使える ◦ paths / parameters / schemas / tags に対して置換処理を行える ▪ 正規表現置換:  { from: /\pet\/(.*)/, to: “/some-prefix/pet/$1” }  ▪ functionによる置換:  (path) => `/some-prefix/${path}`  • 運用方法 ◦ 特定dirに配置した swagger.yaml が自動で読み込まれるように (規約) ◦ paths などに対して必ずサービス名プレフィックスを付与するようにする ◦ OpenAPIをterraform templateとして扱い、tfvarsに定義した変数をバインドする
  11. const serviceDirList = (await fs.readdir('./resources/openapi/services', { withFileTypes: true })) .filter(dirent

    => dirent.isDirectory()) .map(({ name }) => _.kebabCase(name)); // -> [‘aiseki’, ‘porous’, ‘authz’, ...] const createServiceConfig = (serviceName) => { // サービスごとの swagger を読み出す設定を用意する関数 return { url: `./resources/openapi/services/${serviceName}/swagger.yaml`, // 読み込む OpenAPI ファイルの指定 paths: { base: `/${serviceName}`, // ベースパスにサービスプレフィックスを付与 }, securityDefinitions: { rename: { firebase: `${serviceName}-firebase` // securityDefinitions も重複しないようにプレフィックス付与 } }, // tags や operationId などについても同様のリネームを行う } } API Gateway: 運用方法 16 openapi-merge.mjs
  12. const swaggerJson = { swagger: "2.0", info: { title: "BonBon

    API Gateway", description: "An integrated API Gateway across BonBon services.", version: "1.0.0" }, apis: serviceDirList.map(createServiceConfig), // 読み出したサービスDirの配列に対して swagger-combine の設定を作成 } const generate = async () => { try { // merge -> json to yaml -> file output const json = await swaggerCombine(swaggerJson, { useBasePath: true }); const yaml = jsYaml.dump(json, 'utf8'); await fs.writeFile('./resources/openapi/swagger.yaml', yaml); console.log('SUCCESS: OpenAPI spec was integrated.'); } catch(err) { console.error(err); } } API Gateway: 運用方法 17 openapi-merge.mjs
  13. paths: /aiseki/videos: get: security: - firebase: [] x-google-backend: address: ${aiseki_api_url}

    # terraform の templatefile として記述する path_translation: APPEND_PATH_TO_ADDRESS # … API Gateway: 運用方法 18 swagger.yaml locals { api_config = templatefile("${path.module}/../openapi/swagger.yaml", { // templatefile() を利用して swagger nい変数をバインド gateway_project_id = var.project_id, aiseki_api_url = var.aiseki_api_url, // var は apply 時に外部の tfvars ファイルから取得 // ... }) } api_config.tf
  14. API Gateway: 運用方法 19 • API Gatewayに対する CI/CD を Cloud

    Build で整備 (ref. 公式ドキュメント) ◦ CI ▪ swagger-combineによるOpenAPIの統合 ▪ swagger-cliによるバリデーション ▪ terraform plan ◦ CD ▪ swagger-combineによるOpenAPIの統合 ▪ swagger-cliによるバリデーション ▪ terraform apply • API Gatewayのサービスアカウントに 「Cloud Run 起動元」 権限が必要なので、そのロール付与も terraform 内で行う(ひっかかりがち)
  15. API Gateway: 注意点② 20 • 注意点: CORSをサポートしていない ◦ API Gateway

    は Cloud Endpointsにおける allowCors を利用できない ◦ ref. API Gatewayユーザーグループの議論 • 解決策① そもそもCORSが起きないようにする ◦ API Gatewayの前段に Cloud LB を挟み、フロントと同じカスタムドメインを当てる ◦ ref. 公式ドキュメント
  16. API Gateway: 注意点② 21 paths: /api/videos: options: operationId: OptionsVideoList security:

    [] x-google-backend: address: ${aiseki_api_url} path_translation: APPEND_PATH_TO_ADDRESS responses: '200': $ref: "./swagger.yaml#/responses/options" # ... swagger.yaml • 解決策② 各 path に対して OPTIONS メソッドを定義する ◦ OpenAPI 上の CORSを許容したい path に対して OPTIONS メソッドを定義する ▪ ただし、preflight request時には Authorization ヘッダが付与されない ▪ そのため、OPTIONS メソッドのみ security: [] として認証を無効化する ◦ サーバー側では Access-Controll-Allow-Origin などを返す CORS middleware を実装する 現状は解決策②で対応しつつ、徐々に解決策①を適用していく予定
  17. Cloud Run: BonBon における Cloud Run の運用 24 • コンピューティング

    ◦ バックエンドは Cloud Run を採用 • コンテナイメージ ◦ Google Cloud Buildpacks で作成 ◦ Dockerfile のメンテコスト削減 ◦ ※ 一部動画サービスは ffmpeg を利用するため独自 Dockerfileを利用 • 開発環境 ◦ 各種IDEの拡張機能 Cloud Code で手軽に起動 ◦ minikubeベースで動く Cloud Run のローカルエミュレーター (デプロイも可) • CI/CD ◦ Cloud Build を利用 ◦ 他GCPソリューションとの統合が容易(特に引っかかりがちな IAM権限周り) • 監視 ◦ Cloud Logging / Monitoring / Tracing を利用 ◦ 他GCPソリューションとの統合が容易
  18. Cloud Run: 注意点 25 • 注意点: 最小インスタンス設定を行っても idle → active

    の昇格に 約4sec かかる ◦ コールドスタート防止のため最小インスタンス設定を 1 にした ◦ idle インスタンスが存続するようになったが、 CPU 割当は制限されている ◦ 特にシーケンシャルな依存関係を持つ n 個のサービスが Cloud Run で構成される場合、約 4n sec かかる ▪ ex) サービスA → サービスB → サービスC の呼び出しで約 12 sec • 解決策①: Cloud Scheduler の定期ポーリングによるウォームアップ ◦ 実装に手を入れる必要がなく手軽かつ課金料金も安く済む ◦ Cloud Scheduler の最小実行間隔が 1 min のため、わずかだが暖まっていない時間ができてしまう • 解決策②: SIGTERM 無限ループ ◦ アプリケーション内でSIGTERM通知をハンドリングし、自分自身を呼び出すことで暖める ◦ 実装に手を加える必要があるためやや面倒 • 解決策③: Always on CPU を利用する (2021/09 Preview Release) ◦ これによって active 昇格時のオーバーヘッドを根本解決できる ◦ 現在 Preview 機能であり、本件当初はまだ発表されていなかった 基本は最小インスタンス設定 or 解決策①で対応、一部 (基盤マイクロサービスなど) で解決策③を適用している状況
  19. Cloud Run の活用事例と実践 2 27 今日のアジェンダ API Gateway の活用事例と実践 1

    GCPにおける フロントエンドの ソリューション選定 3
  20. フロントエンドのソリューション選定: ホスティング手段 28 • BonBon のフロントエンド技術 ◦ 純粋なReact (CSR only)

    ▪ 現状は Firebase Hosting へのデプロイ ◦ Next.js (CSR only, no SSR/ISR) ▪ ゼロコンフィグの恩恵を受けるため、新規開発では積極的に採用 ▪ これまで以下の 1. を選択していたが、最近は 4. を選択している • GCP における Next.js のホスティング手段 1. Static HTML Export + Firebase Hosting ← API Gatewayとドメイン統合する際に不便 2. Static HTML Export + Cloud Storage + Cloud CDN ← 1. を面倒にしただけ 3. Cloud Functions + Firebase Hosting ← コールドスタート有・ us-central1のみ 4. App Engine + Cloud CDN ← 採用 5. Cloud Run + Cloud CDN ← 4. よりレイテンシが気になる (Always on CPUを使えばアリか)
  21. フロントエンドのソリューション選定: ホスティング手段 29 1. Static HTML Export + Firebase Hosting

    ◦ 実現方法 ▪ next export すると pages/hoge.tsx は hoge.html として出力される ▪ `/hoge` → hoge.html のリライトルールを firebase.json に記述すればよい ▪ または cleanUrls: true とすると、”.html” を削除したルールが設定される ◦ 問題点 ▪ API Gateway の CORS 問題回避のためにフロントとドメインを統合したい ▪ その場合、Cloud LB → Firebase Hosting という歪な構成になる
  22. フロントエンドのソリューション選定: ホスティング手段 30 3. Cloud Functions + Firebase Hosting ◦

    実現方法 ▪ Cloud Functions for Firebase に Next.js の SSR サーバーをデプロイ • 厳密には、サーバースクリプトを Cloud Functions 用の実行ファイル化 ▪ 公式example: https://github.com/vercel/next.js/tree/canary/examples/with-firebase-hosting ◦ 問題点 ▪ 参考記事: Next.jsをFirebase Hostingにデプロイする (ucworkさん) ▪ 初回アクセス時のコールドスタートが遅い ▪ us-central1 以外のリージョンを利用すると動かない • 公式「Firebase Hosting に接続されている関数は us-central1 に配置する必要があります。」
  23. フロントエンドのソリューション選定: ホスティング手段 31 4. App Engine + Cloud CDN ◦

    実現方法 ▪ App Engine に Next.js をデプロイ (つまり SSR 前提) ▪ App Engine の前段に Cloud LB を置き、LBに対してCloud CDNを有効化する ▪ 公式ドキュメントの手順 • 外部IPアドレスの予約 • SSL証明書リソースの作成 • 外部HTTP(S)ロードバランサの作成 • ロードバランサのIPアドレスを利用するようにDNSレコードを登録 ◦ 利点 ▪ 同じロードバランサのバックエンドバケットに API Gatewayを追加すれば、バックエンドとフロントエンドのドメ インを統合できる ▪ Next.jsアプリをVercelからGoogle Cloudに移行した話 (catnoseさん) • Cloud CDN は stale-while-revalidate にも対応している • Cloud CDN ではなく GAE のカスタムドメインだと東京リージョンでレイテンシ悪化
  24. フロントエンドのソリューション選定 32 今後の展望や話しきれなかったこと • ユーザー投稿型プロダクト (動画系サービスなど) の SEO 対策 ◦

    Next.jsであれば ISR による逐次レンダリングを行う ◦ ただし、Vercel 以外だと難しい ◦ そこで、SWR (stale-while-revalidate) を利用する ▪ キャッシュを利用してコンテンツを返しつつ、裏でオリジンを再検証・キャッシュを更新する ▪ 高速なコンテンツデリバリー & 鮮度の高いコンテンツ の両立 ◦ Cloud CDN は SWR に対応しているため、積極的に利用していきたい ◦ ref. stale-while-revalidate対応のCDNでISRのような挙動を実現する (catnoseさん) • プライベート npm パッケージの展開 ◦ Artifact Registry を利用して共通ID基盤実装の npm パッケージなどを社内配布し始めた ◦ Cloud Build との統合も詰まりどころはなく、使いやすかったのでお勧めしたい
  25. まとめ 33 • API Gateway ◦ 認証を API Gateway で吸収しつつ、認可はマイクロサービスで担うとよい

    ▪ 認可サービスはサービスアカウント認証を設けている ◦ 単一の OpenAPI ファイルである必要がある ▪ swagger-combine で統合すると命名の競合が起きなくて楽 ◦ CORS をサポートしていない ▪ アップストリーム側でも preflight request を考慮する必要がある ▪ 今後は Cloud LB を挟んでドメイン統合を行い CORS が起きないようにする • Cloud Run ◦ Cloud Code / Cloud Buildpacks など開発生産性が高い ◦ Cloud Run 同士にシーケンシャルな依存関係があるとオーバーヘッドがかさむ ▪ Cloud Scheduler による定期ポーリングで低コストに暖機できる ▪ 基盤的なマイクロサービスは Always on CPU を適用すべし • フロントエンドのソリューション選定 ◦ Next.jsのホスティング手段は、ドメイン統合・キャッシュ戦略を踏まえて App Engine + Cloud CDN に ◦ プライベートパッケージ配布には Artifact Registry が使いやすかった BonBonのプロダクト開発に関わりたい・医療に関わる人に感動と喜びを与えたい方は是非ご連絡を!