Google Cloud Operations Suite で実現する "頑張らないオブザーバビリティ"

SRE チームの市川恭佑です。

先日、CloudNative Days Tokyo 2023 のプロポーザルを提出したのですが、残念ながら採択に至らなかったので、今回は宇宙最速の(?)供養エントリになります。

シェア・投票など、ご応援をくださった皆様にはこの場でお礼を申し上げます。ありがとうございました。

event.cloudnativedays.jp

背景とか、経緯とか

筆者は、カヤックの SRE チームにちょうど2年ほど在籍しています。とは言っても半年ぐらいは学生アルバイトだったので、正社員としては1年半ほどです。カヤックに入る前も、いくつかの会社で IT エンジニアとしてインターンやアルバイトをしていました。

という訳で、何だかんだ仕事で使うプログラムを書き始めてトータル4年半ほどになりますが、そのうち3年半ほどは全て Amazon Web Services(AWS)でホストされる自社 Web サービスに従事していました。

そこから、約1年前に異動した際に、Google Cloud を使った受託モバイルゲーム開発へと環境が変わりました。これに関連して、以前も GoとテストとSDKとGCP という記事を書きましたが、やはり Google Cloud との出会いは重要な発見をもたらし、大きな成長につながりました。

今回発表する予定だったテーマは、オブザーバビリティです。OpenTelemetry と併せてバズワードっぽい感じがあるので少し引いてみられがちだったり、あんまり実践的な知見が転がっていない印象があります。

しかし、筆者の直近の経験から、Google Cloud の Operations Suite さえ活用すればオブザーバビリティの第一歩を踏み出すのは簡単で、それは本質的な価値を生み出すものであることが分かりました。これを皆様にも知ってもらいたい、というのが今回の動機です。

伝えたかったこと

おそらく多くの方が持ち合わせている認識だとは思いますが、「ビジネスの主戦場でない部分に過剰にコストを掛けるのは良くないし、どこから過剰になるかを見極めるのって難しいけど、とりあえず楽に始めるに越したことはないよね」という前提で読んでいただけたらな、と思います。

楽に実現するためのベストプラクティス!

ということで実践的な話をするつもりでした。大事なのは、以下の8項目です。

8項目もあって多い印象を受けますが、頑張らないために頑張るポイントを選んだ結果なので、ご容赦ください。

  1. ログを統一的に吐いてトレースに紐づけよう!
  2. とりあえず外部に通信する箇所のラッパー メソッドでスパンを開始しよう!
  3. パスによるリクエスト分類を Default or Critical の2段階で実装しよう!
  4. Collector の誘惑を振り払って、ParentBased/RatioBasedで我慢しよう!
  5. 夜間バッチは無理せず、ユーザーのリクエストに起因する部分を優先しよう!
  6. Contrib のソースコードは読もう!
  7. みんなで頑張って PromQL を覚えよう!
  8. Operations Suite UI の使い方を、さりげなく継続的にシェアしよう!

8項目って時点で箇条書きでもボリューム感あるのに、詳細について言いたいことメチャクチャありまして。。。 40分の発表枠で喋れるつもりだったので。。。(未練タラタラ)

ただ、一点だけ、6の文脈で伝えたかったことなのですが、GoのHTTPサーバーでotelhttp使おうとしてる人にめっちゃ注意してほしいことが!

絶対に otelhttp.NewHandler をリクエスト スコープで呼び出さないでください!メモリを食い潰します!

あくまで Contrib は Contrib なので、どちらかというと結論は先ほど挙げたように「ちゃんとソースコード読もうね」なんですが、シグニチャ的にミドルウェアにサクッと入りそうで危ないんですよね。ちなみに、しっかり同じ轍を踏んだ人も見つけました。

github.com

実際に筆者のプロジェクトで、OpenCensus => OpenTelemetry 移行のついでにアレコレ改善して "いい感じ" なオブザーバビリティを提供する Pull Request がマージされた後、負荷試験でメモリリークが発覚して Revert PR が作られたときは正直ちょっと焦りました。

みんなも気をつけてね〜〜!ということなのですが、これを適当に対処すると今度は「スパン名が微妙に分かりにくい!」となります。 ということで、Contribハンドラーをお使いになる場合、初期化時に以下のようにオプションを与えるのがオススメです。

神ハンドラー := otelhttp.NewHandler(h, "server",
    otelhttp.WithSpanNameFormatter(func(_ string, r *http.Request) string {
        return fmt.Sprintf("http.Handle(%s)", r.URL.Path)
    }),
)

ということで具体的な話は一旦ここまで。ちなみに筆者のプロジェクトは、アプリケーション エンジニアの皆様の献身的なご協力により、最近は「なんかおかしい」と思ったらトレースを探してログを辿ってくれる状態になってきました。あとは PromQL を楽しく布教できると、メトリックに対する解像度が上がって「第一原理からのデバッグ」に近づくのかな、と思っています。

それでは、抽象的な話に移ります。

頑張らずに始めることが有効な理由

筆者はオブザーバビリティに関して、とにかく「肩肘張らず、できるところから継続的に取り組むことが大事」という姿勢を一貫しています。なんかそれっぽい話に響くかもしれませんが、マジでコレなんです。ほんまに。

詳しく説明する前に、Manning 出版社『単体テストの考え方/使い方』の第一章二節にて述べられている、以下のような命題について引用させてください。

Good Design  => Easy to Test
Hard to Test => Bad Design

これは単体テストのしやすさと設計の良さに関する言及です*1が、筆者はオブザーバビリティにも同じことが言えると考えます。 つまり、良い設計であればオブザーバビリティが導入しやすく、オブザーバビリティ導入の難しさは悪い設計の証左である、ということです。

そして、これはまさにベストプラクティス5の「夜間バッチは無理しない」にドンピシャな話なんです。 もしピンと来なかったら、適当なバッチ処理でトレースを取ってみてください。何の違和感もなく素直に導入できるのであれば、あなたのプロジェクトは相当に祝福されています。違和感があれば、それは設計の問題です。

これについては、同じく Manning 出版社『APIデザイン・パターン』の第三部を読んでいただけると確信できると思います。

第三部の前半では、1秒以内でリクエストを返すようなAPIを前提として、「統一的な方法で、REST を上手に拡張しようね」ということが書いてあります。実際に、多くのプロジェクトにおいて、そのようなAPIでは(完全に研ぎ澄まされたものではなくとも)ある一定の規則に従ってシグニチャが定められているはずです。

それに対して、バッチやジョブといった部分のAPIはどうでしょうか?第三部の後半では、これらについても統一的なインタフェースを提唱しています。それに対して、実際のプロジェクトでは「そもそも再実行機能がない」とか「適切な実行単位で分割されていない」といった状態も多く見られます。

まさしくこの差......設計の良し悪しの、世間一般における傾向こそが「夜間バッチは無理せず、ユーザーのリクエストに起因する部分を優先しよう!」の理由なのです。

「オブザーバビリティを導入したい!けどココは難しい!」と思ったら、それは悪い設計の証左なので、とりあえずスキップして入れやすいところから入れてチームに布教してください。そうするうちに、自然と「やっぱりココにもトレースとか入れたいよね」という話題が上がります。*2

そこからどうしろって?安心してください。その時点で、あなたはチームを巻き込むことに成功しているのです。 最初から完全な状態のオブザーバビリティを導入しようと躍起になったりせず、適度に肩の力を抜いてこそ、チームの関心をソフトウェア設計の改善に向けることができるのです。

結局のところ、より良いソフトウェアの開発・運用のためには、ソフトウェア設計という問題は避けて通れません。 たとえ Kubernetes などの(比較的)難しい技術要素から逃げることはできても、設計からは逃げられないのです。 だからこそ「無理せず簡単なところから、オブザーバビリティを漸進的に導入する」ということは、輝いて見える難解な技術要素を導入することよりも、確実かつ普遍的にビジネス上の価値を提供できるのです。

Operations Suite は第一歩に最適

さて、いい話っぽくなってしまいましたが、ここでゴリゴリ宣伝させてください。

どのベンダーのソリューションを使っても同じように上手くいく、なんて話はありません。

たとえば、サードパーティのソリューションも、最近は各ベンダー標準化にが対応し、魅力的なものが沢山あります。 しかし、新たに導入しようとすると、契約に際してさまざまな社内手続きが必要になります。

Google Cloud の Operations Suite は、妥当な使い方をしていれば法外な料金を請求されることはないので、すぐに導入できます。このハードルの低さがもたらす効果を侮ってはいけません。*3

その上、同じクラウドに閉じていれば、プロジェクトや組織といった名前空間・権限管理の仕組みにずれが生じることもありません。 権限管理のコストは言うまでもなく、各種テレメトリー シグナルの問い合わせに使うクエリがシンプルになるだけでも、認知負荷の軽減に大きく貢献します。これマジです。

ところで、オブザーバビリティに関する以下のブログに、面白い言及があります。

dev.henry.jp

例えば、昔はCIサーバーにお金をかける発想は乏しかったですが、今はそこにコストをかけるのは当たり前になり、そこに力をかけられるサービスのほうが生き残れるようになりました。

CI のメタファーでいうと、最近は GitHub なら Actions と言った調子で、サードパーティよりも VCS に統合された CI サービスを選択することが増えてきているように思えます。これには様々な理由があると思いますが、オブザーバビリティにおいても、同じような流れ(=コンピューティング サービスに統合されたソリューションが選択されるようになる)が発生するのは想像に難しくありません。

そして、クラウドに閉じたオブザーバビリティ ソリューションの中において、Operations Suite は UI の簡単さで群を抜いているのです!発表の中でデモができれば実感いただきやすかったと思いますが、今回は推しポイントをサッと紹介するに留めます。

たとえば Cloud Logging では、最近のログなら Logs Explorer の画面にちょろっとクエリを書けば OK、昔のログを集計・保全するなら Sink で BigQuery なり Cloud Storage に流せば OK、という感じで、とにかくシンプルです。これは、筆者が Google Cloud に移ってから最も感動した点です。もう、ログを探すためにインフラチームに相談したり、ログ転送コンポーネントの監視やスケーリングに悩む必要はありません!もちろん、ログを吐くときにトレースとスパンのIDを入れておけば、Cloud Trace の Trace Explorer 画面から辿ることができます。

また、Cloud Monitoring でも Metrics Explorer の画面で CHART | TABLE | BOTH という表示オプションを活用すると、Prometheus の式ブラウザに近い体験が得られます。PromQL さえ覚えてしまえば、秘伝のタレと化しているダッシュボードに頼らなくとも、高い解像度でプロアクティブに必要なメトリックを取得できるのです。使い込んでいくと「Range Vector の TABLE 表示がサポートされていない」など惜しいところにも出会いますが、Operations Suite 全体で UI の改善が積極的に行われており、今後の期待が高まります。

まとめ

ということで、手短にまとめます。

  1. 無理せず簡単なところから
  2. でも最終的な理想を忘れずに
  3. Operations Suite は最高!

カヤックではオブザーバビリティ大好きエンジニアを募集しています!

hubspot.kayac.com

*1:主張の詳細が気になる方は、ぜひ本書をお手に取ってください

*2:実際、筆者のチームでもそうなりました。今は検証環境だけ夜間バッチもトレースを有効化して、比較的多量のスパン発行を許容しています。

*3:もちろん Google Cloud を使っているプロジェクトであれば、という前提です。

flutter unity widgetを使ってVPS対応のARアプリをFlutterに組み込んでみた

面白プロデュース事業部 技術部の藤澤です。主にUnityを扱った案件を担当しています。

この記事では、flutter unity widgetを使ってFlutterアプリ上に、 Unity製のVPSに対応したARアプリを組み込む方法についてご紹介します。

以前ハッカソンにて、Flutterアプリ上で動作する特定の空間に紐づいたAR機能を実装する必要がありました。Flutter経験が皆無の私でも実装できるアプローチを探った結果、flutter unity widgetというFlutter Packageの存在を知りました。

flutter unity widget

flutter unity widget はビルドしたUnityをFlutterアプリ上にWidgetの形式で組み込むことができるFlutter Packageです。2Dや3Dはもちろん、なんとARFoundation(UnityでARを実装することができるPlugin)も動くようです。

VPS(Visual Positioning System)

VPSは、サービス上に事前に登録された、現実空間を撮影した画像群(マップ)と、端末からのリアルタイムなGPSやカメラ画像情報等を紐づけることで、より正確な方向や位置を推定する技術を指します。

この技術を用いることで、特定のランドマークに訪れたことを判定して、AR演出を実行することも比較的容易になります。

いくつかのサービスからVPSを実現できますが、今回はImmersalというサービスを採用しました。

開発環境

FlutterやUnity、各種Pluginは以下のバージョンで構成されていました。

Flutter 3.13.4
flutter_unity_widget ^2022.2.0
Unity 2021.3.25f1
ARFoundation 4.2.8
XR Plugin Management 4.4.0
ARCore XR Plugin 4.2.8
Immersal SDK 1.19.2
Universal RP 12.1.11

また今回の対象プラットフォームはAndroidとし、開発環境にOpenJDKとAndroidSDK & NDKToolsがインストール済みの状態とします。

簡単なサンプルが動くところまで

まずは、SkyboxとCubeが写っているシンプルな画面がFlutterアプリ上に表示されるようにしてみます。

flutter unity widgetの導入にあたって、こちらの記事をとても参考にさせて頂きました。

qiita.com

pubspec.ymlの編集

pubspec.ymlにflutter unity widgetを追加します。

dependencies:
  flutter_unity_widget: ^2022.2.0

今回はFlutter 3.13.4なので、2022.2.0を導入しました。

Unityプロジェクトの作成、local.propertiesの編集

Flutterプロジェクトroot直下に、unityというディレクトリを作成し、その下にUnityプロジェクトを作成します。

Unityプロジェクト作成後、Edit>Preferences>External Toolsを開き、Android NDK Installed with Unityの欄からNDKのpathをコピーします。

Flutterプロジェクト内のandroid/local.propertiesを開き、以下を追記します。

ndk.dir={Android NDK Installed with Unityでコピーしたpath}

UnityPackageをインポート

flutter-unity-view-widgetのリポジトリからUnityPackageをダウンロードし、対象のUnityプロジェクトにインポートします。いくつか種類がありますが、今回はfuw-2022.2.0.unitypackageを使用しました。

BuildSettingsとPlayerSettingsの変更

File>BuildSettingsを開き、PlatformTargetをAndroidで選択し、SwitchPlatformを実行します。

PlayerSettingsを選択し、以下の設定を変更します。

ScriptBackend -> IL2CPP
Target Architectures -> ARMv7, ARM64を選択

ビルドデータの出力

いよいよFlutterに組み込むためのビルドデータを生成します。Flutter>ExportAndroid(Debug)を選択で、ビルドが開始されます。

ビルド後、ConsoleViewに-- Android Debug Build: SUCCESSFUL --というログが出力されていれば、正常にビルド完了しているようです。

Flutterプロジェクトにandroid/unityLibraryという形でビルドデータが生成されているはずです。

dartファイルの作成

Unityを組み込んだページを作成します。リポジトリのREADMEに記載されているサンプルを利用します。

minSdkVersionの変更

私の環境の場合、このまま実行しようとするとminSdkVersionでバージョンが合わずエラーを出力しました。android/app/build.gradleminSdkVersionにUnityProjectで指定されているMinimum API Levelを指定することで、対処しました。

Flutterアプリが書き出される

正常に実行されると、Unityが組み込まれたFlutterアプリが書き出されているかと思います。

ARFoundation(ARCore)を動かす

本題のARが動く状態にします。

各種Packageをインポート

Window>Package Managerを選択し、以下をインポートします。

ARFoundation 4.2.8
XR Plugin Management 4.4.0
ARCore XR Plugin 4.2.8

特にXR Plugin Managementのバージョンが重要で、4.3.1〜4.3.3をインポートしてしまうと、ビルド生成時にAndroidManifest.xmlを毎回破棄してしまう不具合が発生して、正常に動作しないようです。そのため4.2.2 or 4.4をインポートしてくださいとREADMEに記載されています。

BuildSettingsを変更

ARを動かすには、追加で以下のBuildSettingsの変更が必要になります。

Auto Graphics API -> チェック外す
Graphics APIs -> OpenGLES3だけを選択
XR Plug-in Management -> ARCoreをチェック
Minimum API Level -> Android 7.0 'Nougat'

またUnity側の変更に伴い、Flutterプロジェクト内build.gradleのminSdkVersionを再度変更する必要があります。

settings.gradleを変更

ここで一度ビルドしてみると、私の環境では以下のようなエラーが出力されました。

A problem occurred evaluating project ':unityLibrary'.
> Project with path 'xrmanifest.androidlib' could not be found in project ':unityLibrary'.

調べた結果、以下のIssueに辿り着きました。

github.com

どうもandroid/settings.gradleに以下を追加する必要があるようです。

include ':unityLibrary:xrmanifest.androidlib'

無事動きました。 Unityが実行されてもカメラ映像が真っ黒だ!みたいな場合は、アプリにカメラアクセスの権限が付与されていない可能性が高いです。

Immersalを動かす

次は、Immersalを使ってVPSができるようにしてみます。

といっても、こちらはFlutter用に何か特別なことをする必要はなく、通常通りにImmersalを導入すれば動いてしまいます。Immersalの導入方法の詳細は割愛させて頂きますが、ざっくり以下の準備をします。(導入方法の詳細は公式ドキュメントをご参照ください)

・Mapperアプリを使用して、検出したい空間の写真を複数枚撮影(今回は40枚)
・ImmersalSDKをUnityプロジェクトに導入
・各種必要なコンポーネント(ImmersalSDK、ARSpace、ARLocalizer)をアタッチ

以下のような空間マップ情報を登録し・・・

こんな感じで、実空間を検出できるようになりました。

FlutterとUnity間で通信する

FlutterとUnity間で値を送れるようです。

FlutterからUnityに値を送る

Flutter側は、UnityWidgetonUnityCreatedで受け取れる UnityWidgetControllerから以下のようの値が送ることができます。

// 第1引数はメッセージを送りたいコンポーネントがアタッチされているGameObject名
// 第2引数はコールしたいメソッド名
// 第3引数は送りたい値の情報
_unityWidgetController.postMessage('GameObjectName', 'MethodName', variable);

Unity側は、postMessageで指定した名前のGameObjectに、指定した名前のメソッドが定義されたコンポーネントをアタッチすることで、値を受け取ることができます。

UnityからFlutterに値を送る

Unity側は、UnityMessageManagerというコンポーネントをScene上のGameObjectにアタッチして、以下のように値を送ることができます。

UnityMessageManager.Instance.SendMessageToFlutter(variable);

Flutter側は、UnityWidgetonUnityMessageから値を受け取ることができます。

全部組み合わせて・・・

最終的に、こんな感じのデモを作ってみました。

まとめ

flutter unity widgetを使って、Flutter上で動くVPSに対応したAR機能をUnityで実装しました。説明は割愛しましたが、URPやScreenSpaceのInput機能など、動くか際どいと思っていた機能もしっかり動作していました。すごい。

懸念点としては、やはりUnityビルドを含む分アプリサイズは大きくなる印象だったので、その辺りのチューニングは必要になりそうです。

FlutterにUnityを組み込まないといけなくなることは少ないかもしれませんが、少しリッチな3Dの表現や、豊富なUnity向けSDKをFlutter側に持ち込めるのは、ユニークなコンテンツを作る手助けになるかもしれないと感じました。

ARコンテンツをカヤックと一緒につくりたい方は、下記よりお問い合わせください!

www.kayac.com

カヤックではユニークなコンテンツを作りたいエンジニアを募集しています!

hubspot.kayac.com