Enjoy Architecting

Twitter: @taisho6339

最愛の妻が亡くなった

6/17の22:28に最愛の妻が亡くなった。肺栓塞症による急死だった。

なぜ自分がこんな駄文をインターネットに公開するような愚行をしているかというと、そうしないと頭がおかしくなり、どうにかなってしまいそうだからだ。

妻は育った環境が劣悪で、ずっとうつ病複雑性PTSDと闘病してきた。また自分も育ってきた環境がとても良いとは言えず、自身で思考をすることを長い間許されて来ず、あらゆる場所で人格否定と過干渉、暴力ばかりされてきた人生だった。そんな自分が希死念慮を抱くのは自然なことだった。そんな自分を救ってくれたのが妻だった。自分も病気で苦しんでいるのに、誰よりも自分を心配し、限界なときに無理やり病院に連れて行ってくれ、認知行動療法を受けさせ、何度も話し合い、歪みきった自分の認知を矯正してくれたのはほかでもない妻だった。妻がいなければ自分はとっくにこの世を去っていただろうと思う。

今度は自分が妻を助ける番だと思っていた。いろんな投薬治療も試し、いろんなカウンセリングも試し、閉鎖病棟への入院など様々なアプローチでなんとか治せないか一緒に模索してきた。妻は動物が好きだったため、しょっちゅういろんな動物を連れてきたが、世話はできないのでワンオペも進んでやってきた。犬が好きだったので大家に頼み込んで怒られながらもなんとか飼育を許可してもらった。試せることは全部試してきたつもりだった。それでも一行に病状は良くならず、暗いトンネルをずっと進んでいる状態が続き、最終的にはうつ病による運動不足から血栓が足にでき、それが原因で肺栓塞で急死した。自分も限界に擦り切れるまで尽くしてきたが最後まで自分は妻を病気から救うことができなかった。

2週間経ち、ようやく文字を打てるようになったが今でも茫然自失している。転職の軸やキャリアップは自分のためももちろんあったが、妻のためでもあった。妻は生きる指針だった。それを急に失い、自分はこれからどうして生きていけばいいのか分からない。仕事は休職している。復帰できる目処も立っていない。

LINEヤフーを卒業しました

2021年1月にLINEに入社してから4年1ヶ月勤めてきましたが、2025/2/14をもって卒業しました。この4年を総評すると「最高に楽しく、かけがえのない財産となる4年間」でした。

在職期間については感謝しかなく、この会社はこんなに良い機会に恵まれている(N=1)ということを知ってほしくて初めて退職エントリを書いています。

LINEに入社したきっかけ

元々私はインフラ・バックエンド開発のフリーランスとして活動していましたが、フリーランスを続けていくことにキャリアリスクを感じて会社員に戻ることを考えていました。今後もエンジニアとしてキャリアップしていくためには、

  • 大規模トラフィックなど複雑なシステム要件に対して裁量を持って開発ができること
  • 他チームや他組織とも積極的にコミュニケーションを取り、広い視野でプロジェクトを進める経験ができること

の2軸で仕事をしていく必要があり、フリーランスだとその機会に恵まれにくいと感じたためです。そこでLINEを受けてみたところ、無事にオファーをもらえたので入社しました。

入社してからやったこと

Private CloudのSRE

最初は、Private Cloudの横断SREとして各クラウドプロダクトの開発チームのモニタリングの標準化からスタートしました。当事はOpenTelemetryが成熟していなかったため、なんとなくログはFluentd、メトリクスはPrometheus、TraceはZipkinかJaegerみたいな状況だったと思います。そこでまずは要件が複雑だったログ基盤から着手し、ログ収集方法と保存場所の標準化を進めました。

特にGrafana Lokiはまだ情報が十分に普及しておらず、一日2TB以上のログをためていくためにはコードからアーキテクチャやパラメータの意味を理解し、チューニングしていく必要がありました。その過程でバグや足りない機能などが出てきたので積極的にUpstreamにも貢献していきました。当時キャッチアップした内容はこちらにまとめており、英語で書いたおかげで海外でもそれなりに読まれているようで嬉しかったです。

また、GrafanaのSlackでも積極的にナレッジを共有して議論したりしていたため、Grafana LabsからTシャツもプレゼントしてもらえました。宝物です。プロジェクト自体は実はかなり失敗ポイントが多く、それも含めて貴重な学びの経験でした。

Prometheus互換の内製時系列データベース開発チームへ

上記の経験から英語を使って外国籍のエンジニアと働いてみたい、時系列データベースを使うのではなく作ってみたいと思いはじめ、社内のポジションを探していたところ空いていたので異動させてもらいました。チームに日本人がいなかったにも関わらず当時の私の英語力は本当に低く、「アッ…サウンズグットゥミ…」でとりあえず納得した感じを出すか、「ワッドゥユシンク…?」ととりあえず誰かにリダイレクトすることしかできなかった、英語版ちいかわのほうがまだ流暢に話せるレベルでした。それでも温かく迎え入れてくれ、会議で拙い英語で意思を伝えるときもだまってゆっくり聞いてくれ、徐々に話せるように育ててくれたことには感謝しかありません。

このチームに入ってからは運用やモニタリングの改善など、できることからやって信頼を貯金していくことで、大きい取り組みであるバックエンドストレージのCassandraを置き換えるためのリアーキテクチャを一任させてもらいました。

バイナリレイアウトレベルでデータ構造を考えて、どのようにデータをシャーディングし、どのように分散書き込みを行い、どのように分散キャッシュを実装してパフォーマンスを出すかをすべて0ベースで考えて実装できる機会はあとにも先にもこれだけなのではないでしょうか。ものすごく不確実性が高いプロジェクトでしたが無事に事故もなくローンチして、Cassandraに比肩するレベルのパフォーマンスが出せたことは本当に涙が出るほど嬉しかったです。当時一緒にやったチームメイトと心の底から喜びあったのを今でも覚えています。

LINEに入ってよかったこと

あげたらきりがないのですが、ざっくり下記のような感じでした。

  • 年齢に関係なく、ソフトスキル、ハードスキル両面で優れた人がたくさんいた。特に若手の視座の高さには常に驚かされ、刺激されていました
  • 英語が話せるようになった
  • 異動が柔軟なので多様な経験をさせてもらえる
    • マネージャーやってプレイヤーに戻る人がいたり、SREやってプロダクト開発に行ったり割と自由に希望すれば叶う環境だった
  • 大規模なデータ量、トラフィックを扱い、多くのユーザ、ステークホルダーを抱えながらプロジェクトを進める経験をたくさんさせてもらった
  • Mid → Senior → Staffの一通りのレベルの仕事を経験させてもらえた
  • 待遇がものすごく良かった (入社してから約1.7倍年収をあげてもらえた)
    • 国内のソフトウェアエンジニアでここまで出してくれる企業は本当に少ないと思います

なぜ辞めるのか?

シンプルに家庭の事情です。正直不満はないし積極的に辞めたい気持ちは最後までなかったです。私の場合は次の職場を決める条件として次の3つがあります。

  • 現職より年収が上がること
  • 自分が楽しいと感じるエンジニアリングができること
    • システムの運用・構築難易度が高い、まだ誰もやったことがないものを作るなど
  • 多国籍のエンジニアと英語で働けること

これらをすべて満たすポジションは国内にはほとんどないのが実情です。つまり自分がワクワクする求人を見つけたら常に応募し続けるくらいでないとチャンスはやって来ません。そこでたまたま次の転職先から良いオファーをもらえたため決断しました。なにより転職先は、会社としてまだ誰もなし得なかったチャレンジに関わる仕事であり、このタイミングで国内からそこに携われることにとてもワクワクしています。現職を辞めてとても寂しい気持ちと、次の挑戦が楽しみな気持ちですごく複雑な心情です。辞めると伝えてからいろんな方から個別に温かい言葉をもらい、良い齢こいて泣きそうになりました。

最後に

本当に濃密で最高な4年間でした。そしてあのときLINEへの入社を決めて本当に良かったです。辞めたあと、「あー楽しかったな…やりたかったこと全部できたしちゃんと貢献もできたな…本当に良い4年間だったな」と感じたのは初めてでした。

チームメンバー、上司の方々、そして在職中関わってくださったすべての方々へ感謝いたします。

2024年の振り返り

早いもので2024年が終わってしまいました。今年も自分の考えていることの記録のため、振り返りをします。

お仕事

今年やってきたこと

今年から1段レイヤを上げてStaff Software Engineerのような仕事を始めました。主に「チーム全体で面白い仕事で良いアウトプットを出せるようにすること」と「組織横断の取り組み」にフォーカスするようになりました。

前者は技術的に面白くビジネス的にもROIの高い施策を起案し、簡単な方針とデザインを作り、信頼のおけるメンバーにあとを託して自分はレビューやサポートに徹しました。

後者については、別組織にアクティブにシステムモニタリングに関する課題感や我々のプロダクトに対する意見をヒアリングしに行ってそれをロードマップに反映したり、社内の他のプラットフォームとの統合を推進するために各組織の要件を明確にして整理し、ガバナンスを定義して上手いこと連携できるように努めてきました。

Staff Software Engineerというキャリア

私の場合は横断組織にいるわけではなく、あくまでも一つのプロダクトチームの中にいるロールとしてのStaff Software Engineerです。この立場だととにかく自分からアクティブに仕事を定義し、他の組織の人々にコミュニケーションを取りに行かないと何もできません。実際にやってみると昔と違って対人恐怖がなくなっているので比較的楽しんでやれるなと感じた一方、まだまだゴリゴリプロダクト開発もしたいという気持ちと常に戦っていました。StaffにならずにあえてSeniorで止める人も多いという話も納得感を感じました。

また、プロダクト付きのStaff Software Engineerは常に需要があるわけではなく、フェーズによって必要になったりならなかったりしそうだなということが分かりました。例えば今はプラットフォームの統合プロジェクトがあるのでこういう動きが必要ですが、このあとは必要なくなるのかもしれません。ずっとこういう仕事がしたいならプロダクトチームの中にいるのではなく、やはり複数チームを見るポジションにつくのだろうなと思います。

いずれにせよ、これを続けるか否かは冷静に考える必要がありそうです。

登壇

今年もいくつか登壇させていただきました。特にKubeDay Japanでは初めて英語で発表させていただき、海外のエンジニアとも交流することで良い刺激になりました。会社の英語の先生にも英語プレゼンの良いアドバイスをたくさんもらったので今後に活かします。

  • Scaling Time-Series Data to Infinity: A Kubernetes-Powered Solution with Envoy - Hiroki Sakamoto


ポッドキャスト

友人とポッドキャストを試しにやってみました。アラサーエンジニアのキャリアのお悩みを雑に話す感じでしたが、意外と反響があったので近いうちに続編をやりたいです。

https://x.com/taisho6339/status/1839277685077573646

リモートワーク廃止について

会社からプレスが出て色々と世の中で騒がれていますが1社員としては家庭の都合上とても困っています。ただ、会社は株主の持ち物なので株主や経営者の判断であれば1従業員としては受け入れざるを得ないのかなと思っています。社員の福利厚生や働き方を重視するのも結局は経営上都合が良いからであって、今回の判断もおそらく何らかの合理性によってされたのだと思います。私の立場からはそれが分からないので、良いことなのか悪いことなのかわかりません。結局は自分と家族にとって最善の選択をするしかないと思っています。

来年の抱負

実はJAISTへの博士前期課程での進学を検討していて、もうすぐ入試を受ける予定です。無事に受かることができたら進学したいと思います。理由は、AIが盛り上がってくるこの先の時代で技術者として生きていくためには、潜在的な課題を発見・定義し、課題のアプローチ方法を発想して実現まで持っていくという極めて抽象的なところから具体的なレイヤまで持っていくことが大事だと考えたからです。具体的なレイヤだけしか扱うことができなければそのうちAIに置換されてしまいますし、技術者として面白いことができなくなるかもしれません。このタイミングで研究に取り組んでみることは非常に投資価値が高いと判断しました。頑張ります。

2023年の振り返り

今年も仕事納めをしたので一年間を振り返りたいと思います。

2023年の仕事を振り返って

今年も全社モニタリング基盤の時系列データベースの開発・運用に携わってきました。

その中でも今年は「出来るか出来ないか分からないけど頑張って乗り越えて結果を出さないといけない仕事」がとても多い年でした。

内製のIn-Memoryデータベースのメモリフラグメンテーション問題を解決したり、

既存の構成にObject Storageを活用した独自のmetrics保存・クエリ処理レイヤを実装して加えることで、年間運用費を1億円弱削るための取り組みをしたり、

自分史上過去一難易度が高い仕事を必死に頭を振り絞って取り組んできました。

特に後者はCNDT2023の登壇では話す時間が全然ありませんでしたが、 データ保存よりクエリ処理の方が100倍大変で、 キャッシュをチューニングして高速化したり、 転置インデックスのインデックスを作ってインデックスのデコードと検索を部分処理できるようにして負荷を下げたり、 自分とチームメイトの技術力の集大成を詰め込みました。 せっかくなのでどこかの場で発表させていただけないかと画策しています。

自分は就職前の大学生の頃から一貫して「プラットフォーム」を自ら実装したいと言っており、ようやく夢がかなってずっとやりたかった仕事が出来ている感覚があり、幸い評価もしていただけているので、かなりのプレッシャーを感じつつもメンタルを安定させてやって来られました。

多国籍チームで働くということ

日本人が一人しかいないチームでそろそろ2年ほど働いていることになります。

率直に言ってしまうと今のチームのカルチャーがすごく好きでとても働きやすいです。 「自分は自分のままでいい」という個人が肯定される風土が特に気に入っていて、 日本の組織でよく観測される「社会人ならxxでなくてはいけない」「エンジニアならxxができないといけない、yyを知っていないといけない」みたいな無意識の規範意識が強制されることがありません。

苦手なことを無理に頑張らなくていいし、無理に周りに合わせる必要がなく、個々の性質を最大限尊重されているように感じますし、自分も意識的に尊重するようにしています。 知らないことはお互いに気軽に教え合いますし、些細なことでもお礼を言い合っていて、X(Twitter)で悪口を書かれてたりとか、高圧的に接されたりとか、人格否定のようなこともありません。

レイオフが当たり前な文化圏から人が来ているからなのか、たまたまなのか分からないですが、この先いつか転職するとしても似たような風土の場所が良いなと思います。

英語

毎日英語学習を続けているお陰でだいぶ英語でストレスを受けることがなくなってきました。 今年は、TOEFLとかIELTSを受けるための登竜門と言われている英検準一級を取ってみました。 無事に受かったので、来年もなにかしら受けたいと思っています。

今の課題としては、とにかくリスニングをもっと伸ばしたいと思っていて、そのためにシャドーイングと多読を毎日やっています。 シャドーイングはシャドテンというサブスクで毎日添削してもらいながら行っていて、

多読はデータベースや分散システム関連の論文を気の向くままに読んでいます。 特に出てくる語彙や理論も仕事で馴染み深いので比較的スラスラ読めますし、知らない単語・熟語も覚えてすぐに使い道があるので楽しくやれています。

シャドーイングと多読を始めてからグッと英語力が伸びた感覚があるので、今後もずっと続けていく予定です。

来年の抱負

来年も引き続き英語・分散システム・データベースをテーマに好きなことで生きていこうと思います

2022年の振り返り

今年ももうすぐ終わるので去年までと同様、一年間を振り返りたいと思います。

時系列データベースの開発チームへ異動

今年の4月から、Private CloudのSREチームから時系列データベース開発チームへジョブチェンジしました。 いきなり日本人が自分一人だけのチームに移り、すべての業務が突然英語になったことで戸惑うことも多かったものの、 ようやくチームにも慣れ、なんとか業務を回せています。

直近では、数億件のデータをどう効率的に分散処理し、どうスケーラブルかつ運用コストをあまりかけずに保存するか、 といったテーマでアーキテクチャ改善のプロジェクトを主導させてもらえており、とても楽しく働くことができています。

異動についての公式インタビュー記事

英語について

今年一番苦労し、力を入れてきたのは英語でした。 チームにJOINしたばかりの頃はCEFRでいうとB1のレベルで、自分だけ議論の内容についていけない、突然話を振られてパニクってしまうなどたくさん辛い経験をしました。 オーバーラッピング、ボキャブラリ習得、発音練習、スピーキング練習をひたすら毎日2時間程度やり続けることで現在はCEFR B2レベルまで上がり、 なんとか業務上は問題にならない程度の英語力を身につけることができました。 とはいえまだなんの苦もなく自己表現できるには至っていないので、来年も引き続き練習していこうと思っています。

※単語帳はDistinction 2000、文法書はGrammer In Use一億人の英文法、発音は英語耳ELSA Speak、オーバラッピングはVoiceTubeに非常に助けられました

SREからSWEへのキャリアチェンジ

2021年の振り返りで、

そして今回のGrafana Lokiでの活動を通して、 自分もLokiのような分散アーキテクチャなソフトウェアを作る仕事をしてみたいという気持ちをとても強くしました。

ということを書いているのですが、この思いが諦めきれず思い切って異動してみました。 SREでは新しいソフトウェア、システムを作るというよりは、適切にツール、ソフトウェアを要件に合わせて選択し、運用に乗せるのが仕事で、 オリジナルな仕組み、ツールなどはオーバーエンジニアリングや負債になりやすいので、避けられるのであれば避けるというスタンスでした。 これはこれで面白い仕事だと感じつつも、潜在的にそういったものをしがらみなく自分自身で開発してみたいという気持ちがとても強かったというのが大きな理由です。

結果この異動は大成功で、自分はプロダクト側にいたほうが仕事のスコープや要件、関係者、ユーザのニーズをイメージしやすく、 自分からちゃんと導入まで持っていける具体的な提案もしやすいため、向いているのだなということに気がつくことができました。

また、大抵の場合、時系列データベースそのものを作るみたいな仕事はビジネス上妥当な理由がつけにくく、 無理やり作ったとしても負債になりがちですが、今の会社だと思いっきり気にせず打ち込めるのが嬉しいポイントです。

今年のアウトプット

現在担当しているプロダクトでも使われているRaftについて、論文を読んで自分なりに実装してみました。 https://github.com/taisho6339/rs-raft

今後のキャリアについて思うこと

引き続き自分が楽しめて、市場価値も高い分野という軸で開拓していきたいと思っています。 その軸で考えると今現在自分が関わっている分散システムの開発運用という分野は、 日本の会社だとあまりポジションはないですが、海外を見ればたくさんあるので引き続き英語と一緒に頑張っていこうと思っています。

一方で、オンプレの会社だし、世のクラウドサービスへの知見がどんどん風化していくことに不安を覚えることもあります。 ただ、今の仕事でしっかり気合を入れて取り組むことで、分散システム設計の根本の知識や経験が得られるので、 こういった基盤となるスキルセットさえあれば、後から必要に応じてキャッチアップして良いアウトプットができると信じています。 また、クラウドであれば詳しい人はどの会社にもいるだろうし、ソリューションアーキテクトのようなロールの方々もプロバイダ側にいたりするので、 気負う必要はないし、むしろ棲み分けているおかげで一緒に組んで良いシナジーが出せるのではと考えています。

来年の抱負

ということで、来年はもう少しアウトプットの比重をあげつつ、 英語、分散システム、データベース分野に引き続き力を入れてがんばります。

2021年の振り返り

今年は結婚、LINEへの転職を経て、自分にとって大きな転換期となる年でした。 今年も例年と同様に振り返り、来年の糧としたいと思います。

LINEでの仕事のスタート

今年1月からLINEに入社してPrivate Cloudを作る部署のSREとして仕事をはじめました。 背景についてはこちら。 https://taisho6339.hatenablog.com/entry/2020/12/31/191912

Private Cloud開発者用の横断Platformを開発、運用する、というミッションを担い、 専らログ基盤の構築に邁進してきた一年でした。 Managed Fluentdの提供や、Fluentdを管理するためのKubernetes Operatorなどを作ったり、 Grafana Lokiを使った20 TB / day規模の大規模ログモニタリング基盤を構築したり、 なかなかにアグレッシブな仕事ができました。

※具体的な内容については下記にて発表させていただきました。

Reliable Log Aggregation System in Multi-Tenant Kubernetes cluster

Grafana Lokiで構築する大規模ログモニタリング基盤

フリーランスから転身してみて

結論から言うとLINEに入社して良かったと思っています。 現在の僕のロールはシニアソフトウェアエンジニアなので、 部署を横断した価値提供や、よりハイレベルな要件下でのシステムデザイン、開発、運用が求められます。 そのため要件整理から各所への合意形成、そして開発リリースまで一貫して、裁量を与えられて仕事をさせていただきました。 技術的にも、ただ既存のミドルウェアやライブラリなどを使えば済むような要件はなく、 内部動作や場合によってはソースコードレベルで理解していないといけないようなレベルの仕事も多く、 ソフトスキル、技術スキル両面で大きく前進できた一年だと思っています。

最もOSS活動に勤しんだ一年

そして今年は、人生で最もOSSプロダクトに真剣に向き合った年になりました。

今回LINEの仕事でGrafana Lokiを使うに辺り、 かなり気合いを入れて、検証、内部動作理解に努めてきました。 結果、のめり込むほど面白さを感じ、 本体のコードに10個ほどContributeしたり、

https://github.com/grafana/loki/pulls?q=is%3Apr+author%3Ataisho6339+is%3Aclosed

いろんな国のユーザとSlackやIssue上で議論したりしていました。

クエリシャーディング、レプリケーション、キャッシュ、クラスタリング、Consistent Hashなど、 分散システムの基本的な仕組みを愚直に組み合わせることで、 S3のようなほどほどなレイテンシの安価なストレージ上で、 高速にリアルタイムなデータ検索を実現しているアーキテクチャにロマンを感じました。

Lokiのことが好きになりすぎてファンブックのようなものまで作ってしまいました。

Grafana Loki Deep Dive

今後のキャリアについて思うこと

もともと「コンピュータサイエンスをゴリゴリに意識しないと成果を出せないエンジニアリング」にとても強い関心があり、 そのためアプリケーションレイヤから徐々に下周りのレイヤに降りるようにキャリアを開拓してきました。 特に最近はそういったレイヤはうまくクラウドやライブラリ、フレームワークで抽象化されているため、 いっそそれらを作ることに関心を持ち始めました。 そして今回のGrafana Lokiでの活動を通して、 自分もLokiのような分散アーキテクチャなソフトウェアを作る仕事をしてみたいという気持ちをとても強くしました。

日本でいうとやはりポジションは少ないのですが、 英語さえクリアできればそういったポジションもたくさんあるのだということが実感として感じることができ、 もっと気合を入れて長い目で継続して英語をやっていくぞ、という気持ちになりました。

今年のアウトプットまとめ

登壇

OSS活動

Grafana LokiへのContribution

Grafana Loki Deep Dive

ブログ

https://taisho6339.hatenablog.com/archive/2021

来年に向けて

来年は今年以上に、英語、OSS活動、低レイヤの開発をテーマに邁進していこうと思います。

高負荷環境でFluentdを安定運用するための3つの観点

本記事について

Fluentdは機能としてはシンプルですが、 高負荷環境で安定的に運用するためにはある程度の知識が求められます。

そこで、本記事ではそれなりにログ流量の高い環境下で私が考慮した観点をまとめました。

本記事では、KubernetesでFluentdの信頼性を担保するための3つの観点に加え、

「高負荷時の安定運用」に焦点を当て、

  1. 負荷分散
  2. 適切なモニタリング
  3. トラブルシューティングとチューニング

の3つの観点について整理しています。

前提となるアーキテクチャ

アーキテクチャとしては実際に私が構築した図の構成を前提とします。

f:id:taisho6339:20210422151430p:plain

アーキテクチャの特徴

1つのKubernetesクラスタに、FluentdがForwarderとAggregatorという2つのロールで存在しています。

  • Forwarder

    • DaemonSetでデプロイされる
    • 各コンテナの出力ログをあつめ、Aggregatorに送信することだけが唯一の責務
    • Aggregatorの名前解決はServiceリソースで行う
  • Aggregator

    • StatefulSetでデプロイされ、バッファファイルやposfileの永続化のためにPersistentVolumeがアタッチされる
    • ForwarderからTCPでログを受け取る
    • filterを用いた加工処理、最終的なデータストアへのログ送信を担う

1. 負荷分散

負荷分散として考慮することは、

  • Aggregatorの水平スケール手段の確立
  • 複数のAggregatorに均等にログが送られること

です。 AggregatorはStatefulSetなので、HPAを使った自動スケールや手動操作によって簡単に水平スケールすることができます。 しかし、均等にログを送るためには踏み込んだ考慮が必要です。 Aggregatorの前段にL4ロードバランサーやKafkaなどのメッセージングキューを置くことでより精密な分散ができますが、 今回はあくまでもDNSによる名前解決の構成で考えます。

※近い将来にKafkaの導入を検討しています

DNSキャッシュについて考慮する

FluentdはデフォルトでDNSで名前解決したIPを永続的にキャッシュします。 よって、このままだと仮にAggregatorがスケールしたとしても新しく作成されたPodにルーティングされてくれません。 そこでDNSキャッシュの時間を短く or 無効にすることで水平スケールに対応できるようにします。

※その分DNS側に負荷がかかるので許容できない場合はLBやメッセージキューを検討することになります

expire_dns_cache

Client-side DNS round robin

DNSラウンドロビンでリクエストが偏る場合は、クライアントサイドの分散設定を考慮するのが有効です。 forwarder pluginには、dns_round_robin設定があり、 これを有効にすると、DNSから返却されたIPリストからランダムで一つピックアップして使用する、という挙動になります。

2. モニタリングで備える

Fluentdで負荷に備えるためには、モニタリングで見るべきポイントを明確にしておく必要があります。 そこで大前提として意識しなければならないのは、「ログの経路」と、各経路での「単位時間辺りのログが流れる量」です。

ログの経路とログの流量の大原則

ログはForwarder、Aggregatorのそれぞれのポイントで以下のような経路をたどっていきます。

  1. In Forwarder: 監視対象のログファイルから読み取り、加工処理(filter, match)をしてバッファに書き出し
  2. In Forwarder: バッファに溜まったログを、チャンク単位でFlush Queueにキューイング
  3. In Forwarder: Flush QueueからAggregatorへ送信
  4. In Aggregator: Chunk単位でログを受信し、加工処理(filter, match)をしてバッファに書き出し
  5. In Aggregator: バッファに溜まったログを、チャンク単位でFlush Queueにキューイング
  6. In Aggregator: Flush Queueから各送信先へ送信

つまり大原則として、Forwarder、Aggregator両サイドにおいて、

  • 単位時間あたりのログからバッファに書き込まれる量 < 単位時間あたりのバッファからFlush Queueuにキューイングされる量
  • 単位時間あたりのFlush Queueにキューイングされる量 < 単位時間あたりのFlush Queueから宛先に送られる量

である状態をなるべくの間維持しておかなくてはなりません。 そうでなければ、常に一定量のバッファがたまった状態になり一定時間遅れで集計され続けるか、最悪の場合バッファが溢れて容量限界を迎えてしまいます。

以上の点を踏まえて監視を行っていきます。

監視すべきメトリクス

まず他のコンテナと同様に、

  • CPU使用率
  • メモリ使用率
  • プロセスの死活監視
  • ファイルディスクリプタの使用率(※取得可能な場合)

などはモニタリングします。

それに加えてFluentdは、monitoring-prometheusに記載されている通り、Prometheus形式のメトリクスをexportすることができ、

記載されているmetricsのように、output時に発生したエラー数や、Slow Flushの数、Retryの数などもエラー検知に役に立ちます。

また、負荷計測の観点では下記のメトリクスをチェックしておくことで適切なログ流量設定の参考にすることができます。

ログの流量に関する重要メトリクス

メトリクス名 説明 目的
fluentd_output_status_buffer_total_bytes buffer stage + buffer queueを合計したログのデータ量 全体的なバッファのたまる速度、はける速度をモニタリング
fluentd_output_status_buffer_stage_byte_size buffer stage領域に溜まっているログのデータ量 バッファにたまる速度、Flush Queueにキューイングされる速度をモニタリング
fluentd_output_status_queue_byte_size buffer queue領域に溜まっているログのデータ量 Flush Queueにキューイングされる速度、実際にFlushされる速度をモニタリング
buffer_available_buffer_space_ratios bufferの有効な残りスペースの割合 バッファが足りそうかをモニタリング

私の推進するプロジェクトでは、上記メトリクスを使ってダッシュボードを作り常に可視化しています。

3. トラブルシューティングに対応する

最後のセクションではトラブルシューティングに役立つコマンド、ノウハウと、おまけとして私が実際に遭遇した障害ケースを紹介します。

前提1: FluentdのThreadモデル

Fluentdの過負荷をトラブルシュートするにあたって、Threadモデルを理解しておく必要があります。

Fluentdで抑えておくべきThreadの種類は以下の3つです。

  • event_thread

    • input pluginが作るevent_loop用のスレッド
    • リクエストを受けて加工し、バッファに書き込むまでが仕事
  • enqueue_thread

    • flushできるChunkをflush queueにenqueueするスレッド
  • flush_thread

    • キューイングされたChunkを実際の宛先に送信するスレッド

前提2: トラブルシューティングの流れ

基本的にFluentdが過負荷になったり、ログが送られなくなった場合、私は下記のような流れでトラブルシュートしています。

  • top -H コマンドで過負荷になっているFluentdのスレッドを特定する
  • netstat, lsofを使い、接続は確立しているかを確認する
  • tcpdumpを使い、データは実際に送られているかなどを確認する
  • straceを使ってstuckしているsyscallを確認する
  • SIGCONT signalを飛ばしてsigdumpを吐き出し、どこでstuckしているのかを確認する

Fluentdはsigdumpが組み込まれており、SIGCONT signalを飛ばすことで特定の場所にこのようなスレッドダンプのファイルを吐き出してくれます。 つまり、stuckしている場合はこのdumpファイルを吐き出すことで実際にRubyコードのどの部分でstuckしているのかを可視化することができます。

Case1: 1つのChunkサイズが大きすぎてAggregatorが過負荷になった

事象としては下記の状態になっていました。

  • 一定時間ごとにAggregatorのCPU使用率が100%近くで張り付く
  • Forwarderへtimeout時間までにackを返せずにログ送信が失敗するエラーがたくさん出ている
  • ずっとCPU使用率が張り付いているわけはなく、定期的にバラバラのaggregatorに負荷が集中して収まるのを繰り返している

よって、負荷が高まっているコンテナ内で

top -H

を実行したところ、前述した1つのevent_threadだけが高負荷なことが分かりました。 つまり、受け取るところ or 加工するところ or バッファに書き込むところのどこかがボトルネックになっていることになります。

また、

netstat

を実行したところForwarderと接続確立していたのは1つだけだったため、リクエストの数が高負荷になっているわけでもなさそうでした。 またバッファに書き込むところが詰まっているならIO待ちの割合が増えそうですがそんなこともなくCPUも高負荷なので、消去法で加工するところが重いと仮説を立てました。

そこでForwarderから送るChunkあたりのサイズを小さくしたところ、無事に解決しました。

参考: Buffering Parameters

Case2: Flush Threadが常時CPU使用率100%近くの過負荷になった

事象としては、

  • Aggregatorの送信先のElasticsearchクラスタに全くログが送られなくなった
  • AggregatorコンテナのCPU使用率が常時100%近くで張り付いている

ということが発生しました。 topコマンドの結果、flush threadが張り付いていることが分かりました。 また、netstatしてもESへの接続がなく、tcpdumpで確認しても一切データが送られていない状態であることも分かりました。 そこでflush threadに対してstraceすると、getrandomというsyscallを無限に呼び続けていることが確認できました。 つまり確実にどこかの処理でstuckしているので、SIGCONT signalを投げてスレッドダンプをとったところ、下記のようなダンプが取れました。

Thread #<Thread:0x0000562c82ebbf90@flush_thread_2 /srv/fluentd/lib/ruby/gems/2.7.0/gems/fluentd-1.12.3/lib/fluent/plugin_helper/thread.rb:70 run> status=run priority=0
      /srv/fluentd/lib/ruby/2.7.0/securerandom.rb:123:in `urandom'
      /srv/fluentd/lib/ruby/2.7.0/securerandom.rb:123:in `gen_random_urandom'
      /srv/fluentd/lib/ruby/2.7.0/securerandom.rb:73:in `bytes'
      /srv/fluentd/lib/ruby/2.7.0/resolv.rb:606:in `random_number'
      /srv/fluentd/lib/ruby/2.7.0/resolv.rb:606:in `random'
      /srv/fluentd/lib/ruby/2.7.0/resolv.rb:625:in `block in allocate_request_id'
      /srv/fluentd/lib/ruby/2.7.0/resolv.rb:622:in `synchronize'
      /srv/fluentd/lib/ruby/2.7.0/resolv.rb:622:in `allocate_request_id'
      /srv/fluentd/lib/ruby/2.7.0/resolv.rb:842:in `sender'
      /srv/fluentd/lib/ruby/2.7.0/resolv.rb:527:in `block in fetch_resource'
      /srv/fluentd/lib/ruby/2.7.0/resolv.rb:1124:in `block (3 levels) in resolv'
      /srv/fluentd/lib/ruby/2.7.0/resolv.rb:1122:in `each'
      /srv/fluentd/lib/ruby/2.7.0/resolv.rb:1122:in `block (2 levels) in resolv'
      /srv/fluentd/lib/ruby/2.7.0/resolv.rb:1121:in `each'
      /srv/fluentd/lib/ruby/2.7.0/resolv.rb:1121:in `block in resolv'
      /srv/fluentd/lib/ruby/2.7.0/resolv.rb:1119:in `each'
      /srv/fluentd/lib/ruby/2.7.0/resolv.rb:1119:in `resolv'
      /srv/fluentd/lib/ruby/2.7.0/resolv.rb:521:in `fetch_resource'
      /srv/fluentd/lib/ruby/2.7.0/resolv.rb:507:in `each_resource'
      /srv/fluentd/lib/ruby/2.7.0/resolv.rb:402:in `each_address'
      /srv/fluentd/lib/ruby/2.7.0/resolv.rb:116:in `block in each_address'
      /srv/fluentd/lib/ruby/2.7.0/resolv.rb:115:in `each'
      /srv/fluentd/lib/ruby/2.7.0/resolv.rb:115:in `each_address'
      /srv/fluentd/lib/ruby/2.7.0/resolv.rb:58:in `each_address'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/excon-0.81.0/lib/excon/socket.rb:110:in `connect'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/excon-0.81.0/lib/excon/socket.rb:49:in `initialize'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/excon-0.81.0/lib/excon/connection.rb:463:in `new'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/excon-0.81.0/lib/excon/connection.rb:463:in `socket'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/excon-0.81.0/lib/excon/connection.rb:118:in `request_call'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/excon-0.81.0/lib/excon/middlewares/mock.rb:57:in `request_call'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/excon-0.81.0/lib/excon/middlewares/instrumentor.rb:34:in `request_call'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/excon-0.81.0/lib/excon/middlewares/idempotent.rb:19:in `request_call'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/excon-0.81.0/lib/excon/middlewares/base.rb:22:in `request_call'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/excon-0.81.0/lib/excon/middlewares/base.rb:22:in `request_call'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/excon-0.81.0/lib/excon/connection.rb:273:in `request'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/faraday-excon-1.1.0/lib/faraday/adapter/excon.rb:31:in `block in call'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/faraday-1.4.1/lib/faraday/adapter.rb:55:in `connection'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/faraday-excon-1.1.0/lib/faraday/adapter/excon.rb:31:in `call'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/faraday-1.4.1/lib/faraday/rack_builder.rb:154:in `build_response'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/faraday-1.4.1/lib/faraday/connection.rb:492:in `run_request'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/elasticsearch-transport-7.12.0/lib/elasticsearch/transport/transport/http/faraday.rb:48:in `block in perform_request'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/elasticsearch-transport-7.12.0/lib/elasticsearch/transport/transport/base.rb:288:in `perform_request'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/elasticsearch-transport-7.12.0/lib/elasticsearch/transport/transport/http/faraday.rb:37:in `perform_request'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/elasticsearch-transport-7.12.0/lib/elasticsearch/transport/client.rb:191:in `perform_request'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/elasticsearch-api-7.12.0/lib/elasticsearch/api/actions/bulk.rb:69:in `bulk'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/fluent-plugin-elasticsearch-5.0.3/lib/fluent/plugin/out_elasticsearch.rb:1062:in `send_bulk'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/fluent-plugin-elasticsearch-5.0.3/lib/fluent/plugin/out_elasticsearch.rb:875:in `block in write'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/fluent-plugin-elasticsearch-5.0.3/lib/fluent/plugin/out_elasticsearch.rb:874:in `each'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/fluent-plugin-elasticsearch-5.0.3/lib/fluent/plugin/out_elasticsearch.rb:874:in `write'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/fluentd-1.12.3/lib/fluent/plugin/output.rb:1138:in `try_flush'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/fluentd-1.12.3/lib/fluent/plugin/output.rb:1450:in `flush_thread_run'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/fluentd-1.12.3/lib/fluent/plugin/output.rb:462:in `block (2 levels) in start'
      /srv/fluentd/lib/ruby/gems/2.7.0/gems/fluentd-1.12.3/lib/fluent/plugin_helper/thread.rb:78:in `block in thread_create'

見る限りfluentd側に問題がありそうなので、issueを掘ってみると全く同じ事象が報告されていたため、 一旦事象が再現しないバージョンに変えてデプロイすることで解決しました。

まとめ

Fluentdを安定運用するために考慮した観点と実際のトラブルケースなどについて紹介してきました。 今回はクライアント側の設定で負荷分散したりと、シンプルさを優先していますが、 将来的にはForwarderとAggregatorの間にKafkaを導入することで、より効率的かつ安全な負荷&障害対策ができることを期待しています。