# Onoe's Portfolio — Full content > 株式会社マネーフォワードでソフトウェアエンジニアとして勤務しています。SRE NEXTのコアスタッフもしています。最近はSRE、クラウドコンピューティング、分散システムに強い興味があります。 This file concatenates the full markdown of every blog post published by Hiroya Onoe (尾上 寛弥). Japanese and English versions are both included. See [llms.txt](https://www.onoe.dev/llms.txt) for the structured index. --- ## About Name: 尾上 寛弥 (おのえ ひろや) 株式会社マネーフォワードでソフトウェアエンジニアとして勤務しています。SRE NEXTのコアスタッフもしています。最近はSRE、クラウドコンピューティング、分散システムに強い興味があります。 Links: - GitHub: https://github.com/hiroyaonoe - X (Twitter): https://x.com/hiroyanoe - Resume (JA): https://www.onoe.dev/resume-ja.pdf - Resume (EN): https://www.onoe.dev/resume-en.pdf --- # Reinventing-the-Wheel Learning in the Age of AI: Lessons from Building an AI Agent with AI - URL: https://www.onoe.dev/en/blog/reinventing-the-wheel-with-ai/ - Language: en - Published: 2025-12-24 - Tags: Tech, AI, Learning > Reinventing-the-Wheel Learning in the Age of AI: Lessons from Building an AI Agent with AI It’s Christmas Eve. I hope everyone is having a good holiday season. I attended a conference in Nashville, Tennessee last week1 and got a little taste of American Christmas. In this post, I’d like to share my current thoughts on what reinventing-the-wheel learning should look like in the age of AI, based on my experience actually trying it out. info この記事の日本語版はこちらです。 info This article is the December 24th entry of the Money Forward Engineers Advent Calendar 2025. What Is Reinventing-the-Wheel Learning? Here, I use this term to refer to “a learning method where you reimplement a well-established technology from scratch, step by step.” I believe this learning method is common among software engineers. It’s an extremely powerful way to build a foundational understanding of a technology. However, since I couldn’t find a single term to describe it, I’ll call it “reinventing-the-wheel learning” in this article. There are many self-build tutorial contents available on the internet and in books for practicing reinventing-the-wheel learning across various technologies. Off the top of my head, here are some examples: Build Your Own C Compiler (Japanese) Build Your Own OS (Japanese) Build Your Own Computer System (Japanese edition of “The Elements of Computing Systems”) Build Your Own Wasm Runtime (Japanese) Build Your Own Kubernetes Scheduler (Japanese) Build Your Own Deep Learning Framework (Japanese edition of “Deep Learning from Scratch”) This approach is also common in university courses. For example, the Processor & Compiler Lab, a well-known course in the University of Tokyo’s Department of Information Science, follows this approach. I personally experienced building a processor from scratch and a programming language implementation at Kyoto University2. These self-build tutorials walk you through implementing concepts step by step with explanations along the way. Since implementing everything from scratch would be too difficult, they typically focus on a minimal set of important concepts. Learners read the tutorials, understand the concepts, and code along to see things work. On the other hand, for technologies without existing self-build tutorials, it’s difficult to even know what concepts to learn or how to structure the implementation, making reinventing-the-wheel learning quite challenging. Even when tutorials do exist, if the prerequisites don’t match the learner’s background, it can be hard to get started. For example, if the tutorial uses a programming language you’re unfamiliar with, you first need to learn that language. There’s also the natural language barrier – tutorials written in languages other than your native tongue can be difficult to follow. How to Approach Reinventing-the-Wheel Learning in the Age of AI This led me to think: by fully leveraging AI, we could freely generate self-build tutorials that don’t yet exist and practice reinventing-the-wheel learning for any technology, tailored to our own prerequisites. In the approach I’ll introduce here, you use Deep Research to learn the overview of the technology and the implementation plan, then use a Coding Agent to deepen understanding while having it implement the code. First, ask AI about the overview of the technology and the steps to implement it. Use a Deep Research-class AI for broad investigation. I want to deepen my understanding of [target technology] by implementing it from scratch. Please design a step-by-step implementation plan and tutorial. [Conditions such as programming language, prerequisites, etc.] Use the response and further conversation with the AI to get a rough understanding of the technology. Then, move on to implementation right away. Since the AI handles the implementation as well, use a Coding Agent. Write instructions for the Coding Agent like this: I want to build [target technology] from scratch for learning purposes. Here are the prerequisites: [Conditions such as programming language, prerequisites, etc.] Please follow these guidelines: - Do not write any code beyond what I explicitly instruct, so we can implement step by step. - Before each step, create a new design document in the docs/design directory. - Explain and walk me through the code every time you update a file. - [Other general implementation guidelines] Then, instruct the agent to proceed with implementation following the steps planned with Deep Research. For each step, discuss with Deep Research and the Coding Agent what to implement, and choose what you want to build. At each step, have the AI create design documents beforehand and explain the implemented code, asking questions as you go to deepen your understanding of the technology. Building an AI Agent with AI I actually tried building an AI Agent from scratch using AI3. While there are some existing tutorials for building AI Agents, none of them met all of the following criteria, so I decided to fully leverage AI for learning: Focused on learning (not building a product with AI Agents) Explanations available in Japanese Can be implemented in Go, which I’m proficient in I asked Gemini Deep Research the following: I want to deepen my understanding of AI Agents by implementing one from scratch. Please design a step-by-step implementation plan and tutorial. I'll use OpenAI's API for the LLM. I want to implement it in Go. The full output report is too long to include here, but in summary: The basic pattern is ReAct (Reason+Act), which iterates through Thought, Action, and Observation from Input to produce an Output (Final Answer) Thought: Analyze the current task and history, plan what to do next Action: Call an (external) tool with specific parameters (Final Answer is also treated as a tool) Observation: Get the tool execution result There are two implementation approaches for tool usage: Use OpenAI Tool Calling API The API guarantees valid tool responses Implement ReAct Text Parsing yourself Since the LLM generates tool responses as text, they may not always be valid Implementation steps (for ReAct Text Parsing): Design the system prompt Build the Text Parser Implement the ReAct Loop Implement various tools (Further steps) Implement multi-agent systems I chose to implement ReAct Text Parsing myself since I wanted to build from the ground up. With a rough understanding in hand, I moved on to actual implementation using a Coding Agent. I used Claude Code for this. First, I wrote a CLAUDE.md file like the following. In practice, I refined and updated CLAUDE.md as implementation progressed. I want to build an AI Agent from scratch for learning purposes. Here are the prerequisites: - CLI tool - Implement in Go - Use OpenAI's API for the LLM - Adopt ReAct Text Parsing Please follow these guidelines: - Do not write any code beyond what I explicitly instruct, so we can implement step by step. - Before each step, create a new design document in Japanese in the docs/design directory. - Explain and walk me through the code every time you update a file. - Read CLAUDE.md before starting each step. - Write appropriate test code. However, do not make external calls (such as to the OpenAI API). Create mocks as needed, but do not write meaningless tests. - Verify that make build and make test succeed at the end of each step. - Update the following files to their latest state at the end of each step: - README.md - docs/features.md - Description of each CLI tool feature - docs/package-dependencies.md - Go package/directory dependency diagram in mermaid format - Run git commit and git push after completing each step. Then, following the plan from Deep Research, I instructed the agent to proceed with the following steps. I discussed with the Coding Agent what steps to take and chose what to implement based on what I wanted to learn: Project setup Implement OpenAI API calls Implement chat interface Implement ReAct Parsing Integrate ReAct with chat Implement tool invocation and execution Implement various tools (text processing, file operations, command execution, etc.) In the end, I had a working CLI tool that could interactively invoke simple tools and return results. I had also wanted to implement Deep Research and MCP tool calling, but haven’t gotten to those yet. Learning Efficiency In total, it took roughly 8 hours from the initial research to getting a rough understanding of AI Agents. This learning method lets you proceed in a way that aligns with your existing knowledge. For example, since I was already familiar with Go CLI tool development patterns, I could easily follow the implementation. Because there’s no mismatch with your prerequisites, I believe learning efficiency improves. Having the Coding Agent handle all the implementation also contributes to efficiency. Some might argue that typing out all the code yourself (i.e., “code transcription”) helps internalize the learning better. However, I believe what matters is understanding the code, not writing it. Instead of transcribing, I make sure to thoroughly understand every piece of code the AI outputs. This includes not just reading the code, but also having the AI explain it and discussing it. Of course, if you have the time, there’s nothing wrong with transcribing the code yourself. It might even be more enjoyable from the perspective of building something with your own hands. Personally, my past tendency has been to start with the intention of transcribing but gradually get lazy and resort to copy-pasting, so I don’t worry too much about transcription. Can You Gain Sufficient Knowledge? This approach has a slight drawback: there’s no guarantee that the AI is providing sufficient knowledge. It’s possible that there are still fundamental things about AI Agents that I should know. There’s no way to discover that within this learning method alone. For example, the initial research didn’t cover how to compress and maintain context. You need to trust the AI regarding how deep is deep enough. However, the same could be said for traditional self-build tutorials – you can only trust that the tutorial covers enough. Additionally, with better-crafted prompts, you may be able to dig deeper into the AI’s knowledge to some extent. Applying This to Technologies with No Existing Tutorials For building an AI Agent, there are already some tutorials available at least in English/Python. The AI may have referenced these during its research. So, can this approach work for technologies with absolutely no existing tutorials? As a test, I asked Deep Research to investigate “building a Zig compiler in Go”4. It proposed the following steps: Project setup Implement Lexer (tokenization) Implement Parser (AST construction) Implement ZIR (Lowering) Implement Sema (interpretation and AIR generation) Implement CodeGen (QBE output) This is only a surface-level evaluation since I didn’t actually attempt the build, but it appears to correctly combine general compiler implementation techniques with Zig compiler architecture specifics. I’d like to try applying this to other technologies in the future. This approach may be difficult to apply to technologies that don’t consist entirely of software implementation. This method works precisely because it can fully leverage a Coding Agent when everything is software-based. It may not be well-suited for technologies that require hardware, or infrastructure and cloud resource technologies where there’s value in experimenting through a GUI console. That said, perhaps recent AI-powered browsers could make it applicable to those domains as well. Is Reinventing-the-Wheel Learning Still Necessary in the Age of AI? After actually going through this process by trial and error, I felt it ended up closely resembling a product development workflow. In product development, you also start with a broad investigation and design, then design feature by feature and implement while understanding the code. If that’s the case, when you want to learn a prerequisite technology before starting product development, you might not need to go through reinventing-the-wheel learning at all. You could simply learn the technology as you develop the actual product. On the other hand, I believe reinventing-the-wheel learning remains effective when the goal is to broadly learn a technology as foundational knowledge. Another key difference between reinventing-the-wheel learning and product development lies in how much you apply the brakes. In product development, there’s growing discussion around how to delegate work to AI while maintaining quality without applying too many brakes5. In reinventing-the-wheel learning, on the other hand, it’s crucial to apply the brakes thoroughly and ensure you understand every piece of code the AI outputs. Conclusion I’ve briefly summarized my thoughts while building an AI Agent from scratch. Since I’ve only tried this once through trial and error, there may well be better approaches. I’d like to experiment with other technologies going forward. Whether you’re about to try this for the first time after reading this article or are already practicing it, I’d love to hear your thoughts and experiences. https://x.com/hiroyanoe/status/2000082437066653901 ↩︎ Many of these university course materials are publicly available online, making them excellent learning resources. ↩︎ My original motivation was to learn about AI Agents, which led me to start building one. As I progressed, I realized this approach could be generalized, which led to writing this article. ↩︎ As far as I could find, there are no existing tutorials for building a Zig compiler available on the internet. ↩︎ This presentation was very insightful on the topic of brakes and quality in AI-assisted development: https://speakerdeck.com/watany/its-only-the-end-of-special-time ↩︎ --- # AI時代における車輪の再発明型学習のあり方 〜AI Agent自作 with AIから学ぶ〜 - URL: https://www.onoe.dev/blog/reinventing-the-wheel-with-ai/ - Language: ja - Published: 2025-12-24 - Tags: Tech, AI, Learning > AI時代における車輪の再発明型学習のあり方 〜AI Agent自作 with AIから学ぶ〜 クリスマスイブですね。皆様いかがお過ごしでしょうか。私は先週テネシー州ナッシュビルで学会に参加しまして1、アメリカのクリスマスをちょっと感じてきました。 今回は、AI時代における車輪の再発明型学習がどうあるべきかについて、実際にやってみた経験をもとに今考えていることを紹介しようと思います。 info The English version of this article is available here. info 本記事は、Money Forward Engineers Advent Calendar 2025の12月24日の記事です。 車輪の再発明型学習とは ここでは、「既に世間で普及している技術について、一からコードを書いてStep by Stepで再実装してみる学習法」を指します。 学習法自体はソフトウェアエンジニアの間では一般的なものだと思います。ある技術を基礎から理解する上でとても強力な学習法です。しかしこれを一言で表す単語が見つからなかったので、本記事では「車輪の再発明型学習」と呼ぶことにします。 またさまざまな技術について、車輪の再発明型学習をするための自作コンテンツが、インターネット記事や本で提供されています。パッと思いつく限りでも以下のようなものがあります。 Cコンパイラ自作 OS自作 コンピュータ全般自作 Wasm Runtime自作 Kubernetes Scheduler自作 Deep Learning自作 また大学などの教育機関での講義・演習においても一般的なのではないでしょうか。東京大学理学部情報科学科の名物とされているプロセッサ・コンパイラ実験もそうですし、 自分自身も京都大学でプロセッサ自作やプログラミング言語処理系自作などを経験しました2。 これらの自作コンテンツは、理解すべき概念について、Step by Stepで解説を交えながら実際に実装していきます。全部を実装するのは難易度が高いので、重要な最小限の概念に絞って解説するケースが多い印象です。学習者は記事を読んで概念を理解しながら、コードを写経して動かしていきます。 一方で、自作コンテンツのない技術では、理解すべき概念や自作の流れ自体がわからないため、車輪の再発明型学習をする難易度が高いのが現状です。 また自作コンテンツが既にある場合でも、その前提知識が学習者にマッチしていないと取り組む難易度が高い状況です。例えば、使用しているプログラミング言語を知らない場合、まずそのプログラミング言語を学ぶ必要があります。日本語以外の自作コンテンツに日本語話者が取り組むのが難しいという、自然言語の問題もあります。 AI時代の車輪の再発明型学習の進め方 そこでAIをフル活用することによって、この世にない自作コンテンツを自由に生成でき、任意の技術について自分にあった前提知識で車輪の再発明型学習ができるのではないかと考えました。 今回紹介する手法では、Deep Researchを用いて学びたい技術の概要と自作の流れを知り、Coding Agentを用いて実装させながら理解を深めます。 まず、学びたい技術の概要とそれを実装するための流れをAIに聞きます。技術について広く調査してもらうため、Deep Researchに相当するAIを使用します。 [対象技術]をフルスクラッチで実装することで、[対象技術]の理解を深めたいです。 Step by Stepで実装するための手順・チュートリアルを考えて。 [使用言語や前提知識などの各種条件]。 これの解答やさらなるAIとの会話により、その技術についての概要をざっと理解します。 そのあとは早速実装に移ります。実装自体もAIに任せるため、Coding Agentを使用します。 Coding Agentの指示を以下のように書きます。 私の学習のためにフルスクラッチでAI Agentを作成したいです。前提は以下の通りです。 [使用言語や前提知識などの各種条件] 以下に従ってください。 - Step by Stepで実装するために、私の命令外のコードは一切書かないこと。 - 各Stepで実装前に、docs/designディレクトリに新しい設計ドキュメントを日本語で作成すること。 - 実装したコードについてファイルを更新するごとに毎回説明・解説すること。 - [その他一般的に実装の上で必要な指示] そしてDeep Researchなどで計画したStepに従って実装を進めるように指示します。各Stepで何を実装するかはDeep ResearchやCoding Agentと議論しながら、自分の実装したいものを選択して指示します。 各Stepにおいて、事前の設計・実装コードの説明をAIにさせ、疑問点などをAIにどんどん聞きながら実装を進めて行きます。 この時のAIとのやり取りで技術への理解を深めます。 AI Agent自作をAIでやってみた 今回はAI Agentの自作をAIを用いて実際にやってみました3。 AI Agentの自作コンテンツ自体はいくつかあるようですが、以下の条件を満たす自作コンテンツがなかったのでAIをフル活用して学習を進めることにしました。 学習にフォーカスしている(AI Agentを用いたプロダクト開発ではない) 日本語での解説がある 自分が詳しいGoで書ける Gemini Deep Researchに以下を聞きます。 AI Agentをフルスクラッチで実装することで、AI Agentの理解を深めたいです。 Step by Stepで実装するための手順・チュートリアルを考えて。 LLMにはOpenAIのAPIを使用します。Goで実装したいです。 出力されたレポートの全文は長いので省略しますが、要約すると以下の内容でした。 InputからThought, Action, Observationを順に繰り返してOutput(Final Answer)を返すReAct(Reason+Act)パターンが基本である Thought: 現在のタスクと履歴を分析し、次に何をすべきかの計画を立てる Action: 特定のパラメータを引数にして(外部)ツールを呼び出す(Final Answerも一つのツールとして扱う) Observation: ツールの実行結果を取得する ツールを使用するための2つの実装方法がある OpenAI Tool Calling APIを使う ToolのレスポンスがValidであることをAPIが保証してくれる ReAct Text Parsingを自分で実装する Toolのレスポンス自体をLLMが返すためValidなレスポンスでない可能性がある 実装ステップ(ReAct Text Parsingの場合) システムプロンプトの検討 Text Parserの構築 ReAct Loopの実装 各種ツールの実装 (さらなるステップ) マルチエージェントの実装 一から実装して理解したいので、ReAct Text Parsingを自分で実装することにします。ものすごくざっくり理解したので、ここからはCoding Agentを使用して実際に実装に取り掛かります。今回はClaude Codeを使用します。 まず以下のようなCLAUDE.mdを書きます。実際には、実装を進めながらCLAUDE.mdも修正したり追記したりしてこの状態に至っています。 私の学習のためにフルスクラッチでAI Agentを作成したいです。前提は以下の通りです。 - CLIツール - Goで実装する - LLMにはOpenAIのAPIを使用する - ReAct Text Parsingを採用する 以下に従ってください。 - Step by Stepで実装するために、私の命令外のコードは一切書かないこと。 - 各Stepで実装前に、docs/designディレクトリに新しい設計ドキュメントを日本語で作成すること。 - 実装したコードについてファイルを更新するごとに毎回説明・解説すること。 - Stepの開始前に必ずCLAUDE.mdを読むこと。 - testコードを適切に書くこと。ただし外部へのアクセス(OpenAI APIなど)をしない範囲で実装すること。必要に応じてmockを作成して良いが、意味のないテストは書かないこと。 - Step完了時にmake buildやmake testが成功するのを確認すること。 - Step完了時に以下のファイルを最新の状態に更新すること。 - README.md - docs/features.md - CLIツールの各機能についての説明 - docs/package-dependencies.md - mermaid形式で記述したGoのpackage/directoryの依存関係 - Step完了後に、git commitとgit pushを実行すること。 そして最初のDeep Researchの調査をもとに、以下のようなStepを指示しました。Coding Agentにも最初どのようなStepで進めるべきか聞き、それにある程度従いながら、自分の実装したいものをその都度指示していきました。 プロジェクトのセットアップ OpenAI API呼び出し実装 チャットインターフェース実装 ReAct Parsing実装 ReActとチャットの統合 ツール呼び出しと実行機能実装 各種ツールの実装(テキスト処理・ファイル処理・コマンド実行など) 最終的に、対話的にやり取りして簡単なツールを呼び出して結果を返してくれるCLIコマンドが完成しました。 本当はDeep ResearchやMCPツール呼び出しの実装などもしたかったのですが、まだできていません。 学習効率 今回は、最初の調査から始めておよそ8時間ほどでAI Agentをざっくり学ぶことができました。 この学習方法だと自分の前提知識に沿った方法で進めることができます。 例えば、GoでのCLIツールの自作の作法はある程度把握していたので、容易に実装を把握することができました。 この点、前提知識のずれが生じない分学習効率が良くなると考えています。 また実装を全てCoding Agentに任せていることでも効率化できています。 コードを全て写経した方が、学習内容が身につきやすいという意見もあるかもしれません。 しかし、コードを書くのではなく理解することが重要であると考えています。 写経する代わりに、AIが出力したコードは徹底的に理解するようにしています。これはコードを読むだけでなく、AIにコード解説させながら議論することも含みます。 もちろん時間をかけて良いなら写経をするのも全く問題はありません。 動くものを自分の手で作りたいという観点では写経した方が楽しいかもしれません。個人的には、最初はそのつもりで写経していっても、次第にめんどくさくなってコピペするのが今までの傾向だったというのもあり、あまり写経に拘らずに実装をしています。 十分な知識を得られるか この手法には少し欠点があります。それはAIが十分な知識を提供してくれているかを保証できないという点です。 今回、AI Agentについてまだ知るべき基本的な知識が残っている可能性もあります。それはこの学習方法では知る方法はありません。 例えば、コンテキストをどうやって圧縮しながら保持するのかなどの知識は最初の調査では説明がありませんでした。 どこまで深掘ってその技術を学習すれば十分なのかについてはAIを信じる必要があります。 しかし、従来の自作コンテンツについても、その自作コンテンツを信じるしかないため同じであるとも考えられます。 また、プロンプトを上手く書いてAIの知識を深掘っていけばある程度改善できるかもしれません。 世の中に自作コンテンツが一切ない技術での適用 AI Agent自作については、少なくとも英語・Pythonでの記事が既にいくつかあるようです。AIは調査の際にこれらを参照した可能性があります。 では、世の中に一切自作コンテンツがない技術についてこの手法は適用できるのでしょうか。 試しに「GoでZigコンパイラ自作」をDeep Researchに調査させてみました4。すると以下のようなStepを提示されました。 プロジェクトのセットアップ Lexerの実装(トークン化) Parserの実装(AST構築) ZIRの実装(Lowering) Semaの実装(解釈とAIR生成) CodeGenの実装(QBE出力) 実際に自作に取り組んだわけではなくあくまで表面的な評価に過ぎませんが、一般的なコンパイラの実装方法とZigコンパイラのアーキテクチャの特徴を上手く理解しているように見えます。 この点は今後他の技術についても自作をしてみたいと思っています。 またソフトウェア実装で完結しない技術についてはこの手法の適用は難しいかもしれません。 この手法はソフトウェア実装で完結するからこそCoding Agentをフル活用できています。 ハードウェアが求められるような技術や、GUIコンソールで試してみることにも一定の価値があるインフラやクラウドリソースに関連する技術には向いていないかもしれません。もしかしたら最近のAIブラウザを活用すればそれらでも適用できるかもしれませんが。 そもそもAI時代に車輪の再発明型学習が必要か 実際に手探りでAIを使った自作を進めてみた結果、簡単なプロダクト開発に近いフローになったと感じました。 プロダクト開発でも最初に全体の大まかな調査・設計をします。その後、機能ごとに設計をし、コードを理解しながら実装させます。 そうであれば、何かプロダクト開発を始める前に前提技術を学習したいと思ったとき、わざわざ車輪の再発明型学習で学ぶ必要はないかもしれません。実際にプロダクトの開発を進めながら前提技術についても理解していけば良いからです。 一方で、基礎知識として広く技術を学びたいという目的であれば、依然として車輪の再発明型学習は有効であると考えています。 また、車輪の再発明型学習がプロダクト開発と違う点としては、どれだけブレーキを踏むかにあると思います。プロダクト開発においては、最近はどれだけブレーキを踏まずに開発をAIに任せながら品質を担保するか、という議論もされています5。一方で、車輪の再発明型学習においては徹底的にブレーキを踏みながらAIが出力したコードを理解することが重要です。 おわりに AI Agentを自作しながら考えていたことを簡単にまとめてみました。 私自身手探りで一回トライしただけなので、まだまだ良い方法があるかもしれません。他の技術についてもこれから試してみたいと思います。 この記事を読んでこれからやってみる方も、すでに実践している方も、ぜひ皆さんの意見もシェアしていただけると幸いです。 https://x.com/hiroyanoe/status/2000082437066653901 ↩︎ こういった大学の講義資料の一部はインターネット上で公開されていることも多いので、学習資料としておすすめです。 ↩︎ そもそもAI Agentについて学びたいというモチベーションが最初にあってAI Agent自作を進めています。AI Agent自作を進めるうちにもっと一般化できるのではないかと思い、本記事の執筆に至っています。 ↩︎ 自分の調べた限りだとZigコンパイラの自作コンテンツはインターネット上では見つけられませんでした。 ↩︎ AI開発におけるブレーキと質についてはこの内容がとても学びになりました: https://speakerdeck.com/watany/its-only-the-end-of-special-time ↩︎ --- # アーキテクチャConference 2025に参加しました! - URL: https://www.onoe.dev/blog/moneyforward-archconf2025/ - Language: ja - Published: 2025-12-02 - Tags: Tech, MoneyForward, Architecture, Event > こんにちは、Onoeです。普段はプラットフォームエンジニアとしてマネーフォワードグループ全体のプロダクトを支える基盤の開発運用をしています。今回はアーキテクチャConference 2025に2日間現地参加した話をお伝えします! ※ 外部リンク https://moneyforward-dev.jp/entry/2025/12/02/100000 に移動します --- # 社内CTFイベントで学ぶDatadog - URL: https://www.onoe.dev/blog/moneyforward-datadog/ - Language: ja - Published: 2025-07-25 - Tags: Tech, MoneyForward, Datadog, Event > こんにちは、25新卒のOnoeです。普段はオンプレミスプラットフォームの開発運用をしています。 部署に配属されてしばらく経ち、日々たくさんのことを学びつつ頑張っています。本記事ではDatadog様監修のもとマネーフォワード社内で企画・開催したDatadog CTFイベントに参加した体験記をお届けします。 この記事では、Datadog CTFでどのようなことを行ったのか、学んだこと、そしてイベントの雰囲気をお伝えできればと思います。 ※ 外部リンク https://moneyforward-dev.jp/entry/2025/07/25/100000 に移動します --- # Running RDMA in Containers on Kubernetes and Benchmarking Performance - URL: https://www.onoe.dev/en/blog/rdma-container/ - Language: en - Published: 2025-03-31 - Tags: Tech, RDMA, Container, Kubernetes, SR-IOV > I got RDMA working in containers on Kubernetes using SR-IOV, so I'm documenting the process including the issues I ran into. I also benchmarked the performance of RDMA vs TCP/IP. I got RDMA working in containers on Kubernetes using SR-IOV, so I’m documenting the process including the issues I ran into. I also benchmarked the performance of RDMA vs TCP/IP. info この記事の日本語版はこちらです。 What We’ll Do Create SR-IOV VFs (Virtual Functions) Assign VFs to containers on Kubernetes Add RDMA devices to containers on Kubernetes Benchmark RDMA vs TCP/IP performance Environment A Kubernetes cluster is built on two Ubuntu machines (AMD EPYC 7282 16-Core Processor) directly connected via Mellanox Technologies MT27800 Family [ConnectX-5] (a RoCEv2-capable 100 Gbps Ethernet NIC). Kubernetes is configured to use this NIC for inter-node communication. The container runtime is containerd. SR-IOV We’ll create one VF from the PF (Physical Function). Follow the official documentation1. First, check the BIOS settings to ensure SR-IOV and IOMMU are enabled. Then add the following grub parameters and reboot: $ cat /etc/default/grub ... GRUB_CMDLINE_LINUX="amd_iommu=on iommu=pt pci=realloc" ... Modify the NIC parameters: $ sudo mst start Starting MST (Mellanox Software Tools) driver set Loading MST PCI module - Success Loading MST PCI configuration module - Success Create devices Unloading MST PCI module (unused) - Success $ sudo mst status MST modules: ------------ MST PCI module is not loaded MST PCI configuration module loaded MST devices: ------------ /dev/mst/mt4119_pciconf0 - PCI configuration cycles access. domain:bus:dev.fn=0000:41:00.0 addr.reg=88 data.reg=92 cr_bar.gw_offset=-1 Chip revision is: 00 $ sudo mlxconfig -d /dev/mst/mt4119_pciconf0 q Device #1: ---------- Device type: ConnectX5 Name: MCX515A-CCA_Ax_Bx Description: ConnectX-5 EN network interface card; 100GbE single-port QSFP28; PCIe3.0 x16; tall bracket; ROHS R6 Device: /dev/mst/mt4119_pciconf0 Configurations: Next Boot ... NUM_OF_VFS 0 SRIOV_EN False(0) ... $ sudo mlxconfig -d /dev/mst/mt4119_pciconf0 set SRIOV_EN=1 NUM_OF_VFS=1 ... $ sudo mlxconfig -d /dev/mst/mt4119_pciconf0 q Device #1: ---------- Device type: ConnectX5 Name: MCX515A-CCA_Ax_Bx Description: ConnectX-5 EN network interface card; 100GbE single-port QSFP28; PCIe3.0 x16; tall bracket; ROHS R6 Device: /dev/mst/mt4119_pciconf0 Configurations: Next Boot ... NUM_OF_VFS 1 SRIOV_EN True(1) ... After rebooting, run the following: $ ibv_devices device node GUID ------ ---------------- mlx5_0 abcdef0300ghijkl $ echo 1 > /sys/class/net/enp65s0np0/device/sriov_numvfs $ ibv_devices device node GUID ------ ---------------- mlx5_0 abcdef0300ghijkl mlx5_1 0000000000000000 $ ip link ... 4: enp65s0np0: mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000 link/ether ab:cd:ef:gh:ij:kl brd ff:ff:ff:ff:ff:ff vf 0 link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff, spoof checking off, link-state auto, trust off, query_rss off ... 9: enp65s0v0: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether lm:no:pq:rs:tu:vw brd ff:ff:ff:ff:ff:ff permaddr 01:23:45:67:89:01 ... At this point, we can confirm that mlx5_1 has been created. TCP/IP communication using the VF is now possible (once you assign an IP address). However, the VF’s GUID is currently 0. There is a known issue where RDMA doesn’t work in this state: # https://docs.nvidia.com/networking/display/mlnxofedv451010/known+issues 1047616 | Description: When node GUID of a device is set to zero (0000:0000:0000:0000), RDMA_CM user space application may crash. Workaround: Set node GUID to a nonzero value. Keywords: RDMA_CM Set the GUID to a non-zero value. I’m not sure what the correct value should be, but since the physical NIC’s GUID is formed by inserting 0300 in the middle of the MAC address, I followed the same pattern: $ echo lm:no:pq:03:00:rs:tu:vw | sudo tee /sys/class/net/enp65s0np0/device/sriov/0/node $ echo 0000:41:00.1 | sudo tee /sys/bus/pci/drivers/mlx5_core/unbind $ echo 0000:41:00.1 | sudo tee /sys/bus/pci/drivers/mlx5_core/bind $ ibv_devices device node GUID ------ ---------------- mlx5_0 abcdef0300ghijkl mlx5_1 lmnopq0300rstuvw At this point, the SR-IOV VF is created and ready for RDMA. Containers and SR-IOV Now let’s assign the VF we created to a container. The proper approach would be to use SR-IOV CNI plugin2 and Multus3, but this time we’ll do it manually using nerdctl and ip commands. We’ll proceed assuming you already have a Pod running on Kubernetes. First, find the network namespace and make it manageable with the ip command: $ NAME=testtest $ CONTAINERID=$(kubectl get pod $NAME -o json | jq -r '."status"."containerStatuses"[0]."containerID"' | sed 's/containerd:\/\///') $ PID=$(sudo nerdctl --namespace k8s.io inspect $CONTAINERID --format '{{.State.Pid}}') $ NETNSNAME=k8s-$NAME $ sudo ln -s /proc/$PID/ns/net /var/run/netns/$NETNSNAME Placing a file under /var/run/netns makes it accessible to the ip command. Move the VF from the host’s network namespace to the container’s network namespace: $ ip netns k8s-testtest $ VFLINKNAME=enp65s0v0 $ VFLINKADDR=192.168.0.1/24 $ sudo ip link set dev $VFLINKNAME netns $NETNSNAME $ sudo ip -n $NETNSNAME link set dev $VFLINKNAME up $ sudo ip -n $NETNSNAME addr add $VFLINKADDR dev $VFLINKNAME Now the container can directly use the VF. Containers and RDMA To use RDMA from a container, you need to make the devices under /dev/infiniband visible to the container. If the container has privileged access, you can simply mount it from the Pod manifest4. If you don’t want to grant privileges, you need to assign devices to the container using the Kubelet Device API. Using smarter-device-manager5, you can treat devices as Kubernetes resources, similar to CPU and memory. First, add the DaemonSet following the official sample: # https://gitlab.com/arm-research/smarter/smarter-device-manager/-/blob/master/smarter-device-manager-ds.yaml apiVersion: apps/v1 kind: DaemonSet metadata: name: smarter-device-manager namespace: device-manager labels: name: smarter-device-manager role: agent spec: selector: matchLabels: name: smarter-device-manager updateStrategy: type: RollingUpdate template: metadata: labels: name: smarter-device-manager annotations: node.kubernetes.io/bootstrap-checkpoint: "true" spec: priorityClassName: "system-node-critical" hostname: smarter-device-management hostNetwork: true dnsPolicy: ClusterFirstWithHostNet containers: - name: smarter-device-manager image: registry.gitlab.com/arm-research/smarter/smarter-device-manager:v1.1.2 imagePullPolicy: IfNotPresent securityContext: allowPrivilegeEscalation: false capabilities: drop: ["ALL"] resources: limits: cpu: 100m memory: 15Mi requests: cpu: 10m memory: 15Mi volumeMounts: - name: device-plugin mountPath: /var/lib/kubelet/device-plugins - name: dev-dir mountPath: /dev - name: sys-dir mountPath: /sys - name: config mountPath: /root/config volumes: - name: device-plugin hostPath: path: /var/lib/kubelet/device-plugins - name: dev-dir hostPath: path: /dev - name: sys-dir hostPath: path: /sys - name: config configMap: name: smarter-device-manager Next, specify /dev/infiniband in the ConfigMap: apiVersion: v1 kind: ConfigMap metadata: name: smarter-device-manager namespace: device-manager data: conf.yaml: | - devicematch: infiniband nummaxdevices: 20 At this point, the Node manifest looks like this: status: allocatable: cpu: "32" ephemeral-storage: "885012522772" hugepages-1Gi: 8Gi hugepages-2Mi: "0" memory: 123272716Ki pods: "110" smarter-devices/infiniband: "20" capacity: cpu: "32" ephemeral-storage: 960300048Ki hugepages-1Gi: 8Gi hugepages-2Mi: "0" memory: 131763724Ki pods: "110" smarter-devices/infiniband: "20" Then add it as a request in the Pod manifest: spec: containers: - ... resources: limits: smarter-devices/infiniband: 1 requests: smarter-devices/infiniband: 1 Now the container can use RDMA. By the way, when starting a container with nerdctl without Kubernetes, you can simply add the option --device=/dev/infiniband (the same should work for Docker). Performance Benchmarking Using Netperf6, we measured throughput, latency, and CPU time. We also measured connect & close time with custom code. To use RDMA with socket API code, we LD_PRELOADed rsocket7. host-remote-tcp: TCP/IP for host-to-host communication host-remote-roce: RoCEv2 for host-to-host communication flannel-remote-tcp: TCP/IP between containers on different hosts using Flannel as the CNI Plugin sriov-remote-roce: RoCEv2 between containers on different hosts using SR-IOV VFs Detailed descriptions of other conditions are omitted. RoCEv2 outperforms TCP/IP in throughput, latency, and CPU time. On the other hand, connect & close time is significantly higher with RoCEv2. Conclusion Despite various constraints in practical use, RDMA lives up to its reputation. https://enterprise-support.nvidia.com/s/article/howto-configure-sr-iov-for-connect-ib-connectx-4-with-kvm--infiniband-x#jive_content_id_II_Enable_SRIOV_on_the_MLNX_OFED_driver ↩︎ https://github.com/k8snetworkplumbingwg/sriov-cni ↩︎ https://github.com/intel/multus-cni ↩︎ https://github.com/kubernetes/kubernetes/issues/5607#issuecomment-766089905 ↩︎ https://gitlab.com/arm-research/smarter/smarter-device-manager ↩︎ https://github.com/HewlettPackard/netperf ↩︎ https://linux.die.net/man/7/rsocket ↩︎ --- # Kubernetes上のコンテナでRDMAを動かして性能計測してみる - URL: https://www.onoe.dev/blog/rdma-container/ - Language: ja - Published: 2025-03-31 - Tags: Tech, RDMA, Container, Kubernetes, SR-IOV > Kubernetes上のコンテナでSR-IOVを用いてRDMAを動かしたのでハマった内容を含めて方法をメモしておきます。ついでにRDMAとTCP/IPで性能比較もしてみました。 Kubernetes上のコンテナでSR-IOVを用いてRDMAを動かしたのでハマった内容を含めて方法をメモしておきます。 ついでにRDMAとTCP/IPで性能比較もしてみました。 info The English version of this article is available here. 何をやるか SR-IOVのVFを生やす Kubernets上のコンテナにVF (Virtual Function) を割り当てる Kubernets上のコンテナにRDMAのdeviceを追加 RDMAとTCP/IPで性能比較 環境 Mellanox Technologies MT27800 Family [ConnectX-5] (RoCEv2対応の100Gbps Ethernet NIC) で直結された2台のUbuntuマシン (AMD EPYC 7282 16-Core Processor) 上でKubernetesクラスタを構築しています。 Kubernetesはノード間通信にこのNICを使うように設定されています。 コンテナランタイムにはcontainerdを使っています。 SR-IOV PF (Physical Function)から1個のVFを生やします。公式のドキュメント1に従います。 まずはBIOSの設定を見てSR-IOVとIOMMUが有効になっていることを確認します。 次に以下のようにgrubのパラメータを追加して再起動します。 $ cat /etc/default/grub ... GRUB_CMDLINE_LINUX="amd_iommu=on iommu=pt pci=realloc" ... NICのパラメータを変更します。 $ sudo mst start Starting MST (Mellanox Software Tools) driver set Loading MST PCI module - Success Loading MST PCI configuration module - Success Create devices Unloading MST PCI module (unused) - Success $ sudo mst status MST modules: ------------ MST PCI module is not loaded MST PCI configuration module loaded MST devices: ------------ /dev/mst/mt4119_pciconf0 - PCI configuration cycles access. domain:bus:dev.fn=0000:41:00.0 addr.reg=88 data.reg=92 cr_bar.gw_offset=-1 Chip revision is: 00 $ sudo mlxconfig -d /dev/mst/mt4119_pciconf0 q Device #1: ---------- Device type: ConnectX5 Name: MCX515A-CCA_Ax_Bx Description: ConnectX-5 EN network interface card; 100GbE single-port QSFP28; PCIe3.0 x16; tall bracket; ROHS R6 Device: /dev/mst/mt4119_pciconf0 Configurations: Next Boot ... NUM_OF_VFS 0 SRIOV_EN False(0) ... $ sudo mlxconfig -d /dev/mst/mt4119_pciconf0 set SRIOV_EN=1 NUM_OF_VFS=1 ... $ sudo mlxconfig -d /dev/mst/mt4119_pciconf0 q Device #1: ---------- Device type: ConnectX5 Name: MCX515A-CCA_Ax_Bx Description: ConnectX-5 EN network interface card; 100GbE single-port QSFP28; PCIe3.0 x16; tall bracket; ROHS R6 Device: /dev/mst/mt4119_pciconf0 Configurations: Next Boot ... NUM_OF_VFS 1 SRIOV_EN True(1) ... 再起動してから以下を実行します。 $ ibv_devices device node GUID ------ ---------------- mlx5_0 abcdef0300ghijkl $ echo 1 > /sys/class/net/enp65s0np0/device/sriov_numvfs $ ibv_devices device node GUID ------ ---------------- mlx5_0 abcdef0300ghijkl mlx5_1 0000000000000000 $ ip link ... 4: enp65s0np0: mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000 link/ether ab:cd:ef:gh:ij:kl brd ff:ff:ff:ff:ff:ff vf 0 link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff, spoof checking off, link-state auto, trust off, query_rss off ... 9: enp65s0v0: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether lm:no:pq:rs:tu:vw brd ff:ff:ff:ff:ff:ff permaddr 01:23:45:67:89:01 ... この時点でmlx5_1が生えていることが確認できます。 (IPアドレスを割り振れば)VFを使ってTCP/IPでの通信ができるようになっています。 しかし現状はVFのGUIDが0です。この状態だとRDMAが使えないという既知の問題があります。 # https://docs.nvidia.com/networking/display/mlnxofedv451010/known+issues 1047616 | Description: When node GUID of a device is set to zero (0000:0000:0000:0000), RDMA_CM user space application may crash. Workaround: Set node GUID to a nonzero value. Keywords: RDMA_CM GUIDを0以外に設定します。 何の値にするのが正解なのかは分かりませんが、物理NICのGUIDはMACアドレスの間に0300を挟んだものになっていたのでそれに合わせてみます。 $ echo lm:no:pq:03:00:rs:tu:vw | sudo tee /sys/class/net/enp65s0np0/device/sriov/0/node $ echo 0000:41:00.1 | sudo tee /sys/bus/pci/drivers/mlx5_core/unbind $ echo 0000:41:00.1 | sudo tee /sys/bus/pci/drivers/mlx5_core/bind $ ibv_devices device node GUID ------ ---------------- mlx5_0 abcdef0300ghijkl mlx5_1 lmnopq0300rstuvw ここまででSR-IOVのVFを生やしてRDMAが使える状態になりました。 コンテナとSR-IOV 生やしたVFをコンテナに割り当てます。 SR-IOV CNI plugin2とMultus3を使うのが正しい方法だと思いますが、今回はnerdctlとipコマンドを使って手動でやります。 Kubernetesで適当なPodを作っている前提で進めます。 まずはnetwork namespaceの実体を見つけ出してIPコマンドで管理可能な状態にします。 $ NAME=testtest $ CONTAINERID=$(kubectl get pod $NAME -o json | jq -r '."status"."containerStatuses"[0]."containerID"' | sed 's/containerd:\/\///') $ PID=$(sudo nerdctl --namespace k8s.io inspect $CONTAINERID --format '{{.State.Pid}}') $ NETNSNAME=k8s-$NAME $ sudo ln -s /proc/$PID/ns/net /var/run/netns/$NETNSNAME /var/run/netnsの配下にファイルを置くことでipコマンドで操作できるようになります。 ホストのnetwork namespaceにあるVFをコンテナのnetwork namespaceに移します。 $ ip netns k8s-testtest $ VFLINKNAME=enp65s0v0 $ VFLINKADDR=192.168.0.1/24 $ sudo ip link set dev $VFLINKNAME netns $NETNSNAME $ sudo ip -n $NETNSNAME link set dev $VFLINKNAME up $ sudo ip -n $NETNSNAME addr add $VFLINKADDR dev $VFLINKNAME これでコンテナからVFを直接使えるようになりました。 コンテナとRDMA コンテナからRDMAを使うためには/dev/infiniband配下のdeviceをコンテナから見えるようにする必要があります。 コンテナに特権が付いている場合は、Podのmanifestからマウントするだけで使えるようになります4。 特権をつけたくない場合はKubelet Device APIを使ってdeviceをコンテナに割り当てる必要があります。 smarter-device-manager5を使うとdeviceをKubernetes上でCPUやメモリと同様に扱えるようになります。 まず公式のサンプル通りにDaemonSetを追加します。 # https://gitlab.com/arm-research/smarter/smarter-device-manager/-/blob/master/smarter-device-manager-ds.yaml apiVersion: apps/v1 kind: DaemonSet metadata: name: smarter-device-manager namespace: device-manager labels: name: smarter-device-manager role: agent spec: selector: matchLabels: name: smarter-device-manager updateStrategy: type: RollingUpdate template: metadata: labels: name: smarter-device-manager annotations: node.kubernetes.io/bootstrap-checkpoint: "true" spec: priorityClassName: "system-node-critical" hostname: smarter-device-management hostNetwork: true dnsPolicy: ClusterFirstWithHostNet containers: - name: smarter-device-manager image: registry.gitlab.com/arm-research/smarter/smarter-device-manager:v1.1.2 imagePullPolicy: IfNotPresent securityContext: allowPrivilegeEscalation: false capabilities: drop: ["ALL"] resources: limits: cpu: 100m memory: 15Mi requests: cpu: 10m memory: 15Mi volumeMounts: - name: device-plugin mountPath: /var/lib/kubelet/device-plugins - name: dev-dir mountPath: /dev - name: sys-dir mountPath: /sys - name: config mountPath: /root/config volumes: - name: device-plugin hostPath: path: /var/lib/kubelet/device-plugins - name: dev-dir hostPath: path: /dev - name: sys-dir hostPath: path: /sys - name: config configMap: name: smarter-device-manager 次にConfigMapで/dev/infinibandを指定します。 apiVersion: v1 kind: ConfigMap metadata: name: smarter-device-manager namespace: device-manager data: conf.yaml: | - devicematch: infiniband nummaxdevices: 20 この時点でNodeのmanifestが以下のようになります。 status: allocatable: cpu: "32" ephemeral-storage: "885012522772" hugepages-1Gi: 8Gi hugepages-2Mi: "0" memory: 123272716Ki pods: "110" smarter-devices/infiniband: "20" capacity: cpu: "32" ephemeral-storage: 960300048Ki hugepages-1Gi: 8Gi hugepages-2Mi: "0" memory: 131763724Ki pods: "110" smarter-devices/infiniband: "20" あとはPodのmanifestにrequestとして追加します。 spec: containers: - ... resources: limits: smarter-devices/infiniband: 1 requests: smarter-devices/infiniband: 1 これでコンテナからRDMAを使えるようになりました。 ちなみにKubernetesを使わずにnerdctlでコンテナを起動する場合はオプションに--device=/dev/infinibandと書けばよいです(Dockerも同じはず)。 性能計測 Netperf6を使ってスループット・レイテンシ・CPU時間を計測してみます。 また自分で書いたコードでconnect&closeの時間も計測してみます。 ソケットAPIを用いたコードでRDMAを使うためにrsocket7をLD_PRELOADでロードします。 host-remote-tcp: ホスト間通信でTCP/IPを使う host-remote-roce: ホスト間通信でRoCEv2を使う flannel-remote-tcp: CNI PluginとしてFlannelを使って別ホスト上のコンテナ間でTCP/IPを使う sriov-remote-roce: SR-IOVのVFを使って別ホスト上のコンテナ間でRoCEv2を使う その他細かい条件の説明は省略します。 スループット・レイテンシ・CPU時間はTCP/IPよりもRoCEv2の方が優れていますね。 その代わりconnect&closeの時間がかなりかかっていることが分かります。 まとめ 実際に使うには色々と制約があるもののさすがRDMAです。 https://enterprise-support.nvidia.com/s/article/howto-configure-sr-iov-for-connect-ib-connectx-4-with-kvm--infiniband-x#jive_content_id_II_Enable_SRIOV_on_the_MLNX_OFED_driver ↩︎ https://github.com/k8snetworkplumbingwg/sriov-cni ↩︎ https://github.com/intel/multus-cni ↩︎ https://github.com/kubernetes/kubernetes/issues/5607#issuecomment-766089905 ↩︎ https://gitlab.com/arm-research/smarter/smarter-device-manager ↩︎ https://github.com/HewlettPackard/netperf ↩︎ https://linux.die.net/man/7/rsocket ↩︎ --- # マネーフォワード サマーインターン体験記 第二弾「ソフトウェアエンジニアの成長環境」 - URL: https://www.onoe.dev/blog/moneyforward-intern/ - Language: ja - Published: 2025-02-14 - Tags: Internship, MoneyForward, Interview > マネーフォワードで技術広報をしている id:luccafort です。 今回はエンジニアインターンの方へ向けたブログ第二弾として、尾上さんと中森さんのインタビューをお届けします。 本シリーズはマネーフォワードのインターンを体験された方向けにそれぞれテーマを用意して、インタビューした内容をご紹介します。 ※ 外部リンク https://moneyforward-dev.jp/entry/2025/02/14/180000 に移動します --- # Participated in ISUCON14 (6,659 points) - URL: https://www.onoe.dev/en/blog/isucon14/ - Language: en - Published: 2024-12-09 - Tags: Tech, ISUCON, Go > Participated in ISUCON14 (6,659 points) This year, I once again participated in ISUCON14 as team MONOS with Saza and Moririn. We scored 6,659 points, placing 277th overall. Compared to last time (13th overall, 3rd among students), it was quite a disappointing result, but we had a fun day. info この記事の日本語版はこちらです。 GitHub repository: https://github.com/saza-ku/isucon14 ISUCON13 (previous edition): https://onoe.dev/blog/isucon13 info This article is part of the Money Forward Kansai Advent Calendar 2024, published on December 9th. The previous article was by umisora: “Scalebaseを使ってHubSpotのカオス化を防ぎながら、BizOps業務をスマート化したお話”. Before the Contest As a team, we practiced with ISUCON 13, 12 finals, and 9 qualifier. I didn’t get much individual practice this year. As with last time, we used the convenient template Saza had built, which makes setup, deployment, and benchmarking easy to run. In addition to alp, pt-query-digest, pprof, and netdata, this year we also added tracing with OpenTelemetry & Jaeger. Benchmark results are automatically written to GitHub Issues upon completion. Reference: https://github.com/saza-ku/isucon14/issues/66 Contest Day Early Phase This year, the initial setup went very smoothly, and we had the first benchmark completed around 10:30. I started by noticing the high volume of GET /api/chair/notification requests and increased RetryAfterMs from 30ms to 3000ms. The other two added indexes and handled standard optimizations and multi-server setup. [10:29] 643(#2) : Initial quick benchmark(3718092) [10:33] 654(#3) : Initial full benchmark(6fdc857) [10:48] 1717(#5) : Add index on ride_statuses(#6) [10:53] 2961(#7) : Set RetryAfterMs to 3000ms(#8) [10:56] 2019(#9) : Add index on chair_locations(#10) (score likely dropped because #8 wasn’t included) [10:59] 3204(#11) : Add index on rides(#13) [11:02] 3950(#12) : Add index on chairs(f68c81a) [11:17] 4965(#16) : Move MySQL to 2nd server(#13) [11:24] 4837(#18) : Add index on chairs(#19) [11:39] 4796(#20) : Standard optimizations(#21) Middle Phase The distance_table extraction that we had been working on since the morning was completed just after noon. It was related to the top slow query. First, we extracted the subquery that served as the distance_table into a standalone table. Then, at the point of writing to the chair_locations table, we pre-calculate total_distance and total_distance_updated_at. Furthermore, since the endpoint POST /api/chair/coordinate that writes to the chair_locations table allows delayed data propagation, we update the distance_table via batch processing. Moririn noticed that there was only one place writing to the chair_locations table, which made the implementation possible. Count: 164 Time=1.14s (186s) Lock=0.00s (0s) Rows=4.9 (797), isucon[isucon]@isucon1 SELECT id, owner_id, name, access_token, model, is_active, created_at, updated_at, IFNULL(total_distance, N) AS total_distance, total_distance_updated_at FROM chairs LEFT JOIN (SELECT chair_id, SUM(IFNULL(distance, N)) AS total_distance, MAX(created_at) AS total_distance_updated_at FROM (SELECT chair_id, created_at, ABS(latitude - LAG(latitude) OVER (PARTITION BY chair_id ORDER BY created_at)) + ABS(longitude - LAG(longitude) OVER (PARTITION BY chair_id ORDER BY created_at)) AS distance FROM chair_locations) tmp GROUP BY chair_id) distance_table ON distance_table.chair_id = chairs.id WHERE owner_id = 'S' [12:22] 4895(#26) : Remove N+1 in getLatestRideStatus(#27) [12:55] 5750(#35) : Extract distance_table(#22) Late Phase This is where everyone got stuck. Despite having sufficient resources, the number of users wasn’t increasing and load wasn’t building up. While monitoring the benchmarker’s behavior, Saza and Moririn worked on improving the matching algorithm and tuning various parameters to improve user satisfaction, but nothing seemed to work. Implementation branch 1 (WIP): https://github.com/saza-ku/isucon14/tree/fix-matcing Implementation branch 2 (WIP): https://github.com/saza-ku/isucon14/tree/fix-matcing-2 Moririn and I were working on eliminating N+1 queries, but we kept hitting bugs in the implementation. I was working on improving the N+1 in chairPostCoordinate. I managed to implement the batch processing part, but ultimately couldn’t complete the subsequent N+1 elimination. Implementation branch (WIP): https://github.com/saza-ku/isucon14/tree/coordinate-named-exec Judging that eliminating N+1 queries wouldn’t matter unless we could increase the number of users, I dropped the N+1 work and started implementing SSE (Server-Sent Events) for GET /api/app/notification. I managed rides by RideID using channels, pushing to a queue on ride_statuses table updates and popping to return responses. However, this also didn’t work in the end. Implementation branch (WIP): https://github.com/saza-ku/isucon14/tree/notification In the end, our score didn’t improve from the early afternoon onward. It was quite frustrating. We finally removed the measurement tools and got lucky with a benchmark run to finish at 6,659 points. [13:37] 6304(#44): Batch processing for chairPostCoordinate(#45) [17:50] 6659 : Remove measurement tools(30d5b06 ) Reflection I think the matching algorithm was the bottleneck, and unless we could improve it, no other bottlenecks would surface. I’m better at infrastructure-level optimizations, so I realized I’m quite weak in situations where the bottleneck doesn’t manifest as resource shortage but rather as user satisfaction and benchmarker behavior. Understanding user and benchmarker behavior is very important, so I want to improve in this area going forward. Closing Thoughts I had a great time again this year. Starting next year, we’ll no longer be a student team, but we want to keep pushing forward. --- # ISUCON14に参加しました(6659点) - URL: https://www.onoe.dev/blog/isucon14/ - Language: ja - Published: 2024-12-09 - Tags: Tech, ISUCON, Go > ISUCON14に参加しました(6659点) 今年もSaza, MoririnとチームMONOSとしてISUCON14に参加しました。 結果は6659点で総合277位でした。 前回(総合13位 学生3位)と比べるとかなり悔しい結果になりましたが、一日楽しめました。 info The English version of this article is available here. GitHubレポジトリ: https://github.com/saza-ku/isucon14 ISUCON13(前回):https://onoe.dev/blog/isucon13 info 本記事はMoney Forward Kansai Advent Calender 2024 12月9日の記事です。前回の記事はumisoraさんの「Scalebaseを使ってHubSpotのカオス化を防ぎながら、BizOps業務をスマート化したお話」でした。 前日まで チームでの練習として13・12本戦・9予選をやっていました。 今年は自分一人での練習はあまりできませんでした。 前回と同様にSazaが作ってくれた便利なテンプレートを使っていて、セットアップやデプロイ、計測までを簡単に実行できるようになっています。 alp, pt-query-digest, pprof, netdataに加えて今年はOpenTelemetry&Jaegerによるトレーシングもできるようになりました。 ベンチマークが終了するとIssueに全ての結果が書き込まれるようになっています。 参考: https://github.com/saza-ku/isucon14/issues/66 当日 序盤 今年は初動のセットアップがかなりスムーズに進み、10:30頃には初期計測が完了しました。 自分は最初にGET /api/chair/notificationのリクエストの多さを見て、RetryAfterMsを30msから3000msに伸ばしました。 他2人はIndexを貼ったり、秘伝のタレや複数台構成をやったりしてくれました。 [10:29] 643(#2) : 初動の簡易計測(3718092) [10:33] 654(#3) : 初動のフル計測(6fdc857) [10:48] 1717(#5) : ride_statusesのindex追加(#6) [10:53] 2961(#7) : RetryAfterMsを3000msに(#8) [10:56] 2019(#9) : chair_locationsのindex追加(#10) (おそらく#8が入っていないため点数が下がっている) [10:59] 3204(#11) : ridesのindex追加(#13) [11:02] 3950(#12) : chairsのindex追加 (f68c81a) [11:17] 4965(#16) : MySQLを2台目に(#13) [11:24] 4837(#18) : chairsのindex追加(#19) [11:39] 4796(#20) : 秘伝のタレ(#21) 中盤 午前からずっと時間をかけていたdistance_tableの切り分けが昼過ぎに完了しました。 一番上にあったスロークエリに関連するものです。 まずdistance_tableとなっているサブクエリを丸ごと切り出して一つのテーブルにします。 そしてchair_locationsテーブルへの書き込みをする時点で、total_distanceとtotal_distance_updated_atを計算しておくようにします。 さらにchair_locationsテーブルへの書き込みをするエンドポイントであるPOST /api/chair/coordinateはデータ反映の遅延が許されているため、バッチ処理でdistance_tableを更新するようにします。 chair_locationsテーブルへの書き込みが1箇所しかないということにMoririnが気づいてくれて実装することができました。 Count: 164 Time=1.14s (186s) Lock=0.00s (0s) Rows=4.9 (797), isucon[isucon]@isucon1 SELECT id, owner_id, name, access_token, model, is_active, created_at, updated_at, IFNULL(total_distance, N) AS total_distance, total_distance_updated_at FROM chairs LEFT JOIN (SELECT chair_id, SUM(IFNULL(distance, N)) AS total_distance, MAX(created_at) AS total_distance_updated_at FROM (SELECT chair_id, created_at, ABS(latitude - LAG(latitude) OVER (PARTITION BY chair_id ORDER BY created_at)) + ABS(longitude - LAG(longitude) OVER (PARTITION BY chair_id ORDER BY created_at)) AS distance FROM chair_locations) tmp GROUP BY chair_id) distance_table ON distance_table.chair_id = chairs.id WHERE owner_id = 'S' [12:22] 4895(#26) : getLatestRideStatusのN+1削除(#27) [12:55] 5750(#35) : distance_tableを切り分け(#22) 終盤 ここから全員が詰まります。 各種リソースは足りているのに、ユーザー数が増えず負荷がかからない状態になりました。 ベンチマーカーの挙動を見ながらユーザーの評価を向上させるべく、SazaとMoririnがマッチングアルゴリズムの改善や各種パラメータの調整に取り組んでくれましたが、どれもうまくいかない状態でした。 実装ブランチ1(WIP): https://github.com/saza-ku/isucon14/tree/fix-matcing 実装ブランチ2(WIP): https://github.com/saza-ku/isucon14/tree/fix-matcing-2 自分とMoririnはN+1の解消を進めていましたが、実装をバグらせてうまくいかない状態でした。 自分はchairPostCoordinateのN+1改善を進めていました。batch処理までは実装できたのですが、その後のN+1の改善は最終的に実装できませんでした。 実装ブランチ(WIP): https://github.com/saza-ku/isucon14/tree/coordinate-named-exec N+1を解消してもユーザーが増えない限り意味がないという判断で、自分はN+1解消を中断してGET /api/app/notificationにおけるSSE(Server-Sent Events)の実装に取り掛かりました。RideIDごとにチャネルで管理し、ride_statusesテーブルの更新時にキューにPush、Popしてレスポンスを返すように実装しました。しかしこれも最終的にはうまく動きませんでした。 実装ブランチ(WIP): https://github.com/saza-ku/isucon14/tree/notification 結局昼過ぎからスコアは伸びませんでした。かなり苦しかったです。 最終的に計測ツールを削除してからガチャを回して6659点になりました。 [13:37] 6304(#44): chairPostCoordinateのbatch処理(#45) [17:50] 6659 : 計測ツール削除(30d5b06 ) 反省 マッチングアルゴリズムがボトルネックになっていて、それを改善できないと他のボトルネックが現れない状態だったのかなと思います。 自分はインフラ周りの改善の方が得意なので、ボトルネックがリソース不足として現れずにユーザーの評価・ベンチマーカーの挙動として現れるという状況はかなり苦手なんだなと分かりました。 ユーザー・ベンチマーカーの挙動を把握できるようにするというのはとても大事なので今後改善していきたいです。 終わりに 今年も楽しめました。来年からは学生チームではなくなりますが、また頑張りたいと思います。 --- # Building a Home VM Infrastructure with KubeVirt - URL: https://www.onoe.dev/en/blog/kubevirt/ - Language: en - Published: 2024-06-15 - Tags: Tech, VM, Kubernetes, KubeVirt, Network, Ubuntu, Container Overlay Network > KubeVirt is a tool for managing VM infrastructure. It manages VMs on Kubernetes in the same way as containers. I tried KubeVirt to easily spin up VMs at home, so I'll share the method and my impressions. KubeVirt is a tool for managing VM infrastructure. With KubeVirt, you can manage VMs on Kubernetes in the same way as containers. I tried KubeVirt to easily spin up VMs at home, so I’ll share the method and my impressions. info この記事の日本語版はこちらです。 What is KubeVirt? When you describe VMs as manifests, KubeVirt’s Controller creates the VMs for you. The VMs exist on the same network as containers, so you can manage communication with containers and access control using Kubernetes mechanisms. A CLI tool called virtctl (kubectl virt) is provided, which allows you to start and stop VMs, and connect to VMs via ssh, console, vnc, etc. There is also a subproject called Containerized Data Importer (CDI). It provides a DataVolume resource that abstracts PersistentVolumeClaim (PVC), enabling you to download VM images and clone DataVolumes for use when starting VMs. For architecture details, this slide deck is very helpful, so I’ll leave the details to that resource. Environment There are two nodes, both with virtualization features enabled. All manifests on my home Kubernetes cluster are managed in my-k8s-cluster and deployed with ArgoCD. By following the README to create a cluster, you should be able to create nearly the same environment (except for IP addresses and domains). Links to the code referenced in this article are also included for your reference. NFS Server As preparation, we need to set up a StorageClass and PersistentVolume (PV) for storing VM data. This time, we’ll set up an NFS server on one of the nodes and make it available as a StorageClass using the NFS CSI driver for Kubernetes. Run the following on the node: sudo apt install nfs-kernel-server sudo mkdir -p /export/nfs sudo chmod 777 /export/nfs cat << EOF >> /etc/exports /export/nfs 192.168.0.0/24(rw,no_root_squash,no_subtree_check) EOF sudo systemctl enable nfs-blkmap.service --now sudo exportfs -a 192.168.0.0/24 is the network where the nodes reside. Then apply the following manifest: # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/storage-class/mandoloncello-nfs.yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: mandoloncello-nfs provisioner: nfs.csi.k8s.io parameters: server: mandoloncello.node.internal.onoe.dev # Node address share: /export/nfs mountPermissions: "777" reclaimPolicy: Retain volumeBindingMode: Immediate mountOptions: - nfsvers=4.2 allowVolumeExpansion: true Since Dynamic Volume Provisioning is enabled, there’s no need to prepare PVs manually. When there’s a PVC waiting to be bound, the Driver creates a PV and binds it. Multus Since we want to connect VMs not only to the container network but also to the host network, we’ll set up Multus, a Meta CNI Plugin. With Multus, you can attach multiple NICs to a container. KubeVirt natively supports Multus, allowing you to attach multiple NICs to VMs as well. First, create a bridge called br0 on all nodes (reference: Creating a bridge-connected VM with KVM). Then apply Multus following the official instructions, and also apply a NetworkAttachmentDefinition for br0. # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/multus/bridge.yaml apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition metadata: name: underlay-bridge namespace: kube-public spec: config: | { "cniVersion": "0.3.1", "name": "underlay-bridge", "type": "bridge", "bridge": "br0", "ipam": { "type": "host-local", "subnet": "192.168.0.0/24" } } As an example, let’s add the following annotation to an arbitrary nginx Pod and apply it: # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/example/nginx.yaml#L16-L25 annotations: k8s.v1.cni.cncf.io/networks: | [ { "name": "underlay-bridge", "namespace": "kube-public", "interface": "eth1", "ips": [ "192.168.0.171" ] } ] When you enter the nginx Pod and check the IP addresses, you’ll see the following: $ kubectl exec -it nginx-55bb7d4dbd-n4blx -- /bin/bash root@nginx-55bb7d4dbd-n4blx:/# apt update && apt install iproute2 ... root@nginx-55bb7d4dbd-n4blx:/# ip a 1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: eth0@if34: mtu 1450 qdisc noqueue state UP group default qlen 1000 link/ether fe:ee:b0:c3:79:f5 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.10.141.152/32 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::fcee:b0ff:fec3:79f5/64 scope link valid_lft forever preferred_lft forever 3: eth1@if35: mtu 1500 qdisc noqueue state UP group default link/ether aa:3a:8d:5c:bf:c1 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 192.168.0.171/24 brd 192.168.0.255 scope global eth1 valid_lft forever preferred_lft forever inet6 2400:2650:8022:3c00:a83a:8dff:fe5c:bfc1/64 scope global dynamic mngtmpaddr valid_lft 293sec preferred_lft 293sec inet6 fe80::a83a:8dff:fe5c:bfc1/64 scope link valid_lft forever preferred_lft forever eth0@if34 (10.10.141.152) is from the regular container network. In addition, there’s eth1@if35 (192.168.0.171), which is the host network NIC. The same can be done for VMs. Installing KubeVirt and CDI Apply KubeVirt following the official instructions. The configuration manifest (kubevirt.io/v1.KubeVirt) has been partially modified as follows: # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/kubevirt/kubevirt-cr.yaml apiVersion: kubevirt.io/v1 kind: KubeVirt metadata: name: kubevirt namespace: kubevirt spec: configuration: network: permitBridgeInterfaceOnPodNetwork: false developerConfiguration: featureGates: - ExpandDisks imagePullPolicy: IfNotPresent The changes from the defaults are permitBridgeInterfaceOnPodNetwork: false and ExpandDisks. Both will be explained later. CDI is also applied following the official instructions. The configuration manifest (cdi.kubevirt.io/v1beta1.CDI) is as follows: # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/kubevirt/cdi-cr.yaml apiVersion: cdi.kubevirt.io/v1beta1 kind: CDI metadata: name: cdi spec: config: podResourceRequirements: limits: cpu: '1' memory: 5Gi imagePullPolicy: IfNotPresent infra: nodeSelector: kubernetes.io/os: linux tolerations: - key: CriticalAddonsOnly operator: Exists workload: nodeSelector: kubernetes.io/os: linux The change from the defaults is config.podResourceRequirements. The default limits were too small, causing OOMKills during VM image downloads, so they were increased. Downloading the VM Image Before creating a VM, apply a DataVolume to download the VM image: # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/playground/vm-image.yaml apiVersion: cdi.kubevirt.io/v1beta1 kind: DataVolume metadata: name: ubuntu-image-2404 spec: storage: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi storageClassName: mandoloncello-nfs source: http: url: https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img We’re using Ubuntu 24.04 (noble) this time. I initially tried using an ISO file, but booting didn’t work properly, so I’m using a cloud image (img file) instead. By the way, KubeVirt also provides image files as Container Disks. We won’t use it this time, but you can create a VM from a Container Disk as follows: # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/example/vm.yaml#L44 volumes: - name: containerdisk containerDisk: image: quay.io/containerdisks/ubuntu:22.04 Creating the VM Now let’s apply the manifest that defines the VM. The full manifest is here, but since it’s long, I’ll explain it section by section. DataVolume We clone the DataVolume for the VM image we created earlier and use it for VM creation. While you could define another DataVolume separately, DataVolumes can be described as templates within the VM manifest. You can clone as follows: # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/playground/vm-pg-1.yaml#L9-L23 dataVolumeTemplates: - metadata: name: vm-pg-1 spec: storage: accessModes: - ReadWriteOnce resources: requests: storage: 64Gi storageClassName: mandoloncello-nfs source: pvc: name: ubuntu-image-2404 namespace: playground This is where the ExpandDisks setting comes into play. The DataVolume for the VM image is set to 5 GiB, while this DataVolume is set to 64 GiB. The PVC requests 64 GiB, but the VM that runs on it can only see 5 GiB. By enabling ExpandDisks, the VM can see the full 64 GiB. Resource Configure the CPU and Memory for the VM. The CPU and Memory specified here are what the VM sees. # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/playground/vm-pg-1.yaml#L30-L34 domain: cpu: cores: 8 memory: guest: 8Gi You can also set requests and limits in a separate section, just like regular Pods. These are used for VM scheduling and don’t represent the actual resources visible to the VM. The values of domain.cpu and domain.memory must be between the requests and limits. If domain.cpu and domain.memory are not set, the resources.requests values will be visible to the VM instead. # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/playground/vm-pg-1.yaml#L55-L61 resources: requests: cpu: 500m memory: 512Mi limits: cpu: '8' memory: 8Gi Volume Configure the disks as follows: # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/playground/vm-pg-1.yaml#L36-L45 disks: - disk: bus: virtio name: disk0 bootOrder: 1 - cdrom: bus: sata readonly: true name: cloudinitdisk bootOrder: 2 The actual backing storage is configured as follows: # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/playground/vm-pg-1.yaml#L68-L72 volumes: - name: disk0 persistentVolumeClaim: claimName: vm-pg-1 - cloudInitNoCloud: The first disk, disk0, is the PVC from earlier. The second, cloudinitdisk, is used for CloudInit. It is configured as follows: # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/playground/vm-pg-1.yaml#L74-L86 userData: | #cloud-config hostname: vm-pg-1 users: - name: onoe ssh_import_id: gh:hiroyaonoe lock_passwd: false passwd: $6$salt$IxDD3jeSOb5eB1CX5LBsqZFVkJdido3OUILO5Ifz5iwMuTS4XMS130MTSuDDl3aCI6WouIL9AjRbLCelDCy.g. shell: /bin/bash sudo: ALL=(ALL) NOPASSWD:ALL uid: 1000 ssh_pwauth: true disable_root: false Network As explained earlier, in addition to the regular container network, we connect to the host network using Multus. In addition to the manifest configuration, we also use CloudInit for setup. # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/playground/vm-pg-1.yaml#L46-L52 interfaces: - name: default masquerade: {} bootOrder: 3 - name: underlay bridge: {} bootOrder: 4 # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/playground/vm-pg-1.yaml#L62-L67 networks: - name: default pod: {} - name: underlay multus: networkName: kube-public/underlay-bridge # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/playground/vm-pg-1.yaml#L87-L95 networkData: | version: 2 ethernets: enp1s0: dhcp4: true enp2s0: dhcp4: false addresses: [192.168.0.162/24] gateway4: 192.168.0.1 The first interface, default (enp1s0), is the container network. It connects to the container network through a virt-launcher Pod that is created when the VM starts. In masquerade mode, the VM is on the same network as virt-launcher (separate from the container network, defaulting to 10.0.2.0/24). The VM receives an IP address from virt-launcher via DHCP. Communication between the VM and the container network is achieved through NAT by virt-launcher. If masquerade mode is not used, some CNI Plugins may not be able to communicate properly, and the setting to enforce masquerade mode is permitBridgeInterfaceOnPodNetwork: false. The second interface, underlay (enp2s0), is the host network. It is associated with the NetworkAttachmentDefinition we created earlier (namespace: kube-public, name: underlay-bridge). The address is statically assigned using CloudInit. Starting the VM Once the DataVolume download and clone are complete, setting running: true will create a VirtualMachineInstance. A virt-launcher Pod is also created. This Pod uses libvirtd and qemu to create the actual VM. As explained earlier, virt-launcher also manages the VM’s network. Let’s actually connect to the VM. There are several methods including console, vnc, and ssh. Here we’ll use ssh. There are also several ways to SSH in. The first is using virtctl with virtctl ssh vm-pg-1. The second is SSHing through the host network. The third is exposing port 22 as a NodePort Service and SSHing through the container network. virtctl is generally the easiest, but if you want to use specific SSH options, the second or third method is better. Let’s connect to the VM and check various things: onoe@vm-pg-1:~$ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 39 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 8 On-line CPU(s) list: 0-7 ... onoe@vm-pg-1:~$ free -h total used free shared buff/cache available Mem: 7.8Gi 520Mi 7.2Gi 1.1Mi 300Mi 7.2Gi Swap: 0B 0B 0B onoe@vm-pg-1:~$ df -h Filesystem Size Used Avail Use% Mounted on tmpfs 794M 1.1M 793M 1% /run /dev/vda1 61G 1.5G 60G 3% / tmpfs 3.9G 0 3.9G 0% /dev/shm tmpfs 5.0M 0 5.0M 0% /run/lock /dev/vda16 881M 61M 758M 8% /boot /dev/vda15 105M 6.1M 99M 6% /boot/efi tmpfs 794M 12K 794M 1% /run/user/1000 onoe@vm-pg-1:~$ ip a 1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host noprefixroute valid_lft forever preferred_lft forever 2: enp1s0: mtu 1450 qdisc pfifo_fast state UP group default qlen 1000 link/ether b6:29:60:82:0b:eb brd ff:ff:ff:ff:ff:ff inet 10.0.2.2/24 metric 100 brd 10.0.2.255 scope global dynamic enp1s0 valid_lft 86301118sec preferred_lft 86301118sec inet6 fe80::b429:60ff:fe82:beb/64 scope link valid_lft forever preferred_lft forever 3: enp2s0: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 62:d0:72:71:e8:f5 brd ff:ff:ff:ff:ff:ff inet 192.168.0.162/24 brd 192.168.0.255 scope global enp2s0 valid_lft forever preferred_lft forever inet6 2400:2650:8022:3c00:60d0:72ff:fe71:e8f5/64 scope global dynamic mngtmpaddr noprefixroute valid_lft 297sec preferred_lft 297sec inet6 fe80::60d0:72ff:fe71:e8f5/64 scope link valid_lft forever preferred_lft forever Everything is configured correctly. Impressions of Using KubeVirt One advantage of KubeVirt is the ability to run VMs on the container network. However, when migrating an existing VM infrastructure to Kubernetes, rather than migrating VMs as-is to KubeVirt on Kubernetes, it might be easier in terms of both migration cost and future management cost to simply replace VMs with containers and run them on Kubernetes. KubeVirt may be valuable for use cases where VMs are absolutely necessary, but if the number of such VMs is small, building individual networks might be sufficient. Another advantage is managing VMs as Infrastructure as Code. While this is also achievable with tools like Terraform, it’s convenient to manage VMs using the familiar Kubernetes framework. However, this isn’t limited to KubeVirt – the same applies to PVs, StatefulSets, etc. – managing stateful resources in Kubernetes, which uses declarative configuration management, can be quite challenging. Kubernetes aims to converge to the desired state through reconciliation, but it doesn’t guarantee that the resource actually exists. This might not be an issue with proper management, but personally it feels like it could be painful. My impressions turned out somewhat negative, but I haven’t used OpenStack or similar tools, and those who operate large-scale VM infrastructure in production environments might have different opinions. Conclusion Working with KubeVirt, NFS, Multus, and related technologies was a great learning experience. While it’s overkill for a home VM infrastructure, it’s fun, so I plan to keep running it. --- # KubeVirtを使って自宅VM基盤を構築する - URL: https://www.onoe.dev/blog/kubevirt/ - Language: ja - Published: 2024-06-15 - Tags: Tech, VM, Kubernetes, KubeVirt, Network, Ubuntu, Container Overlay Network > VM基盤を管理するツールとして、KubeVirtがあります。KubeVirtはKubernetes上でコンテナと同じようにVMを管理します。自宅で簡単にVMを立てられるようにするためにKubeVirtを試してみたので、方法と感想をお伝えします。 VM基盤を管理するツールとして、KubeVirtがあります。KubeVirtを使うとKubernetes上でコンテナと同じようにVMを管理できます。自宅で簡単にVMを立てられるようにするためにKubeVirtを試してみたので、方法と感想をお伝えします。 info The English version of this article is available here. KubeVirtとは VMをmanifestsとして記述すると、KubeVirtのControllerが良い感じにVMを作成してくれます。この時VMはコンテナと同じネットワーク上に存在するので、コンテナとの通信やアクセス制御などもKubernetesの仕組みに基づいて管理できます。 virtctl(kubectl virt)というCLIツールが提供されており、これを用いてVMをstart, stopしたり、ssh, console, vncなどでVMに接続したりできます。 またContainerized Data Importer(CDI)というサブプロジェクトがあります。DataVolumeというPersistentVolumeClaim(PVC)を抽象化したリソースによってVMイメージをダウンロードしたり、さらにDataVolumeをCloneしてVM起動時に使えるようにしたりできます。 アーキテクチャなどについてはこのスライドがとても分かりやすかったので詳しくはこちらにお任せします。 環境 ノードは2つあり、どちらとも仮想化機能が有効化されている状態です。 自宅のKubernetes上のmanifestは全てmy-k8s-clusterで管理しており、ArgoCDでデプロイしています。READMEを見ながらクラスタを作成すれば、(IPアドレスやドメインを除いて)ほぼ同じ環境を作れるはずです。この記事に記載しているコードへのリンクも記載しているので参考にご覧ください。 NFSサーバー 準備として、VMのデータを保存するためのStorageClassとPersistentVolume(PV)を用意します。 今回はノードの一つにNFSサーバーを用意し、NFS CSI driver for Kubernetesを用いてStorageClassとして使えるようにします。 ノード上で以下を実行します。 sudo apt install nfs-kernel-server sudo mkdir -p /export/nfs sudo chmod 777 /export/nfs cat << EOF >> /etc/exports /export/nfs 192.168.0.0/24(rw,no_root_squash,no_subtree_check) EOF sudo systemctl enable nfs-blkmap.service --now sudo exportfs -a 192.168.0.0/24はノードのあるネットワークです。 そして以下のmanifestをapplyします。 # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/storage-class/mandoloncello-nfs.yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: mandoloncello-nfs provisioner: nfs.csi.k8s.io parameters: server: mandoloncello.node.internal.onoe.dev # ノードのアドレス share: /export/nfs mountPermissions: "777" reclaimPolicy: Retain volumeBindingMode: Immediate mountOptions: - nfsvers=4.2 allowVolumeExpansion: true Dynamic Volume Provisioningが有効なので、PVは用意しなくても大丈夫です。Bind待ちのPVCがあるとDriverがPVを作成してBindしてくれます。 Multus 今回はVMをコンテナネットワークだけでなくホストネットワークにも接続したいため、Meta CNI PluginであるMultusを用意します。Multusを使えば、コンテナに複数のNICをattachすることが出来ます。KubeVirtはmultusをネイティブにサポートしており、VMにも同様に複数のNICをattachすることが出来ます。 まず全てのノードでbr0というブリッジを作成します(参考: KVMでホストとブリッジ接続したVMを作成する)。そして 公式の手順に基づいてMultusをapplyし、br0に対応したNetworkAttachmentDefinitionもapplyします。 # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/multus/bridge.yaml apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition metadata: name: underlay-bridge namespace: kube-public spec: config: | { "cniVersion": "0.3.1", "name": "underlay-bridge", "type": "bridge", "bridge": "br0", "ipam": { "type": "host-local", "subnet": "192.168.0.0/24" } } ここで例として適当なnginx Podに以下のannotationを追加してapplyします。 # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/example/nginx.yaml#L16-L25 annotations: k8s.v1.cni.cncf.io/networks: | [ { "name": "underlay-bridge", "namespace": "kube-public", "interface": "eth1", "ips": [ "192.168.0.171" ] } ] nginx Podに入ってIPアドレスを見てみると以下のようになっています。 $ kubectl exec -it nginx-55bb7d4dbd-n4blx -- /bin/bash root@nginx-55bb7d4dbd-n4blx:/# apt update && apt install iproute2 ... root@nginx-55bb7d4dbd-n4blx:/# ip a 1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: eth0@if34: mtu 1450 qdisc noqueue state UP group default qlen 1000 link/ether fe:ee:b0:c3:79:f5 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.10.141.152/32 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::fcee:b0ff:fec3:79f5/64 scope link valid_lft forever preferred_lft forever 3: eth1@if35: mtu 1500 qdisc noqueue state UP group default link/ether aa:3a:8d:5c:bf:c1 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 192.168.0.171/24 brd 192.168.0.255 scope global eth1 valid_lft forever preferred_lft forever inet6 2400:2650:8022:3c00:a83a:8dff:fe5c:bfc1/64 scope global dynamic mngtmpaddr valid_lft 293sec preferred_lft 293sec inet6 fe80::a83a:8dff:fe5c:bfc1/64 scope link valid_lft forever preferred_lft forever eth0@if34(10.10.141.152)が通常のコンテナネットワークのものです。それに加えてeth1@if35(192.168.0.171)というNICがあります。これがホストネットワークのものになります。 VMでも同様のことが出来ます。 KubeVirtとCDIのインストール KubeVirtを公式の手順に基づいてapplyします。 設定manifest(kubevirt.io/v1.KubeVirt)は一部以下のように変更しています。 # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/kubevirt/kubevirt-cr.yaml apiVersion: kubevirt.io/v1 kind: KubeVirt metadata: name: kubevirt namespace: kubevirt spec: configuration: network: permitBridgeInterfaceOnPodNetwork: false developerConfiguration: featureGates: - ExpandDisks imagePullPolicy: IfNotPresent デフォルトからの変更点はpermitBridgeInterfaceOnPodNetwork: falseとExpandDisksです。両方とも後述します。 CDIも公式の手順に基づいてapplyします。 設定manifest(cdi.kubevirt.io/v1beta1.CDI)は以下のとおりです。 # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/kubevirt/cdi-cr.yaml apiVersion: cdi.kubevirt.io/v1beta1 kind: CDI metadata: name: cdi spec: config: podResourceRequirements: limits: cpu: '1' memory: 5Gi imagePullPolicy: IfNotPresent infra: nodeSelector: kubernetes.io/os: linux tolerations: - key: CriticalAddonsOnly operator: Exists workload: nodeSelector: kubernetes.io/os: linux デフォルトからの変更点はconfig.podResourceRequirementsです。デフォルトのlimitだと値が小さすぎてVMイメージのダウンロード時にOOMKillされてしまったので変更しています。 VMイメージのダウンロード VMを作成する前にVMイメージをダウンロードするDataVolumeをapplyします。 # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/playground/vm-image.yaml apiVersion: cdi.kubevirt.io/v1beta1 kind: DataVolume metadata: name: ubuntu-image-2404 spec: storage: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi storageClassName: mandoloncello-nfs source: http: url: https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img 今回はUbuntu24.04(noble)を使います。最初はisoファイルを使ってみたのですがbootがうまくいかないのでcloud image(imgファイル)を使います。 ちなみにですがKubeVirtがimageファイルをContainer Diskとしても提供しています。 今回は使いませんが以下のようにContainer DiskからVMを作成することができます。 # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/example/vm.yaml#L44 volumes: - name: containerdisk containerDisk: image: quay.io/containerdisks/ubuntu:22.04 VMの作成 ここからVMを定義するmanifestをapplyします。全体像はこちらですが長いので順番に解説していきます。 DataVolume 先ほど作成したVMイメージのためのDataVolumeをCloneしてVM作成に使います。もう一つDataVolumeを定義しても良いのですが、DataVolumeはVMのmanifestにtemplateとして記述できます。以下のように記述すればCloneできます。 # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/playground/vm-pg-1.yaml#L9-L23 dataVolumeTemplates: - metadata: name: vm-pg-1 spec: storage: accessModes: - ReadWriteOnce resources: requests: storage: 64Gi storageClassName: mandoloncello-nfs source: pvc: name: ubuntu-image-2404 namespace: playground ここで先ほどの設定ExpandDisksが生きてきます。VMイメージのためのDataVolumeは5GiBに設定されている一方で、このDataVolumeは64GiBに設定されています。この時PVCとしては64GiB要求されますが、後に実行されたVMからは5GiBしか見えません。ExpandDisksを設定することによってVMからも64GiB見えるようになります。 Resource VMが使うCPU, Memoryを設定します。ここで指定したCPU, MemoryがVMから見えます。 # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/playground/vm-pg-1.yaml#L30-L34 domain: cpu: cores: 8 memory: guest: 8Gi また別の箇所で以下のように通常のPodと同じようにrequest, limitも設定できます。これはVMのスケジューリングに使われるものであり、実際にVMから見えるリソースではありません。domain.cpu, domain.memoryの値はrequestsとlimitsの中間である必要があります。ちなみにdomain.cpu, domain.memoryが設定されていないと、resources.requestsの値が代わりにVMから見えます。 # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/playground/vm-pg-1.yaml#L55-L61 resources: requests: cpu: 500m memory: 512Mi limits: cpu: '8' memory: 8Gi Volume 以下のようにDiskを設定します。 # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/playground/vm-pg-1.yaml#L36-L45 disks: - disk: bus: virtio name: disk0 bootOrder: 1 - cdrom: bus: sata readonly: true name: cloudinitdisk bootOrder: 2 実体は以下のように設定します。 # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/playground/vm-pg-1.yaml#L68-L72 volumes: - name: disk0 persistentVolumeClaim: claimName: vm-pg-1 - cloudInitNoCloud: 1つ目のdisk0は先ほどのPVCです。2つ目のcloudinitdiskはCloudInitのために使用します。ここでは以下のように設定しています。 # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/playground/vm-pg-1.yaml#L74-L86 userData: | #cloud-config hostname: vm-pg-1 users: - name: onoe ssh_import_id: gh:hiroyaonoe lock_passwd: false passwd: $6$salt$IxDD3jeSOb5eB1CX5LBsqZFVkJdido3OUILO5Ifz5iwMuTS4XMS130MTSuDDl3aCI6WouIL9AjRbLCelDCy.g. shell: /bin/bash sudo: ALL=(ALL) NOPASSWD:ALL uid: 1000 ssh_pwauth: true disable_root: false Network 先ほど説明した通り、通常のコンテナネットワークに加え、Multusを用いてホストネットワークにも接続します。manifestの記述に加えてCloudInitを用いた設定もします。 # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/playground/vm-pg-1.yaml#L46-L52 interfaces: - name: default masquerade: {} bootOrder: 3 - name: underlay bridge: {} bootOrder: 4 # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/playground/vm-pg-1.yaml#L62-L67 networks: - name: default pod: {} - name: underlay multus: networkName: kube-public/underlay-bridge # https://github.com/hiroyaonoe/my-k8s-cluster/blob/30daa0da2767d6f4b490b781a1b3f119dd1ac427/argocd/manifests/playground/vm-pg-1.yaml#L87-L95 networkData: | version: 2 ethernets: enp1s0: dhcp4: true enp2s0: dhcp4: false addresses: [192.168.0.162/24] gateway4: 192.168.0.1 1つ目のdefault(enp1s0)がコンテナネットワークです。VM実行時に作成されるvirt-launcherというPodを通じてコンテナネットワークと繋がります。masquaradeモードの場合、VMはvirt-launcherと同じネットワーク(コンテナネットワークとは別でありデフォルトは10.0.2.0/24)にいます。VMはDHCPでvirt-launcherからIPアドレスを受け取ります。VMとコンテナネットワーク間の通信はvirt-launcherがNATすることで実現します。ここでmasquaradeモードにしない場合、CNI Pluginによってはうまく通信できない可能性があるらしく、masquaradeモードを強制する設定がpermitBridgeInterfaceOnPodNetwork: falseです。 2つ目のunderlay(enp2s0)がホストネットワークです。先ほど作成したNetworkAttachmentDefinition(namespaceがkube-publicでnameがunderlay-bridge)に関連づけます。CloudInitを用いて静的にアドレスを決定しています。 起動 DataVolumeのダウンロードやCloneが完了した状態でrunning: trueになっていればVirtualMachineInstanceが作成されます。またvirt-launcherというPodが作成されます。このPodがlibvirtdやqemuを使って実際のVMを作成します。virt-launcherは先ほど説明した通りVMのネットワークも管理します。 実際にVMに入ってみます。console, vnc, sshなどいくつか方法がありますが、ここではsshを使います。sshするにもいくつか方法があります。1つ目はvirtctlを用いる方法でvirtctl ssh vm-pg-1で接続できます。2つ目はホストネットワークを通じてsshする方法です。3つ目は22番ポートをNodePort Serviceとして公開し、コンテナネットワークを通じてsshする方法です。基本的にはvirtctlが楽ですが、細かいsshのオプションを付けたいなら2つ目か3つ目が良いでしょう。 実際にVMに入って色々確認します。 onoe@vm-pg-1:~$ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 39 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 8 On-line CPU(s) list: 0-7 ... onoe@vm-pg-1:~$ free -h total used free shared buff/cache available Mem: 7.8Gi 520Mi 7.2Gi 1.1Mi 300Mi 7.2Gi Swap: 0B 0B 0B onoe@vm-pg-1:~$ df -h Filesystem Size Used Avail Use% Mounted on tmpfs 794M 1.1M 793M 1% /run /dev/vda1 61G 1.5G 60G 3% / tmpfs 3.9G 0 3.9G 0% /dev/shm tmpfs 5.0M 0 5.0M 0% /run/lock /dev/vda16 881M 61M 758M 8% /boot /dev/vda15 105M 6.1M 99M 6% /boot/efi tmpfs 794M 12K 794M 1% /run/user/1000 onoe@vm-pg-1:~$ ip a 1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host noprefixroute valid_lft forever preferred_lft forever 2: enp1s0: mtu 1450 qdisc pfifo_fast state UP group default qlen 1000 link/ether b6:29:60:82:0b:eb brd ff:ff:ff:ff:ff:ff inet 10.0.2.2/24 metric 100 brd 10.0.2.255 scope global dynamic enp1s0 valid_lft 86301118sec preferred_lft 86301118sec inet6 fe80::b429:60ff:fe82:beb/64 scope link valid_lft forever preferred_lft forever 3: enp2s0: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 62:d0:72:71:e8:f5 brd ff:ff:ff:ff:ff:ff inet 192.168.0.162/24 brd 192.168.0.255 scope global enp2s0 valid_lft forever preferred_lft forever inet6 2400:2650:8022:3c00:60d0:72ff:fe71:e8f5/64 scope global dynamic mngtmpaddr noprefixroute valid_lft 297sec preferred_lft 297sec inet6 fe80::60d0:72ff:fe71:e8f5/64 scope link valid_lft forever preferred_lft forever ちゃんと設定できていることがわかりました。 KubeVirtを使ってみた感想 コンテナネットワーク上にVMを立てることができるのはKubeVirtのメリットの一つとして挙げられます。ただし今運用されているVM基盤をKubernetesに移行したいとなった時に、VMのままKubeVirt on Kubernetesに移行するくらいなら、VMをコンテナに置き換えて素直にKubernetesで動かした方が移行コスト・将来の管理コスト含めて楽なのではないかと思いました。どうしてもVMでしか動かせないというユースケースならKubeVirtを使う価値はあるかもしれませんが、そのVMの数が少ないなら個別にネットワークを構築しても良さそうです。 またもう一つのメリットはVMをIaCとして管理できることです。これはTerraformなどでもできることですが、慣れ親しんだKubernetesの仕組みに則って管理できるのは便利です。 ただKubeVirtに限らずPV、StatefulSetなどにも言えることですが、宣言的な構成管理をするKubernetesでStatefulなリソースを管理するのは結構辛いのではないかと思います。KubernetesはReconcileを通じてあるべき姿に収束することを目指しますが、実際にそのリソースが確実に存在することは保証されません。ここら辺は適切に管理できれば問題ないのかもしれませんが、自分としては辛そうに感じました。 ちょっと否定的な感想になってしまいましたが、自分はOpenStackなども触ったことがないですし、プロダクト環境で大規模なVM基盤を運用している方ならもしかしたら違う感想になるかもしれません。 まとめ KubeVirtやNFS、Multusなどを触れてとても勉強になりました。自宅VM基盤としてはオーバースペックですが、楽しいので今後も運用していきたいと思います。 --- # Building an sgkey and Playing with TinyGo - URL: https://www.onoe.dev/en/blog/sgkey/ - Language: en - Published: 2023-12-09 - Tags: Tech, Keyboard, TinyGo, Go > Building an sgkey and playing with TinyGo At the after-party of Go Conference mini 2023 Winter IN KYOTO1, sago35 gave me an sgkey micropad assembly kit. I had already been interested in custom keyboards, and sago35’s talk “Continued: The World of Custom Keyboards Made with TinyGo2” along with the live coding was so interesting that I decided to buy the necessary parts and have some fun with sgkey and TinyGo3. info この記事の日本語版はこちらです。 #kyotogo の懇親会でお配りした sgkey の作り方はここにあります。追加で買わないといけないのは、以下です。続く返信で購入場所例を書きます。 * ピンソケット 1x7 * 2 * XIAO RP2040 オプションで以下。 * 液晶モジュール * 抵抗 10kΩ * ピンソケット 1x4https://t.co/ptszji4qxb — takasago (@sago35tk) December 4, 2023 Assembling the sgkey Follow the guide. I prepared a Seeeduino XIAO (not RP2040) along with the optional LCD display. Solder the diodes, resistors, pin sockets, and key switches in order. I hadn’t soldered since middle school, so my technique was a bit rough. After that, attach the keycaps, XIAO, and LCD module, then add the rubber feet to complete the build. Running sgkey with TinyGo Sample Program First, flash the sample program. Select xiao as the target, not xiao-rp2040. $ tinygo flash -target=xiao -size=short . code data bss | flash ram 81744 2284 7332 | 84028 9616 At this point, macOS recognized it as a keyboard. This is handled nicely by sago35/tinygo-keyboard. You can type “tinygo” with the 6 keys, and it’s also displayed on the LCD. Pretty impressive. LED Blinking Next, let’s try blinking an LED. The Seeeduino XIAO has 4 LEDs, 3 of which can be controlled. Let’s blink one of the blue LEDs (LED2). package main import ( "machine" "time" ) func main() { led := machine.LED2 led.Configure(machine.PinConfig{Mode: machine.PinOutput}) for { led.High() time.Sleep(500 * time.Millisecond) led.Low() time.Sleep(500 * time.Millisecond) } } You can see the blue LED blinking. The green LED is always on. Maybe I should have made it flash more aggressively. Running sgkey with Vial sago35/tinygo-keyboard supports Vial, which lets you change key mappings via a GUI and easily configure layers. Changes made in the browser are reflected on the sgkey in real time. I wonder how it writes to the sgkey. Since it prompts you to select a device from the browser, I assume there’s some API that allows communication between the browser and the device. This time, I set keys 1-5 on layer 1 and keys 6-0 on layer 2, with a toggle to switch between layers. And just like that, I can input all 10 digits with only 6 keys. It might be fun to display the current layer on the LCD as well. By the way, the ProductID and VendorID in the sample vial.json are 0x000a and 0x2e8a, but for the xiao they are 0x802f and 0x2886. So I rewrote vial.json and ran go run cmd/gen-def/main.go to update def.go, but the behavior was the same whether I changed them or not. I’m not very familiar with this area, so I’m not sure why. Closing Thoughts TinyGo is amazing — you can write embedded programs with the same feel as regular Go. Since I’ve already bought all the electronics tools, I’d like to try a few more things4. Thank you, sago35! I also gave a talk — please check out my slides here ↩︎ https://sago35.hatenablog.com/entry/2023/12/05/095108 ↩︎ mazrean, who also received an sgkey, assembled it at lightning speed ↩︎ I’ve been eyeing the keyball61 for a while ↩︎ --- # sgkeyを組み立ててTinyGoで遊ぶ - URL: https://www.onoe.dev/blog/sgkey/ - Language: ja - Published: 2023-12-09 - Tags: Tech, Keyboard, TinyGo, Go > sgkeyを組み立ててTinyGoで遊ぶ Go Conference mini 2023 Winter IN KYOTO1の懇親会でsago35さんにsgkeyというマイクロパッドの組み立てキットをいただきました。元々自作キーボードが気になっていたというのもありますが、sago35さんの発表「続) TinyGo で作る自作キーボードの世界2」とそのライブコーディングが面白く興味が湧いたので、いろいろ買い揃えてsgkeyとTinyGoで遊んでみたいと思います3。 info The English version of this article is available here. #kyotogo の懇親会でお配りした sgkey の作り方はここにあります。追加で買わないといけないのは、以下です。続く返信で購入場所例を書きます。 * ピンソケット 1x7 * 2 * XIAO RP2040 オプションで以下。 * 液晶モジュール * 抵抗 10kΩ * ピンソケット 1x4https://t.co/ptszji4qxb — takasago (@sago35tk) December 4, 2023 sgkeyの組み立て ガイドに従います。自分はSeeeduino XIAO(RP2040ではない)とオプションの液晶を用意しました。 順番にダイオード・抵抗・ピンソケット・キースイッチをはんだ付けしていきます。中学生以来なので下手です。 その後キーキャップ・XIAO・液晶モジュールをはめて、最後にゴム足をつけたら完成です。 TinyGoでsgkeyを動かす サンプル 最初にサンプルのプログラムを書き込みます。ターゲットはxiao-rp2040ではなくxiaoを選びます。 $ tinygo flash -target=xiao -size=short . code data bss | flash ram 81744 2284 7332 | 84028 9616 この時点でmacOSからはキーボードと認識されました。ここら辺はsago35/tinygo-keyboardが良い感じにやってくれているようです。 6つのキーでtinygoと打てて、液晶にも表示されていることがわかります。すごい。 Lチカ 次にLチカをやってみます。Seeeduino XIAOにはLEDが4つあり、そのうち3つは操作できるようです。青色のLEDのうちの一つ(LED2)を点滅させてみます。 package main import ( "machine" "time" ) func main() { led := machine.LED2 led.Configure(machine.PinConfig{Mode: machine.PinOutput}) for { led.High() time.Sleep(500 * time.Millisecond) led.Low() time.Sleep(500 * time.Millisecond) } } 青色LEDが点滅していることがわかります。緑色LEDは常に点灯しているものです。もっと厳つく光らせても良かったかも。 Vialでsgkeyを動かす sago35/tinygo-keyboardはVialに対応しており、これを使えばGUIでキー配置を変更できる&レイヤー配置が簡単にできるようです。 ブラウザで変更するとリアルタイムでsgkeyに反映されるのですが、どういう仕組みでsgkeyに書き込んでいるのでしょうか。最初にブラウザからデバイスの選択を促されるので、ブラウザとデバイス間での通信を許可するAPIでもあるのでしょうか。 今回はレイヤー1に12345を、レイヤー2に67890のキーを設定し、トグル形式でレイヤーを切り替えられるようにします。 こんな感じで6キーだけで10個の数字を入力できました。 液晶に現在のレイヤーを表示させても面白いかもしれません。 ちなみにサンプルのvial.jsonに書かれているProductIDとVendorIDは0x000aと0x2e8aですが、xiaoでは0x802fと0x2886です。なのでvial.jsonを書き換えた上でgo run cmd/gen-def/main.goを実行してdef.goを更新してみたのですが、書き換えても書き換えなくても動作は変わりませんでした。ここら辺は詳しくないのでよく分かりません。 終わりに 普段のGoの感覚で組み込みプログラムをかけるTinyGoすごいです。 せっかく電子工作の道具を買い揃えたので、もう少しいろいろやってみたいと思います4。 sago35さんありがとうございました。 自分も発表したのでぜひこちらからスライドをご覧ください ↩︎ https://sago35.hatenablog.com/entry/2023/12/05/095108 ↩︎ 一緒にsgkeyを貰っていたmazreanさんは爆速で組み立ててました ↩︎ keyball61が前から気になっています ↩︎ --- # Exploring the eBPF-based OpenTelemetry Auto-Instrumentation Library for Go - URL: https://www.onoe.dev/en/blog/otel-go-inst/ - Language: en - Published: 2023-12-05 - Tags: Tech, Go, OpenTelemetry, eBPF > I discovered opentelemetry-go-instrumentation, a library that enables automatic OpenTelemetry instrumentation for Go. It leverages eBPF. Let me walk through running and briefly examining this library. I discovered opentelemetry-go-instrumentation, a library that enables automatic OpenTelemetry instrumentation for Go. It leverages eBPF. Let me walk through running and briefly examining this library. info この記事の日本語版はこちらです。 info This article is the Day 5 entry for the Open Telemetry 2023 Advent Calendar. The Day 4 entry was “Implementing an OpenTelemetry Collector confmap provider” by @aereal. warning At the time of writing, opentelemetry-go-instrumentation is Work in Progress (v0.8.0-alpha). Please note that it may have changed significantly since then. What is Auto-Instrumentation? In distributed tracing centered on OpenTelemetry, you need to add instrumentation to your application to propagate Context and output Metrics, Logs, and Traces. This work requires modifying application code and is tedious. To automate this instrumentation – that is, to achieve it without modifying application code – solutions have been implemented for languages like Java1. However, unlike Java or Python, Go is natively compiled to machine code. This means you cannot add code at runtime. I had assumed that auto-instrumentation for Go would be difficult2. The opentelemetry-go-instrumentation library introduced here attempts to achieve auto-instrumentation for Go using eBPF. What is eBPF? eBPF is a Linux technology that safely runs user-defined programs in a sandboxed environment in kernel space. Research and development are active in the fields of Observability and Tracing, centered on Networking. I’ve briefly introduced some papers I’ve read, so feel free to check them out: https://onoe.dev/blog/paper-reading-2/ https://onoe.dev/blog/paper-reading-3/ Personally, I associate eBPF primarily with container networking, especially around Cilium. In opentelemetry-go-instrumentation, eBPF is used to attach to the running process’s code and variables. Running It Before diving deeper, let’s run it. There’s a getting-started guide3, so we’ll follow that. Preparation Create a kind k8s cluster & load the image: $ kind create cluster --name=otel-go-inst $ make docker-build $ kind load docker-image otel-go-instrumentation --name=otel-go-inst Deploy the application: $ kubectl apply -k docs/getting-started/emojivoto/ namespace/emojivoto created serviceaccount/emoji created serviceaccount/voting created serviceaccount/web created service/emoji-svc created service/voting-svc created service/web-svc created deployment.apps/emoji created deployment.apps/vote-bot created deployment.apps/voting created deployment.apps/web created Deploy Jaeger: $ kubectl apply -f docs/getting-started/jaeger.yaml -n emojivoto deployment.apps/jaeger created service/jaeger created $ kubectl port-forward svc/jaeger 16686:16686 -n emojivoto Before Instrumentation Let’s take a look at the application before instrumentation: $ kubectl port-forward svc/web-svc 8080:80 -n emojivoto It appears to be an emoji voting application. Even after sending some requests, no Traces are visible in Jaeger. After Instrumentation Deploy the instrumented version of the application: $ kubectl apply -f docs/getting-started/emojivoto-instrumented.yaml -n emojivoto deployment.apps/emoji configured deployment.apps/voting configured deployment.apps/web configured The instrumentation container definition is here. It works by running with elevated privileges in the same Pod as the target container and sharing the process namespace4. Only a container definition is added – no changes are made to the application code or image. After interacting with the application again, let’s check Jaeger5. At this point, I noticed the application was getting connection refused from Jaeger: 2023/11/30 15:41:05 traces export: Post "http://jaeger:4318/v1/traces": dial tcp 10.96.187.248:4318: connect: connection refused Changing the image from jaegertracing/opentelemetry-all-in-one to jaegertracing/all-in-one fixed the issue, so I submitted a PR to upstream. With just this, we can see quite detailed Traces. The application did feel a bit sluggish, though I’m not sure if that’s just my imagination. I’d need to measure properly to know for sure. How It Works Let’s read through the documentation6. The operations that require instrumentation can be broadly divided into three: Read and write SpanContext7 from HTTP/gRPC requests and responses Create Spans Store SpanContext in eBPF Maps The eBPF program analyzes the stack and CPU registers to access user code and variables. To read and write SpanContext from structures like http.Request, it needs to know the offset of that field within the structure. However, offsets change whenever the structure definition is modified. offsets-tracker analyzes these offsets and saves the information in JSON files organized by version and structure. In step 1, offsets-tracker is used to read and write SpanContext from structures like http.Request and grpc.ClientConn. In step 2, Spans are automatically created at appropriate points. For example, a Span is created when sending a gRPC request within an HTTP Server handler. This library also supports manually created spans. In that case, it updates the SpanContext. In step 3, SpanContext is stored in eBPF Maps so that it can be used in other places within the same goroutine. For example, the SpanContext from a received HTTP request is stored in an eBPF Map and retrieved when sending the HTTP response. SpanContext updates are needed when a new Span is received in step 1 or when the current Span is updated in step 2. In the current implementation, the eBPF Map uses the goroutine ID as the key and the SpanContext as the value. Therefore, sharing SpanContext across multiple goroutines is difficult. In the future, they are considering tracking the tree-structured dependencies between goroutines. Additionally, timestamps need to be captured at Span start and end. uretprobes, which call eBPF code at the end of a function, apparently don’t work well with Go. Instead, return statements are detected and uprobes are placed just before them to call the eBPF code that collects the end timestamp. info I’m still learning about eBPF and OpenTelemetry, so please don’t hesitate to point out any mistakes in this article. Conclusion The documentation was well-organized and very easy to research. This time I didn’t read through the implementation in detail, so I’d like to investigate further next time. I think it would be extremely useful once development progresses and it can be used in more situations. On the other hand, I’m concerned about the brute-force nature of the approach, potential performance overhead, and the security implications of granting container privileges. I’ll continue following this project. https://github.com/open-telemetry/opentelemetry-java-instrumentation ↩︎ Auto-instrumentation was also a challenge for PiCoP, which I was developing at the time ↩︎ https://github.com/open-telemetry/opentelemetry-go-instrumentation/tree/v0.8.0-alpha/docs/getting-started ↩︎ https://kubernetes.io/ja/docs/tasks/configure-pod-container/share-process-namespace/ ↩︎ A bot that votes randomly was also deployed, so I didn’t actually need to interact with the app manually ↩︎ https://github.com/open-telemetry/opentelemetry-go-instrumentation/tree/v0.8.0-alpha/docs ↩︎ Contains TraceID, SpanID, etc. ↩︎ --- # eBPFを使ったOpenTelemetryのGo自動計装ライブラリを調べる - URL: https://www.onoe.dev/blog/otel-go-inst/ - Language: ja - Published: 2023-12-05 - Tags: Tech, Go, OpenTelemetry, eBPF > opentelemetry-go-instrumentationというGoでOpenTelemetryの自動計装を実現するライブラリを知りました。eBPFを活用しているようです。このライブラリを実際に動かしてみながら簡単に調べてみます。 opentelemetry-go-instrumentationというGoでOpenTelemetryの自動計装を実現するライブラリを知りました。eBPFを活用しているようです。このライブラリを実際に動かしてみながら簡単に調べてみます。 info The English version of this article is available here. info 本記事はOpen Telemetry 2023 Advent Calender第5日目の記事です。第4日目の記事は@aerealさんの「OpenTelemetry Collectorのconfmap providerを実装してみる」でした。 warning 本記事執筆時点のopentelemetry-go-instrumentationはWork in Progress(v0.8.0-alpha)となっています。その後大きく変わっている可能性にご注意を。 自動計装とは? OpenTelemetryを中心とした分散トレーシングでは、Contextを伝播したりMetrics, Logs, Tracesを出力したりするための計装(Instrumentation)をアプリケーションに施す必要があります。この作業はアプリケーションに手を加える必要があり手間です。そこで計装を自動化する、つまりアプリケーションのコードに手を加えずに実現する方法がJavaなどで実現されています1。 しかしGoはJavaやPythonなどとは違って、マシンコードにネイティブにコンパイルされます。そのため実行時にコードを追加することができません。少なくとも自分はGoでの自動計装は難しいと思っていました2。 今回紹介するopentelemetry-go-instrumentationはeBPFを使ってGoでの自動計装を実現しようと試みています。 eBPFとは? eBPFはカーネル空間のサンドボックス環境で安全にユーザー定義のプログラムを実行するLinuxの技術です。Networkingを中心としてObservabilityやTracingの分野での研究開発が盛んです。自分が読んだ論文をいくつか簡単に紹介しているのでよければご覧ください。 https://onoe.dev/blog/paper-reading-2/ https://onoe.dev/blog/paper-reading-3/ 個人的にはCiliumを中心としてコンテナネットワーク関連でよく使われる印象を持っています。 opentelemetry-go-instrumentationでは、eBPFを用いて実行プロセスのコードと変数にattachしているようです。 動かしてみる 調べる前に動かしてみます。getting-started3があるのでそれに従います。 準備 kindでk8sクラスタ作成&imageをロード $ kind create cluster --name=otel-go-inst $ make docker-build $ kind load docker-image otel-go-instrumentation --name=otel-go-inst アプリケーションをデプロイ $ kubectl apply -k docs/getting-started/emojivoto/ namespace/emojivoto created serviceaccount/emoji created serviceaccount/voting created serviceaccount/web created service/emoji-svc created service/voting-svc created service/web-svc created deployment.apps/emoji created deployment.apps/vote-bot created deployment.apps/voting created deployment.apps/web created Jaegerをデプロイ $ kubectl apply -f docs/getting-started/jaeger.yaml -n emojivoto deployment.apps/jaeger created service/jaeger created $ kubectl port-forward svc/jaeger 16686:16686 -n emojivoto 計装前 計装前にどんなアプリケーションか見てみます。 $ kubectl port-forward svc/web-svc 8080:80 -n emojivoto 絵文字を投票するアプリケーションのようです。いくつかリクエストを送ってからJaegerを見てもTraceは見れません。 計装後 計装したバージョンのアプリケーションをデプロイ $ kubectl apply -f docs/getting-started/emojivoto-instrumented.yaml -n emojivoto deployment.apps/emoji configured deployment.apps/voting configured deployment.apps/web configured 計装用のコンテナ定義はこちら。特権を与えた上で計装対象のコンテナと同じPodで動作させる&プロセス名前空間を共有する4ことで実現しています。コンテナ定義を追加するだけでアプリケーションのコード・イメージには変更はありません。 再びアプリケーションを操作してからJaegerを見ます5。 ここでアプリケーションがJaegerからconnection refusedされていることに気づきました。 2023/11/30 15:41:05 traces export: Post "http://jaeger:4318/v1/traces": dial tcp 10.96.187.248:4318: connect: connection refused imageをjaegertracing/opentelemetry-all-in-oneからjaegertracing/all-in-oneに変更したら動いたので、PRをupstreamに投げておきました。 これだけでかなり詳細なTraceを見ることができました。 ちなみになんとなくアプリケーションの動作が重くなった気がしますが気のせいでしょうか。ちゃんと計測してみないと分かりませんが。 仕組み ドキュメントを読んでいきます6。 計装が必要な処理は大きく以下の3つです。 HTTP/gRPCのリクエスト・レスポンスのSpanContext7を読み書きする Spanを作成する SpanContextをeBPF Mapに格納する eBPFプログラムはスタックとCPUレジスタを解析してユーザーコードと変数にアクセスします。このときhttp.Requestのような構造体のSpanContextを読み書きするためには、構造体内のそのフィールドのオフセットを知る必要があります。しかしオフセットは構造体定義が変更されるたびに変化します。そこでオフセットを解析し、その情報をバージョン・構造体ごとにJSONに保存してくれるのがoffsets-trackerです。 1ではoffsets-trackerを利用して、http.Requestやgrpc.ClientConnといった構造体のSpanContextを読み書きしているようです。 2では適切な位置で自動でSpanを作成します。例えばHTTP Serverのハンドラ内でgRPCのリクエストを送信するときにSpanを作成します。 またこのライブラリは手動で作成したスパンにも対応しています。その場合はSpanContextを更新します。 3ではSpanContextをeBPF Mapに格納することで、同じgoroutine内の他の場所でSpanContextを利用できるようにしています。例えば受け取ったHTTPリクエストのSpanContextをeBPF Mapに格納し、最後HTTPレスポンスを返す際に取り出して書き込むといった流れです。SpanContextの更新は、1で新たなSpanを受け取った際や2で現在のSpanが更新された際に必要です。 現在の実装ではeBPF MapのKeyをgoroutine IDに、ValueをSpanContextとしています。そのため複数goroutineに跨ったSpanContextの共有は難しいそうです。将来的にはgoroutineの木構造の依存関係をトラッキングすることも考えているようです。 他には、Spanの開始・終了時にタイムスタンプを取得する必要があります。関数の最後でeBPFコードを呼び出すuretprobesはGoとの相性が悪いらしく、代わりにreturn句を検出して直前にuprobesを置くことで終了タイムスタンプを収集するeBPFコードを呼び出しているそうです。 info eBPFやOpenTelemetryについてまだまだ勉強中なので、本記事で間違っているところなどがあれば遠慮なくご指摘ください。 終わりに ドキュメントが整備されており非常に調べやすかったです。今回は実装までちゃんと読めていないので次はもっと調べてみたいと思っています。 開発が進んでもっと色々な状況で使用できたら非常に便利だと感じました。一方で実現方法のゴリ押し感・動作が重い可能性・コンテナに特権を与えるセキュリティ上の問題が気になります。引き続き追っていきたいと思います。 https://github.com/open-telemetry/opentelemetry-java-instrumentation ↩︎ 今自分が開発中のPiCoPでも同様に計装が必要でその手間が課題でした ↩︎ https://github.com/open-telemetry/opentelemetry-go-instrumentation/tree/v0.8.0-alpha/docs/getting-started ↩︎ https://kubernetes.io/ja/docs/tasks/configure-pod-container/share-process-namespace/ ↩︎ 適当に投票してくれるBotもデプロイされていたので操作しなくてもよかったっぽいです ↩︎ https://github.com/open-telemetry/opentelemetry-go-instrumentation/tree/v0.8.0-alpha/docs ↩︎ TraceID, SpanIDなどを含んだもの ↩︎ --- # 3rd Place Student / 13th Overall at ISUCON13 (111,625 points) - URL: https://www.onoe.dev/en/blog/isucon13/ - Language: en - Published: 2023-11-26 - Tags: Tech, ISUCON, Go > 3rd Place Student / 13th Overall at ISUCON13 (111,625 points) Together with Saza and Moririn, we placed 3rd among student teams and 13th overall at ISUCON13 with a score of 111,625 points. Our team name was MONOS. It was my first time participating, while the other two had competed in the previous edition. I’ll jot down what we did in chronological order as a retrospective. info この記事の日本語版はこちらです。 GitHub repository: https://github.com/Saza-ku/isucon13 Our team member Saza’s write-up is here. Before the Contest For practice, I individually worked through private-isu and the ISUCON 11 qualifier, and as a team we tackled the ISUCON 12 qualifier, 11 finals, and 12 finals. For both practice and the actual contest, we used a great template Saza had built. It bundles scripts and documentation that make setup, deployment, and benchmarking easy to run. It was incredibly convenient and made practice much more efficient. We used alp, pt-query-digest, pprof, and netdata as our measurement tools, and benchmark results were automatically written to GitHub Issues upon completion. Contest Day Early Phase At the start, Saza handled instance and repository setup through the initial benchmark, while Moririn and I read the manual and codebase. After setup was complete, we ran benchmarks a few times but the initialization kept failing due to DNS resolution issues. The root cause was that the IP address configuration for DNS resolution (the environment variable ISUCON13_POWERDNS_SUBDOMAIN_ADDRESS) was set to the public IP of the second instance by default — switching it to the first instance fixed the problem. Due to this issue and some benchmarker bugs, it took a while to get measurement results. Meanwhile, Moririn and I discussed that livecomments seemed important and went ahead with removing unnecessary SQL queries based on intuition. Saza quickly applied the standard optimizations and set up a multi-server configuration (NGINX+App+PowerDNS, PowerDNS MySQL, App MySQL). [11:43] 3300(#14) : Initial setup(#16) [12:10] 3864(#21) : Ranking improvement(#12) [12:13] 4500(#23) : Multi-server setup(#23) [12:22] 5682(#24) : NG word search improvement(#2) [12:37] ?(#29): Ranking improvement 2(#31) [12:43] 4500(#33) : Multi-server setup 2(#32) [12:54] 9000(#35): Index on livestream_tags(#36) Middle Phase After we started getting proper measurement results, we divided the work as follows: Me: Icon-related optimizations Saza: DNS water torture attack countermeasures Moririn: Slow query optimization I worked on two improvements for icons: “removing image data from the DB” and “returning 304 responses.” “Removing from the DB” is a common optimization seen in past ISUCONs. (#41) “Returning 304” was an improvement based on the application manual. The app includes a hash value (SHA256) of the image data in the User response. When the benchmarker fetches image data, it sends this hash value in the If-None-Match HTTP header, so if the hash matches, we can return 304 Not Modified. I first attempted to have NGINX serve the images directly. Specifically, I turned on NGINX’s etag functionality and changed the hash value returned by the app from plain SHA256 to the format NGINX uses (hex of LastModified UNIX timestamp-hex of Length)1. I expected NGINX would then handle returning 304 or the file data as appropriate. However, the benchmarker’s consistency check failed, revealing that the hash value couldn’t be changed from SHA256. There was also an NGINX plugin to change the etag algorithm2, but it didn’t work, so I gave up on this approach. (implementation branch) In the end, I stored the hash values in Memcached keyed by UserName, and when fetching images, if the If-None-Match header value matched, I returned 304. This drastically reduced SELECT queries on the icons table, resulting in a major improvement. Incidentally, the If-None-Match header value has " (double quotes) on both ends of the hash, which caused cache misses and some debugging pain. (#75, #90) [13:15] 16941(#40) : Remove icon from DB(#41) [13:26] 19508(#43) : Slot improvement(#44) [15:00] 38091(#49) : Livecomment and reaction improvement(#50, #53)3 [15:34] 45424(#60) : Index on ng_words(#59) [16:13] 63000(#73) : Return 304 for icon(#75) Late Phase After finishing the icon improvements, I added indexes while reviewing slow queries. (#6, #84) The DNS water torture attack countermeasures Saza was working on turned out to be quite challenging4. He tried storing records in memory and temporarily returning A records even for invalid subdomains while having the app return errors, but things weren’t going well and it looked like a tough struggle. Moririn was steadily improving SQL queries. The fillXXXResponse methods spanned multiple endpoints, which made it somewhat tricky as they were likely to conflict with my changes. About 40 minutes before the end, I noticed that the theme SELECT query at the top of the slow query list could be cached. After preparing log and measurement tool cleanup, I implemented it at full speed and it worked on the first try. (#100) It got a bit tight at the end, but after removing logs and measurement tools, we restarted, ran the benchmark, and did a browser check to finish. The final score was 111,625 points. [16:32] 68000(#79) : Index on livecomments(#6) [16:32] 73000(#83) : Index on reactions(#84) [17:04] 74000(#89) : Forgotten IconHash cache(#90) [17:14] 84529(#93) : Search improvement(#93) [17:40] ?(#99) : Theme cache(#100) [17:50] 111625(final) : Remove logs/measurement tools, DB Wait(#95, #98) Improvements We Couldn’t Make Eliminating the N+1 queries around livecomments was difficult, but considering the benchmarker’s behavior where more comments and reactions lead to more tips and users, I think the improvement would have been significant. We also discussed that if we could counter the DNS water torture attack, it would reduce the resource usage of the PowerDNS MySQL server, allowing us to split the app onto the second instance5. Closing Thoughts There were plenty of standard optimization opportunities alongside the new challenge of the DNS server, making it a very enjoyable day where we could go all out. It’s a shame we didn’t place in the top ranks, but we want to achieve better results next year. https://www.denzow.me/entry/2017/11/26/221442 ↩︎ https://github.com/kkung/nginx-static-etags/ ↩︎ We briefly hit 2nd place at this point (https://x.com/saza_ku/status/1728447246826635414) ↩︎ Looking at the DNS water torture attack article written by the problem setter, we discussed that this was probably the background behind the challenge ↩︎ During the post-mortem, we were surprised to see multiple teams had built their own DNS servers ↩︎ --- # ISUCON13で学生3位 総合13位だった(111625点) - URL: https://www.onoe.dev/blog/isucon13/ - Language: ja - Published: 2023-11-26 - Tags: Tech, ISUCON, Go > ISUCON13で学生3位 総合13位だった(111625点) Saza, MoririnとISUCON13で学生3位、総合13位(スコアは111625点)を獲得した。チーム名はMONOSで自分は初出場、他二人は前回も出場している。 振り返りがてら時系列順にやったことを書き留めておく。 info The English version of this article is available here. GitHubレポジトリ: https://github.com/Saza-ku/isucon13 チームメンバーSazaの記事はこちら 前日まで 事前練習は自分一人でprivate-isu・11予選を、チームで12予選・11本戦・12本戦をやっていた。 練習・本番共にSazaが作ってくれたいい感じのテンプレートを使っている。これはセットアップやデプロイ、計測までを簡単に実行できるスクリプトやドキュメントをまとめたものである。非常に便利で練習が捗った。 計測ツールはalp, pt-query-digest, pprof, netdataを使っていて、ベンチマークが終了するとIssueに全ての結果が書き込まれるようになっている。 当日 序盤 初動はSazaがインスタンス・レポジトリのセットアップから初期計測まで、自分とMoririnがマニュアル・コードを読むという感じに分担していた。 セットアップが終わって何度か計測してみると、DNS解決の問題で初期化処理に失敗するようになった。初期設定ではDNS解決で返すIPアドレスの設定(環境変数ISUCON13_POWERDNS_SUBDOMAIN_ADDRESS)が2台目インスタンスのPublic IPになっていたのが原因で、1台目にするとうまく動いた。 この問題やベンチマーカーの不具合などから計測結果がなかなか出ず、自分とMoririnはlivecommentが重要そうという話をしながらエスパーで無駄なSQLクエリの削減を進めていた。Sazaはさっと秘伝のタレ、複数台構成(NGINX+App+PowerDNS, PowerDNSのMySQL, AppのMySQL)をやってくれた。 [11:43] 3300(#14) : 初動(#16) [12:10] 3864(#21) : ランク改善(#12) [12:13] 4500(#23) : 複数台構成(#23) [12:22] 5682(#24) : NGWord検索改善(#2) [12:37] ?(#29): ランク改善2(#31) [12:43] 4500(#33) : 複数台構成2(#32) [12:54] 9000(#35): livestream_tagsにindex(#36) 中盤 ちゃんと計測結果が出た後は以下のように分担した。 自分: icon関係 Saza: DNS水責め攻撃 Moririn: スロークエリ 自分はiconの画像データを「DBから剥がす」「304で返す」の2つの改善をした。 「DBから剥がす」に関しては今までのISUCONでもよくある改善である。(#41) 「304で返す」に関してはアプリケーションマニュアルの方法に基づく改善である。アプリはUserのレスポンスに画像データのハッシュ値(SHA256)を含めて返している。ベンチマーカーは画像データを取得する際にこのハッシュ値をHTTPヘッダIf-None-Matchにつけて送信するため、ハッシュ値が一致するなら304 Not Modifiedを返して良いというものである。 最初は画像をNGINXで返そうと試みた。具体的には、NGINXでetagの機能をONにし、アプリが返すハッシュ値を単純なSHA256からNGINXが使用する形式(LastModifiedのUNIX時間の16進数-Lengthの16進数)に変更した1。こうすることで、NGINXがいい感じに304を返したりファイルデータを返したりしてくれることを期待していた。しかしベンチマーカーを回した結果整合性チェックに失敗し、ハッシュ値はSHA256から変更できないと判明した。NGINXのetagのアルゴリズムを変更するプラグインもあった2が、うまくいかず断念した。(実装ブランチ) 最終的には、UserNameをキーにしてMemcachedにハッシュ値を保存し、画像の取得時にIf-None-Matchヘッダの値と一致すれば304を返すようにした。iconsテーブルへのSELECTが激減したので大幅改善である。ちなみにIf-None-Matchヘッダの値はハッシュ値の両端に"(ダブルクオーテーション)がついているため、キャッシュがヒットせずデバッグに手こずった。(#75, #90) [13:15] 16941(#40) : iconをDBから剥がす(#41) [13:26] 19508(#43) : slot改善(#44) [15:00] 38091(#49) : livecomment, reaction改善(#50, #53)3 [15:34] 45424(#60) : ng_wordsにindex(#59) [16:13] 63000(#73) : iconを304で返す(#75) 終盤 icon改善が終わった後、自分はスロークエリを見ながらindexを貼ったりしていた。(#6, #84) Sazaが担当しているDNS水責め攻撃の対策はかなりの難易度のようであった4。レコードをオンメモリで保存したり、一旦不正サブドメインもAレコードを返してアプリでエラーを返すようにしたりしていたが、なかなかうまくいかずかなり苦しそうであった。 Moririnは地道にSQLクエリ改善をしてくれていた。fillXXXResponseのメソッドが複数エンドポイントにまたがっており、自分の変更とコンフリクトしそうで若干大変であった。 最後40分ほどになってからスロークエリの一番上にあるthemeのSELECTをキャッシュできることに気づいた。ログや計測ツール削除の準備だけしてから爆速で実装しなんとか1発で動作した。(#100) 最後は少しギリギリになったが、ログや計測ツール削除後に再起動し、ベンチマーク実行とブラウザチェックをして終了。 最終スコアは111625点であった。 [16:32] 68000(#79) : livecommentsにindex(#6) [16:32] 73000(#83) : reactionsにindex(#84) [17:04] 74000(#89) : IconHashのキャッシュし忘れ(#90) [17:14] 84529(#93) : search改善(#93) [17:40] ?(#99) : themeのキャッシュ(#100) [17:50] 111625(最終) : ログ・計測ツール削除, DB Wait(#95, #98) できなかった改善 livecomment周りのN+1改善は難しいが、コメントやリアクションをたくさん投稿&表示できると投げ銭やユーザーが増えるというベンチマーカーの挙動を考えると改善効果は大きいかなと思った。 またDNS水責め攻撃の対策ができればPowerDNSのMySQLサーバーのリソース使用量が減るので、2台目インスタンスにアプリを分割したいよねという話もしていた5。 終わりに 定石の改善箇所もたくさんありながらDNSサーバーという新たな鬼門も現れ、とても楽しく1日全力を出せた。 入賞できず残念だが来年は良い結果を残したい。 https://www.denzow.me/entry/2017/11/26/221442 ↩︎ https://github.com/kkung/nginx-static-etags/ ↩︎ ここで一瞬2位になった(https://x.com/saza_ku/status/1728447246826635414) ↩︎ 出題者の方が書いたDNS水責め攻撃の記事を見て、出題の背景やろなぁという話をしていた ↩︎ 感想戦を見ているとDNSサーバーを自作しているチームが複数いて驚いた ↩︎ --- # Book Review: Staff Engineer — Leadership Beyond the Management Track - URL: https://www.onoe.dev/en/blog/review-staff-engineer/ - Language: en - Published: 2023-09-27 - Tags: Book, Career > A review of 'Staff Engineer: Leadership Beyond the Management Track' I had been wondering for a while what my long-term career as an engineer would look like, so I purchased this book shortly after its release. I won’t cover everything, but I’ll share my review while quoting some parts that caught my attention. info この記事の日本語版はこちらです。 About the Book This book systematically summarizes the role of Staff Engineers, based on interviews with people working in that position. It covers topics such as what a Staff Engineer is, the expected roles, and how to become one. The first half presents a systematic summary based on interviews, while the second half contains the actual interview content. Career-related books from overseas often contain content that isn’t applicable in Japan. However, I felt that this book offers universally useful insights—both in Japan and elsewhere—because the original author was mindful of not limiting the discussion to Silicon Valley, and the translator added interviews with Japanese engineers. What Is a Staff Engineer? “Staff Engineer” is a perfect role for “technical leaders” who want to remain hands-on engineers for their entire career, rather than moving into management positions like manager or CTO (Chief Technology Officer). The idea that software engineers inevitably drift away from technology and toward management as they advance is a familiar dilemma in Japan. Apparently, this problem exists outside Japan as well. This book explains the career path of a technical leader1. Of course, as a leader, management tasks such as mentoring subordinates and team building are important parts of the job. But what distinguishes it from a typical management role is the emphasis on technical decision-making and continuing to write code (even if with less time). This book provides a systematic overview of what technical leaders do, and I feel it clarified aspects that I previously understood only vaguely. I learned that “Staff” also carries the meaning of an advisor, and that it is an established role in the United States, serving as both an “engineering leader” and an “executive aide.” I learned this term just before buying the book. In Japan, few companies seem to use this title yet. As the book mentions, catchy terminology can help spread the underlying concept (SRE being a good example), so I hope the term “Staff Engineer” will help popularize the idea of technical leadership as well. Classification of Staff Engineers The book classifies Staff Engineers into four types: Tech Lead guides a given team toward the right approach and execution. Architect is responsible for the direction, quality, or approach in critical areas. Solver dives deep into arbitrarily complex problems and carves a path forward. Right Hand acts as an aide, representing the concerns of a senior executive and leveraging the executive’s authority and capabilities to manage complex organizations. It’s worth noting that not all Staff Engineers fit neatly into these four categories—many have a mix of these roles or do other work as well. Also, these terms are specific to this book’s classification and are not necessarily widely established, so one should be careful with definitions when using them outside the context of this book. In fact, I had heard the terms “Tech Lead” and “Architect” before but in different senses, and even in the interview sections of this book, there seemed to be some mixing of meanings. In my own opinion, these four types can be classified along two axes: team-oriented vs. not, and broad vs. narrow technical scope. Tech Leads and Architects act as leaders who drive one or several specific teams, while Solvers and Right Hands move between teams as needed, like utility players. Additionally, Tech Leads and Solvers tend to specialize in deep, narrow technical domains, while Architects and Right Hands cover broader technical areas. I admit this feels somewhat forced and may be an oversimplification, so take it as just a reference. How to Become a Staff Engineer There are many detailed points, but the four major important things are: Work on a Staff Project Start writing your Promotion Packet early Get into the room where decisions are made and stay there Build your visibility A Staff Project is a significant project worthy of someone becoming a Staff Engineer. Opinions are divided on whether it’s strictly necessary. A Promotion Packet is the documentation needed during the promotion review process. It includes details of accomplished Staff Projects, achievements, and peer evaluations. By updating it regularly rather than only at the final stage of promotion, you can clearly understand your current standing and identify areas for improvement. This section was very helpful as it focuses not on technical skills but on what soft skills are needed and how to conduct yourself. I noticed that except for “getting into the room where decisions are made,” these are things that are also necessary when job hunting as a new graduate, just at a different level. It seems best to be aware of these things from an early stage and keep at them continuously. Summary The Staff Engineer title is still a distant prospect for me. However, this book has significantly clarified how to position myself early on to become a Staff Engineer, as well as what to expect from—and what is expected by—those already working as Staff Engineers, and how to collaborate with them effectively2. Since my takeaways from this book will likely change at different stages of my career, I’d like to revisit it periodically. I wondered whether CTOs were included, but it seems CTOs are classified as management roles because they focus on business-oriented decision-making and don’t write code. Some CTOs I know work in a way that resembles a senior Staff Engineer, so this classification should be taken as specific to this book. ↩︎ For the details, I encourage you to read the book itself. ↩︎ --- # 『スタッフエンジニア マネジメントを超えるリーダーシップ』を読んだ - URL: https://www.onoe.dev/blog/review-staff-engineer/ - Language: ja - Published: 2023-09-27 - Tags: Book, Career > 『スタッフエンジニア マネジメントを超えるリーダーシップ』を読んだのでレビュー 遠い将来のエンジニアとしてのキャリアはどうなるのかなぁと以前から思っていたのもあり、発売後すぐに購入した。 全てではないがいくつか目に止まった部分を引用しながらレビューする。 info The English version of this article is available here. 本書について 本書はスタッフエンジニアとして働く方々へのインタビューをもとに、スタッフエンジニアを体系的にまとめたものである。 そこにはスタッフエンジニアとは何か・期待される役割・なる方法といった話が含まれる。前半にはインタビューをもとにした体系的なまとめが、後半には実際のインタビュー内容が記載されている。 こういったキャリアに関する洋書は、日本では参考にならない話ばかりのことも多い。しかし本書は、原著者がシリコンバレーに限った話にならないように意識していたり、翻訳者が日本人のインタビューを追加していたりすることで、日本でもそれ以外でも普遍的に役立ちそうな話になっていると感じた。 スタッフエンジニアとは この「Staff Engineer(スタッフエンジニア)」は、マネジャーやCTO(最高技術責任者)といったマネジメント職に就くのではなく、技術を武器に「生涯現役のエンジニアでありたい」とする「テクニカルリーダー」にぴったりの職種です。 ソフトウェアエンジニアは役職が上がるにつれて技術に触れることが少なくなってマネジメント職に寄らざるを得ない、という話は日本ではよく聞くジレンマではないだろうか。どうやら日本以外でもこの問題はあるようだ。本書は技術者としてのリーダーのキャリアについて解説している1。もちろんリーダーとして働く以上、部下の育成やチームビルディングなどの観点からマネジメントも重要な仕事として含まれるのだが、それに加えて技術的な意思決定に重きを置く・(時間は減っても)コードを書き続けるという部分が通常のマネジメント職とは異なる部分である。 本書は技術者としてのリーダーがどういった仕事をするのかについて体系的にまとまっており、今まで曖昧にしか知らなかった部分が明確になった気がする。 Staff には参謀といった意味もあり、「エンジニアのリーダー」および「幹部の補佐役」として米国では定着している役職であることを知りました。 本書を買う直前に自分はこの用語を知った。日本ではこの役職名を使っている会社はまだ少なそうである。本書にも書いてあったが、キャッチーな用語と合わせて概念も広まることがあるので(例えばSRE)、スタッフエンジニアという用語とともにテクニカルリーダーの考え方も広まってほしい。 スタッフエンジニアの分類 本書では、スタッフエンジニアを4つに分類している。 テックリードが与えられたチームをアプローチや実行へと導く。 アーキテクトは重要分野において方向や質、あるいはアプローチに責任を負う。 ソルバー(解決者)は任意の複雑な問題を深く掘り下げ、前進する話を切り開く。 右腕(ライトハンド)は補佐役として会社幹部の関心を代表し、幹部の能力と権限を借りて複雑な組織の運営にあたる。 必ずしも全てのスタッフエンジニアがこの4つに分類できるわけではなく、4つが混合していたりそれ以外の仕事もあることに注意が必要だ。 また用語自体はあくまで本書での分類であって、一般にこの用語が定着しているわけではなさそうなので、本書の前提なしにこれらの単語を出す場合は定義に注意したほうがよさそうである。現にテックリードやアーキテクトは以前も聞いたことがあるが別の意味で使っていたし、何なら本書のインタビュー部分でも混在している部分があったように思う。 これは自分の考えだが、この4つはチーム指向か否か・扱う技術が広いか否かの2軸で分類できるのではないか。 テックリードとアーキテクトは一つまたは複数の決まったチームを引っ張るリーダーとして動く一方で、ソルバーと右腕は遊撃手のように必要なチームを行き来しながら動く。 またテックリードとソルバーは狭く深い技術領域を得意としており、アーキテクトと右腕は広く浅い技術領域を得意としている。 若干無理矢理な気はしており、単純には分類できなさそうなのであくまで参考程度に書いておく。 スタッフエンジニアになるには 細かい話は色々あるが大きく重要なことは以下の4つである。 スタッフプロジェクトに取り組む プロポーションパケットを早い段階から書く 意思決定が行われる部屋に入ってとどまる 認知度を上げる スタッフプロジェクトとは、スタッフエンジニアになるにふさわしい重要なプロジェクトのことである。 これについては人によって必要かどうかの意見が分かれるようだ。 プロポーションパケットとは、昇進審査の段階で必要な文書のことである。成し遂げたスタッフプロジェクトの詳細や功績・他人からの評価などが含まれる。昇進の最終段階だけではなく普段から定期的に更新することで、自分の現状や足りない部分を明確に把握することができる。 技術スキルの話ではなく、ソフトスキルの面で何が必要なのか・どうやって立ち振る舞うべきかについてまとまっており非常に参考になった。 「意思決定が行われる部屋に入ってとどまる」以外は、レベルの違いはあれど新卒の就活でも必要なことだなぁと思った。早い段階からずっと意識して続けていくのが良いのであろう。 まとめ 役職自体は自分にとってまだまだ先の話ではある。しかし、スタッフエンジニアになるために早いうちからどう立ち回っておくべきか、あるいは既にスタッフエンジニアとして働く方に何を期待するか・されるか・どのように一緒に働くか、といったことがかなり明確になった2。それぞれのキャリアの段階で本書の感想も変わりそうなので、定期的に読み返したい本である。 CTOは含まれないのかと思ったが、CTOはあくまで経営寄りの意思決定に重きを置いていてコードは書かないからマネジメント職に分類されているようだ。自分の知っているCTOの中にはスタッフエンジニアの上位的な働き方をしている方もいるので、あくまで本書での分類と考えたほうがよさそう。 ↩︎ 細かい話は本書を読んでみてほしい。 ↩︎ --- # 【インターン参加記】GitHub Actions Self-Hosted Runnerによる負荷試験環境構築の自動化 - URL: https://www.onoe.dev/blog/recruit-intern/ - Language: ja - Published: 2023-09-15 - Tags: Internship, Recruit, Kubernetes, Terraform, GitHub Actions > ※ 外部リンク https://techblog.recruit.co.jp/article-654/ に移動します ※ 外部リンク https://techblog.recruit.co.jp/article-654/ に移動します --- # Paper Reviews: Container Overlay Networks - URL: https://www.onoe.dev/en/blog/paper-reading-3/ - Language: en - Published: 2023-05-31 - Tags: Paper, Container Overlay Network, eBPF, Network, Kubernetes > Reviews and thoughts on container overlay network papers read in May The streak was broken over Golden Week. Things settled down in the latter half of May, so I’m resuming. Related to lower-layer tracing, I’ll introduce papers on container overlay networks using eBPF. info この記事の日本語版はこちらです。 vNetTracer: Efficient and Programmable Packet Tracing in Virtualized Networks K. Suo, Y. Zhao, W. Chen and J. Rao, “vNetTracer: Efficient and Programmable Packet Tracing in Virtualized Networks,” 2018 IEEE 38th International Conference on Distributed Computing Systems (ICDCS), Vienna, Austria, 2018, pp. 165-175, doi: 10.1109/ICDCS.2018.00026. Overview Proposes using eBPF for dynamic, non-intrusive tracing of packet delivery across boundaries and virtualized networks A group that primarily researches container overlay networks and virtualized networks Thoughts It seems like PacketID propagation isn’t done system-wide and is only used for matching between sender and receiver – if so, couldn’t this be done without PacketID? Can’t individual packets really be identified using only TCP/UDP header information? Since analysis seems to be done offline anyway, analyzing headers at that point should eliminate communication overhead They load a kernel module to reduce overhead, but how much reduction does it actually achieve? Reducing disk operations during log storage is important, but how often do disk operations actually occur? If it can still function without the kernel module, I’d want to consider the tradeoff between the effort of loading the kernel module and the overhead reduction There are several eBPF-based tracing tools today, but was there really nothing back then (2018)? Efficient Network Monitoring Applications in the Kernel with eBPF and XDP M. Abranches, O. Michel, E. Keller and S. Schmid, “Efficient Network Monitoring Applications in the Kernel with eBPF and XDP,” 2021 IEEE Conference on Network Function Virtualization and Software Defined Networks (NFV-SDN), Heraklion, Greece, 2021, pp. 28-34, doi: 10.1109/NFV-SDN53031.2021.9665095. Overview Proposes a network monitoring framework that consolidates common packet processing tasks from all network analysis applications and executes them in kernel space using eBPF and XDP, launching applications only when necessary to reduce resource usage and overhead Many of the authors research SDN and NFV Thoughts The novelty seems to lie in extracting common processing from multiple network monitoring applications and orchestrating those applications, but is the use of eBPF and XDP for that common processing really new? Weren’t individual monitoring apps already using eBPF and XDP? I’d like to revisit this after building more knowledge about SDN and NFV Bypass Container Overlay Networks with Transparent BPF-driven Socket Replacement S. Choochotkaew, T. Chiba, S. Trent and M. Amaral, “Bypass Container Overlay Networks with Transparent BPF-driven Socket Replacement,” 2022 IEEE 15th International Conference on Cloud Computing (CLOUD), Barcelona, Spain, 2022, pp. 134-143, doi: 10.1109/CLOUD55607.2022.00033. Overview Proposes bypassing container overlay networks to reduce overhead by having a host-side agent use eBPF and ptrace to replace Pod network namespace communication with Host (default) network namespace communication High usability because it doesn’t modify user processes; safe because it doesn’t require privilege escalation of user processes, preventing container escape attacks A group at IBM Research Tokyo. The first author has several publications on containers, Kubernetes, and BPF. Thoughts They seem to replace sockets with mirrored ones at connection establishment time and bypass subsequent communication, but how would this work for connectionless communication? They probably give up and go through the Pod’s network namespace as usual Wouldn’t having the client/server O2H (proposed) daemons communicate with each other every time a connection is established introduce significant overhead? Socket replacement apparently happens after the connection is established and communication begins, so the daemon-to-daemon communication might happen during that window Couldn’t packet loss occur from replacing sockets mid-communication? After reading the Slim paper, I wonder what the differences from Slim are One possibility is that by using the Pod’s network namespace until the connection is established on the Host’s network namespace, connection establishment overhead is reduced Using eBPF is just an implementation-level difference Slim: OS Kernel Support for a Low-Overhead Container Overlay Network D. Zhuo, K. Zhang, Y. Zhu, H. H. Liu, M. Rockett, A. Krishnamurthy, and T. Anderson, “Slim: OS kernel support for a Low-Overhead container overlay network,” in 16th USENIX Symposium on Networked Systems Design and Implementation (NSDI 19). Boston, MA: USENIX Association, Feb. 2019, pp. 331–344. [Online]. Available: https://www.usenix.org/conference/nsdi19/presentation/zhuo Overview Proposes Slim, a container overlay network that replaces Container Namespace sockets with Host Namespace sockets at connection establishment time Reduces container overlay network overhead by having packets pass through the OS kernel’s network stack only once Thoughts Communicating from SlimSocket to SlimRouter for every connection establishment seems to introduce significant overhead The overhead of connection establishment is discussed in the paper, and it’s apparently still faster overall than going through a conventional container overlay network Short-lived connections would be considerably slow, so connection reuse becomes important They use something called LD_PRELOAD. This could probably also be done with eBPF – how would they differ? It’s probably less secure than eBPF at the very least The mention that systems requiring static linking (such as applications written in Go) need binary patching is concerning (I don’t fully understand this) It’s mentioned briefly but seems like a significant drawback If the same thing could be achieved with eBPF, this drawback might be eliminated, but I’m not sure The implementation repository was published, so I’d like to read it (https://github.com/danyangz/slim) XMasq: Low-Overhead Container Overlay Network Based on eBPF S. Lin, P. Cao, T. Huang, S. Zhao, Q. Tian, Q. Wu, D. Han, X. Wang, and C. Zhou, “Xmasq: Low-overhead container overlay network based on ebpf,” 2023. [Online]. Available: https://doi.org/10.48550/arXiv.2305.05455 Overview Proposes bypassing the container overlay network by using eBPF to cache information for each container pair during the first round trip, then rewriting packet headers for subsequent packets The cached information primarily includes Host/Container MAC/IP addresses for source and destination, the container’s NIC index, and a key that uniquely identifies the container pair within a host pair Thoughts This seemed like a very innovative and interesting proposal It was just submitted to arXiv at the beginning of the month, so I’m curious about future developments Aren’t there side effects from writing the Restore Key into ID, DSCP, or Options? If Container Live Migration is achieved by deleting the cache when a container’s placement host changes, is there any point in setting a cache refresh interval? Each host needs to cache information about related container pairs, but wouldn’t the cache size become quite large as the number of containers increases? Maybe it’s fine because any given container only communicates with a limited number of other containers? Since it uses round trips to populate source and destination container information on each other’s hosts, this wouldn’t be applicable for UDP communication that unilaterally sends packets (with no response) Though such communication patterns are probably quite rare If access control rule matching results were cached in the Masq Map, there would be no need to check the Rule Map every time Apparently, running traceroute from within a container would reveal underlay network information, so this might be difficult to use when tenants aren’t trusted Slim was quite careful about this The GitHub repository URL was listed but returned a 404 I wanted to see the implementation, so that’s unfortunate – I wonder if it will eventually be published --- # コンテナオーバーレイネットワーク関連の論文紹介&感想 - URL: https://www.onoe.dev/blog/paper-reading-3/ - Language: ja - Published: 2023-05-31 - Tags: Paper, Container Overlay Network, eBPF, Network, Kubernetes > 5月に読んだコンテナオーバーレイネットワーク関連論文の紹介&感想 ゴールデンウィークを挟んで途切れてしまった。5月後半からは落ち着いたので再開する。 低レイヤでのトレーシングに関連して、eBPFを利用したコンテナオーバーレイネットワークに関する論文を紹介。 info The English version of this article is available here. vNetTracer: Efficient and Programmable Packet Tracing in Virtualized Networks K. Suo, Y. Zhao, W. Chen and J. Rao, “vNetTracer: Efficient and Programmable Packet Tracing in Virtualized Networks,” 2018 IEEE 38th International Conference on Distributed Computing Systems (ICDCS), Vienna, Austria, 2018, pp. 165-175, doi: 10.1109/ICDCS.2018.00026. 概要 境界を超えたパケット伝達・仮想化ネットワークのトレースをeBPFで動的・非侵入的に実現する提案 コンテナオーバーレイネットワークや仮想化ネットワークを中心に研究してるグループ 感想 システム全体でPacketIDの伝播はしておらず、sender, receiver間の紐付けのみに利用しているっぽいが、それならPacketIDなくてもできるのでは? TCP, UDPヘッダの情報のみで個々のパケットを識別って本当にできないんかな 解析はどうせオフラインでやっているっぽいので、その時にヘッダも解析すれば通信のオーバーヘッドはなさそう オーバーヘッド削減のためにカーネルモジュールをロードしてるがどれくらい削減できるのか? ログ保存時に発生するディスク操作を削減することは重要だが、そもそもディスク操作がどれくらい起こるものなのか カーネルモジュールなくても一応動作自体はするのであれば、カーネルモジュールをロードする手間と削減できるオーバーヘッドのトレードオフを考えたい eBPFを使ったトレーシングって今だといくつかあるけど、本当に当時(2018)はなかったんかな? Efficient Network Monitoring Applications in the Kernel with eBPF and XDP M. Abranches, O. Michel, E. Keller and S. Schmid, “Efficient Network Monitoring Applications in the Kernel with eBPF and XDP,” 2021 IEEE Conference on Network Function Virtualization and Software Defined Networks (NFV-SDN), Heraklion, Greece, 2021, pp. 28-34, doi: 10.1109/NFV-SDN53031.2021.9665095. 概要 全てのネットワーク分析アプリケーションから共通するパケット処理タスクを統合し、eBPFやXDPを用いてカーネル空間で実行・必要な場合のみアプリケーションを起動することでリソースやオーバーヘッドを削減するネットワーク監視フレームワークの提案 著者はSDNやNFVの研究している人が多い 感想 複数のネットワーク監視アプリから共通の処理を切り出してアプリをオーケストレーションするというところに新規性がありそうだが、その共通の処理にeBPF, XDPを使用したという点は新しいんだろうか?個々の監視アプリも今までeBPF, XDPを使用していたというわけではない? SDN, NFVあたりの知識をもっとつけてから読みたい Bypass Container Overlay Networks with Transparent BPF-driven Socket Replacement S. Choochotkaew, T. Chiba, S. Trent and M. Amaral, “Bypass Container Overlay Networks with Transparent BPF-driven Socket Replacement,” 2022 IEEE 15th International Conference on Cloud Computing (CLOUD), Barcelona, Spain, 2022, pp. 134-143, doi: 10.1109/CLOUD55607.2022.00033. 概要 Podのnetwork namespaceの通信をHost(default)のnetwork namespaceの通信へと置き換えることでコンテナオーバーレイネットワークをバイパスしてオーバーヘッドを減らす処理を、Host側のエージェントがeBPFとptraceを用いて実行する提案 ユーザープロセスを変更しないためユーザビリティが高い・ユーザープロセスの権限昇格が必要ないのでコンテナエスケープ攻撃を防げて安全 東京のIBM研究所のグループ。筆頭著者はコンテナとかKubernetes, BPF関連の研究がいくつか 感想 コネクション確立時にミラーリングしたソケットに置き換えて以降の通信をバイパスしているようだが、コネクションレス通信ではどうするんだろうか 諦めて通常通りPodのnetwork namespaceを通ってそう コネクション確立時にClient/ServerのO2H(proposed)デーモン同士が毎回通信しているとオーバーヘッドが大きそうだがそうでもないんかな ソケットの置き換えはコネクションが確立されて通信が開始された後にされるらしいので、デーモン同士の通信はその間にやってるのかな 通信途中にソケットを置き換えることによるパケットロスは起こり得ないんだろうか? Slimの論文を読んでから思ったけど、Slimとの差分は何なのだろうか? Hostのnetwork namespaceでコネクションを確立するまではPodのnetwork namespaceで通信することで、コネクション確立のオーバーヘッドを減らしているというのはありそう eBPFを使っているというのは実装面での違いでしかないし Slim: OS Kernel Support for a Low-Overhead Container Overlay Network D. Zhuo, K. Zhang, Y. Zhu, H. H. Liu, M. Rockett, A. Krishnamurthy, and T. Anderson, “Slim: OS kernel support for a Low-Overhead container overlay network,” in 16th USENIX Symposium on Networked Systems Design and Implementation (NSDI 19). Boston, MA: USENIX Association, Feb. 2019, pp. 331–344. [Online]. Available: https://www.usenix.org/conference/nsdi19/presentation/zhuo 概要 コネクション確立時にContainer NamespaceのソケットをHost Namespaceのソケットに置き換えるContainer Overlay NetworkであるSlimを提案 パケットがOSカーネルのネットワークスタックを一度だけ通るようになるのでContainer Overlay Netwokのオーバーヘッドが低減される 感想 コネクション確立のたびにSlimSocketからSlimRouterに通信するのは相当オーバーヘッドが大きそう コネクション確立のオーバーヘッドの問題点は論文中でも述べられていて、従来のContainer Overlay Networkを経由するよりは総合的には速いっぽい 短いコネクションだとかなり遅そうなのでコネクションの再利用が重要になってくるのか LD_PRELOADなるものを使っているのか。eBPF使ってもできそうだがどう違う? 少なくともeBPFよりセキュアではなさそう 静的リンクを必要とするシステム(Goで書かれたアプリなど)ではバイナリにパッチが必要だと書いてあるのが気になった(よく分かっていない) サラッと書いてあるが結構なデメリットでは? eBPFで同じことができるならこのデメリットはなくなりそうだがどうなんだろう 実装レポジトリが公開されていたので読んでみたい( https://github.com/danyangz/slim ) XMasq: Low-Overhead Container Overlay Network Based on eBPF S. Lin, P. Cao, T. Huang, S. Zhao, Q. Tian, Q. Wu, D. Han, X. Wang, and C. Zhou, “Xmasq: Low-overhead container overlay network based on ebpf,” 2023. [Online]. Available: https://doi.org/10.48550/arXiv.2305.05455 概要 eBPFを用いて、最初のラウンドトリップのパケットでコンテナペアごとに情報をキャッシュしておき、その後のパケットヘッダを書き換えることでコンテナオーバーレイネットワークをbypassする提案 キャッシュする情報は主にSrc/DstそれぞれでHost/ContainerのMac/IPアドレスとContainerのNIC index、コンテナペアをホストペア内で一意に識別するKey 感想 すごく革新的で面白そうな提案だと思った 今月初めにarXivに投稿されたばかりなので動向が気になる Restore KeyをID, DSCP, Optionのいずれかに書き込むことによる副作用はないのかな コンテナの配置ホストを移動する際にキャッシュを削除することでContainer Live Migrationを実現しているのならば、キャッシュのrefresh時間を設定してrefreshする意味はあるんだろうか? 各ホストごとに関係するコンテナペアの情報をキャッシュする必要があるが、コンテナが増えるとキャッシュサイズがかなり大きくならないか? あるコンテナが通信するコンテナは限られているから問題ない? ラウンドトリップを利用して送信元と送信先コンテナの情報を互いのホストで埋めているので、UDPで一方的にパケットを送り続ける(レスポンスがない)通信の場合は適用きないな そんな通信なかなかなさそうではある Masq Mapにアクセス制御ルールの照合結果をキャッシュしておけば毎回Rule Mapを見る必要は無くなりそう コンテナ内からtracerouteするとアンダーレイネットワーク情報が分かってしまうとのことなので、テナントが信用できない場合は利用が難しいかも? Slimはここら辺結構気をつけてた GitHubレポジトリのURL載ってたけど404だった 実装見たかったので残念だが、いずれ公開されるのだろうか --- # Paper Reviews: Distributed Tracing - URL: https://www.onoe.dev/en/blog/paper-reading-2/ - Language: en - Published: 2023-05-09 - Tags: Paper, Distributed Tracing, eBPF, Network > Reviews and thoughts on distributed tracing papers read from 5/1 to 5/3 Continuing with distributed tracing papers. The first two are classics from before Dapper. The last one is a recent paper that uses eBPF for lower-layer tracing. info この記事の日本語版はこちらです。 X-Trace: A Pervasive Network Tracing Framework R. Fonseca, G. Porter, R. H. Katz, S. Shenker, and I. Stoica, “X-trace: A pervasive network tracing framework,” in Proceedings of the 4th USENIX Conference on Networked Systems Design & Implementation, ser. NSDI'07. USA: USENIX Association, 2007, p. 20. Overview Proposes comprehensive tracing by propagating unified metadata across different applications and network layers and administrative domains, constructing a tree that shows the flow of requests The authors were networking researchers at UC Berkeley at the time. Each has since gone on to achieve remarkable things in various fields (OpenFlow, RAID, Mesos, etc.) Thoughts As one of the earliest papers on annotation-based tracing, the problem setting is simpler and easier to understand compared to recent large-scale and complex systems Rather than implementing within a specific service infrastructure under a single administrator, they aim to support this across the entire internet and all protocols, crossing administrative boundaries – quite an ambitious vision Tracing across multiple applications (pushNext) is still mainstream and makes sense, but is there really a need for tracing across multiple layers (pushDown)? Especially since embedding metadata in application-layer protocols, TCP, and IP respectively would result in data duplication and significant overhead without much practical benefit Tracing is useful for diagnosing performance under normal conditions, but the anomaly diagnosis shown in the Usage Scenarios (particularly the DNS story) seems achievable through other means Anomalies in individual components should be discovered and reported by their respective administrators; there shouldn’t be a need for another administrator to learn about them first through traces While the desire to trace across administrative boundaries is understandable, in practice, disclosing even partial trace data to others seems difficult (from a security risk and confidential information leakage perspective) If you’re doing comprehensive tracing but each party collects and analyzes trace data independently, it defeats the purpose I wonder how this stands today? OpenTelemetry exists, but I haven’t heard of administrators sharing trace data with each other, so it’s probably still difficult Causeway: Operating System Support For Controlling And Analyzing The Execution Of Distributed Programs A. Chanda, K. Elmeleegy, A. L. Cox, and W. Zwaenepoel, “Causeway: Operating system support for controlling and analyzing the execution of distributed programs,” in Proceedings of the 10th Conference on Hot Topics in Operating Systems - Volume 10, ser. HOTOS'05. USA: USENIX Association, 2005, p. 18. Overview Facilitates the development of meta-applications that require metadata propagation by supporting metadata propagation at the kernel level in distributed programs through Causeway Thoughts Can this really eliminate application-level instrumentation? The metadata injection and access interface seems designed for meta-applications (actors) to call, but wouldn’t it be necessary to pass that metadata to the application? Is communication overhead not considered? The demand for protocol-independent metadata propagation with reduced application instrumentation has existed since this long ago, yet there still doesn’t seem to be a fundamental solution, suggesting it’s an extremely difficult problem The idea of propagation at lower layers could achieve more with today’s technology Kernel extensions could potentially be done with eBPF The paper is so old that it’s difficult to understand the assumed terminology, and it’s hard to gauge how novel it was at the time Enhancing Packet Tracing of Microservices in Container Overlay Networks using eBPF C. Lee, R. Yoshitani, and T. Hirotsu, “Enhancing packet tracing of microservices in container overlay networks using ebpf,” in Proceedings of the 17th Asian Internet Engineering Conference, ser. AINTEC ‘22. New York, NY, USA: Association for Computing Machinery, 2022, p. 53–61. [Online]. Available: https://doi.org/10.1145/3570748.3570756 Overview Proposes using eBPF to extend annotation-based tracing with latency measurement in container overlay networks A group at Hosei University researching distributed systems and tracing. The first author is from Toyota’s research lab and works on distributed systems, SDN, and NFV. Thoughts When it comes to container overlay networks and eBPF, Cilium comes to mind, but it’s curious that there’s no mention of it at all It’s good that they support Flannel and Calico with VXLAN and IPIP analysis, but wouldn’t it be easier to extend Cilium? The problem that annotation-based tracing only considers the application layer is valid, and the idea of extending it to the infrastructure layer is interesting Since we’re already incurring the cost of propagating tracing context, it would be nice to find more uses for it I didn’t know there was a group in Japan doing this kind of research I’d like to read or re-implement their code as a way to study eBPF --- # 分散トレーシング関連の論文紹介&感想 - URL: https://www.onoe.dev/blog/paper-reading-2/ - Language: ja - Published: 2023-05-09 - Tags: Paper, Distributed Tracing, eBPF, Network > 5/1~5/3に読んだ分散トレーシング関連論文の紹介&感想 引き続き分散トレーシング関連の論文を紹介する。最初2つはDapper以前の古典とも言える論文。最後の1つはeBPFを用いて低いレイヤでのトレーシングをする最近の論文。 info The English version of this article is available here. X-Trace: A Pervasive Network Tracing Framework R. Fonseca, G. Porter, R. H. Katz, S. Shenker, and I. Stoica, “X-trace: A pervasive network tracing framework,” in Proceedings of the 4th USENIX Conference on Networked Systems Design & Implementation, ser. NSDI’07. USA: USENIX Association, 2007, p. 20. 概要 異なるアプリケーションやネットワークレイヤー、管理者間で統一したメタデータを伝播することで、リクエストの流れを示すツリーを構築し、包括的なトレーシングをする提案 当時のUC Berkeleyでネットワーク関連の研究してた人たち。今はそれぞれがいろんな分野ですごい業績を上げているっぽい(OpenFlow, RAID, Mesos, etc.) 感想 アノテーションベーストレーシングにおける最初期の論文なので問題設定が単純で、最近の大規模&複雑なものに比べて理解しやすかった 一つの管理者による特定のサービスインフラで実装するのではなく、管理者の境界を超えてインターネット全体や全てのプロトコルでこれをサポートすることを目指しているようで壮大 複数アプリケーション間でのトレーシング(pushNext)は今でも主流なのでわかるが、複数レイヤー間でのトレーシング(pushDown)をする必要性はあるのだろうか 特にアプリケーション層プロトコル・TCP・IPにそれぞれメタデータ含めると、データが重複するしオーバーヘッドが大きくなる割にあまり有用性がないような トレーシングは平常時のパフォーマンス診断に有用なものであって、Usage Senarioで示すような異常診断(特にDNS話)は他の方法でもできるような気がする 各コンポーネントの異常は各管理者が発見したのち通知するはずで、トレースによって別管理者が勝手に先に知る必要はない 管理者の境界を超えてトレーシングしたいという気持ちはわかるが、現実的には一部のみであってもトレースデータを他者に開示するのは難しいような気がする(セキュリティリスク・機密情報漏えいの点で) 包括的にトレーシングしても個々でトレースデータ収集して分析するなら意味ないし 今はどうなってるんやろ?OpenTelemetryはあるけど管理者間でトレースデータを共有する話は聞いたことがないのでやっぱり厳しいのかな Causeway: Operating System Support For Controlling And Analyzing The Execution Of Distributed Programs A. Chanda, K. Elmeleegy, A. L. Cox, and W. Zwaenepoel, “Causeway: Operating system support for controlling and analyzing the execution of distributed programs,” in Proceedings of the 10th Conference on Hot Topics in Operating Systems - Volume 10, ser. HOTOS’05. USA: USENIX Association, 2005, p. 18. 概要 分散プログラムにおけるメタデータ伝播をカーネルレベルでサポートするCausewayによって、メタデータ伝播を必要とするメタアプリケーションの開発を容易にする 感想 これで本当にアプリケーションへの計装をなくせるのか? メタデータの注入・アクセスインターフェースはメタアプリケーション(アクター)が叩く想定っぽいけど、そのメタデータをアプリケーションに渡す必要はないのだろうか 通信のオーバーヘッドとかは考慮してないのか プロトコル非依存にしたりアプリケーションへの計装を減らしてメタデータ伝播したいという要求はこんな昔からあるのに、今でも根本的な解決策はないっぽいので非常に難しい より下位レイヤでの伝播というアイデアは今の技術だともっとできることがありそう カーネル拡張するというのはeBPFとかでできるかも 古の論文すぎて、前提となる用語の理解が難しかったり、当時どれくらい新規性があったのかが分かりにくかったりする Enhancing Packet Tracing of Microservices in Container Overlay Networks using eBPF C. Lee, R. Yoshitani, and T. Hirotsu, “Enhancing packet tracing of microservices in container overlay networks using ebpf,” in Proceedings of the 17th Asian Internet Engineering Conference, ser. AINTEC ’22. New York, NY, USA: Association for Computing Machinery, 2022, p. 53–61. [Online]. Available: https://doi.org/10.1145/3570748.3570756 概要 eBPFを利用して、アノテーションベースのトレーシングをコンテナオーバーレイネットワークのレイテンシ測定に拡張する提案 法政大学で分散システム・トレーシングの研究してるグループ。筆頭著者はトヨタの研究所の方で分散システム・SDN、NFVとか。 感想 コンテナオーバーレイネットワーク(CNI)&eBPFといえばCiliumだと思うがそこに一切触れていないのが気になった Flannel, Calicoに対応してVXLAN, IPIPを用いた解析をサポートしているのは良いが、Ciliumを拡張するともっと簡単にできるのでは アノテーションベースのトレーシングがアプリケーション層しか考慮していないという問題はその通りだと思うので、それをインフラ層に拡張する発想は面白い せっかくそれなりのコストをかけてトレーシングコンテクストを伝搬させているんだから、さらに他のことにも使えないかな 日本でこんな研究してるグループがあるとは eBPFの勉強がてらここら辺の実装読むor再実装してみたい --- # Paper Reviews: Distributed Tracing & Borg - URL: https://www.onoe.dev/en/blog/paper-reading-1/ - Language: en - Published: 2023-04-29 - Tags: Paper, Distributed Tracing, Kubernetes > Reviews and thoughts on distributed tracing and Borg papers read from 4/25 to 4/29 Inspired by https://joisino.hatenablog.com/, I decided to try reading papers every day as much as possible. To help maintain the habit, I’m publishing excerpts from my paper notes along with my thoughts. To start, I’ll introduce some well-known papers in the distributed tracing field, which is related to my own research, along with papers from the recently held NSDI'23, and the Borg paper from Google, which Kubernetes is based on. info この記事の日本語版はこちらです。 Dapper, a Large-Scale Distributed Systems Tracing Infrastructure B. H. Sigelman, L. A. Barroso, M. Burrows, P. Stephenson, M. Plakal, D. Beaver, S. Jaspan, and C. Shanbhag, “Dapper, a large-scale distributed systems tracing infrastructure,” Google, Inc., Tech. Rep., 2010. [Online]. Available: https://research.google.com/archive/papers/dapper-2010-1.pdf Overview Proposes an annotation-based distributed tracing tool and introduces use cases at Google Achieves low overhead, application-level transparency, scalability, and online analysis Thoughts Is it still the case today that all services communicate through a common RPC mechanism? Nowadays, wouldn’t instrumentation be needed for each communication protocol? It’s impressive that a large-scale system like Google’s has a unified application development framework The mainstream approach in current distributed tracing/APM papers and tools is propagating a Trace ID, and I wonder if Dapper was the first to do this at such a large scale in the early days Pivot Tracing: Dynamic Causal Monitoring for Distributed Systems J. Mace, R. Roelke, and R. Fonseca, “Pivot tracing: Dynamic causal monitoring for distributed systems,” in Proceedings of the 25th Symposium on Operating Systems Principles, ser. SOSP ‘15. New York, NY, USA: Association for Computing Machinery, 2015, p. 378–393. [Online]. Available: https://doi.org/10.1145/2815400.2815415 Overview A tracing system that can dynamically determine which metrics to record and capture causal relationships between events across system boundaries The authors have published many well-known distributed tracing papers, primarily around Canopy and X-Trace Thoughts Is the advantage of Dynamic Instrumentation really that significant? The effort of defining tracepoints vs. direct instrumentation doesn’t seem that different Query-based aggregation and causal relationship extraction seems useful Dynamic Instrumentation seems useful enough that it could be applied more effectively to tracing, but the fact that it isn’t widely used suggests there might be some drawbacks It would be interesting to read through all the distributed tracing papers by these authors The Benefit of Hindsight: Tracing Edge-Cases in Distributed Systems L. Zhang, Z. Xie, V. Anand, Y. Vigfusson, and J. Mace, “The benefit of hindsight: Tracing Edge-Cases in distributed systems,” in 20th USENIX Symposium on Networked Systems Design and Implementation (NSDI 23). Boston, MA: USENIX Association, Apr. 2023, pp. 321–339. [Online]. Available: https://www.usenix.org/conference/nsdi23/presentation/zhang-lei Overview A distributed tracing method that performs tail sampling with low overhead by retroactively collecting trace data only when triggered, enabling collection of edge-case traces The authors are primarily from MPI-SWS and work on distributed systems and cloud-related topics; the last author is the researcher behind Pivot Tracing, Canopy, and Tracing Plane Thoughts Being a cutting-edge top-conference paper from a group that has published many well-known distributed tracing papers, the classification and history of distributed tracing was explained clearly and was very educational They seem to have put effort into the data structures for managing trace data before collection, but couldn’t this alone improve conventional tail sampling to some extent? If you add a trigger that samples randomly, could you also do non-edge-case tracing simultaneously? Looking at the results, it seems that collecting all trace data has a greater impact on overhead than tracing all requests. If that’s the case, head sampling might be better than tail sampling for non-edge-case tracing. By integrating well with OpenTelemetry instrumentation, existing systems could be traced, increasing the number of evaluation targets – something I’d like to reference Canopy: An End-to-End Performance Tracing And Analysis System J. Kaldor, J. Mace, M. Bejda, E. Gao, W. Kuropatwa, J. O’Neill, K. W. Ong, B. Schaller, P. Shan, B. Viscomi, V. Venkataraman, K. Veeraraghavan, and Y. J. Song, “Canopy: An end-to-end performance tracing and analysis system,” in Proceedings of the 26th Symposium on Operating Systems Principles, ser. SOSP ‘17. New York, NY, USA: Association for Computing Machinery, 2017, p. 34–50. [Online]. Available: https://doi.org/10.1145/3132747.3132749 Overview An annotation-based tracing system that separates instrumentation from analysis, making each customizable, enabling low-level tracing across applications with different characteristics while allowing high-level modeling through aggregation Research by J. Mace (of Pivot Tracing) and a group at Facebook Thoughts The separation of instrumentation and analysis, and tracing across applications with different characteristics, which they emphasize, seem like things others have done as well Beyond that, it felt more like an introduction of Facebook’s system, and I couldn’t quite identify the novelty Is the contribution that they consolidated the individual techniques that others have also done? Is the novelty in collecting low-level log data in various formats and modeling it into a unified format that’s easy to aggregate? Large-Scale Cluster Management at Google with Borg A. Verma, L. Pedrosa, M. Korupolu, D. Oppenheimer, E. Tune, and J. Wilkes, “Large-scale cluster management at google with borg,” in Proceedings of the Tenth European Conference on Computer Systems, ser. EuroSys ‘15. New York, NY, USA: Association for Computing Machinery, 2015. [Online]. Available: https://doi.org/10.1145/2741948.2741964 Overview Proposes Borg, a cluster manager that runs hundreds of thousands of jobs from thousands of applications across multiple clusters spanning tens of thousands of machines, and discusses lessons learned from 10 years of operation at Google and how they were applied to Kubernetes Thoughts Learning about Borg’s design philosophy helped me understand the reasoning behind Kubernetes’ design. The discussion of the complex problem setting and how it influenced Kubernetes was particularly interesting. Borg gives the impression of being inferior to Kubernetes, which makes sense since Kubernetes was built with Borg as a reference, but I wonder what the current version of Borg used at Google looks like. --- # 分散トレーシング関連・Borgの論文紹介&感想 - URL: https://www.onoe.dev/blog/paper-reading-1/ - Language: ja - Published: 2023-04-29 - Tags: Paper, Distributed Tracing, Kubernetes > 4/25~4/29に読んだ分散トレーシング関連・Borgの論文の紹介&感想 https://joisino.hatenablog.com/ に影響を受けて出来るだけ毎日論文を読んでみることにした。 継続するためにも、書いた論文メモの抜粋と感想を公開する。 最初は自分の研究にも関係ある分散トレーシングの分野から有名な論文や開催されたばかりのNSDI'23の論文と、Kubernetesの元になったGoogleのBorgを紹介する。 info The English version of this article is available here. Dapper, a Large-Scale Distributed Systems Tracing Infrastructure B. H. Sigelman, L. A. Barroso, M. Burrows, P. Stephenson, M. Plakal, D. Beaver, S. Jaspan, and C. Shanbhag, “Dapper, a large-scale distributed systems tracing infrastructure,” Google, Inc., Tech. Rep., 2010. [Online]. Available: https://research.google.com/archive/papers/dapper-2010-1.pdf 概要 アノテーションベースの分散トレーシングツールを提案し、Googleでの活用事例を紹介 低オーバーヘッド・アプリケーションレベルの透過性・スケーラビリティ・オンライン解析を実現 感想 全てのサービスが共通のRPCの仕組みで通信しているというのは今でもやっていることなのか?今だとそれぞれの通信プロトコルに対する計装が必要なのでは Googleみたいな大規模なシステムでアプリ開発のフレームワークが共通化されてるのはすごい 今の分散トレース・APMに関する論文・ツールのメジャーはTrace IDを伝播する形式だが、初期の頃に一番大規模にやったのがDapperなのかな Pivot Tracing: Dynamic Causal Monitoring for Distributed Systems J. Mace, R. Roelke, and R. Fonseca, “Pivot tracing: Dynamic causal monitoring for distributed systems,” in Proceedings of the 25th Symposium on Operating Systems Principles, ser. SOSP ’15. New York, NY, USA: Association for Computing Machinery, 2015, p. 378–393. [Online]. Available: https://doi.org/10.1145/2815400.2815415 概要 記録するメトリクスを動的に決定でき、かつシステム境界を超えてイベントの因果関係を記録できるトレーシングシステム 著者はCanopyとかX-Traceとかを中心にそれぞれ分散トレーシングの有名な論文をたくさん出してる 感想 Dynamic Instrumentationのメリットであるトレースポイントを定義する手間と直接計装する手間の差はそこまで変わらんのでは クエリによる集計&因果関係の抽出は有用そう Dynamic Instrumentation便利そうなのでトレーシングにもっと上手く使えそうだがあまり使われてないのは何かデメリットがあるのかな 著者が書いてる分散トレーシングの論文を一通り読むのも面白そう The Benefit of Hindsight: Tracing Edge-Cases in Distributed Systems L. Zhang, Z. Xie, V. Anand, Y. Vigfusson, and J. Mace, “The benefit of hindsight: Tracing Edge-Cases in distributed systems,” in 20th USENIX Symposium on Networked Systems Design and Implementation (NSDI 23). Boston, MA: USENIX Association, Apr. 2023, pp. 321–339. [Online]. Available: https://www.usenix.org/conference/nsdi23/presentation/zhang-lei 概要 トリガーされた時にのみ遡及的にトレースデータを収集することによって、低オーバーヘッドでテールサンプリングをし、エッジケースのトレースをデータを収集する分散トレーシング手法 著者はMPI-SWSを中心とした分散システム・クラウド関連の人たちで、last authorはPivot Tracing&Canopy&Tracing Planeの人 感想 最新のトップ会議論文&有名な分散トレーシングの論文たくさん出してるグループなだけあって、分散トレーシングの分類や歴史についてわかりやすく解説されてて勉強になった 収集前のトレースデータを管理するためのデータ構造を工夫してそうだが、これだけで従来のテールサンプリングもある程度改善しないのかな ランダムにサンプリングするトリガーも追加すると、エッジケースでないトレーシングも同時にできる? 結果を見る感じ、全リクエストをトレースすることより、全トレースデータを収集することの方がオーバーヘッドに影響が大きいのか。それならエッジケースでないトレーシングならテールよりヘッドサンプリングの方が良さそう。 OpenTelemetryの計装にうまく合わせることで、既存システムのトレースができ評価対象が増るので、参考にしたい Canopy: An End-to-End Performance Tracing And Analysis System J. Kaldor, J. Mace, M. Bejda, E. Gao, W. Kuropatwa, J. O’Neill, K. W. Ong, B. Schaller, P. Shan, B. Viscomi, V. Venkataraman, K. Veeraraghavan, and Y. J. Song, “Canopy: An end-to-end performance tracing and analysis system,” in Proceedings of the 26th Symposium on Operating Systems Principles, ser. SOSP ’17. New York, NY, USA: Association for Computing Machinery, 2017, p. 34–50. [Online]. Available: https://doi.org/10.1145/3132747.3132749 概要 計装と分析を分離しそれぞれをカスタマイズ可能にすることで、異なる性質のアプリで低レベルのトレースをしながら集約によって高レベルのモデリングを可能にしたアノテーションベースのトレーシング Pivot Tracingの人(J Mace)とFacebookのグループによる研究 感想 強調している計装と分析の分離・異なる性質のアプリ間のトレーシングは他でもやってそうだが それ以外はFacebookのシステムの紹介という感じで、新規性があまりわからなかった 他でもやってそうな個々の話をまとめたということ? 低レベルな様々な形式のログデータを収集して、集約しやすい統一の形式にモデリングすることが新しい? Large-Scale Cluster Management at Google with Borg A. Verma, L. Pedrosa, M. Korupolu, D. Oppenheimer, E. Tune, and J. Wilkes, “Large-scale cluster management at google with borg,” in Proceedings of the Tenth European Conference on Computer Systems, ser. EuroSys ’15. New York, NY, USA: Association for Computing Machinery, 2015. [Online]. Available: https://doi.org/10.1145/2741948.2741964 概要 数万台のマシンにまたがる複数のクラスタ上で、数千個のアプリからなる数十万のジョブを走らせるクラスタマネージャーBorgの提案と、Googleで10年間運用してきた教訓をどのようにKubernetesに生かしたかの話 感想 Borgの設計思想を知ることでKubernetesの設計の理由も知れた気がする。特に複雑な問題設定・Kubernetesにどう生かしたかの話は面白かった。 BorgはKubernetesに劣っている印象で、KubernetesがBorgを参考にしているのだから当たり前だが、今のGoogleで使っているBorgはどうなってるんだろうか。 --- # Review: 'Kubernetes Complete Guide, 2nd Edition' (Kubernetes完全ガイド 第2版) - URL: https://www.onoe.dev/en/blog/review-k8s-perfect-guide/ - Language: en - Published: 2023-02-04 - Tags: Book, Kubernetes > A review of 'Kubernetes Complete Guide, 2nd Edition' (Kubernetes完全ガイド 第2版) I received this book about a year ago, but I finally made time to read it, so here is my review. Since it is a reference-style book, I picked and chose only the topics I was less familiar with. info この記事の日本語版はこちらです。 I had never systematically studied Kubernetes before and had been getting by with fragmented knowledge, so reading this book really helped fill in the gaps. In particular, I learned a lot about (Cron)Jobs, Service APIs, PVCs, security-related topics, and logging, which were areas I was less familiar with. I think this book is well suited for people who already have some knowledge of Kubernetes and want to fill in gaps or use it as a reference. It explains the necessary information in a systematic way. Each topic is covered with an explanation followed by examples with hands-on exercises, so it is not just a dry enumeration of facts and is easy to absorb. The target version is 1.18, which is somewhat dated, but most of the core concepts of Kubernetes have not changed, so there is still plenty to learn. On the other hand, this is probably not the right book for someone with no prior knowledge of Kubernetes. It is a bit too detailed and might be overwhelming. I personally started with “Shikumi ga Wakaru Kubernetes” (しくみがわかるKubernetes)1, and I think a book like that, where you learn step by step while actually running things, is better as a first read. Also, as stated in the preface, this book focuses on elements that application developers are likely to use — in other words, elements required for the CKAD certification. Therefore, the coverage of Kubernetes architecture is limited to an overview of individual components. For example, if you want to implement a custom operator or contribute to Kubernetes itself, you will need additional resources. Regardless of your role, this book systematically covers the essential knowledge that anyone working with Kubernetes should have, and it lives up to its reputation as an excellent book. I chose it simply because it was the only Kubernetes book available at my university library, but it is a good book too. The only slight downside is that it uses Azure instead of AWS or GCP. ↩︎ --- # 『Kubernetes完全ガイド 第2版』を読んだ - URL: https://www.onoe.dev/blog/review-k8s-perfect-guide/ - Language: ja - Published: 2023-02-04 - Tags: Book, Kubernetes > 『Kubernetes完全ガイド 第2版』を読んだのでレビュー 1年程前にいただいたのだが、やっと時間を作って読んだのでレビュー。辞書的な本なので、自分が知らなさそうなテーマだけつまみ食い。 info The English version of this article is available here. Kubernetesについては体系的に学んだことがなく、断片的な知識だけで生きてきたので、この本を読んでその間をしっかり埋められた。 特に自分があまり知らなかった(Cron)Job, Service APIs周り, PVC, セキュリティ関連、ロギングについてはとても勉強になった。 Kubernetesについてある程度知っている人が知識を埋めたり、辞書として活用するのに適した本だと思う。 必要な情報を体系的に説明してくれる。 またそれぞれの情報について解説→例を出して実際に動作といった感じで進んでいき、単なる羅列ではないので頭に入ってきやすい。 対象のバージョンが1.18と若干古いが、Kubernetesのコアとなる大半の概念については変わらないので十分学べる。 一方でKubernetesについて全く知らない人が最初に読む本ではなさそう。少し詳しすぎるので挫折するような気がする。自分は最初に『しくみがわかるKubernetes』を読んだが1、こんな感じで順に動かしながら学んでいく本の方が最初に読むのに良さそう。 また「はじめに」にも書いてあるとおり、この本はアプリケーション開発者が利用する可能性のある要素、言い換えるとCKADに必要な要素を中心に解説している。なのでKubernetesのアーキテクチャについては、個々のコンポーネントの概要について解説するにとどまっている。例えばカスタムオペレータを実装したいとか、Kubernetes本体にコントリビュートしたいとかの場合は追加の情報が必要になる。 どんな立場であれKubernetesに関わる人が必ず知っておくべき内容が体系的に纏まっており、評判通り良本である。 選んだ理由は大学の図書館にこれしかなかったからだがこれも良い本。AWSやGCPではなくAzureを使うのが若干微妙だが。 ↩︎ --- # [Go] Digging Up net.Conn: http and mysql Edition - URL: https://www.onoe.dev/en/blog/go-net-conn/ - Language: en - Published: 2023-02-02 - Tags: Tech, Go, Network > How to extract net.Conn from Go's net/http and go-sql-driver/mysql to directly read and write HTTP and MySQL connections This article explains how to extract net.Conn from Go’s net/http and go-sql-driver/mysql to directly read and write HTTP and MySQL connections. While rarely needed in typical library usage, this can be useful when you want to rewrite payloads at the transport layer level. info この記事の日本語版はこちらです。 net/http We will cover both Client and Server. Client The http.Client struct has a Transport field. This field determines the transport layer used for communication. By default, DefaultTransport is used, which is an instance of the Transport struct. We use the DialContext (or DialTLSContext) field of this Transport struct to access net.Conn. DialContext is a function that determines how TCP connections are created. By default, it uses the DialContext method of net.Dialer. DialTLSContext works the same way but targets encrypted connections using TLS. Create a wrapper function around DialContext (or DialTLSContext) as shown below, and access net.Conn within it. For example, you can store the necessary values in a Context and use them to write to net.Conn. type valueKeyType int var valueKey contextKeyType = iota func main(){ client := http.Client{ Transport: wrapTransport(nil) } // Store an arbitrary value in the Context and attach it to the request ctx := context.WithValue(context.Background(), valueKey, "test") req, err := http.NewRequestWithContext(ctx, "GET", "http://~", nil) if err != nil { log.Fatal(err) } resp, err := client.Do(req) ... } func wrapTransport(base *http.Transport) *http.Transport { if base == nil { base = http.DefaultTransport.(*http.Transport) } t.DialContext = wrapDialContext(base.DialContext) t.DialTLSContext = wrapDialContext(base.DialTLSContext) return t } func wrapDialContext(dc func(ctx context.Context, network, addr string) (net.Conn, error)) func(ctx context.Context, network, addr string) (net.Conn, error) { if dc == nil { return nil } return func(ctx context.Context, network, addr string) (net.Conn, error) { conn, err := dc(ctx, network, addr) if err != nil { return nil, err } // Extract the value from the Context value := ctx.Value(valueKey) // Perform operations on net.Conn conn.Write([]byte(value)) return conn, err } } Note that you need to be careful about Keep-Alive. HTTP reuses connections as much as possible, sending multiple requests and responses over a single connection. This means the extracted net.Conn may be shared across multiple requests. To prevent this, set DisableKeepAlives to true in the Transport struct. func wrapTransport(base *http.Transport) *http.Transport { ... t.DisableKeepAlives = true ... } Server By using the ConnContext field of the http.Server struct to include net.Conn in the Context, you can access net.Conn through the Context when handling requests. type connKeyType int var connKey connKeyType = iota func main() { http.HandleFunc("/", handler) server := &http.Server{ Addr: ":8080", ConnContext: ConnContext, } log.Fatal(server.ListenAndServe()) } func handler(w http.ResponseWriter, r *http.Request) { ctx := r.Context() conn := ctx.Value(connKey) conn.Write([]byte("test")) ... } func ConnContext(ctx context.Context, conn net.Conn) context.Context { return context.WithValue(ctx, connKey, conn) } Note that how you extract the Context from http.Request in the handler differs depending on the framework (e.g., Echo). go-sql-driver/mysql go-sql-driver/mysql is a client for communicating with MySQL servers in Go. It is implemented as a driver for database/sql, which provides a common interface for SQL-related operations. Similar to net/http.Client, this library also provides a DialContext function, which we can use. type valueKeyType int var valueKey contextKeyType = iota func main() { // Use "mytcp" as the network in the Data Source Name when opening the DB (you can also use "tcp") mysql.RegisterDialContext("mytcp", DialContext("tcp")) db, err := sql.Open("mysql", "user:password@mytcp(localhost:3306)/database") if err != [ log.Fatal(err) ] defer db.Close() // Store an arbitrary value in the Context and attach it to the request ctx := context.WithValue(context.Background(), valueKey, "test") _, err := db.ExecContext(ctx, "INSERT INTO ...") ... } func DialContext(netP string) mysql.DialContextFunc { return func(ctx context.Context, addr string) (net.Conn, error) { // The default TCP connection creation is hardcoded, so we replicate it here // ref: https://github.com/go-sql-driver/mysql/blob/bcc459a906419e2890a50fc2c99ea6dd927a88f2/connector.go#L48-L49 nd := net.Dialer{} conn, err := nd.DialContext(ctx, netP, addr) if err != nil { return nil, err } // Extract the value from the Context value := ctx.Value(valueKey) // Perform operations on net.Conn conn.Write([]byte(value)) return conn, err } } Similar to Keep-Alive in net/http.Client, MySQL also reuses connections by default. To prevent this, set MaxIdleConns to 0. func main() { ... defer.db.Close() db.SetMaxIdleConns(0) ... } Note that transport layer implementation depends on each driver, so you cannot achieve the same thing with database/sql alone (not limited to MySQL). Conclusion This article covered net/http and go-sql-driver/mysql. If other libraries also provide functions like DialContext or ConnContext, you may be able to do the same thing with them. I would like to investigate and write about those if I get the chance. I needed this for my research, but there were no reference articles available, so I figured it out by reading the library source code. This is admittedly a niche topic, but precisely because of that, I hope it proves useful when someone needs it. 2023-12-02 Update: I gave a talk about this topic at Go Conference mini 2023 Winter IN KYOTO, so please check out the slides as well. References https://pkg.go.dev/net/http https://github.com/go-sql-driver/mysql --- # 【Go】net.Connを掘り起こす http, mysql編 - URL: https://www.onoe.dev/blog/go-net-conn/ - Language: ja - Published: 2023-02-02 - Tags: Tech, Go, Network > Goのnet/http, go-sql-driver/mysqlからnet.Connを取り出し、HTTPやMySQLのコネクションを直接読み書きする方法を紹介する Goのnet/http, go-sql-driver/mysqlからnet.Connを取り出し、HTTPやMySQLのコネクションを直接読み書きする方法を紹介する。通常のライブラリ利用ではほとんど必要ないが、トランスポート層レベルでペイロードを書き換えたい時などに役立つ。 info The English version of this article is available here. net/http Client, Serverそれぞれについて説明する。 Client http.Client構造体はTransportフィールドを持つ。このフィールドは通信に使用するトランスポート層を定めるものである。デフォルトではDefaultTransportが使用され、これはTransport構造体のインスタンスである。 このTransport構造体のDialContext(DialTLSContext)フィールドを利用してnet.Connにアクセスする。 DialContextはTCPコネクションの生成方法を定める関数である。デフォルトではnet.DialerのDialContextを使用する。DialTLSContextも同様だが、こちらはTLSを用いて暗号化された通信を対象とする。 以下のようにDialContext(DialTLSContext)をwrapする関数を作成し、その中でnet.Connにアクセスする。 例えば必要な値をContextに含めておいて、それを利用してnet.Connに書き込むといったことができる。 type valueKeyType int var valueKey contextKeyType = iota func main(){ client := http.Client{ Transport: wrapTransport(nil) } // Contextに任意の値を含めておいてリクエストに付与 ctx := context.WithValue(context.Background(), valueKey, "test") req, err := http.NewRequestWithContext(ctx, "GET", "http://~", nil) if err != nil { log.Fatal(err) } resp, err := client.Do(req) ... } func wrapTransport(base *http.Transport) *http.Transport { if base == nil { base = http.DefaultTransport.(*http.Transport) } t.DialContext = wrapDialContext(base.DialContext) t.DialTLSContext = wrapDialContext(base.DialTLSContext) return t } func wrapDialContext(dc func(ctx context.Context, network, addr string) (net.Conn, error)) func(ctx context.Context, network, addr string) (net.Conn, error) { if dc == nil { return nil } return func(ctx context.Context, network, addr string) (net.Conn, error) { conn, err := dc(ctx, network, addr) if err != nil { return nil, err } // Contextから値を取り出す value := ctx.Value(valueKey) // net.Connに対する処理 conn.Write([]byte(value)) return conn, err } } なおKeep Aliveには注意する必要がある。HTTPでは可能な限りコネクションを再利用し、一つのコネクションで複数のリクエスト・レスポンスをやり取りする。 つまり取り出したnet.Connは複数のリクエストで使いまわされている可能性がある。 防ぎたい場合は、Transport構造体のDisableKeepAlivesをtrueにする。 func wrapTransport(base *http.Transport) *http.Transport { ... t.DisableKeepAlives = true ... } Server http.Server構造体のConnContextフィールドを利用してnet.ConnをContextに含めることで、handleする際にContextを通じてnet.Connにアクセスできるようにする。 type connKeyType int var connKey connKeyType = iota func main() { http.HandleFunc("/", handler) server := &http.Server{ Addr: ":8080", ConnContext: ConnContext, } log.Fatal(server.ListenAndServe()) } func handler(w http.ResponseWriter, r *http.Request) { ctx := r.Context() conn := ctx.Value(connKey) conn.Write([]byte("test")) ... } func ConnContext(ctx context.Context, conn net.Conn) context.Context { return context.WithValue(ctx, connKey, conn) } なおhandlerでhttp.RequestからContextを取り出す処理は、echoなどのフレームワークごとにやり方が異なる。 go-sql-driver/mysql go-sql-driver/mysqlはGoでMySQLサーバーに通信する際のクライアントとなる。SQL関連のインターフェースをまとめるdatabase/sqlのdriverとして実装される。 net/http.Clientと同様に、このライブラリにもDialContextが用意されている。それを利用する。 type valueKeyType int var valueKey contextKeyType = iota func main() { // "mytcp"をDBオープン時のData Sourse Nameに使用する("tcp"を指定してもよい) mysql.RegisterDialContext("mytcp", DialContext("tcp")) db, err := sql.Open("mysql", "user:password@mytcp(localhost:3306)/database") if err != nil [ log.Fatal(err) ] defer db.Close() // Contextに任意の値を含めておいてリクエストに付与 ctx := context.WithValue(context.Background(), valueKey, "test") _, err := db.ExecContext(ctx, "INSERT INTO ...") ... } func DialContext(netP string) mysql.DialContextFunc { return func(ctx context.Context, addr string) (net.Conn, error) { // デフォルトのTCPコネクションの生成がハードコードされているのでこちらにも書く // ref: https://github.com/go-sql-driver/mysql/blob/bcc459a906419e2890a50fc2c99ea6dd927a88f2/connector.go#L48-L49 nd := net.Dialer{} conn, err := nd.DialContext(ctx, netP, addr) if err != nil { return nil, err } // Contextから値を取り出す value := ctx.Value(valueKey) // net.Connに対する処理 conn.Write([]byte(value)) return conn, err } } net/http.ClientのKeep Aliveと同様に、MySQLでもデフォルトではコネクションを再利用する。防ぎたい場合は、MaxIdleConnsを0にする。 func main() { ... defer.db.Close() db.SetMaxIdleConns(0) ... } なおトランスポート層レベルでの実装はそれぞれのdriver依存なので、MySQLに限らないdatabase/sqlで同様の実装はできない。 まとめ 今回はnet/httpとgo-sql-driver/mysqlについて紹介した。それ以外のライブラリでもDialContext, ConnContextといった関数が用意されていれば同様のことが可能かもしれない。 気が向けば調べて記事にしたい。 研究で必要になったが参考になる記事がなく、ライブラリのコードを読んで調べた。 なかなか需要のない話ではあるが、だからこそ必要になった時に役立てばうれしい。 2023-12-02追記: Go Conference mini 2023 Winter IN KYOTOでこの内容について話したのでスライドもご覧ください。 参照 https://pkg.go.dev/net/http https://github.com/go-sql-driver/mysql --- # Creating a Bridge-Connected VM with KVM - URL: https://www.onoe.dev/en/blog/kvm-bridge/ - Language: en - Published: 2022-11-05 - Tags: Tech, Ubuntu, KVM, Network > Step-by-step guide to creating a bridge-connected VM using KVM, with a focus on the tricky parts I created a VM using KVM on an Ubuntu PC at home. To allow SSH access to the VM from external hosts, I connected the VM to a bridge network created on the host. I ran into several issues along the way, so I’m documenting the entire procedure here. info この記事の日本語版はこちらです。 Environment Host OS Ubuntu 22.04.1 LTS Guest OS Ubuntu 20.04.5 LTS Procedure I’ll skip the steps for installing KVM on Ubuntu (refer to the official instructions). Create a Bridge I referenced this article. Remove the Default Network When you install KVM, a bridge called virbr0 should already exist by default. This bridge uses NAT to allow the VM to access external networks. With this bridge, communication between the host and VM is possible, but external hosts cannot access the VM. Since we don’t need it for our purposes, let’s remove it. The default configuration is registered as “default” in KVM, so we delete it. $ virsh net-destroy default $ virsh net-undefine default Create a Bridge with netplan and Attach the NIC We’ll use netplan. Edit an existing file or create a new one under /etc/netplan. The host NIC enxf8e43bb371e8 has a static IP address of 192.168.0.130, so we’ll use that address. # /etc/netplan/99_config.yaml network: version: 2 ethernets: enxf8e43bb371e8: dhcp4: false dhcp6: false bridges: br0: interfaces: [enxf8e43bb371e8] addresses: [192.168.0.130/24] gateway4: 192.168.0.1 nameservers: addresses: [192.168.0.1, 8.8.8.8] parameters: stp: false dhcp4: false dhcp6: false $ sudo netplan apply This creates the new bridge br0. The IP address that was previously assigned to enxf8e43bb371e8 now appears under br0. Also, the master br0 label on the enxf8e43bb371e8 entry confirms that the NIC is properly connected to the bridge. $ ip a 1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: enxf8e43bb371e8: mtu 1500 qdisc fq_codel master br0 state UP group default qlen 1000 link/ether f8:e4:3b:b3:71:e8 brd ff:ff:ff:ff:ff:ff (skip) 16: br0: mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 72:e2:b6:e9:05:35 brd ff:ff:ff:ff:ff:ff inet 192.168.0.130/24 brd 192.168.0.255 scope global noprefixroute br0 valid_lft forever preferred_lft forever inet6 fe80::70e2:b6ff:fee9:535/64 scope link valid_lft forever preferred_lft forever Register the bridge with KVM. Create the following host-bridge.xml file. host-bridge Register it with KVM. $ virsh net-define host-bridge.xml $ virsh net-start host-bridge $ virsh net-autostart host-bridge Verify that it’s registered correctly. $ virsh net-list --all Name State Autostart Persistent ------------------------------------------------ host-bridge active yes yes Disable Bridge Netfilter info Addendum on June 12, 2024 The root cause of the VM not being able to reach external networks was that iptables was set to DENY the FORWARD chain. Therefore, this issue can also be resolved by changing the default policy for FORWARD to ACCEPT. This is where I got stuck. Even after creating the VM in the next step, it couldn’t reach external networks. The cause was Bridge Netfilter blocking traffic through the bridge. Following this article, I disabled Bridge Netfilter on the host. Add the following line to /etc/sysctl.conf. (Depending on your environment, a similar setting may already exist in /etc/sysctl.conf or /etc/sysctl.d/*, so be sure to check.) net.bridge.bridge-nf-call-iptables = 0 Apply the changes. $ sudo sysctl -p Verify the update was applied correctly. $ sudo sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-iptables = 0 Create a VM Create a VM with Ubuntu 20.04. Adjust the memory, CPU cores, disk size, etc. as needed. $ virt-install \ --name vm1 \ --ram=2048 \ --disk size=10 \ --network network=host-bridge \ --vcpus 1 \ --os-type linux \ --os-variant ubuntu20.04 \ --graphics none \ --location 'http://archive.ubuntu.com/ubuntu/dists/focal/main/installer-amd64/' \ --extra-args "netcfg/disable_autoconfig=true console=ttyS0,115200n8 --- console=ttyS0,115200n8" An important note here is the console=ttyS0,115200n8 --- console=ttyS0,115200n8 part in --extra-args. If you’re doing everything via CLI, you’ll need to connect to the VM later with virsh console vm1 to configure SSH and other settings. Without enabling serial console access, you won’t be able to interact with the VM at all. For more details, see this article. Also, netcfg/disable_autoconfig=true prevents the installer from automatically assigning an address via DHCP during installation. Whether this is necessary depends on your environment, but in my case I wanted to assign a static address, so I used this option. (Reference) I looked into whether it was possible to specify a static address at this point, and while it seems possible on RHEL, I couldn’t find a way to do it on Ubuntu. Ubuntu Installation An installation wizard will appear, so follow the on-screen instructions to complete the installation. Configure SSH After installation, if the console connection is still active, you can proceed directly. Otherwise, connect manually. $ virsh console vm1 Install the SSH server and register your keys. $ sudo apt update $ sudo apt install openssh-server $ mkdir ~/.ssh $ wget "https://github.com/hiroyaonoe.keys" -O ~/.ssh/authorized_keys After exiting, you should be able to SSH in using the username and IP address you specified during installation (if using DHCP, look up the assigned address accordingly). Conclusion Networking configuration is tricky. In particular, the Bridge Netfilter issue took a long time to resolve, so I hope this helps someone. Now that I have VM infrastructure set up on my home Ubuntu PC, I’d like to try building a Kubernetes cluster on it. --- # KVMでホストとブリッジ接続したVMを作成する - URL: https://www.onoe.dev/blog/kvm-bridge/ - Language: ja - Published: 2022-11-05 - Tags: Tech, Ubuntu, KVM, Network > KVM を使ってホストとブリッジ接続したVMを作成する手順を、詰まった箇所を中心に記録しておく 家にあるUbuntuPCでKVMを使ってVMを作成した。VMを外部ホストからsshで接続できるようにするため、ホストで作成したブリッジネットワークにVMを接続したが、色々と詰まったので全体の手順と一緒にまとめておく。 info The English version of this article is available here. 環境 ホストOS Ubuntu 22.04.1 LTS ゲストOS Ubuntu 20.04.5 LTS 手順 UbuntuにKVMを入れる手順は省略(公式の手順を参照) ブリッジを作成 こちらを参考にした。 デフォルトのネットワークを削除する KVMをインストールすると、デフォルトでvirbr0というブリッジが作成されているはずである。 このブリッジはNATを使うことでVMが外のネットワークにアクセスできるようにしている。 これがあればホストとVMの間では通信ができるが、外部ホストからVMにアクセスすることはできない。今回は不要なので削除する。 KVM側ではdefaultという設定として記述されているのでこれを削除する。 $ virsh net-destroy default $ virsh net-undefine default netplanでブリッジを作成しNICを接続する netplanを使う。/etc/netplan 配下のファイルで適当なものを修正するか新しく作成する。 ホストのNIC enxf8e43bb371e8 には静的に 192.168.0.130 を割り当てているのでこのアドレスを使う。 # /etc/netplan/99_config.yaml network: version: 2 ethernets: enxf8e43bb371e8: dhcp4: false dhcp6: false bridges: br0: interfaces: [enxf8e43bb371e8] addresses: [192.168.0.130/24] gateway4: 192.168.0.1 nameservers: addresses: [192.168.0.1, 8.8.8.8] parameters: stp: false dhcp4: false dhcp6: false $ sudo netplan apply すると新しいブリッジ br0 が作成される。最初は enxf8e43bb371e8 についていたIPアドレスの記載が br0に移る。また enxf8e43bb371e8 の欄に master br0 と書いてあるので、ちゃんとNICがブリッジに接続されていることがわかる。 $ ip a 1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: enxf8e43bb371e8: mtu 1500 qdisc fq_codel master br0 state UP group default qlen 1000 link/ether f8:e4:3b:b3:71:e8 brd ff:ff:ff:ff:ff:ff (skip) 16: br0: mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 72:e2:b6:e9:05:35 brd ff:ff:ff:ff:ff:ff inet 192.168.0.130/24 brd 192.168.0.255 scope global noprefixroute br0 valid_lft forever preferred_lft forever inet6 fe80::70e2:b6ff:fee9:535/64 scope link valid_lft forever preferred_lft forever KVMにブリッジを認識させる。以下の host-bridge.xml を作成する。 host-bridge KVMに登録する。 $ virsh net-define host-bridge.xml $ virsh net-start host-bridge $ virsh net-autostart host-bridge 正しく登録されていることを確認。 $ virsh net-list --all Name State Autostart Persistent ------------------------------------------------ host-bridge active yes yes Bridge Netfilterを無効化する info 2024年6月12日追記 ここでVMが外部ネットワークに繋がらない根本原因はiptablesでFORWARDをDENYしていたことが原因だった。 なのでFORWARDのデフォルトポリシーをACCEPTに変更することでも解決可能である。 ここで詰まった。この後VMを作成してもVMが外部のネットワークに繋がらない。 原因はBridge Netfilterがブリッジを介した通信を遮断しているせいだった。 こちらを参考にホストでBridge Netfilerを無効化する。 /etc/sysctl.conf に以下の行を追記。(環境によっては同様の設定が /etc/sysctl.conf や /etc/sysctl.d/* にあるかもしれないので要確認) net.bridge.bridge-nf-call-iptables = 0 以下を実行して更新。 $ sudo sysctl -p 正しく更新されたことを確認。 $ sudo sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-iptables = 0 VMを作成 Ubuntu20.04 でVMを作成する。メモリやコア数、ディスクサイズなどは適宜設定する。 $ virt-install \ --name vm1 \ --ram=2048 \ --disk size=10 \ --network network=host-bridge \ --vcpus 1 \ --os-type linux \ --os-variant ubuntu20.04 \ --graphics none \ --location 'http://archive.ubuntu.com/ubuntu/dists/focal/main/installer-amd64/' \ --extra-args "netcfg/disable_autoconfig=true console=ttyS0,115200n8 --- console=ttyS0,115200n8" ここで要注意なのが --extra-args の console=ttyS0,115200n8 --- console=ttyS0,115200n8 である。 全ての設定をCLIのみで行う場合、後程 virsh console vm1 でVMに接続してsshなどの設定を行う必要があるが、コンソールにシリアル接続できるようにしておかないと何も動かなくなる。 詳しい話はこちら。 また netcfg/disable_autoconfig=true はインストール時に自動でDHCPで動的にアドレスを指定しようとするのを防ぐ。必要かどうかは環境によるが、今回は静的にアドレスを指定したかったのでこうする。(参照) ここで静的にアドレスを指定できないのかと調べたが、RHELでは指定できそうだがUbuntuでは見つからなかった。 Ubuntuインストール ウィザードが出てくるので説明手順に従ってインストールする。 sshの設定をする インストール完了後、そのままコンソールに接続してくれたならそれで良いが、そうでない場合は手動で接続する。 $ virsh console vm1 sshサーバーをインストールして、鍵を登録する。 $ sudo apt update $ sudo apt install openssh-server $ mkdir ~/.ssh $ wget "https://github.com/hiroyaonoe.keys" -O ~/.ssh/authorized_keys これでexitしてから、インストール時に指定したユーザー名とIPアドレス(DHCPの場合はよしなに調べる)を使えばssh接続できるはずである。 まとめ ネットワーク周りの設定は難しい。特にBridge Netfilterの設定は解決に時間がかかったので役にたてばうれしい。 これで自宅のUbuntuPCにVMを立てる用意が整ったので、Kubernetesクラスタ立てるとかやってみたい。 ---