自己紹介と目次

相原魁氏:今日は「趣味と仕事の違い、現場で求められるアプリケーションの可観測性」について、僕の自室からお送りしようと思います。株式会社LIFULLの相原です。よろしくお願いします。

(スライドを示して)今日の目次ですが、「だいたい趣味のアプリケーションってこういうことをやりがちだよね」みたいな話をした後に、では仕事だとどういうことが求められるのか。その上で、どういう実装をすると可観測性を満たせるのかという話をしていこうと思います。

あらためまして、株式会社LIFULLの相原です。僕は2015年にサポーターズのイベントでLIFULLと出会って、新卒入社して、かれこれ7年経つところです。もともと技育祭みたいなイベントに出てLIFULLと知り合ったので、サポーターズに近いものを感じます。

LIFULLではソフトウェアエンジニアをしていて、プラットフォームエンジニアリングやSREみたいな領域のスペシャリストを務めています。仕事ではKubernetesというコンテナのオーケストレーションツールがあるのですが、それをベースとした内製のPaaS(Platform as a Service)で、「Heroku」や「Vercel」のような有名なサービスのようなものを、社内向けとして作っている「KEEL」というプロダクトの開発や運用をしています。

趣味では主にRustを書いていて、全文検索ライブラリや分散検索エンジンを書いている人間です。

そのPlatform as a Serviceを提供するにあたって、可観測性のプラットフォームを開発していたり、プライベートで分散システムを開発しているので、可観測性には一家言ある感じになっています。

簡単にLIFULLの紹介です。「LIFULL HOME'S」という不動産の情報サイトを運営している会社です。意外とエンジニアリングのイメージがないかもしれないのですが、エンジニアは正社員で今だいたい150名から200名ぐらいいるはずで、連結で1,500名くらい(※1,548 名) の規模の会社になっています。

私が開発している内製PaaSのKEELは、コマンドを一発叩くだけで完璧なアプリケーション実行環境が社内向けに手に入るようなコンセプトのソフトウェアです。可観測性に加えてデプロイやセキュリティ、あとはアプリケーションの信頼性やパフォーマンスに関して、全部を提供するプラットフォームを作っています。

特徴的なのが、コードジェネレータを中心とした開発者体験の提供で、コマンドを一発叩くだけで完璧な環境が手に入るのがコンセプトです。

よくあるような、プルリクエストを作成するとプルリクエストの内容がプレビューできる機能であったり、Content Trustのような、なんでもかんでもを詰め込んだプラットフォームを提供しています。

LIFULLはエンジニアブログをやっていて、そこにいろいろなエントリーを書いているので、興味がある方がいればご覧ください。

本日のゴールは、みなさんに入社してから可観測性に優れたアプリケーションを実装できるようになってもらうことです。私のスペシャリティの特性上、少しWebに寄った話になると思うので、そこはご了承ください。

趣味のアプリケーションでやってしまいがちなこと

ではさっそく、趣味のアプリケーションでやりがちなことということで、僕もよくやってしまうことですが、「だいたいこういうのあるよね」という話をします。

よくありがちなのは、「エラーをなんとなくただロギングするだけ」ということです。エラーのハンドリングをどうするのかを考えるのがちょっと面倒くさいから、とりあえずロギングしておく。

(スライドを示して)これはGoの疑似コードですが、maybeErrorというエラーが返ってくる関数を叩いた時に、エラーが出てきたら適当にロギングするだけとか。

そもそもエラーはハンドリングしない。趣味だからさぼってしまうとか。

Rustだったらunwrap()するだけとか、Rubyだったら、rescueを書いて全部例外を握り潰して適当にやってしまうとか。

あと、実行時間の計測をするのはすごくすばらしいことですが、ログは我々にとってはすごく身近なものなので、ログでなんとなく実行時間の計測をがんばってしまうとか。そもそも実行時間を気にしない(とか)。

あとは、けっこうおしいところまでやっていて、doSomethingでエントリーポイントのメイン関数でエラーをちゃんと受け取ったんだけど、詰めが甘くて、ただそれをロギングしただけとか。"%+v"は、Goでスタックトレースつきでエラーを吐いてくれるみたいなフォーマットのオプションですが、そんな感じです。

仕事と趣味で異なる点は「メンテナンスを自分自身が未来永劫行うか」

では、これをやっていて何がいけないんだという話ですが、私が思うに、趣味と仕事の違いの最たるものは、開発者である自分自身がそのアプリケーションを未来永劫メンテナンスし続けるわけではないところにあると思います。

つまり、実装に詳しくない人が状況を判断できる必要がある。転職したり異動したりすることで、自分が開発したアプリケーションをほかの人に引き継がなきゃいけないわけですが、そういう時に困ってしまうというのが、趣味と仕事の最大の違いです。

言い換えると、ソースコードを読むことも書き換えることもなく必要な情報を取得できるのが、仕事で書くアプリケーションに必要な特性だと思っています。

そこで何をやればいいのかというと、「可観測性」という制御理論という分野で言われている概念があります。システムの外部出力を観測することで、内部状態を推測可能かどうかの尺度、つまり、ソースコードを読まなくても外部出力からだけでアプリケーションの内容を観測できるのが可観測性という概念です。

最近はMicroservicesという開発手法の1つで、アプリケーションのドメインを細かく分けて、それらをすべて独立したアプリケーションとして動かす設計のパターンがあります。つまり、Webのアプリケーションは分散システムに似た構成になってきていて、それによってシステムの構成が複雑化して、可観測性の重要性が増してきています。

可観測性の基本的なシグナルの「Logs」

可観測性を測るにあたって、Primary Signalsと呼ばれる可観測性の基本的な3つのシグナルがコミュニティで言われています。それが「Logs」「Traces」「Metrics」です。今からこの3つについて説明していきます。

まず「Logs」です。これはたぶん一番身近なもので、コマンドを叩いた時に出てくるとか、その手のものです。Logsは特にフォーマットのないテキストのデータで、よくあるものがファイルに書かれたり、標準出力・標準エラーに出力されるものです。

種類としては、アプリケーションログがあります。(スライドを示して)例えばこれは、「ブロックをアップロードしました」みたいなアプリケーションのログであったり、HTTPだと「HTTPのサーバのアクセスログがあって、HTTPサーバのリクエストを受けた時に、どんなリクエストヘッダでどんなURIにリクエストが来たか」というのもログです。

毛色が変わったところでは、監査ログというものがあります。これは、主に認証のあるアプリケーションで、「誰が何をどうしたか」というイベントを記録するログです。上場企業には監査が定期的にあって、社員も怪しげな行動をしていないかをログとして提出しなければいけないのですが、そういう時にこういうログを出力しておく必要があります。

可観測性の基本的なシグナルの「Metrics」

次は「Metrics」です。Metricsはインデックスがついている数値データです。Logsはフォーマットはありませんでしたが、Metricsは「OpenMetrics」という標準化が進んでいます。これはLogsと比較して数値データであるため、保存や計測が非常に安価というのが特徴です。

出力方法は、アプリケーションの内部で計測・出力する方法と、エージェントが外部から観測・出力する方法の2パターンあります。このあたりはちょっと後で説明しようと思うので、今は流して大丈夫です。

ここでさっそく余談ですが、OpenMetricsはもともとデファクトスタンダードといってもいいような「Prometheus」というモニタリングシステムがあって、そのPrometheusがMetricsを公開する時のフォーマットを参考に標準化されたフォーマットです。

これにはいろいろなデータタイプがあるという話が書いてあるのですが、いろいろな表現方法でMetricsを表現できます。実際、エージェントやアプリケーションは、このフォーマットに従って出力することでMetricsの収集を可能にするという指標です。

Metricsにどんな種類があるのかという話ですが、一番身近かと思われるのが、CPUやメモリの使用量です。(スライドを示して)例えばLinuxだと、「/proc」というディレクトリからこういう情報を取得できます。

Linuxの/procはプロセスの情報を持つ疑似ファイルシステムになっていて、例えば「/proc//stat」だと、スライドの画像にあるような情報が手に入って。これも実は14番目、15番目がCPUに関する情報だったりしています。

実際にこういうのは、監視に関するフルマネージドサービスのエージェントを導入すると勝手に収集してくれていることが大半なので、「自分たちでCPUのMetricsを取得しよう」みたいなことはあまり考えなくて済みます。

あとちょっと身近なところだと、アプリケーションのエラー数です。アプリケーションのエラーが起きた時は、ログで「エラーが起きました」と吐いてもいいのですが、先ほど話したように、ログはテキストデータでMetricsは数字データなので、Metricsで表現したほうが安上がりです。

(スライドを示して)今ここに書いてあるのは、Kubernetesのautoscalerというコンポーネントのログのソースコードです。このソフトウェアでは、エラーがあった時にmetrics.RegisterErrorという関数を呼び出して、Metricsを出力しています。このように、ログの代わりにMetricsでエラーの数を表現するのはよくやることです。

あとは、Garbage Collectorに関する情報もMetricsとして表現するとよいです。

最後に、ミドルウェアの各種情報もMetricsと言えます。これは古くからNginxだとモジュールを入れることによって、アクティブなコネクション数をいろいろ取得できるようになっています。ここもエージェントが収集しに行って、Metricsとして出力することがよく行われます。Metricsはだいたいこのくらいの種類です。

ミドルウェアごとになにかを出力して、それをエージェントが収集しに行くのは少し大変です。ではコミュニティはどうしているのかというと、先ほどデファクトスタンダードと言ったモニタリングシステムのエージェントである「Prometheus Exporter」がオープンソースとしてたくさんあって。

例えばMySQL Server Exporterを導入すると、MySQLに関する詳細な情報がPrometheusのMetricsの形式で出力できます。Prometheusの公式ページに対応しているエクスポーターがリストで載っているので、興味があればご覧ください。

先ほど話したOpenMetricsのフォーマットは、PrometheusのMetrics表現と互換性があるので、今OpenMetricsで標準化が進んでいますが、今後はPrometheus Exporterを使っていけます。

インデックスとカーディナリティという話をしたいです。先ほど「Metricsはインデックスつきの数値データ」という話をしましたが、インデックスはMetricsのラベルという表現をもとに、インデックスをしています。

RDB(Relational Database)ではカラムの中に入る値のバリエーションをカーディナリティと呼びますが、このラベルのバリエーションが増えると、Metricsの保存のコストが増えてしまいます。

例えば、HTTPのリクエストごとに何かのMetricsを出力したいとなった時にはURI(Uniform Resource Identifier)をラベルにするわけですが、連番で9999までURIの取り得る数値があった場合、ラベルの数が最大で9999個になってしまうので、こういう情報を表現するのにはMetricsは向かないという特徴があります。そのため、こういった詳細な情報は、その保存のコストが多少高くてもLogsを使うのがよいです。

(次回につづく)