生成AI時代の学習の10ステッププロセスを再考する
この記事はフィヨルドブートキャンプ Advent Calendar 2025の23日目の記事です。 昨日は、LEFさんのRails の find はどう動く? Active Recordの内部実装を見てみよう! - LEFログでした。
この記事では、SOFT SKILLSで紹介されている「学習の10ステップ」を、生成AI時代の視点で再整理します。
- はじめに
- 学習の10ステッププロセス
- 10ステッププロセスとフィヨルドブートキャンプの学習プロセスとの類似性
- 独学における10ステッププロセス
- AIツールの整理
- AI活用した10ステッププロセス
- おわりに
はじめに
『SOFT SKILLS ソフトウェア開発者の人生マニュアル』という書籍には、スキルを素早く習得するための「学習の10ステッププロセス」が紹介されています。この書籍は第一版が2016年、第二版が2022年の2月頃に発売されました。 ChatGPTが登場した2022年11月以前に書かれた書籍であるため、現在のような生成AI(以下、AI)については考慮されていません。 そこで、現在のようなAI時代に合わせて、AIを活用した10ステッププロセスを考えてみるというのが本記事の内容になります。
学習の10ステッププロセス
まず10ステッププロセスについて簡単に書きます。プロセス前半の①〜⑥までのステップが準備フェーズで、 プロセス後半の⑦〜⑩が実行フェーズになります。
準備フェーズではざっくりと学習内容のカリキュラムを作り、実行フェーズでは作成したカリキュラムに沿って学習を進めます。
準備フェーズ
①全体像をつかむ
学習範囲を決める前に学習テーマの全体像をざっくりと知る必要があります。Webサイトで検索して記事を流し読みしたり、 書籍の目次や最初の章など読んで対象の大きさを知ります。ここでは深掘りはせずにざっくりと調べる程度でよいと述べられていました。
②学習範囲を決める
ざっくりと学習対象となる全体像の地図を手にしたら、学習テーマをサブテーマに分解して、 学習対象を絞り込みます。書籍では、デジタルカメラ撮影を学習テーマとした場合に、常識的な時間内にデジタルカメラ撮影のすべてを網羅的に学ぶことは不可能なので、たとえばポートレイト写真の撮り方を知りたいならそれを学習範囲に指定します。
③成功の基準を決める
何ができるようになったらできたと言えるかを先に決めます。 たとえば、Rubyで主要な言語機能を利用したCLIアプリをかけるようになるなどです。
④参考資料を探す
Webサイトの記事や書籍、動画などの学習範囲に関連した資料をできるだけたくさん収集します。 ⑥のステップで最良の参考資料を絞り込むため、ブレーンストーミングのアイデア出しのように質を問わずに できるだけ収集することがよいと述べられています。
⑤学習プランを立てる
④で収集した参考資料をもとに学習プランを立てます。これは、RubyでCLIアプリを作ることが学習範囲の場合、rbenvやlsコマンド、wcコマンドなどのサブテーマ(プラクティス)に分割します。分割したサブテーマをどの順番に学習するかも学習プランを立てる時に考慮します。
ここで分割したサブテーマごとに、実行フェーズの⑦〜⑩を繰り返します。
⑥参考資料を絞り込む
⑤で作成した学習プランのサブテーマに役立つ参考資料を、④で収集した資料の中から絞り込みます。
実行フェーズ
準備フェーズの学習プランの各サブテーマごとに実行します。
⑦ある程度使えるようにするための方法を学ぶ
Hello Worldのやり方だったり、インストールのセットアップ方法などが例に挙げられます。 収集した参考資料からやり方を探し出して実行します。
⑧遊び倒す
基本的な使い方を学んだら、自由に触って学びます。 ここでいろいろ触って思い浮かんだ疑問を⑨で収集した参考資料をもとに解消します。
⑨役に立つことができるところまで学ぶ
⑧のステップで浮かんだ疑問などを解消します。 ここでは、②で決めた成功の基準と照らし合わせて、目標を達成したかも判断します。 目標を達成していない場合は、参考資料などで学んだ後に ⑧〜⑨のステップを繰り返すことになるかと思います。
⑩教える
最後にブログやLTの発表、日報などで学んだことをアウトプットします。 学んだことをアウトプットすることを通じて、学習範囲の内容を自分が理解できているかや記憶の定着などに効果があると考えています。
最近読んだ最高の勉強法という書籍に記載されている学習法に、精緻的質問や自己説明というテクニックがあります。 精緻的質問はなぜこうなるのか(Why)や、どのようにするか(How)などを自分に質問します。また、自己説明は学習内容の理解について自分に説明します。
これらのテクニックを意識してアウトプットすることで学習効果を高めることもできる気がします。また、この書籍には精緻的質問や自己説明以外に、アクティブリコールや分散学習、インターリービング、ファインマンテクニックといったテクニックついても記載されているので、個人的にはぜひ読んで欲しいなと思います。
10ステッププロセスとフィヨルドブートキャンプの学習プロセスとの類似性
ここまで学習の10ステッププロセスについて簡単に書きました。 この10ステッププロセスについて書籍を改めて再読し気付いた点になりますが、この学習プロセスはフィヨルドブートキャンプのカリキュラムで実施しているプロセスとほぼ同じなのではないか、という点です。
10ステッププロセスにおける準備フェーズは、ブートキャンプにおけるカリキュラムに相当するのではないかと考えています。 フィヨルドブートキャンプのカリキュラムは運営者が作成したものですが、生徒が効率的に学習してスキルアップできるよう、学習範囲・プラクティス・参考資料を含めたカリキュラム全体を、推奨される学習順に沿って設計しています。
たとえば、下記の添付図はRailsエンジニアコースのカリキュラム(①の全体像)の一部です。
(学習内容の全体はこちらで見ることができます)
このカリキュラムのGit & GitHubやRubyが学習範囲で、学習範囲内の項目が⑤の学習プランのサブテーマ(プラクティス)に相当します。それぞれの学習範囲の最初のプラクティスでは、カリキュラムの全体像における学習範囲の位置が地図のように分かるようになっています。

また、各プラクティスでは、実行フェーズの⑦を実行するための参考資料や⑧〜⑨を実行するための参考資料は十分に厳選して取り揃えられています。その他にプラクティスごとに終了条件が決められており、メンターによる提出物のレビューを通して、⑨の役に立つところまで学ぶを実行し、目標を達成することができます。
最後にフィヨルドブートキャンプの学習システムの日報の仕組みによって学んだことをアウトプットすることもでき、⑩の教えるも達成できるようになっています。その他にも、Q&Aや輪読会、LTの発表などアウトプットする機会が準備されています。
2021年頃の少し古いブログ記事ですが、フィヨルドブートキャンプの雰囲気を感じることができると思います。
フィヨルドブートキャンプでは、これまで文系出身の方や他分野の未経験の方を即戦力のWebエンジニアとしておよそ数年で育てることができるのか?と疑問に感じることもありましたが、10ステッププロセスのスキル習得のプロセスと本質的には同じであると感じています*1。このことからフィヨルドブートキャンプの学習プロセス(学習システム)の強みを発見することができた気がします。
独学における10ステッププロセス
基本的には、この学習の10ステッププロセスは独学でスキルを素早く学習するためのプロセスです。 フィヨルドブートキャンプの卒業後やフィヨルドブートキャンプの Railsエンジニアコース/フロントエンドコースのカリキュラム外のスキルを習得したい場合には、自分で①〜⑥の準備フェーズのカリキュラムを自分で作成する必要があります。また、メンターも不在になるため、自分でメンターを探すか、代替策を探して、実行フェーズにおける⑨のフィードバックも得られるとありがたいです。
現在はAI活用の時代になっているため、AIを活用して効率化する方法を考えたいと思います。 しかし、フィヨルドブートキャンプでは、提出物・チーム開発・自作サービスへのAI活用を禁止(成果物をAIに作ってもらうこと)はされているため、 フィヨルドブートキャンプでのこれらのAI活用を推奨するものではありません。
(フィヨルドブートキャンプにログイン可能な人のみ閲覧可能)
AIツールの整理
自分自身はそこまでAIツールは使い倒してはいないですが、AIツールは様々であるため、一旦 ここで想定するAIツールを整理したいと思います。
以前読んだ2024年に出版された書籍には、AIツールは3つの種類で分類されています。
| 類型 | 概要 | 代表的なAIツール |
|---|---|---|
| 対話型 | 人と自然言語で対話しながら、思考整理・理解支援・意思決定を助けるAI | ChatGPT、Claude、Gemini、Perplexity、NotebookLM、Dia(AIブラウザ)、DeepWiki、CodeWiki |
| 補完型 | 人のコーディング作業中にリアルタイムで候補や続きを提示し、生産性を高めるAI | GitHub Copilot、Gemini Code Assist、Cursor |
| エージェント型 | 目的を与えると、タスク分解から実行までを自律的に行うAI | Claude Code、Codex、Gemini CLI、GitHub Copilot、Cursor |
使用していないAIツールもあり、正確ではないかもしれません。現在は完全に3つのいずれかに分類されるというよりも、複数のカテゴリを併せ持つAIツールが増えている気がします。
自分が主に使用しているAIツールは以下になります。
| AIツール名 | 種別 | 主な用途 |
|---|---|---|
| ChatGPT | 対話型 | 調査 。たまにDeep Researchを利用 |
| Gemini | 対話型 | 調査。たまにDeep Researchを利用 |
| NotebookLM | 対話型(資料特化) | 調査。複数のリンクやYouTube動画をもとに概要を把握。音声概要やスライド生成など |
| DeepWiki | 対話型(+エージェント型、調査特化) | リポジトリ全体を解析し、構造・設計・依存関係をWiki形式で自動整理・説明 |
| CodeWiki | 対話型(+エージェント型、調査特化) | DeepWkiと同様 |
| Dia | 対話型(ブラウザ) | ブラウザに組み込まれたチャットUIで閲覧しているサイトの要約や調査 |
| GitHub Copilot | 補完型(+対話型) | IDE内でのコード自動補完、コメント生成、簡易レビュー。Copilot Chatにより対話的操作も可能 |
| Claude Code | エージェント型 | コード生成・修正・リファクタリング、CLI/エディタ連携による開発支援。主にターミナルのCLIから利用 |
AI活用した10ステッププロセス
前節で整理したAIツールをもとに活用できる点を検討します。
準備フェーズ
①全体像をつかむ
学習テーマの全体像をつかむために、対話型のChatGPTで簡単に学習テーマについて質問したり、NotebookLMで学習テーマについて検索し、検索したソースをもとにざっくりと全体像をつかむことができるかと思います。

たとえば、Hotwire Railsで検索すると、関連するソースが表示されます。

これらのソースをインポートして、チャットで質問します。

他にも音声概要といったAI生成のポッドキャストを聴いたり、マインドマップやインフォグラフィックを見ることが全体像をつかむ助けになるかもしれません。

②学習範囲を決める
①で調査した内容をもとに学習対象を絞り込みます。 学習範囲は自分の興味をもとに決めればよいと思うため、対話型のChatGPTなどと相談しながら決めるとよいかもしれません。
③成功の基準を決める
このステップも、これまでの調査をもとに、対話型のChatGPTなどと相談しながら決めるとよいかもしれません。
④参考資料を探す
参考資料の収集は、NotebookLMが得意かと思います。 NotebookLMの検索からソースを追加したり、YouTube動画やローカルのPDFドキュメントも簡単に追加できます。
Chrome拡張のNotebookLM Importerを使うと、WebサイトやYouTubeサイトから簡単にインポートできます。
⑤学習プランを立てる
ここも前ステップで収集した参考資料の内容をもとに対話型のChatGPTなどと相談しながら決めるとよいかもしれません。 最近はNotebookLMとGeminiが相互に連携することができるようになりました。この連携機能が利用できるようになると、 NotebookLMで収集した参考資料をもとにGeminiでNotebookLMのドキュメントを取り込み、Geminiと相談して学習プランを立てることもできるかもしれません。

NotebookLMの追加ボタンからドキュメントを取り込むことができます。

取り込んだNotebookLMをもとにGeminiで対話している例です。

この際、学習プランのプラクティスの終了条件も検討するとよいと考えています。
ところで、立てた学習プランの内容はどこに記載するとよいか考えています。個人的には、Notionを使っているので、Notionで準備した学習テーマ用のページに学習プランの表を作成して、表の中に各プラクティスのページへのリンクを貼るのもよいと考えています。もしくは、独学用の学習システムを構築するか、OSSのbootcampアプリをフォークして活用させていただくのもありかもしれません。
⑥参考資料を絞り込む
NotebookLMで参考資料を収集した場合は、⑤の学習プランで立てたプラクティスに関連した資料以外を削除します。 理想的には、ここで収集した資料とプラクティスの内容を関連づけしたいところです。
実行フェーズ
⑦ある程度使えるようにするための方法を学ぶ
⑥の参考資料をもとに、最初の一歩として使えるようになるための方法を実行します。 基本的には公式サイトのドキュメントを参照したり、簡単なチュートリアルを実行したりすることで学ぶことになるかと思います。
内容がわからないことがあった際には、公式サイトのドキュメントの内容を対話型のChatGPTに貼り付けて質問したりして解決を図ることもできます。ただし、内容を貼り付ける作業が煩わしいので、個人的にはDiaのようなAIブラウザを使って、公式サイトやチューリアルサイトを開き、直接ブラウザのチャットUIで要約したり、質問したりするのが便利で活用しています。
以下は、bootcampアプリのREADMEをDiaブラウザで開いて、チャットUIでREADMEのインストール手順について質問している例です。

他にもGitHubのソースコードのページを開いて、コードの内容について質問することもできます。

⑧遊び倒す
⑦である程度使えるようになったら、つぎは実際にコードを書いたり、アプリを動かしてみます。 この際に使えるAIツールとしては、補完型のGitHub Copilotやエージェント型のClaude Codeなどになるかと思います。
補完型やエージェント型の活用例としては、
- ライブラリのAPIを呼び出す簡単なサンプルを複数生成してもらうことでライブラリの挙動を把握する
- プロトタイプのコードを生成してもらうことでアプリの動作を把握する
といったことが考えられるかと思います。これらの過程でいろいろ疑問が思い浮かぶと学習のきっかけになるかと思います。
⑨役に立つことができるところまで学ぶ
ここでも、⑧で疑問に感じた点をエージェント型のチャットUIで質問すると、疑問点が解消する可能性があります。 また、実装したコードをエージェント型のClaude Codeなどでコードレビューしてもらったり、リポジトリ内のソースコードを調査してもらったり、AIツールを活用することでフィードバックを得ることができます。これらは独学でスキルを習得する際などのメンター不在の環境においてはフィードバックを得ることができ大変助かります。
その他にも、ソースコードの調査には前述のDiaブラウザを使った対話による質問をすることもできます。また、OSSでGitHubにリポジトリが公開されている場合は、DeepWikiやCodeWikiなどを使ってリポジトリ内のコードを解析し、対話によりアーキテクチャなどのより深い質問をすることができます。

⑩教える
ブログや日報、週報などを書く際に、AIツールに文章の校正やレビューなどのフィードバックをもらうなどが考えられます。 AIツールに文章をすべて生成してもらってアウトプットすることは学習の観点からは学習効果が低いためやめた方がよいと考えています。
ブログや日報などの自分で書いた文章を蓄積し、AIツールに蓄積して与えることで何らかの気付きをもらうことはあるかもしれません。 自分は使用したことはないですが、Obsidianなどのメモアプリツールを使用して知識のデータベースを構築し、メモ間のつながりから新しい知識を発見するのかなと思います。
おわりに
従来の学習の10ステッププロセスを振り返りつつ、 現在のAIツールを活用した新しい10ステッププロセスについて考えてみました。
エージェント型AIツールの登場により、非エンジニアであっても、ある程度動くアプリケーションを生成できる時代になりました。同様に、エンジニアであっても、コードを細部まで深く理解しなくとも、形として動作するものを素早く作れるようになっています。
しかし、こうしたAIの進化した時代においても、自分で学ぶ対象を選び、独学でスキルを習得し、物事の仕組みを理解しようとする姿勢そのものは、依然として重要だと感じています。
そこで本記事では、AIツールを「代替」ではなく「補助」として活用することを前提に、学習を深めるための新しい10ステッププロセスを整理してみました。
AI時代の学習プロセスを考えるうえで、何らかの参考になれば幸いです。
*1:もちろん、チーム開発や自作サービスの作成、就職支援などの違いはあります
ARグラスで広がる作業環境
これは、「フィヨルドブートキャンプ Part 2 Advent Calendar 2023」25日目の記事です。
昨日は、Part 1がsiroemkさんの応用情報技術者試験に合格した時の勉強法、 Part 2がMasafumi OkuraさんのRubyConfTw2023の感想記事 - okuramasafumiのブログ でした。
今年のアドベントカレンダーは以下になります。
目次
- はじめに
- 過去と現在の作業環境
- ARグラス
- MacBookの作業環境
- iPhone15(iPad pro)の作業環境
- Android GalaxyのSamsung DeXの作業環境
- 屋外(野外)で作業する
- 屋内で作業する
- まとめ
- おわりに
はじめに
コロナ禍でリモートワークになり、デスク周りの作業環境を快適にしようと、ディスプレイやキーボード、高額の椅子を購入しました。 たしかに、デスク周りの作業空間は快適になったのですが、ARグラスを今年入手してから物理的なデスク環境に 縛られずに作業空間がさらに拡張された気がします。 ここでは、ARグラスでどのような作業環境が変わったのか、いろいろ試した内容を共有したいと思います。
過去と現在の作業環境
簡単に前後の作業環境を比較します。
コロナ禍の作業環境
主にデスクでの作業を前提として、アイテムを揃えました。
- HHKB Hybrid-Type S
- 分割キーボード(Choco60)
- アーロンチェア *1
- 傾斜台 Angle 10
- iPad pro
- Stream Deck (ショートカットボタン)
- Hueライト
- オットマン
- M1 MacBook
- ディスプレイ 2枚(上下配置)
- Nintendo Swich
- PlayStation 5
- Magic Trackpad
- Apple HomePod Mini
- Apple Air Pods Pro
- Ankerの充電器 (Anker 733 Power Bank )
MacBookはクラムシェルモードで、ディスプレイ2枚と接続して使用します。 傾斜台はデスク上に角度をつけるとキーボードが打ちやすかったり、iPad proでメモが取りやすいです。 オットマンは足を伸ばせるので、アーロンチェアの背もれたに寄りかかりながらの作業が快適です。

現在の作業環境
コロナ禍の頃の作業環境も残ってはいますが、使用頻度は下がっています。
- HHKB Hybrid-Type S
- M1 MacBook
- iPad pro
- XREAL Air / XREAL Air 2 pro 🆕
- iPhone15 🆕
- Nintendo Switch
- PlayStation 5
- Magic Trackpad
- Apple Air Pods Pro
- XREAL Beam 🆕
- Nebula for Mac(ベータ版)🆕
- Android Galaxy S20+ 🆕
- Ankerの充電器 (Anker 733 Power Bank )
MacBookはクラムシェルモードでARグラスのXREAL Air 2 pro と接続し、3画面の仮想ディスプレイを表示できるNebula for Mac *2という ソフトウェアを使って作業しています。
基本的には、Apple関連のデバイスを中心とした作業環境になります。Windows PCは持っていないので、本記事ではWindows PCについては記載していません。 ただし、GPD Win や ROG Ally、Steam DeckなどのWindowsベースの端末とARグラスを組み合わせて作業環境を作ることもできると思いますので、参考になる部分もあるかと思います。
ARグラス
ARグラスの厳密な定義は正直分からないですが、自分が使用した範囲内では外部の映像などの情報を入力として、レンズに情報を投影(ミラーリング)できる サングラス型のデバイスです。PCやスマホなどの端末やSteam Deck、Nintendo Switch、PlayStationなどのゲーミング端末の画面をミラーリングできます。 一般的なAR(Argument Reality) = 仮想現実としては体験したことがないので、基本的にはディスプレイとしての用途で、このARグラスを活用しています。
国内では、Rokid、VITURE、XREALなどのブランドが主要なようですが、正直XREALのARグラスであるXREAL Air / XREAL Air 2 pro 以外のARグラスを使用したことが ないため、どれが良いのか比較できません。(ただし、視力の観点では、XREAL のARグラスには、視度調整機能がなく、視力が悪い場合はお手持ちのメガネを使うか、あるいは別売のインサートレンズを購入して使う必要があります。高額(1万円〜2万円台)のインサートレンズを購入したくない場合は、視度調整機能があるARグラスを購入するといいかもしれません。)
そのため、本記事では、XREALのXREAL Air / XREAL Air 2シリーズを対象として書きます。
XREAL Air/ XREAL Air 2シリーズ
XREALブランドのARグラスです。 上記にも書きましたが、PCやスマホなどの端末やSteam Deck、Nintendo Switch、PlayStationなどのゲーミング端末の画面をミラーリングできます。 DP AltモードのUSB-Cに接続した端末と一本のケーブルの有線接続でミラーリングできます。また、XRAEL Beamというサブデバイスを使うことで、無線接続でミラーリングできます。
最近発売したiPhone15では、ついにUSB-Cがサポートされたため、iPhone15シリーズはお手軽に ミラーリングを楽しめます。AndroidのPixelはUSB-Cがサポートされていますが、DP Altモードが無効化されているので、 ルート化せずにミラーリングすることはできません。
XREALとXREAL Air 2 シリーズの違いはそこまでないのですが、XREAL Air 2シリーズのXREAL Air 2 proには調光機能があり、サングラスのレンズの明るさを透過度0%、30%、100%の3段階で切り替えることができます。*3 この調光機能を使うことで、屋内や屋外での使用で没入感を得ることができるため、現時点で購入するなら、XREAL Air 2 proがおすすめできるかなと思います。
https://www.xreal.com/jp/air2/
XREAL Beam

Wi-Fi経由でワイヤレスに映像をミラーリングすることができます。 また、空間ディスプレイとして3つのモードに対応しています。
- Smooth Follow (0DoF)
ブレ補正がされて頭の動きに追従される
- Sideview (0DoF)
レンズの4角に小さく映像を配置できる。
- Body Anchor (3DoF)
任意の位置に画面を配置できる。
基本的に、XREAL Air 単体でディスプレイのミラーリングされた映像を見ると、頭の動きに追従して目の前に映像が張り付いた感じ(0DoF)になりますが、 空間ディスプレイのBody Anchor (3DoF)モードに切り替えることで、任意の位置に映像を固定化することができます。これは、動画を視聴したり、Kindleなどの本を読むときなど好きな姿勢で見れるので、大変便利に感じています。
また、映像のディスプレイのサイズや距離(深度)の調整を行うことができます。
最近のXREAL Beamのアップデートでは、Androidのアプリ(apkファイル)をインストールできるようになったため、XREAL Beam単体でNetflixを視聴したり、Kindleを閲覧できるようになっています。 ただし、NetflixやAmazon Primeの視聴はDRM制限により、残念ながらSD画質でしか再生ができません。その他にも、Google Playからのアプリのインストールはできなかったり、充電・発熱問題など色々と制限があります。それでも、空間ディスプレイを活用できるデバイスとして価値を感じています。

https://www.xreal.com/jp/beam/
Nebula for Mac(ベータ版)
macOSの作業環境を拡張できる仮想ディスプレイを作成できるソフトウェアです。 最大3画面の仮想ディスプレイを横並びに配置できます。ディスプレイの空間の位置や距離、サイズなどを 調整することができます。
MacBookの作業環境
MacBook上で複数画面で作業できるようにNebula for Macをセットアップします。 また、スマホなどの他の端末からSSH接続できるようにTailscale SSHをセットアップします。
SSH接続以外にも、VSCodeのリモートトンネル機能(vscode.dev経由で接続)やリモートデスクトップなどを使ってリモートアクセスができますが、詳細は割愛します。
ただし、リモートデスクトップについてはいくつか試してみたので、簡単に紹介します。
1つ目は、macOSに標準で搭載されている画面共有アプリ(Apple Remote Desktop)です。 もし、メインマシンとサブマシンがどちらもmacOSであれば、画面共有アプリを使うことで簡単にリモートアクセスができます。メインマシンの設定画面の一般 > 共有 > 画面共有をONにすることで、サブマシンからリモートアクセスすることができます。 サブマシンのMacBookからメインマシンのMacBookに画面共有アプリで接続した感じでは、遅延の問題もあまりなく使うことができました。 また、macOS Sonomaの画面共有アプリは、画面共有アプリが刷新され、Apple Silicon Macでは高パフォーマンス接続が可能となっているようです。
2つ目は、主にゲームプレイ向けのリモートデスクトップツールのParsecです。
これも、メインマシンのMacBookにParsecのアプリをセットアップすることで、同じアカウントでログインしたサブマシンのMacBookやAndroid GalaxyのDeXなどのParsec(クライアント側)アプリを通して、リモートアクセスができます。
サブマシンのMacBookからリモートアクセスすると、低遅延で画面表示が画面全体にフィットするため、非常に使いやすく感じました。 サブマシンのDexからリモートアクセスした場合も、問題なく表示はできましたが、DeX側のタッチ操作をなぜか受け付けず使えなかったです。*4
Nebula for Macをセットアップ
MacBookにARグラスを接続すると、MacBookの画面がミラーリングされます。 この時に、頭を動かすと、ミラーリングされた画面も一緒に頭に追従されます。
複数の画面(2画面と3画面)を使用するには、Nebula for Macというソフトウェアを使用します。 このソフトウェアを使用すると、ARグラスを単体で使用することとは異なり、複数の画面が横並びに固定配置され、 頭を動かしても画面は追従されません。


SSHサーバを有効化する
MacBookでSSHサーバを有効化します。設定の共有→リモートログインを有効化することでSSHを使ってアクセスすることができます。 ただし、ここではTailscale SSHを使って簡易的にSSHでアクセスできるようにします。
Tailscale SSHのセットアップ
Tailscaleは、簡易的にVPNネットワークを構築できるサービスです。同じネットワークに参加している端末同士でアクセスできるようになります。 Tailscale SSHは、現在ベータ版の機能になりますが、ユーザー自身で公開鍵認証のセットアップをしなくとも、Tailscaleのネットワークに参加している 端末からSSHアクセスできる機能を提供します。
上記のドキュメントに記載の手順でTailscaleをインストールします。
Tailscaleをインストール後、以下のコマンドでTailscale SSHの機能を有効化します。
$ tailscale up --ssh

便利なMacアプリ
Rectangle
macOSのウィンドウのリサイズや複数のディスプレイ間でウィンドウを移動するのに便利です。
CatchMouse
macOSの複数のディスプレイ間でカーソルを移動するのに便利です。
iPhone15(iPad pro)の作業環境
iPhoneのスマホでも、ターミナルエミュレータを使って、簡易的な開発作業ができます。 ARグラスの登場前はスマホの画面が小さすぎて、使う気にはなれなかったのですが、ARグラスを使うことで小さな画面も ARグラス上に投影されるため、無理なく作業できるようになったと感じます。
また、iPhone15でDP Altモード対応のUSB-Cが搭載されたことにより、ARグラスと直接有線接続できるようになりました。 ここでは、iOSのターミナルエミュレータを使って、MacBookにSSH接続して作業する方法を記載します。
iSH Shellをインストール
iOSで使用できるターミナルエミュレータとしてiSH Shellというアプリがあります。 これは、iOSのローカル上にAlpine Linux環境を構築できます。
apps.apple.comMacBookにTailscale SSH接続する
リモートマシンのMacBookにSSH接続することも可能 *5 ですが、ここではTailscaleを使ったSSH接続について紹介します。 TailscaleでSSH接続するには、リモートマシンのMacBook上でTailscaleをインストールし、Tailscale SSHを有効化する必要があります。
また、クライアントであるiPhoneにもTailscaleのアプリをインストールします。
apps.apple.comTailscaleでSSH接続する前に、iOSのTailscaleアプリを起動し、Tailscaleのネットワークに参加します。

iSH Shellのターミナルを起動し、通常のSSH接続を行います。
この時、Tailscaleのログイン画面が起動し、ログインが成功すると、MacBookにSSH接続することができます。



FBC Stackを動かす
以前自作したサービスのFBC StackをリモートマシンのMacBookで動かします。

TailscaleのIPアドレスでもアクセスできますが、iPhone側のブラウザでTailscaleのMagic DNSのドメインを使ってアクセスします。

Tailscaleのサービスには、Tailscaleネットワークの内外からhttps/httpでアクセス可能なTailscale FunnelやTailscale Serveなどの機能も提供されているので、興味がある方は調べてみてもいいかもしれません。
Android GalaxyのSamsung DeXの作業環境
Android のGalaxyには、Galaxy S8以降から、スマホの画面をデスクトップモードで表示するDeXという機能が提供されています。 Androidはあまり好きではないのでほとんど触っていなかったのですが、最近中古の端末を購入して触ってみました。
DeXが起動されるとAndroidの端末側がタッチパネルとして操作できるようになるので、トラックパッドは基本的に不要になります。 また、キーボードはBluetooth接続が可能なため、HHKBのキーボードを使って操作できます。
作業環境の観点では、Google Playで配布されているUserLAndというターミナルエミュレータやSSHクライアントのアプリをインストールすることで、 開発作業を行うこともできます。RubyのインストールやBootcampのアプリをローカル環境で動かすことはできました。
Samsung DeXを動かす
Android Galaxy S20+ は、DP Altモード対応のUSB-Cを持っているので、ARグラスを直接有線接続することができます。 ARグラスを端末に接続すると、DeXが起動し、ARグラスにDeXモードのデスクトップがミラーリングされます。


ARグラスのDeXの画面をキャプチャするのが難しいので、他の外部ディスプレイにHDMI出力した画面を載せます。(外部ディスプレイのアスペクト比があってないので、画面が横に伸びています。) 基本的には、HDMI出力した画面がARグラスにミラーリングされます。

※ どうやら、FjordBootCampでは、12/21(木)〜1/7(日)の期間で年末学習キャンペーンを実施中のようです。
UserLAndをセットアップ
以下のGoogle Playからアプリをインストールします。
ターミナルエミュレータのUserLAndを使うと、UbuntuのLinuxをインストールすることができます。 UserLAndやUbuntuのセットアップ手順の詳細は割愛します。

ConnectBotでUserLAndに接続
UserLAndにSSH接続するには、UserLAnd側でSSHサーバの起動などのセットアップを事前に実施しておきます。UserLAndのSSHサーバは、 デフォルトでdropbearを使用しているようです。セキュリティ的にパスワード認証ではなく、公開鍵認証でSSH接続できるように セットアップした方が良いと思います。詳細なセットアップ手順は割愛します。
SSHサーバのセットアップ後に、SSHクライアントのConnectBotを使って、UserLAndにSSH接続します。
ConnectBotのアプリは以下のGoogle Playからインストールします。 play.google.com
ローカルでBootcampアプリを動かす
Bootcampアプリを動かすためには、RubyのインストールやRailsなどのgemをインストールする必要があります。 また、データベースに接続するために、PostgreSQLのインストールも事前に必要になります。この辺りのセットアップ手順も 通常のUbuntuでセットアップする手順と同じで、調査すれば分かるので詳細は割愛します。

MacBookにTailscale SSH接続する
Androidでも、Tailscaleのアプリをインストールします。
基本的に iOSのターミナルエミュレータで実行した場合と同じように、 SSHクライアントのConnectBotを使って、MacBookにTailscale SSHで接続できます。SSH 接続の際に、Tailscaleの認証のセッションがない場合は、認証が求められます。

屋外(野外)で作業する
リュックにMacBookを入れたまま、作業できる環境を目指します。 つまり、XREAL AirのARグラスとキーボード、トラックパッドのみを出した状態です。


必要なアイテム
XREAL Air 2 pro
明るい場所でも作業しやすくなるため、調光機能がついている XREAL Air 2 proがおすすめです。
MacBook
持ち運び可能なノートパソコンとしてMacBookを利用します。 MacBookの内蔵ディスプレイとXREAL Airのディスプレイの2つのディスプレイも同時に利用することもできますが、基本はクラムシェルモードで利用したいので、MacBookは閉じた状態で内蔵ディスプレイは利用しません。
MacBookをクラムシェルモードで使用する条件として、以下があります。
- 電源で給電されていること
- 外部ディスプレイが接続されていること
HDMI ダミープラグ or BetterDisplay
内蔵ディスプレイを閉じた状態のMacBookにXREAL Airを接続すると、MacBookの画面がミラーリングされます。 ただし、この接続状態では Nebula for Macのソフトウェアが正常に動作しません。これを回避するために、外部ディスプレイが接続されている状態と認識されるように、HDMIダミープラグかBetterDisplayを使って、仮想ディスプレイを作成します。
ディスプレイエミュレータプラグ ヘッドレス ゴーストエミュレーター フェイクディスプレイ (ヘッドレス-1920x1080-4096x2160 @60Hz
MacBookにHDMI端子がない場合は、仮想ディスプレイを作成できるBetterDisplayというソフトウェアを使いのもOKです。 有料版のソフトウェアですが、仮想ディスプレイの作成は無料でも可能です。
これで、MacBookのクラムシェルモードでXREAL Airを接続して、Nebula for Macを使用できる状態になります。
キーボード
Bluetooth接続でHHKB Hybrid Type-Sを使用しています。 キータッチの押し心地がいいのと、マルチペアリングでMacBookやiPad、iPhone、Androidなどに繋がるので 大変重宝しています。
最近発売されたHHKB Studioには、マウス機能やジェスチャーパッドが内蔵されているため、トラックパッドが不要になるかもしれません。一度、HHKB Studioを店舗で触って試してみましたが、ジェスチャーパッドの性能がいまいちに感じたため、トラックパッドを使用しています。ただ、HHKB Studioだけでキーボードとタッチ操作ができるのはコンパクトでいいなと思います。
トラックパッド
Bluetooth接続でApple のMagic Trackpadを使用しています。 端末のタッチパッドがある場合は、それでもOKかと思います。
電源
屋外で作業するためには、電源が必要になります。 MacBookに給電するために、Ankerの733 Power Bankを使用しています。 およそ、2、3時間くらいは給電できるかと思います。
Anker 733 Power Bank (GaNPrime PowerCore 65W)www.ankerjapan.com
もっと長時間給電したい場合は、ポータブル電源を検討するのもいいでしょう。
Jackery Explorer 100 Pluswww.jackery.jp
電源を使用せずに、MacBookのバッテリーを使用することで、クラムシェルモードを維持する方法もあります。 MacBookのバッテリーを使用した状態で、MacBookの閉じてしまうとスリープ状態に移行してしまいますが、Macの電源管理用のpmsetコマンドを使用することで、スリープ状態を回避することができます。
pmset -gコマンドで、現在の電源周りの設定値を確認することができます。 SleepDisabledの設定値が1の場合に、スリープ状態に移行しないようになります。
$ pmset -g System-wide power settings: SleepDisabled 0 Currently in use: standby 1 Sleep On Power Button 1 hibernatefile /var/vm/sleepimage powernap 1 networkoversleep 0 disksleep 10 sleep 0 (sleep prevented by powerd, Music, coreaudiod, coreaudiod, GroupSessionService, bluetoothd) hibernatemode 3 ttyskeepawake 1 displaysleep 0 (display sleep prevented by Nebula for Mac) tcpkeepalive 1 lowpowermode 0 womp 1
$ sudo pmset -a disablesleep 1 $ pmset -g System-wide power settings: SleepDisabled 1 Currently in use: standby 1 Sleep On Power Button 1 hibernatefile /var/vm/sleepimage powernap 1 networkoversleep 0 disksleep 10 sleep 0 (sleep prevented by powerd, Music, coreaudiod, coreaudiod, GroupSessionService, bluetoothd, sharingd) hibernatemode 3 ttyskeepawake 1 displaysleep 0 (display sleep prevented by Nebula for Mac) tcpkeepalive 1 lowpowermode 0 womp 1
スリープ状態に移行しない場合は、MacBookを閉じてもバッテリーを消費し続けるため、屋外で使用しない場合は SleepDisabledの設定値を元に戻しましょう。
$ sudo pmset -a disablesleep 0
バッグ🎒
XREAL Air や MacBook、電源などが入れば、なんでもいい気がします。 ただし、割と重量が重いので、耐久性があるリュックがいいかもしれません。
ポータル電源が必要な場合は、サイズが大きめのUber Eatsのような配達バッグがあると役立つのではないでしょうか。
ロゴ入り配達バッグ(ブラック) - Delivery Bag with Logo (Black) – Ubereatskit Japan - ウーバーイーツ 配達バッグ 購入
Povo 2.0
屋外でインターネットを使用するには、テザリングなどができる環境が必要です。 Povo以外についてほとんど使ったことないので他の代替品を知らないのですが、Povoは24時間(とはいえ、2日間)で使い放題のオプションがあるので、たまに屋外(旅行中でも)で作業したいときに低価格で利用できて重宝します。
もしかすると、スペースXが提供する通信衛星サービスのスターリンクを使うのもありかなと思ったのですが、電波法により野外での利用に制限があるようですね。
作業場所
- 公園(ベンチ)
- 河川敷
- キャンプ場
- 車

屋内で作業する


必要なアイテム
自宅以外の屋内なら、屋外で必要なアイテムと基本変わらないかもしれかもしれません。 自宅で作業なら、自宅のWi-Fiに接続できるので、テザリング用のPovo 2.0は不要です。 (もちろん、屋内でWi-Fiが完備されている場合は、そちらで代用できます)
作業場所
作業場所としては、以下があります。
- コワーキングスペース
- イベント・勉強会
- ホテル
- ショッピングモール
- カフェ
- 自宅
作業スタイル
作業スタイルとしては、以下があります。
- クッション(ごろ寝)
- 椅子(背もたれ、壁)
- スタンディング
- お風呂(自宅)
割と最近、人をダメにするソファ + Nreal Airでこんな態勢で作業している。https://t.co/R77Zk958MG
— mh@mobiler⚡️ (@mh_mobiler) May 19, 2023
ニトリのゲーミング座椅子や最近発売された回転ゲーミング椅子などは持っていないのですが、ARグラスでの作業と相性がよさそうで 少し気になっています(笑)
お風呂で作業するのは、割りと良かったです*6。ただし、電子機器を扱うので、取り扱いには注意する必要があります。 先日、Atcoder(競プロ)のABCのコンテストにお風呂から参加したのですが、問題が難しく動揺したせいか、風呂フタが傾いてiPhone端末とケーブルを浴槽に落としてしまうことがありました。MacBookやARグラスを落としてしまった際には、かなりショックが大きいことかと思います。*7
あと、屋内で作業する際に、屋外の項目でも記載しているリュック🎒をかけて作業するのも、作業場所やスタイルを変更する際に便利かもしれません。 たまに別の場所に移動して作業したり、スタンディングで作業したり、気分によって変更することができます(笑)
まとめ
いろいろと書いたので、雑にまとめると以下になります。
- ARグラスの登場により、小型のデバイスであるスマホやMacBook(クラムシェルモード)を作業環境として使いやすくなった。
- ARグラスを使うことで、自宅のデスク環境と比べて、さまざまな作業場所や作業スタイルを選択できるようになった。
- 3DoFの空間ディスプレイをサポートするNebula for Macのソフトウェアを使うことで、物理的なディスプレイが不要で、場所によらず複数の画面(2画面と3画面)を利用できるようになった。
- DP Altモード対応のUSB-C対応デバイスとARグラスを簡単に接続できることで、屋内や屋外にかかわらず、大画面で読書や動画視聴ができるようになり、以前に比べて読書や動画視聴の体験が向上した
普段は、メインの作業マシンとしてMacBook を使っていますが、以下の組み合わせで結構便利になったと感じています。
- MacBook(クラムシェルモード)
- HDMIダミープラグ or BetterDisplay (仮想ディスプレイ作成)
- HHKB Hybrid Type-S (キーボード)
- Magic Trackpad (トラックパッド)
- XREAL Air 2 pro (ARグラス)
- Nebula fro Mac (空間ディスプレイをサポートするソフトウェア)
- Anker 733 Power Bank (充電器)
- Povo 2.0 (テザリング)
おわりに
XREAL のARグラスを使用することで、デスク周りの作業空間に縛られずに、自宅や屋外などで快適に作業できる環境ができたように感じます。 フィヨブーでは、プラクティスを学習する上で、本を読んだり、動画を見たり、コードを書いたりしますが、ARグラスを使うことで、 自宅だけでなく、外出先など隙間時間を有効に活用できるのではないかと思い、ARグラスを活用した作業環境について共有させていただきました。 本記事を読んで気になりましたら、ぜひ体験していただきたいなと思います。
また、来年には、CES2024のXREALの新製品の発表やApple Vision proなどの販売も予定されています。これらにより、作業環境がどのように 変わるのか楽しみです。今後も作業環境を快適にしつつ生産性を上げていきたいと思います。
*1:椅子はワーカホリックという店舗でいろいろな椅子を試し座りして選びました。
*2:最近、Nebula for Windows (ベータ版)も出ました。
*3:ただし、0% はXREAL Airのデフォルトの透過度(薄暗い)と同じくらいで、完全に透過されるわけではありません。
*4:Androidの機種やバージョンに依存している かもしれないので、他のGalaxyのDeXでは正常に動作する可能性はあります。
*5:もちろん、AWSやさくらVPSなどのクラウドサービス上に構築した環境にSSH接続することも可能です
*6:令和時代の風呂グラマーはARグラスで作業するといいかもしません笑
*7:水没防止に何らかの対策が必要でしょう
Fjord Boot Campの卒業生が作成したサービスの技術スタックデータベースをリリースしました🎄

これは、「フィヨルドブートキャンプ Part 2 Advent Calendar 2022」25日目の記事です。
昨日は、Part 1がshu91327さんの質問しながら出来るようにしていく、 Part 2がsaeyamaさんのRails/Vue 編集時に画像をD&Dで入れ替えした時のActive Storageの保存方法 でした。
今年のアドベントカレンダーは以下になります。
フィヨルドブートキャンプ Part 1 Advent Calendar 2022 - Adventar
フィヨルドブートキャンプ Part 2 Advent Calendar 2022 - Adventar
目次
- はじめに
- サービス概要
- 機能紹介
- 開発のきっかけ
- どう解決するか
- 技術スタックの類似サービス
- FBC Stackの技術スタック
- FBC Stackのシステム構成
- FBC Stackに掲載している自作サービス
- 開発をした上での気づき
- どう使って欲しいか
- 今後の展望
- おわりに
はじめに
mhと申します。 Fjord Boot Camp(以下、FBC*1と記載)で2021年の6月頃に卒業し、早いもので卒業してから1年半くらい経ちました。 卒業した時にEvent Follow という技術イベント発見というサービスを開発し、Heroku上で今も元気に 動いています。よろしければ、ぜひ使ってみてください😄。
リリース時のブログ:「EVENT FOLLOW」という技術イベント発見サービスをリリースしました!
今回は FBCの卒業生が作成したサービスの技術スタックデータベースのサービスをリリースしましたので、そのサービスのご紹介になります。 特に卒業制作のサービスというわけではなく、ちょうど解決したいFBC関連のサービスがあったので、 このアドベントカレンダーをリリース日に設定し、開発を行いました。
サービス概要
FBC Stackは、FBC卒業生が自作したサービスの技術スタックの情報が有効に蓄積されていない問題を解決したい、FBC卒業生の自作サービスの技術スタックデータベースです。ユーザーは 卒業生の自作サービスの技術スタックを見ることができ、卒業生のWebサービス一覧のドキュメントから個別に自作サービスを発見することとは違い、採用している技術スタックのツールから卒業生の自作サービスを発見できるのが特徴です。
ここでは、卒業生のWebサービス一覧のリンクを参照していますが、実際はFBCのサービス内に同様のドキュメントがあります。 こちらのドキュメントには卒業生が卒業式の時に発表したサービスのデモ動画があります。
機能紹介
サービスでできる機能は以下になります。
- トップ画面で卒業生のサービス一覧を表示
- サービス詳細画面でサービスの技術スタックを表示
- みんなのツールを選択し、各サービスで採用されているツールを一覧表示(現在はアルファベット順)
- ツール一覧をキーワードで検索
- ツール詳細画面でツールを採用しているサービスを一覧表示
卒業生のサービス一覧画面
表示するサービスの数がそこまで多くないため、全てのサービスを表示しています。

サービスの詳細画面
サービスに関連するリンクや技術スタックをカテゴリ別に表示しています。

ツールを採用しているサービス一覧
技術スタック内のツールを選択すると、ツールを採用しているサービス一覧を表示します。ここでは、Next.jsを選択した画面になります。

ツール一覧画面
ツール一覧画面では、現在サービスで採用されているツールを全て一覧表示しています。

ツールの検索画面
ツールのキーワードで検索することで、ツールを絞り込みます。

Amazon EC2を採用しているサービス一覧
ツールの検索画面でAmazonを検索し、Amazon EC2を選択すると採用しているサービス一覧を表示します。

開発のきっかけ
開発背景として2点あります。
1点目
最近は、自作サービスの開発に採用する技術が高度化されてきたなぁと耳にする機会が増えてきました。 通常は、卒業生の数も月に2名くらいでしたが、2022年の3月は7人、4月は6人といっきに増えた時期があり、 このあたりから、自作サービスの技術スタックもモダンな構成が増えてきた印象があります。
他に、最近FBCのDiscordやミートアップとかで、
- 自作サービスの技術レベルが高くなってきている。
- 自作サービスでReact/Next.jsを採用する人が増えてる。
- プラクティスで学んだ技術以外の採用例が増えている。
- OpenAPIを採用している人が他にいたけどだれだっけ?
- ○○さんはChakra UIを採用してましたっけ?
といった声も聞こえてきます。 ただ、こういった感覚値は、自作サービス関連の情報を積極的に追っていてないと 把握することが難しいとも感じていました。
FBCの自作サービスの情報というのは、
- 自作サービスの開発ミーティング(Discord上)
- 自作サービスのDiscord チャンネル
- 自作サービス関連の日報
- 自作サービス関連の開発ドキュメント
- 自作サービス関連のQ&A
- 自作サービスのプラクティスの提出物
- 卒業生のWebサービス一覧のドキュメント (GitHubのリポジトリ、リリースブログ、サービスURL、卒業式のデモ動画)
などを見たり、参加することで知ることができるかと思いますが 、情報がバラバラに点在しているため、自作サービス関連のプラクティスに携わっていないとなかなか厳しいとも感じます。
2点目
Kaigi on Rails で、komagataさんが卒業生の自作サービスをみんなに知って欲しいと紹介するLTを見ました。
このLTを見て、どうやったら、サービスを知ってもらえるかを考えたことがきっかけでもあります。 LTの最後に紹介されている卒業生のサービス一覧は、FBCのサービス内のドキュメントの方の自称メンテナー(笑)として度々更新しているので多少愛着があります。このドキュメントをメンテナンスしていく中で、今後卒業生が増え、自作サービスも蓄積されていくので、その卒業生のサービス一覧のデータを有効に活用していけないかと感じました。
この2点を雑にまとめると、
- 卒業生の自作サービスの技術情報を蓄積することで、自作サービスのトレンドを把握するとともに、 FBC内外の人にも自作サービスを知ってもらう情報を提供できるのではないかと考えた次第です。
どう解決するか
技術情報の蓄積として着目したのが、サービスのリリースブログの広報でもよく使われる技術スタックの情報になります。 FBCでも、リリースブログの他、卒業式のデモの時やGitHubリポジトリに必ずといって記載される内容となっています。
現在は卒業生のWebサービス一覧でGitHubリポジトリやサービスURL、リリースブログなどは一覧で網羅されていますが、 自作サービス間でのつながり(共通点)を見つけることはできません。
そこで、サービスの技術スタックのツールを登録することで、採用しているツール経由でサービスを見つけやすくなると考えました。
FBC内の在校生や卒業生の得られる期待としては、
- 自分の知らない新しいツールを発見できる。
- 自分が検討してい技術スタックに近しいサービス(卒業生)を探せる。
- 検索したツールを使っているサービスを探せる。
例えば、Firebase Authenticationで検索すると、それを使っているサービスを探せます。 ここから、サービスの採用している技術スタックを確認できたり、GitHubのリポジトリのソースコードや FBC内での提出物や日報を見て参考にすることもできるかと思います。
FBC内のメンター(アドバイザー)や運営の方の得られる期待としては、
- 在校生の自作サービスの技術のトレンドを把握できる。
- 誰がどういったツールを使っているかを知ることで、他の生徒にアドバイスができる。
FBC外のスクールを検討している方の得られる期待としては、
- どういったサービスを作れるようになるのか知ることができる。
- どういった技術を使っているのか知ることができる。
FBC外の企業の採用担当の方の得られる期待としては、
- どういった技術を使ってサービスを作ったかを把握できる。
- FBCの技術トレンドを把握できる。
技術スタックの類似サービス
もちろん、技術スタックを調査する類似サービスはいくつか存在します。
技術スタックを調査できる同様のサービスとしては、StackShareやWappalyzarとwhat we useなどのサービスが存在してます。 ただ、これらのサービスは企業や任意のサイトで採用されている技術スタックを調査するのに使われています。
StackShareはサイトや公開リポジトリの技術スタックを登録できますが、ツールのキーワードから横串で採用しているサイトやリポジトリは検索できません。 URLを入力すると、技術スタックのツールを自動スキャンしてくれますが、使った感じでは精度は高くない印象です。
WappanalyzerはChrome拡張で、訪問したサイトでChrome拡張機能を使うと、サイトで採用しているツール情報をバージョン付きで表示してくれます。 ただし、単体でサイトの技術スタックの情報を表示するだけで、登録したサイトや公開リポジトリの技術スタックを横串で検索することはできません。
what we useは企業の技術スタックデータベースではありますが、ツールのキーワードからツールを採用している 企業を横串で検索できることが本サービスと類似しています。ただ、企業版なので、卒業生の技術スタックデータベースとしては活用できません。
FBC Stackの技術スタック

- フロントエンド
- TypeScript 4.9.3
- ChakraUI 2.4.3
- Next.js 13.0.7
- React 18.2.0
- react-markdown 8.0.4
- Linter/Formatter
- ESLint 8.30.0
- Prettier 2.8.1
- テスト
- Cypress 12.2.0
- インフラ
- Vercel
- ストレージ
- CI/CD
- GitHub Actions
サービスの技術スタックの情報やツール情報を登録したMarkdownとツールのロゴデータをGitHubをストレージとして管理しています。
卒業生の自作サービスの登録頻度は高くなく静的サイトでサービスを提供できるため、SSGで静的サイトを生成でき、学習コストが低いNext.jsを採用しました。 また、Next.jsの公式のチュートリアルがYAML Front Matterのメタデータを埋め込んだMarkdownを使ってブログサイトの構築しており、このメタデータで技術スタックの情報をMarkdownに埋め込む方法を思いつきました。
Chakra UIは、UIコンポーネントがあり、StackとHStackといったコンポーネントがあります。自分はiOSアプリ開発をやっていたので、 これらのコンポーネントがUIStackViewやSwiftUIのVStackやHStackのレイアウト方法と似てたこともあり、馴染みやすいと感じ、即採用しました。あと、Chakra UIのチャクラは、漫画のNARTOのチャクラが由来らしく、親しみを感じたことも 後付けながら理由になります(笑)
FBC Stackのシステム構成
- サービスの技術スタックのMarkdownやロゴデータを登録します。
- GitHubにプッシュします。
- GitHubでPRを作ります。この時点でGitHub ActionsのビルドとVercelのプレビューデプロイが行われます。
- PRがマージされたらVercelの本番環境にデプロイされます。

FBC Stackに掲載している自作サービス
現在掲載している自作サービスは以下の基準で掲載しています。
- リリースブログで広報して、自作サービスをリリース済み
→ これは、卒業生のリリースブログの広報を優先したいからです。
- 卒業生がスクールで作成したサービスと異なる自作サービス
→ 例えば、自分が今回作ったFBC Stackのサービスです。
- FBCのBootcampアプリ
→ これは、卒業生全員がチーム開発で実質開発に携わったサービスなので、Bootcampアプリの技術スタックを登録しています。
開発をした上での気づき
Bootcampアプリの開発は、1年半前にチーム開発で携わっていましたが、Bootcampアプリの技術スタックを調査するなかで、ReactやDockerがされているなど、使用されている技術自体の変遷を感じ取ることができました。現在は、VueとReactが混在しているようで、Vue.jsからReactに置き換えが進んでいることを感じ取れましたし、こういった技術スタックの変遷も、バージョン管理してみれると過去の経緯も把握しやすくなるかと思いました。
自作サービスに自作gemを組み込んでいる人がいることを知りませんでした。
自作サービスの採用している技術スタックのツールから、自作サービスのシステム構成がある程度把握できたように思いました。
サービスのデプロイ先もHerokuの無料化のアナウンス以降、別のサービスをデプロイ先に選定している事例が増えてきています。
- フロントエンドはVercel
- バックエンドは、Railway、Fly.io
Herokuで運営していた自作サービスが結構クローズされています。
- 知らないLinterとか知ることができました。
- Next.jsの採用が増えてきていますが、Nuxt.jsを採用した自作サービスが自分しかいなかったです。
どう使って欲しいか
主に技術選定する際に、サービスや新しいツールを知るきっかけになると思います。 また、FBCの人は使っているツールからサービスを知ることで、サービスを開発した人の日報や提出物などをみたり、開発した人とコンタクトするなど、コミュニケーションが発生すると思います。その他、自作サービスの開発にハマった際に、同様のツールを使っているサービスのリポジトリを参考にすると、解決の際のヒントになるかもしれません。
今後の展望
検討している内容を箇条書きで列挙します。
技術スタックのツールだけでなく、タグをつけて、サービスを見つけやすくする。 例えば、SPAやMPAの構成のサービスを見つれるようにする。
技術スタックの変遷がわかるように技術スタックのバージョン管理をする。
- ツールのバージョンで使用しているサービスを検索できるようにする。(Vue2を使っているサービス、Vue3を使っているサービス) 例えば、メジャーバージョンで機能が大きく変わるケースもあるので、バージョンでサービスを絞り混むことができるといいケースもあるかともいます。
おわりに
この自作サービスのアイデアが浮かんだのが、Kaigi on Rails 2022の komagataさんのLTを見たのと、what we useという企業の技術スタックデータベースのQiita記事を読んだことが影響していると思います。
その時にアドベントカレンダーの記事としてFBCを卒業してからの2回目の自作サービスのリリースを行おうと考えました。
たしか、11月の後半くらいは、Next.jsのチュートリアルを触っていて、YAML Front MatterというMarkdwonのメタデータの埋め込みについて知り、12月の初旬はこのメタデータを埋め込んだ技術スタックのデータ構造について技術検証をしていたと思います。
実際に開発を始めたのは、2週間前くらいで、Next.jsのチュートリアルのサンプルコードを拡張していく形で、プロトタイプ的に試行錯誤しながら実装しました。 この時にチュートリアルのサンプルコードのリポジトリのまま実装していたので、一旦動作する形になった1週間前に、新しいリポジトリに新規プロジェクトとしてコードをプッシュしました。そのため、リポジトリの初期コミットが結構でかいサイズになっています。
新しいリポジトリでの初期コミット以降は、Issueを作って、PRベースで開発を行うようにしました。このIssueを作って、PRベースで開発するようになってから、テンポよく作業が進んだので、開発の最初期にきちんとプロジェクトの準備をすべきだったなと反省も感じています。
FBCの自作サービスの開発の時と比べると雑に進めてしまいましたが、2回目の自作サービスは短期間でもありますが目標を定めることで 楽しく開発できたように思います。
ということで、長くなりましたが、この自作サービスがFBC内外の方に少しでもお役に立てれば幸いです! あと、メリークリスマス🎄
手を動かして学ぶネットワーク実験環境入門
これは、「フィヨルドブートキャンプ Part 2 Advent Calendar 2021」25日目の記事です。
- はじめに
- 自作するネットワーク環境の構成
- 仮想環境(Linux VM)の準備
- Network Namespaceを使ったネットワーク環境の構築
- [発展編]Dockerコンテナを使ったネットワーク環境の構築
- おわりに
- 参考資料
- 参考コード
はじめに
今年は、仮想環境のコンテナ周りを学習していましたが、仮想環境のコンテナを隔離する技術のNamespaceについて知ることができました。 そのNemespaceのうちの一つにNetwork Namespaceというコンテナのネットワーク環境を隔離するLinuxカーネルの機能があります。
ある日、このNetwork Namespaceを使ってTCP/IPのネットワークを手を動かして学ぶ方法が書いてある Linuxで手を動かして学ぶTCP/IPネットワーク入門 という 書籍を読んで実際にネットワークを作ってみました。すると、書籍を読んで理解した気になるのとは違って、実際に手を動かすことで、 ネットワークのパケットやルーターの動きなどをより深く理解できるようなりました。また、ネットワーク関連のコマンドやiptablesコマンドなどのLinuxコマンドにも 触ることになったので、Linuxを慣れ親しむ上でも有益になりましたし、ネットワーク技術を今後学ぶ上での知見(ヒント)を多少得ることができました。

この書籍では、ルーターやブリッジ、Source NATやDestination NATなどのネットワーク技術について、Network Namespaceを使って仮想ネットワークを作ることを通して、手を動かしながら学ぶことができます。そこで、これらの個別の要素を組み合わせることで、Dockerのデフォルトのbridgeネットワークのような仮想ネットワークを作る方法について共有したいと思います。
自作するネットワーク環境の構成
Linux VM上のDockerをインストールすると、decker0という名称の仮想ブリッジが作成されます。
また、この時、複数のDockerコンテナを起動した場合、コンテナの仮想インターフェースがLinux VM上の仮想ブリッジに接続されます。
例えば、3つコンテナを起動してみます。
$ docker run -it -d --name c1 nicolaka/netshoot $ docker run -it -d --name c2 nicolaka/netshoot $ docker run -it -d --name c3 nicolaka/netshoot
そうすると、docker0のブリッジ以外に vethc881c6a、vethaaf049a、veth6a8c5f9といった仮想インターフェースを確認できます。
$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> 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: enp0s1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 52:54:00:87:21:22 brd ff:ff:ff:ff:ff:ff
inet 192.168.64.24/24 brd 192.168.64.255 scope global dynamic enp0s1
valid_lft 63744sec preferred_lft 63744sec
inet6 fda6:a15e:b806:8445:5054:ff:fe87:2122/64 scope global dynamic mngtmpaddr noprefixroute
valid_lft 2591952sec preferred_lft 604752sec
inet6 fe80::5054:ff:fe87:2122/64 scope link
valid_lft forever preferred_lft forever
35: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:e7:ab:b9:da brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:e7ff:feab:b9da/64 scope link
valid_lft forever preferred_lft forever
81: vethc881c6a@if80: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 0e:48:32:47:5b:ab brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::c48:32ff:fe47:5bab/64 scope link
valid_lft forever preferred_lft forever
83: vethaaf049a@if82: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 2a:62:d0:50:13:ba brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet6 fe80::2862:d0ff:fe50:13ba/64 scope link
valid_lft forever preferred_lft forever
85: veth6a8c5f9@if84: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether ca:a2:94:e1:5c:c6 brd ff:ff:ff:ff:ff:ff link-netnsid 2
inet6 fe80::c8a2:94ff:fee1:5cc6/64 scope link
valid_lft forever preferred_lft forever
docker0の仮想ブリッジの設定を確認すると、これらの仮想インターフェースがブリッジに接続されているのが確認できます。
$ brctl show docker0
bridge name bridge id STP enabled interfaces
docker0 8000.0242e7abb9da no veth6a8c5f9
vethaaf049a
vethc881c6a
また、コンテナの仮想インターフェースのIPアドレスは、以下のコマンドで、172.17.0.2、172.17.0.3、172.17.0.4が割り当てられています。
$ docker inspect --format '{{ .NetworkSettings.IPAddress }}' c1
172.17.0.2
$ docker inspect --format '{{ .NetworkSettings.IPAddress }}' c2
172.17.0.3
$ docker inspect --format '{{ .NetworkSettings.IPAddress }}' c3
172.17.0.4
このネットワーク環境の構成をネットワーク図にすると、以下のような感じになると思います。
172.17.0.0/16 がコンテナのセグメントで、192.168.64.0/24がおそらくMacのホストとLinux VM間のセグメントになるかと思います。
このネットワーク図のrouterのホストに該当するのは、Linux VMで、wanに該当するのがMacのホストを想定しています。
ここで、Multipassを使って、別のLinux VMを起動すると、192.168.64.0/24のセグメント内のIPアドレス(例えば、192.168.64.21/24)がLinux VMの
インターフェースに割り当てられます。これらのLinux VMもまた、bridge100という名称の仮想ブリッジに接続されているのでしょう。

前置が長くなりましたが、上記のネットワーク図のような構成をNetwork Namespaceを使ってLinux VM上に構築して、ネットワークの学習をしたいというのが 本記事の目的になります。
実際に作成するネットワーク構成は以下になります。

br0がdocker0のような仮想ブリッジで、c1、c2、c3がDockerコンテナのように仮想ブリッジに接続するホスト(Network Namespace)になります。
これらのc1、c2、c3のホストからwanのホストに通信するためには、router のホストでiptablesを使ってSource NAT(IPマスカレード)を定義し、
プライベートアドレスからグローバルアドレスにIPアドレスの変換を行います。

参考までに、Dockerでは、iptablesのnatテーブルに 172.17.0.0/16のSource NATが定義されています。
sudo iptables -t nat -L Chain PREROUTING (policy ACCEPT) target prot opt source destination DOCKER all -- anywhere anywhere ADDRTYPE match dst-type LOCAL Chain INPUT (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination DOCKER all -- anywhere !localhost/8 ADDRTYPE match dst-type LOCAL Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all -- 172.17.0.0/16 anywhere Chain DOCKER (2 references) target prot opt source destination RETURN all -- anywhere anywhere
また、wan側のホストからc2のホストに対して通信するために、router のホストでiptablesを使ってDestination NATを定義します。

同様に、Dockerで -p 80:8080のようにポートを指定して起動した場合は、ホストの80ポートをc2のホストの80ポートに転送する
Destination NATが定義されます。
$ docker run -it -d -p 80:80 --name c2 nicolaka/netshoot
$ sudo iptables -t nat -L Chain PREROUTING (policy ACCEPT) target prot opt source destination DOCKER all -- anywhere anywhere ADDRTYPE match dst-type LOCAL Chain INPUT (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination DOCKER all -- anywhere !localhost/8 ADDRTYPE match dst-type LOCAL Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all -- 172.17.0.0/16 anywhere MASQUERADE tcp -- 172.17.0.3 172.17.0.3 tcp dpt:http-alt Chain DOCKER (2 references) target prot opt source destination RETURN all -- anywhere anywhere DNAT tcp -- anywhere anywhere tcp dpt:http to:172.17.0.3:8080
仮想環境(Linux VM)の準備
MacのLinux VM上でネットワーク環境の構築を行います。 Mac上でLinux VMを動かす方法としては、VirtualBoxを使った方法が有名だと思いますが、 Intel MacのmacOS Montereyで動作しなかったり、M1 Macに対応していないため、別の方法を 使うのが良いかと思います。
手軽にLinux VMを動作させる方法には、主にMultipass と Limaの2つがあります。今回は、私がよく使うMultipass を使った方法について書きます。
ちなみに、Mac上のLinux VMについては、以下の記事も参考になるかと思います。
Multipassを使った仮想環境の準備
HomeBrewでインストール
$ brew install --cask multipass
UbuntuのVMの起動
$ multipass launch 20.04 --name tcpip --mem 5GB --disk 50GB Launched: tcpip
UbuntuのVMにログイン
$ multipass shell tcpip Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.4.0-91-generic aarch64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage System information as of Sat Dec 25 13:45:11 JST 2021 System load: 0.22 Usage of /: 2.6% of 48.29GB Memory usage: 3% Swap usage: 0% Processes: 110 Users logged in: 0 IPv4 address for enp0s1: 192.168.64.24 IPv6 address for enp0s1: fda6:a15e:b806:8445:5054:ff:fe87:2122 0 updates can be applied immediately.
IPフォワーディングの有効化
$ sudo sysctl -w net.ipv4.ip_forward=1 net.ipv4.ip_forward = 1
Network Namespaceを使ったネットワーク環境の構築
Dockerのインストール
Dockerコンテナを活用するため、パッケージマネージャーのapt経由でDockerをインストールします。
$ sudo apt update $ sudo apt install docker.io
Docker用のパッケージインストール後に、sudo なしでdockerコマンドを実行できるように、ユーザーをdockerグループ に追加します。
$ sudo gpasswd -a $USER docker
追加後は、一旦設定を有効化するために、multipassのLinux VMからログアウトし、再ログインしてください。
スクリプトの構築手順
Network Namespaceの作成
前述した各ホストのNetwork Namespaceを作成します。
$ sudo ip netns add ns1 $ sudo ip netns add ns2 $ sudo ip netns add ns3 $ sudo ip netns add router $ sudo ip netns add wan
仮想インターフェースの作成
ペアの仮想インターフェースを作成します。
例えば、ns1の仮想インターフェースns1-veth0とns1-veth0とペアになる仮想ブリッジbr0に接続する仮想インターフェースns1-brが
作成されます。
$ sudo ip link add ns1-veth0 type veth peer name ns1-br0 $ sudo ip link add ns2-veth0 type veth peer name ns2-br0 $ sudo ip link add ns3-veth0 type veth peer name ns3-br0 $ sudo ip link add gw-veth1 type veth peer name wan-veth0
仮想インターフェースをNetwork Namespaceに割り当て
作成したペアの仮想インターフェースを各ホストのNetwork Namespaceに設定します。
$ sudo ip link set ns1-veth0 netns ns1 $ sudo ip link set ns2-veth0 netns ns2 $ sudo ip link set ns3-veth0 netns ns3 $ sudo ip link set ns1-br0 netns router $ sudo ip link set ns2-br0 netns router $ sudo ip link set ns3-br0 netns router $ sudo ip link set gw-veth1 netns router $ sudo ip link set wan-veth0 netns wan
ブリッジの作成
routerのNetwork Namespace上に仮想ブリッジbr0を作成します。
また、仮想ブリッジに、ns1、ns2、ns3のペアとなる仮想インターフェースを割り当てます。
$ sudo ip netns exec router ip link add dev br0 type bridge $ sudo ip netns exec router ip link set ns1-br0 master br0 $ sudo ip netns exec router ip link set ns2-br0 master br0 $ sudo ip netns exec router ip link set ns3-br0 master br0
仮想インターフェースの有効化
デフォルトで仮想インターフェースのステータスがDOWNになっているため、ステータスをUPに変更します。
$ sudo ip netns exec ns1 ip link set ns1-veth0 up $ sudo ip netns exec ns1 ip link set lo up $ sudo ip netns exec ns2 ip link set ns2-veth0 up $ sudo ip netns exec ns2 ip link set lo up $ sudo ip netns exec ns3 ip link set ns3-veth0 up $ sudo ip netns exec ns3 ip link set lo up $ sudo ip netns exec router ip link set ns1-br0 up $ sudo ip netns exec router ip link set ns2-br0 up $ sudo ip netns exec router ip link set ns3-br0 up $ sudo ip netns exec router ip link set br0 up $ sudo ip netns exec router ip link set lo up $ sudo ip netns exec router ip link set gw-veth1 up $ sudo ip netns exec wan ip link set wan-veth0 up $ sudo ip netns exec wan ip link set lo up
仮想インターフェースにIPアドレスやルーティングの割り当て
各ホストのNetwork Namespaceの仮想インターフェースに対して、アドレスの設定を行います。
$ sudo ip netns exec ns1 ip address add 192.0.2.1/24 dev ns1-veth0 $ sudo ip netns exec ns1 ip route add default via 192.0.2.254 $ sudo ip netns exec ns1 ip link set ns1-veth0 address 00:00:5E:00:53:01 $ sudo ip netns exec ns2 ip address add 192.0.2.2/24 dev ns2-veth0 $ sudo ip netns exec ns2 ip route add default via 192.0.2.254 $ sudo ip netns exec ns2 ip link set ns2-veth0 address 00:00:5E:00:53:02 $ sudo ip netns exec ns3 ip address add 192.0.2.3/24 dev ns3-veth0 $ sudo ip netns exec ns3 ip route add default via 192.0.2.254 $ sudo ip netns exec ns3 ip link set ns3-veth0 address 00:00:5E:00:53:03 $ sudo ip netns exec router ip address add 192.0.2.254/24 dev br0 $ sudo ip netns exec router ip address add 203.0.113.254/24 dev gw-veth1 $ sudo ip netns exec wan ip address add 203.0.113.1/24 dev wan-veth0 $ sudo ip netns exec wan ip route add default via 203.0.113.254
ルーターのIPフォワーディングの有効化
パケットの転送を行うために、routerとなるNetwork Namespace上で、net.ipv4.ip_forwardの
カーネルパラメータを1に設定します。
$ sudo ip netns exec router sysctl net.ipv4.ip_forward=1
ルーターのループバックアドレスの転送設定の有効化
routerのNetwork Namespaceからlocalhost経由でホストに転送するために使用します。
$ sudo ip netns exec router sysctl net.ipv4.conf.all.route_localnet=1
ただ、正直この設定がよく理解できていません。
route_localnet - BOOLEAN Do not consider loopback addresses as martian source or destination while routing. This enables the use of 127/8 for local routing purposes. default FALSE
https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt
iptablesのSource NAT(IPマスカレード)の設定
routerのホスト側からwan側のネットワークにパケットを転送するために、Source NATを使って、 プライベートアドレスからグローバルアドレスにIPアドレスの変換を行います。
$ sudo ip netns exec router iptables -t nat \
-A POSTROUTING \
-s 192.0.2.0/24 \
-o gw-veth1 \
-j MASQUERADE
iptablesのDestination NATの設定
routerのwan側やlocalhostから内部のホストns2の80ポートにパケットを転送するために、Destination NATを使って
IPアドレスの変換を行います。
$ sudo ip netns exec router iptables -t nat \
-A PREROUTING \
--dst 203.0.113.254 \
-p tcp \
--dport 80 \
-j DNAT \
--to-destination 192.0.2.2:80
$ sudo ip netns exec router iptables -t nat \
-A OUTPUT \
--dst 203.0.113.254 \
-p tcp \
--dport 80 \
-j DNAT \
--to-destination 192.0.2.2:80
$ sudo ip netns exec router iptables -t nat \
-A OUTPUT \
--dst 127.0.0.1 \
-p tcp \
--dport 80 \
-j DNAT \
--to-destination 192.0.2.2:80
$ sudo ip netns exec router iptables -t nat \
-A POSTROUTING \
-p tcp \
--dst 192.0.2.2 \
--dport 80 \
-j SNAT \
--to-source 203.0.113.254
スクリプトの動作検証
スクリプト全文 (シェルスクリプト)
create_docker_like_bridge_network.sh というスクリプトとして保存します。
#!/usr/bin/env bash
# Network Namespaceの作成
sudo ip netns add ns1
sudo ip netns add ns2
sudo ip netns add ns3
sudo ip netns add router
sudo ip netns add wan
# 仮想インターフェースをNetwork Namespaceに割り当て
sudo ip link add ns1-veth0 type veth peer name ns1-br0
sudo ip link add ns2-veth0 type veth peer name ns2-br0
sudo ip link add ns3-veth0 type veth peer name ns3-br0
sudo ip link add gw-veth1 type veth peer name wan-veth0
# 仮想インターフェースをNetwork Namespaceに割り当て
sudo ip link set ns1-veth0 netns ns1
sudo ip link set ns2-veth0 netns ns2
sudo ip link set ns3-veth0 netns ns3
sudo ip link set ns1-br0 netns router
sudo ip link set ns2-br0 netns router
sudo ip link set ns3-br0 netns router
sudo ip link set gw-veth1 netns router
sudo ip link set wan-veth0 netns wan
# ブリッジの作成
sudo ip netns exec router ip link add dev br0 type bridge
sudo ip netns exec router ip link set ns1-br0 master br0
sudo ip netns exec router ip link set ns2-br0 master br0
sudo ip netns exec router ip link set ns3-br0 master br0
# 仮想インターフェースの有効化
## ns1
sudo ip netns exec ns1 ip link set ns1-veth0 up
sudo ip netns exec ns1 ip link set lo up
## ns2
sudo ip netns exec ns2 ip link set ns2-veth0 up
sudo ip netns exec ns2 ip link set lo up
## ns3
sudo ip netns exec ns3 ip link set ns3-veth0 up
sudo ip netns exec ns3 ip link set lo up
## router
sudo ip netns exec router ip link set ns1-br0 up
sudo ip netns exec router ip link set ns2-br0 up
sudo ip netns exec router ip link set ns3-br0 up
sudo ip netns exec router ip link set br0 up
sudo ip netns exec router ip link set lo up
sudo ip netns exec router ip link set gw-veth1 up
## wan
sudo ip netns exec wan ip link set wan-veth0 up
sudo ip netns exec wan ip link set lo up
# 仮想インターフェースにIPアドレスやルーティングの割り当て
## ns1
sudo ip netns exec ns1 ip address add 192.0.2.1/24 dev ns1-veth0
sudo ip netns exec ns1 ip route add default via 192.0.2.254
sudo ip netns exec ns1 ip link set ns1-veth0 address 00:00:5E:00:53:01
## ns2
sudo ip netns exec ns2 ip address add 192.0.2.2/24 dev ns2-veth0
sudo ip netns exec ns2 ip route add default via 192.0.2.254
sudo ip netns exec ns2 ip link set ns2-veth0 address 00:00:5E:00:53:02
## ns3
sudo ip netns exec ns3 ip address add 192.0.2.3/24 dev ns3-veth0
sudo ip netns exec ns3 ip route add default via 192.0.2.254
sudo ip netns exec ns3 ip link set ns3-veth0 address 00:00:5E:00:53:03
## router
sudo ip netns exec router ip address add 192.0.2.254/24 dev br0
sudo ip netns exec router ip address add 203.0.113.254/24 dev gw-veth1
## wan
sudo ip netns exec wan ip address add 203.0.113.1/24 dev wan-veth0
sudo ip netns exec wan ip route add default via 203.0.113.254
# ルーターのIPフォワーディングの有効化
sudo ip netns exec router sysctl net.ipv4.ip_forward=1
# ルーターのループバックアドレスの転送設定の有効化
sudo ip netns exec router sysctl net.ipv4.conf.all.route_localnet=1
# iptablesのSource NAT(IPマスカレード)の設定
sudo ip netns exec router iptables -t nat \
-A POSTROUTING \
-s 192.0.2.0/24 \
-o gw-veth1 \
-j MASQUERADE
# iptablesのDestination NATの設定
sudo ip netns exec router iptables -t nat \
-A PREROUTING \
--dst 203.0.113.254 \
-p tcp \
--dport 80 \
-j DNAT \
--to-destination 192.0.2.2:80
sudo ip netns exec router iptables -t nat \
-A OUTPUT \
--dst 203.0.113.254 \
-p tcp \
--dport 80 \
-j DNAT \
--to-destination 192.0.2.2:80
sudo ip netns exec router iptables -t nat \
-A OUTPUT \
--dst 127.0.0.1 \
-p tcp \
--dport 80 \
-j DNAT \
--to-destination 192.0.2.2:80
sudo ip netns exec router iptables -t nat \
-A POSTROUTING \
-p tcp \
--dst 192.0.2.2 \
--dport 80 \
-j SNAT \
--to-source 203.0.113.254
スクリプトの実行
作成済みの`create_docker_like_bridge_network.sh`を実行します。
$ chmod + x create_docker_like_bridge_network.sh $ ./create_docker_like_bridge_network.sh net.ipv4.ip_forward = 1 net.ipv4.conf.all.route_localnet = 1
作成済みのNetwork Namespaceの確認
$ ip netns ls wan (id: 4) router (id: 3) ns3 (id: 2) ns2 (id: 1) ns1 (id: 0)
ns1の確認
$ sudo ip netns exec ns1 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> 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
4: ns1-veth0@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 00:00:5e:00:53:01 brd ff:ff:ff:ff:ff:ff link-netns router
inet 192.0.2.1/24 scope global ns1-veth0
valid_lft forever preferred_lft forever
inet6 fe80::8014:38ff:fea0:f294/64 scope link
valid_lft forever preferred_lft forever
$ sudo ip netns exec ns1 ip route
default via 192.0.2.254 dev ns1-veth0
192.0.2.0/24 dev ns1-veth0 proto kernel scope link src 192.0.2.1
ns2の確認
$ sudo ip netns exec ns2 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> 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
6: ns2-veth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 00:00:5e:00:53:02 brd ff:ff:ff:ff:ff:ff link-netns router
inet 192.0.2.2/24 scope global ns2-veth0
valid_lft forever preferred_lft forever
inet6 fe80::ecb7:c5ff:fe7a:ccad/64 scope link
valid_lft forever preferred_lft forever
$ sudo ip netns exec ns2 ip route
default via 192.0.2.254 dev ns2-veth0
192.0.2.0/24 dev ns2-veth0 proto kernel scope link src 192.0.2.2
ns3の確認
$ sudo ip netns exec ns3 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> 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
8: ns3-veth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 00:00:5e:00:53:03 brd ff:ff:ff:ff:ff:ff link-netns router
inet 192.0.2.3/24 scope global ns3-veth0
valid_lft forever preferred_lft forever
inet6 fe80::e4f8:aeff:fed1:8039/64 scope link
$ sudo ip netns exec ns3 ip route
default via 192.0.2.254 dev ns3-veth0
192.0.2.0/24 dev ns3-veth0 proto kernel scope link src 192.0.2.3
routerの確認
$ sudo ip netns exec router ip a
1: lo: <LOOPBACK,UP,LOWER_UP> 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: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 42:07:e4:03:4a:a1 brd ff:ff:ff:ff:ff:ff
inet 192.0.2.254/24 scope global br0
valid_lft forever preferred_lft forever
inet6 fe80::4007:e4ff:fe03:4aa1/64 scope link
valid_lft forever preferred_lft forever
3: ns1-br0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UP group default qlen 1000
link/ether be:4a:4e:40:6e:e1 brd ff:ff:ff:ff:ff:ff link-netns ns1
inet6 fe80::bc4a:4eff:fe40:6ee1/64 scope link
valid_lft forever preferred_lft forever
5: ns2-br0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UP group default qlen 1000
link/ether 42:07:e4:03:4a:a1 brd ff:ff:ff:ff:ff:ff link-netns ns2
inet6 fe80::4007:e4ff:fe03:4aa1/64 scope link
valid_lft forever preferred_lft forever
7: ns3-br0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UP group default qlen 1000
link/ether 8a:87:6d:1d:78:f5 brd ff:ff:ff:ff:ff:ff link-netns ns3
inet6 fe80::8887:6dff:fe1d:78f5/64 scope link
valid_lft forever preferred_lft forever
10: gw-veth1@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 4e:2f:cb:84:ee:a2 brd ff:ff:ff:ff:ff:ff link-netns wan
inet 203.0.113.254/24 scope global gw-veth1
valid_lft forever preferred_lft forever
inet6 fe80::4c2f:cbff:fe84:eea2/64 scope link
valid_lft forever preferred_lft forever
$ sudo ip netns exec router ip route
192.0.2.0/24 dev br0 proto kernel scope link src 192.0.2.254
203.0.113.0/24 dev gw-veth1 proto kernel scope link src 203.0.113.254
wanの確認
$ sudo ip netns exec wan ip a
1: lo: <LOOPBACK,UP,LOWER_UP> 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
9: wan-veth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 26:f9:80:46:ed:0e brd ff:ff:ff:ff:ff:ff link-netns router
inet 203.0.113.1/24 scope global wan-veth0
valid_lft forever preferred_lft forever
inet6 fe80::24f9:80ff:fe46:ed0e/64 scope link
valid_lft forever preferred_lft forever
$ sudo ip netns exec wan ip route
default via 203.0.113.254 dev wan-veth0
203.0.113.0/24 dev wan-veth0 proto kernel scope link src 203.0.113.1
疎通確認
ローカルホストからwan側への疎通
$ sudo ip netns exec ns1 ping -c 1 203.0.113.1 PING 203.0.113.1 (203.0.113.1) 56(84) bytes of data. 64 bytes from 203.0.113.1: icmp_seq=1 ttl=63 time=0.170 ms --- 203.0.113.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.170/0.170/0.170/0.000 ms $ sudo ip netns exec ns2 ping -c 1 203.0.113.1 PING 203.0.113.1 (203.0.113.1) 56(84) bytes of data. 64 bytes from 203.0.113.1: icmp_seq=1 ttl=63 time=0.103 ms --- 203.0.113.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.103/0.103/0.103/0.000 ms $ sudo ip netns exec ns3 ping -c 1 203.0.113.1 PING 203.0.113.1 (203.0.113.1) 56(84) bytes of data. 64 bytes from 203.0.113.1: icmp_seq=1 ttl=63 time=0.154 ms --- 203.0.113.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.154/0.154/0.154/0.000 ms
wan側からホストへの疎通
wan側からホスト側のns2(192.0.2.2)への疎通ができているかを確認するために、 ns2のNetwork Namespaceに接続し、netcatコマンドで80番ポートを待ち受けしておきます。
$ sudo ip netns exec ns2 bash root@tcpip:/home/ubuntu# nc -lvn 80 Listening on 0.0.0.0 80
同様に、wan側のNetwork Namespaceに接続します。(別ターミナルを使います)
$ sudo ip netns exec wan curl 203.0.113.254
上記の実行後に、ns2の標準出力に、接続受信の出力が表示されたことを確認できます。
root@tcpip:/home/ubuntu# nc -lvn 80 Listening on 0.0.0.0 80 Connection received on 203.0.113.254 60020 GET / HTTP/1.1 Host: 203.0.113.254 User-Agent: curl/7.68.0 Accept: */*
また、routerのlocalhost経由のアクセスでも、ns2のホストにアクセスできることが確認できます。
$ sudo ip netns exec router curl localhost
$ root@tcpip:/home/ubuntu# nc -lvn 80 Listening on 0.0.0.0 80 Connection received on 203.0.113.254 36414 GET / HTTP/1.1 Host: localhost User-Agent: curl/7.68.0 Accept: */*
[発展編]Dockerコンテナを使ったネットワーク環境の構築
Network Namespaceを使ってネットワーク環境を構築することができました。 さらに、既存のDockerコンテナのNetwork Namespaceを 活用することで、Dockerコンテナをベースとしたネットワーク環境も構築することができます。
Dockerコンテナのネットワークを構築する方法として有名なのは、Docker Composeだと思います。 ただし、ネットワークの学習を目的とする上では、Namework NamespaceのコマンドをYAMLの設定ファイルに記述できる tinetという便利なツールを使うことができます。
tinetのリポジトリには以下のように書かれています。
TiNET is network emulator environment for network function developer, routing software developer and networking educator. this is very simple tool that generate just shell script to construct virtual network.
DeepLで翻訳すると、以下のようになります。ということで、ネットワーク屋の人以外にも、 ネットワーク学習の教育向けにも活用できるのかと思います。
TiNETは、ネットワーク機能開発者、ルーティングソフトウェア開発者、 ネットワーク教育者向けのネットワークエミュレータ環境です。
また、tinetの作者の方のツイートですが、参考になるかと思います。
ちなみに勉強のアプローチですが, 「network namespaceを生で叩いて, 仮想ネットワークを作ってみて, 色々な設定をする」というのが現時点での最良の勉強方法だと確信しています.
— slankdev (@slankdev) 2021年11月2日
ちなみにnetwork namespaceをいちいち作るのが大変なので我々のチームや, WIDEprojectの学生はこのtinetというツールを使ってその構築を効率化しています.https://t.co/78e8W5vkNz
— slankdev (@slankdev) 2021年11月2日
ホスト用のDockerコンテナのイメージ作成
各ネットワークユーティリティ用のコマンドをインストールしたコンテナイメージとnginxをインストールしたコンテナイメージの 2つを作成します。
ネットワークユーティリティ用のコマンドをインストールしたコンテナイメージ
Dockerfile_network という名称のDockerfileを作成します。
FROM ubuntu
RUN apt-get update && \
apt-get install -y iputils-ping && \
apt-get install -y iptables && \
apt-get install -y vim && \
apt-get install -y iproute2 && \
apt-get install -y tcpdump && \
apt-get install -y netcat && \
apt-get install -y curl
Dockerfileを指定し、netutilsという名称のコンテナイメージを作成します。
$ docker build -f Dockerfile_network -t netutils . Sending build context to Docker daemon 31.74kB Step 1/2 : FROM ubuntu ---> d5ca7a445605 Step 2/2 : RUN apt-get update && apt-get install -y iputils-ping && apt-get install -y iptables && apt-get install -y vim && apt-get install -y iproute2 && apt-get install -y tcpdump && apt-get install -y netcat && apt-get install -y curl
nginxをインストールしたコンテナイメージ
上記のコンテナイメージをベースに作成します。
ここでは、Dockerfile_nginxという名称のDockerfileを作成します。
FROM netutils
RUN apt-get update && \
apt-get install -y nginx
RUN echo "Hello Docker nginx container!" > /var/www/html/index.nginx-debian.html
ENTRYPOINT /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf
上記設定により、コンテナ起動時にENTRYPOINT命令にて 80ポートでnginxが起動されます。
また、疎通確認のために、nginxのHTTPサーバが返すhtmlファイルに対して、
Hello Docker nginx container!の文字列を返すように設定しています。
次に、Dockerfileを指定し、netutilsという名称のコンテナイメージを作成します。
$ docker build -f Dockerfile_nginx -t mynginx .
tinetのインストール
Intel Macを使っている場合
tinetのリポジトリのREADMEに記載のQuick Install通りに実行するとインストールされます。
$ curl -Lo /usr/bin/tinet https://github.com/tinynetwork/tinet/releases/download/v0.0.2/tinet chmod +x /usr/bin/tinet tinet --version
M1 Macを使っている場合
READMEに記載のQuick Install通りに実行しても、インストールに失敗します。おそらく curlでダウンロードしたバイナリが x86-64のアーキテクチャであるため
だと思います。
$ sudo file /usr/bin/tinet /usr/bin/tinet: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, Go BuildID=jrOQWb6v99DQpWDhefnP/nkrgg-bbZ4nDD4NHSrOM/nEL8ZQgelBKEONQiNAIx/Ql1cZnv4FmBvVWzIiIFU, not stripped
そのため、READMEに記載のBuildの手順に従ってビルドを実行します。
ただし、READMEに記載されているgolang:1.12のバージョンでは、ビルドに失敗するため、
回避策として golang:1.17を指定します。
$ git clone https://github.com/tinynetwork/tinet tinet && cd $_ $ docker run --rm -i -t -v $PWD:/v -w /v golang:1.17 go build $ sudo mv tinet /usr/local/bin
上記を実行すると、バイナリがarm64で作成されます。
$ sudo file /usr/local/bin/tinet /usr/local/bin/tinet: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, Go BuildID=LdJoSueuwc0karvXq8PX/cNk60_xkM-SFqx0DM3mt/a7XFmysuOm95ONK51OlB/kriiaVa9SJwzq5fFkrNt, not stripped
スクリプトの構築手順
Network Namespaceのネットワーク構築を使ったスクリプトを利用しますが、スクリプトの設定をtinetの設定ファイルの YAMLファイルに記述します。
この設定ファイルには、主に以下のような3つの設定項目があります
nodes 各ホストのDockerコンテナのイメージや仮想インターフェースを定義します。
node_configs 各ホスト上でコマンドを実行し、仮想インターフェースやルーティングテーブルなどを設定します。
test ネットワーク検証用のテストコマンドを記述します。
nodes項目の定義
各ホストのノードのコンテナイメージや仮想インターフェースを定義します。 ここでは、各ホストでnetuitlsのコンテナイメージを指定しますが、ns2のホストのみwan側からの疎通確認のために、 nginxをインインストール済みのmynginxのコンテナイメージを指定します。
nodes: - name: ns1 image: netutils interfaces: - { name: ns1-veth0, type: direct, args: router#ns1-br0 } - name: ns2 image: mynginx interfaces: - { name: ns2-veth0, type: direct, args: router#ns2-br0 } - name: ns3 image: netutils interfaces: - { name: ns3-veth0, type: direct, args: router#ns3-br0 } - name: router image: netutils interfaces: - { name: ns1-br0, type: direct, args: ns1#ns1-veth0 } - { name: ns2-br0, type: direct, args: ns2#ns2-veth0 } - { name: ns3-br0, type: direct, args: ns3#ns3-veth0 } - { name: gw-veth1, type: direct, args: wan#wan-veth0 } - name: wan image: netutils interfaces: - { name: wan-veth0, type: direct, args: router#gw-veth1 }
node_configs項目の定義
node_configs: - name: ns1 cmds: - cmd: ip addr add 192.0.2.1/24 dev ns1-veth0 - cmd: ip route add default via 192.0.2.254 - cmd: ip link set ns1-veth0 address 00:00:5E:00:53:01 - name: ns2 cmds: - cmd: ip addr add 192.0.2.2/24 dev ns2-veth0 - cmd: ip route add default via 192.0.2.254 - cmd: ip link set ns2-veth0 address 00:00:5E:00:53:02 - name: ns3 cmds: - cmd: ip addr add 192.0.2.3/24 dev ns3-veth0 - cmd: ip route add default via 192.0.2.254 - cmd: ip link set ns3-veth0 address 00:00:5E:00:53:03 - name: router cmds: - cmd: ip link add dev br0 type bridge - cmd: ip link set br0 up - cmd: ip link set ns1-br0 master br0 - cmd: ip link set ns2-br0 master br0 - cmd: ip link set ns3-br0 master br0 - cmd: ip addr add 192.0.2.254/24 dev br0 - cmd: ip addr add 203.0.113.254/24 dev gw-veth1 - cmd: sysctl net.ipv4.ip_forward=1 - cmd: sysctl net.ipv4.conf.all.route_localnet=1 - cmd: >- iptables -t nat -A POSTROUTING -s 192.0.2.0/24 -o gw-veth1 -j MASQUERADE - cmd: >- iptables -t nat -A PREROUTING --dst 203.0.113.254 -p tcp --dport 80 -j DNAT --to-destination 192.0.2.2:80 - cmd: >- iptables -t nat -A OUTPUT --dst 203.0.113.254 -p tcp --dport 80 -j DNAT --to-destination 192.0.2.2:80 - cmd: >- iptables -t nat -A OUTPUT --dst 127.0.0.1 -p tcp --dport 80 -j DNAT --to-destination 192.0.2.2:80 - cmd: >- iptables -t nat -A POSTROUTING -p tcp --dst 192.0.2.2 --dport 80 -j SNAT --to-source 203.0.113.254 - name: wan cmds: - cmd: ip addr add 203.0.113.1/24 dev wan-veth0 - cmd: ip route add default via 203.0.113.254
test項目の定義
ここでは、単純にホスト側からwan側への疎通用のテストコマンドを定義しています。
test: cmds: - cmd: echo "==========================================" - cmd: echo "Connectivity test from ns1 (192.0.2.1)" - cmd: echo "==========================================" - cmd: docker exec ns1 ping -c 1 203.0.113.1 - cmd: echo "==========================================" - cmd: echo "Connectivity test from ns2 (192.0.2.2)" - cmd: echo "==========================================" - cmd: docker exec ns2 ping -c 1 203.0.113.1 - cmd: echo "==========================================" - cmd: echo "Connectivity test from ns3 (192.0.2.3)" - cmd: echo "==========================================" - cmd: docker exec ns3 ping -c 1 203.0.113.1
スクリプトの動作検証
スクリプトの設定ファイル全文 (yaml)
docker_bridge_like_network_spec.yamlという名称でスクリプトを実行します。
--- nodes: - name: ns1 image: netutils interfaces: - { name: ns1-veth0, type: direct, args: router#ns1-br0 } - name: ns2 image: mynginx interfaces: - { name: ns2-veth0, type: direct, args: router#ns2-br0 } - name: ns3 image: netutils interfaces: - { name: ns3-veth0, type: direct, args: router#ns3-br0 } - name: router image: netutils interfaces: - { name: ns1-br0, type: direct, args: ns1#ns1-veth0 } - { name: ns2-br0, type: direct, args: ns2#ns2-veth0 } - { name: ns3-br0, type: direct, args: ns3#ns3-veth0 } - { name: gw-veth1, type: direct, args: wan#wan-veth0 } - name: wan image: netutils interfaces: - { name: wan-veth0, type: direct, args: router#gw-veth1 } node_configs: - name: ns1 cmds: - cmd: ip addr add 192.0.2.1/24 dev ns1-veth0 - cmd: ip route add default via 192.0.2.254 - cmd: ip link set ns1-veth0 address 00:00:5E:00:53:01 - name: ns2 cmds: - cmd: ip addr add 192.0.2.2/24 dev ns2-veth0 - cmd: ip route add default via 192.0.2.254 - cmd: ip link set ns2-veth0 address 00:00:5E:00:53:02 - name: ns3 cmds: - cmd: ip addr add 192.0.2.3/24 dev ns3-veth0 - cmd: ip route add default via 192.0.2.254 - cmd: ip link set ns3-veth0 address 00:00:5E:00:53:03 - name: router cmds: - cmd: ip link add dev br0 type bridge - cmd: ip link set br0 up - cmd: ip link set ns1-br0 master br0 - cmd: ip link set ns2-br0 master br0 - cmd: ip link set ns3-br0 master br0 - cmd: ip addr add 192.0.2.254/24 dev br0 - cmd: ip addr add 203.0.113.254/24 dev gw-veth1 - cmd: sysctl net.ipv4.ip_forward=1 - cmd: sysctl net.ipv4.conf.all.route_localnet=1 - cmd: >- iptables -t nat -A POSTROUTING -s 192.0.2.0/24 -o gw-veth1 -j MASQUERADE - cmd: >- iptables -t nat -A PREROUTING --dst 203.0.113.254 -p tcp --dport 80 -j DNAT --to-destination 192.0.2.2:80 - cmd: >- iptables -t nat -A OUTPUT --dst 203.0.113.254 -p tcp --dport 80 -j DNAT --to-destination 192.0.2.2:80 - cmd: >- iptables -t nat -A OUTPUT --dst 127.0.0.1 -p tcp --dport 80 -j DNAT --to-destination 192.0.2.2:80 - cmd: >- iptables -t nat -A POSTROUTING -p tcp --dst 192.0.2.2 --dport 80 -j SNAT --to-source 203.0.113.254 - name: wan cmds: - cmd: ip addr add 203.0.113.1/24 dev wan-veth0 - cmd: ip route add default via 203.0.113.254 test: cmds: - cmd: echo "==========================================" - cmd: echo "Connectivity test from ns1 (192.0.2.1)" - cmd: echo "==========================================" - cmd: docker exec ns1 ping -c 1 203.0.113.1 - cmd: echo "==========================================" - cmd: echo "Connectivity test from ns2 (192.0.2.2)" - cmd: echo "==========================================" - cmd: docker exec ns2 ping -c 1 203.0.113.1 - cmd: echo "==========================================" - cmd: echo "Connectivity test from ns3 (192.0.2.3)" - cmd: echo "==========================================" - cmd: docker exec ns3 ping -c 1 203.0.113.1
スクリプトの実行
docker_bridge_like_network_spec.yaml の設定ファイルを指定し、tinetのupコマンドとconfコマンドを実行します。
upコマンドはDockerコンテナの起動を行います。confコマンドは、起動中のDockerコンテナに対して仮想インターフェースの
設定などを行います。
これらを同時に実行するための upconf コマンドもあります。
tinet upコマンドの実行
upコマンドでコンテナを起動します。
$ tinet up -c docker_bridge_like_network_spec.yaml | sudo sh -x
+ docker run -td --net none --name ns1 --rm --privileged --hostname ns1 -v /tmp/tinet:/tinet netutils
+ mkdir -p /var/run/netns
+ docker inspect ns1 --format {{.State.Pid}}
+ PID=24154
+ ln -s /proc/24154/ns/net /var/run/netns/ns1
+ docker run -td --net none --name ns2 --rm --privileged --hostname ns2 -v /tmp/tinet:/tinet mynginx
+ mkdir -p /var/run/netns
+ docker inspect ns2 --format {{.State.Pid}}
+ PID=24233
+ ln -s /proc/24233/ns/net /var/run/netns/ns2
+ docker run -td --net none --name ns3 --rm --privileged --hostname ns3 -v /tmp/tinet:/tinet netutils
+ mkdir -p /var/run/netns
+ docker inspect ns3 --format {{.State.Pid}}
+ PID=24311
+ ln -s /proc/24311/ns/net /var/run/netns/ns3
+ docker run -td --net none --name router --rm --privileged --hostname router -v /tmp/tinet:/tinet netutils
+ mkdir -p /var/run/netns
+ docker inspect router --format {{.State.Pid}}
+ PID=24387
+ ln -s /proc/24387/ns/net /var/run/netns/router
+ docker run -td --net none --name wan --rm --privileged --hostname wan -v /tmp/tinet:/tinet netutils
+ mkdir -p /var/run/netns
+ docker inspect wan --format {{.State.Pid}}
+ PID=24463
+ ln -s /proc/24463/ns/net /var/run/netns/wan
+ ip link add ns1-veth0 netns ns1 type veth peer name ns1-br0 netns router
+ ip netns exec ns1 ip link set ns1-veth0 up
+ ip netns exec router ip link set ns1-br0 up
+ ip link add ns2-veth0 netns ns2 type veth peer name ns2-br0 netns router
+ ip netns exec ns2 ip link set ns2-veth0 up
+ ip netns exec router ip link set ns2-br0 up
+ ip link add ns3-veth0 netns ns3 type veth peer name ns3-br0 netns router
+ ip netns exec ns3 ip link set ns3-veth0 up
+ ip netns exec router ip link set ns3-br0 up
+ ip link add gw-veth1 netns router type veth peer name wan-veth0 netns wan
+ ip netns exec router ip link set gw-veth1 up
+ ip netns exec wan ip link set wan-veth0 up
+ ip netns del ns1
+ ip netns del ns2
+ ip netns del ns3
+ ip netns del router
+ ip netns del wan
tinet confコマンドの実行
起動中のコンテナに対して、仮想インターフェースのアドレスやルーティングの設定などを行います。
$ tinet conf -c docker_bridge_like_network_spec.yaml | sudo sh -x + docker exec ns1 ip addr add 192.0.2.1/24 dev ns1-veth0 + docker exec ns1 ip route add default via 192.0.2.254 + docker exec ns1 ip link set ns1-veth0 address 00:00:5E:00:53:01 + docker exec ns2 ip addr add 192.0.2.2/24 dev ns2-veth0 + docker exec ns2 ip route add default via 192.0.2.254 + docker exec ns2 ip link set ns2-veth0 address 00:00:5E:00:53:02 + docker exec ns3 ip addr add 192.0.2.3/24 dev ns3-veth0 + docker exec ns3 ip route add default via 192.0.2.254 + docker exec ns3 ip link set ns3-veth0 address 00:00:5E:00:53:03 + docker exec router ip link add dev br0 type bridge + docker exec router ip link set br0 up + docker exec router ip link set ns1-br0 master br0 + docker exec router ip link set ns2-br0 master br0 + docker exec router ip link set ns3-br0 master br0 + docker exec router ip addr add 192.0.2.254/24 dev br0 + docker exec router ip addr add 203.0.113.254/24 dev gw-veth1 + docker exec router sysctl net.ipv4.ip_forward=1 + docker exec router sysctl net.ipv4.conf.all.route_localnet=1 + docker exec router iptables -t nat -A POSTROUTING -s 192.0.2.0/24 -o gw-veth1 -j MASQUERADE + docker exec router iptables -t nat -A PREROUTING --dst 203.0.113.254 -p tcp --dport 80 -j DNAT --to-destination 192.0.2.2:80 + docker exec router iptables -t nat -A OUTPUT --dst 203.0.113.254 -p tcp --dport 80 -j DNAT --to-destination 192.0.2.2:80 + docker exec router iptables -t nat -A OUTPUT --dst 127.0.0.1 -p tcp --dport 80 -j DNAT --to-destination 192.0.2.2:80 + docker exec router iptables -t nat -A POSTROUTING -p tcp --dst 192.0.2.2 --dport 80 -j SNAT --to-source 203.0.113.254 + docker exec wan ip addr add 203.0.113.1/24 dev wan-veth0 + docker exec wan ip route add default via 203.0.113.254
作成済みのDockerコンテナの確認
docker psコマンドを実行すると、各コンテナが起動していることを確認できます。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7cfd223d072b netutils "bash" 3 minutes ago Up 3 minutes wan 898004063e9a netutils "bash" 3 minutes ago Up 3 minutes router 6f3a6412d755 netutils "bash" 3 minutes ago Up 3 minutes ns3 39da406646fc mynginx "/bin/sh -c '/usr/sb…" 3 minutes ago Up 3 minutes ns2 e5160a7057f6 netutils "bash" 3 minutes ago Up 3 minutes ns1
ns1の確認
$ docker exec -it ns1 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> 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
2: ns1-veth0@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 00:00:5e:00:53:01 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.0.2.1/24 scope global ns1-veth0
valid_lft forever preferred_lft forever
$ docker exec -it ns1 ip route
default via 192.0.2.254 dev ns1-veth0
192.0.2.0/24 dev ns1-veth0 proto kernel scope link src 192.0.2.1
ns2の確認
$ docker exec -it ns2 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> 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
2: ns2-veth0@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 00:00:5e:00:53:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.0.2.2/24 scope global ns2-veth0
valid_lft forever preferred_lft forever
$ docker exec -it ns2 ip route
default via 192.0.2.254 dev ns2-veth0
192.0.2.0/24 dev ns2-veth0 proto kernel scope link src 192.0.2.2
ns3の確認
$ docker exec -it ns3 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> 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
2: ns3-veth0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 00:00:5e:00:53:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.0.2.3/24 scope global ns3-veth0
valid_lft forever preferred_lft forever
$ docker exec -it ns3 ip route
default via 192.0.2.254 dev ns3-veth0
192.0.2.0/24 dev ns3-veth0 proto kernel scope link src 192.0.2.3
routerの確認
$ docker exec -it ns3 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> 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
2: ns3-veth0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 00:00:5e:00:53:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.0.2.3/24 scope global ns3-veth0
valid_lft forever preferred_lft forever
$ docker exec -it ns3 ip route
default via 192.0.2.254 dev ns3-veth0
192.0.2.0/24 dev ns3-veth0 proto kernel scope link src 192.0.2.3
wanの確認
$ docker exec -it wan ip a
1: lo: <LOOPBACK,UP,LOWER_UP> 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
2: wan-veth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether ce:11:e6:78:60:57 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 203.0.113.1/24 scope global wan-veth0
valid_lft forever preferred_lft forever
$ docker exec -it wan ip route
default via 203.0.113.254 dev wan-veth0
203.0.113.0/24 dev wan-veth0 proto kernel scope link src 203.0.113.1
疎通確認
ローカルホストからwan側への疎通
$ docker exec -i ns1 ping -c 1 203.0.113.1 PING 203.0.113.1 (203.0.113.1) 56(84) bytes of data. 64 bytes from 203.0.113.1: icmp_seq=1 ttl=63 time=0.070 ms --- 203.0.113.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.070/0.070/0.070/0.000 ms $ docker exec -i ns2 ping -c 1 203.0.113.1 PING 203.0.113.1 (203.0.113.1) 56(84) bytes of data. 64 bytes from 203.0.113.1: icmp_seq=1 ttl=63 time=0.065 ms --- 203.0.113.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.065/0.065/0.065/0.000 ms docker exec -i ns3 ping -c 1 203.0.113.1 PING 203.0.113.1 (203.0.113.1) 56(84) bytes of data. 64 bytes from 203.0.113.1: icmp_seq=1 ttl=63 time=0.063 ms --- 203.0.113.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.063/0.063/0.063/0.000 ms
tinet testを使っても検証できます。
tinet test -c docker_bridge_like_network_spec.yaml | sudo sh -x + echo ========================================== ========================================== + echo Connectivity test from ns1 (192.0.2.1) Connectivity test from ns1 (192.0.2.1) + echo ========================================== ========================================== + docker exec ns1 ping -c 1 203.0.113.1 PING 203.0.113.1 (203.0.113.1) 56(84) bytes of data. 64 bytes from 203.0.113.1: icmp_seq=1 ttl=63 time=0.058 ms --- 203.0.113.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.058/0.058/0.058/0.000 ms + echo ========================================== ========================================== + echo Connectivity test from ns2 (192.0.2.2) Connectivity test from ns2 (192.0.2.2) + echo ========================================== ========================================== + docker exec ns2 ping -c 1 203.0.113.1 PING 203.0.113.1 (203.0.113.1) 56(84) bytes of data. 64 bytes from 203.0.113.1: icmp_seq=1 ttl=63 time=0.055 ms --- 203.0.113.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.055/0.055/0.055/0.000 ms + echo ========================================== ========================================== + echo Connectivity test from ns3 (192.0.2.3) Connectivity test from ns3 (192.0.2.3) + echo ========================================== ========================================== + docker exec ns3 ping -c 1 203.0.113.1 PING 203.0.113.1 (203.0.113.1) 56(84) bytes of data. 64 bytes from 203.0.113.1: icmp_seq=1 ttl=63 time=0.058 ms --- 203.0.113.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.058/0.058/0.058/0.000 ms
wan側からホストへの疎通
wan側からホスト側のns2(192.0.2.2)への疎通ができているかを確認するために、
wan側から 203.0.113.254に対して、curlコマンドを実行します。
$ docker exec -i wan curl 203.0.113.254 Hello Docker nginx container!
上記コマンドで、ns2のコンテナ上で起動しているnginxからHTMLのレスポンスが返ってきたことを確認できます。 同様に、routerのコンテナ上からlocalhostに対してcurlコマンドを実行すると同様の結果が確認できます。
$ docker exec -i router curl localhost Hello Docker nginx container!
おわりに
いかがだったでしょうか(笑)。 Linuxで手を動かして学ぶTCP/IPネットワーク入門 という書籍で作りながら学んだブリッジやNATなどの学習した個別の要素を組み合わせて、Dockerのようなブリッジネットワークを作ってみるのは面白そうだなと思い、この記事のテーマを思いつきました。
実際に作ってみることで、Dockerの裏側の仕組みや便利さをより一層実感できるようになりました。
また、Network NamespaceやDockerコンテナを活用して、手を動かしながら仮想ネットワークを構築することで、 書籍で理論を学ぶことと異なり、ネットワーク技術についてより深く理解できるようになった気がしています。
今後は、アプリケーションレベルでこのようなネットワーク実験環境を活用できないか模索したいともいます。 例えば、Web配信の技術の書籍に記載されているようなHTTPキャッシュ・リバースプロキシなどの動作も手元のローカル環境で手軽に学習できたらいいなと思います。
最後に、M1 MaxをMacBookも入手したということもあり(笑)、仮想環境を存分に使いこなしつつ、今後も作って壊してを高速に回して楽しんで学習していきたいと思います。
参考資料
ネットワークを学ぶ上で参考になる資料(読んで面白かった書籍など)もあわせて載せておきます。
Dockerのブリッジネットワークの構成については、以下の記事がめちゃくちゃ分かりやすいので、ぜひ読んでほしいと思います。
Dockerのiptabesの仕組みについては、Software Desigin 2021年9月号とSoftware Desigin 2021年10月号の体系的に学ぶDockerネットワークのしくみのiptablesの回が参考
になるかと思います。
tinetを使う上では、以下のYouTube動画やtinetのリポジトリ上のexamplesにある設定例が参考になるかと思います。
参考コード
「EVENT FOLLOW」という技術イベント発見サービスをリリースしました!

- はじめに
- 自作サービスの紹介
- クライアントのサポート対象
- 主要な機能
- イベントデータの定期実行処理
- 技術スタック
- インフラ構成図
- ランニングコスト
- サービスの各種URL
- 作って学んだこと
- サービスに含めなかった機能
- 今後やりたいこと
- 振り返り
- おわりに
はじめに
FJORD BOOT CAMP(フィヨルドブートキャンプ) の Webサービスを作って公開する という最終プラクティスで作成した自作サービスをこの度Webアプリ、iPhoneアプリとしてリリースしましたので、そのサービスのご紹介になります。
Speaker Deck
こちらは、FJORD BOOT CAMP内で自作サービスを発表した際のスライドになります。
本ブログでは、こちらのスライドと同様の構成でサービスの紹介を行います。
Webアプリ
リリースしたWebアプリのサービスURLです。
iPhoneアプリ
リリースしたiPhoneアプリのApp StoreのURLです。
自作サービスの紹介
本サービスのコンセプトは、興味のある技術イベントを見逃さない技術イベント発見サービスになります。これまで、Twitterのフォローしている人(以下、友達)の投稿で、面白(有益)そうなイベントが開催されていたことをイベント開催後に知ることが多かったので、これを解決できないかと思ったことが本サービスを思いついたきっかけになります。
エレベータピッチ
こちらのエレベータピッチの枠組みを用いて、サービス案の検討を行いました。
[1. サービス名]というサービスは、[2. 解決する問題]を解決したい[3. このサービスを使うターゲット]向けの、[4. サービスのカテゴリー]です。ユーザーは[5. このサービスができること]ができ、[6. 競合サービス]とは違って、[7. 差別化要素]が備わっていることが特徴です。
こちらが、作成したエレベータピッチの内容になります。
[EVENT FOLLOW]というサービスは、[自分の興味の方向に近い技術系のイベントを見逃してしまう問題]を解決したい[積極的にイベントを探さずに、自分の興味の方向に近い技術系のイベントを見つけたい人]向けの、[技術系のイベント発見サービス]です。ユーザーは[Twitterの友達がシェアしたDoorkeeperやconnpassなどのイベントを発見すること]ができ、[Doorkeeperやconnpassなどでキーワードで検索すること]とは違って、[検索せずに自分の興味の方向に近いイベントを発見できること]が備わっていることが特徴です。
https://speakerdeck.com/mh_mobile/introducing-event-follow-a-self-made-service?slide=2
1. サービス名
EVENT FOLLOW(読み: イベントフォロー)
2. 解決する問題
自分の興味の方向に近い技術系のイベントを見逃してしまう問題
3. このサービスを使うターゲット
積極的にイベントを探さずに、自分の興味の方向に近い技術系のイベントを見つけたい人
4. サービスのカテゴリー
技術系のイベント発見サービス
5. このサービスができること
Twitterの友達がシェアしたDoorkeeperやconnpassなどのイベントを発見すること
6. 競合サービス
Doorkeeperやconnpassなどでキーワードで検索すること
7. 差別化要素
検索せずに自分の興味の方向に近いイベントを発見できること
クライアントのサポート対象
WebアプリとiPhoneアプリ向けにサービスを提供しています。Webアプリは、PC版とSP版の2つで、スマホ向けにiPhoneアプリを提供しています。*1
iPhoneアプリは、クロスプラットフォーム開発ツールのFlutterで実装しました。ターゲットを絞るため、Androidアプリはターゲットから除き、iPhoneアプリのみをターゲットに提供しています。iPadアプリもデザイン調整やテストなどのリソースの都合上、ターゲットから除いています。*2
主要な機能
ユーザー側から見た機能になります。
Twitterのログイン機能
ユーザーのTwitterアカウントを使って本サービスにログインします。このログインしたユーザーの友達がシェアしたDoorkeeperやconnpassなどのイベント情報がユーザーに提供されます。
ログイン処理には、Firebase Authenticationを使って認証を行なっています。

イベント一覧の表示
Twitterでフォローしたユーザー(友達)が投稿したイベント情報を表示します。イベント情報には、イベントの開催のステータスをわかりやすくするために、5段階に分けてステータス表示しています。

また、Webアプリでは、ページネーションでイベント一覧を表示し、1ページにつき最大10件づつイベントを表示します。
iPhoneアプリの場合は、最下部にスクロールしたタイミングで、最大10件づつページの追加読み込みを行います。
イベントのソート機能
- 以下の4つの種別にもとづきイベント一覧をソートできます。
- Friend数
- 新着順
- 投稿順
- 開催が近い順

イベントの絞り込み機能
- Friend数のソート種別を選択した場合、以下の9つの時間軸の種別にもとづきイベント一覧を絞り込みできます。
- 過去8時間
- 過去24時間
- 過去2日
- 過去3日
- 過去4日
- 過去5日
- 過去6日
- 過去1週間
- All
- Friend数以外のソート種別を選択した場合、以下の4つのFriend数の種別にもとづきイベント一覧を絞り込みできます。
- Friends 1+ *3
- Friends 2+
- Friends 3+
- Friends 4+
- Friends 5+

イベント情報を投稿した友達一覧の表示
イベント情報を投稿した友達のアイコンを一覧表示します。

イベントを投稿した友達のツイートの表示
イベント情報を投稿したツイートやリツイート、引用ツイートの内容を時系列順にします。

イベントデータの定期実行処理
イベント一覧や友達一覧、ツイート一覧などのデータをユーザー側に表示するために、バックエンド側でTwitterからのイベントデータを処理する定期実行処理を開始します。
定期実行処理では、主に6つの実行処理で構成されます。
- ツイート取得
- イベント情報取得
- フォロー取得
- リツイート取得
- 引用リツート取得
- 過去のイベント削除
ツイート取得
Twitter APIのsearch APIを使って、connpassやDoorkeperなどのイベントのリンクが含まれるツイート情報を取得し、データベースに一時的に保存します。
イベント情報取得
データベースに保存されたツイートに含まれるリンクから、イベント種別*4を判定します。特定のイベント詳細のAPIを使って、イベント詳細情報を取得し、データベースに保存します。
フォロー取得
ログインユーザーのTwitterのフォロー情報を取得し、データベースに保存します。
リツイート取得
データベースに保存されたツイートのリツイートをTwitter APIのリツイート取得用のAPIを使って取得します。
引用リツート取得
データベースに保存されたツイートの引用リツイートをTwitter APIのsearch APIを使って取得します。
過去のイベント削除
24時に終了したイベント情報を一括で削除します。

技術スタック
Webアプリの場合は、フロントエンドにNuxt.js、バックエンドにRails APIモードを使っています。
フロントエンド
- Nuxt.js
- Nuxt Compositon API
- Firebase Authentication
バックエンド
アプリケーションサーバ
- Puma 5.2.2
データベース
キャッシュサーバ
- Redis
ツール
インフラ
- Docker Compose(開発環境)
- HerokuのDockerによるデプロイ(本番環境)
- Github Actions
- デプロイ
- ReviewDog
- Slack連携
- 定期的なヘルスチェック
- Lint
- Dependabot
iPhoneアプリのクライアントはFlutter実装で、バックエンドはWebアプリのバックエンドと同じRails APIモードを使用しています。
Flutter 2.0.1
- 状態管理アーキテクチャ
- Firegbase Authentication
インフラ構成図
本番環境のインフラ構成は以下のとおりです。

GitHubのdevelopブランチのソースコードがmainブランチにマージされると、GitHub Actionsを使って、本番環境のHerokuにNuxt.js用とRails APIモード用のDockerコンテナが自動デプロイされます。
ブラウザからのリクエストはNuxt.js用のコンテナが処理します。また、iPhoneアプリやNuxt.jsからのリクエストはRails APIモード用のコンテナが処理します。
PostgreSQLとRedisはHerokuのアドオンで提供され、Rails APIモード用のコンテナからリクエストされます。
定期実行処理は、Rails APIモード用のDockerコンテナのworkerプロセスで実行されます。workerプロセスでは、Clockworkで定期実行処理をスケジューリングし、Twitterのツイートやイベント収集などの処理を実行します。
ランニングコスト
WebアプリはHerokuで動作していますが、Herokuのコンテナとアドオンで月額$30くらいランニングコストがかかりました。内訳は、以下のようになっています。
- Nuxt.js(Heroku Web Dyno) $7
- Rails APIモード(Heroku Web Dyno + Worker Dyno) $14
- PostgreSQL(Heroku Addon) $9
データベースは、無料枠の1万レコードでレコード数が収まらなかったので、1000万レコードを格納できるPostgreSQLのHobbyプランを使いました。
Web Dynoに関しては、フリープランでGitHub Actionsで定期的にアクセスしてコンテナが停止しないようにする方法も検討しました。ただし、定常的にアクセスできると良いと考え、スリープ状態に移行しないHobbyプランに課金することにしました。
サービスの各種URL
WebアプリとiPhoneアプリのリポジトリURLとサービスURLをそれぞれ記載します。
Webアプリ
リポジトリURL
サービスURL
iPhoneアプリ
リポジトリURL
サービスURL(App Store)
作って学んだこと
今回の自作サービスは、バックエンド側で様々なイベント情報を取得し、それらのイベント情報を加工して表示するシンプルな構成だったと思います。サービスとしてシンプルな構成ではありますが、この自作サービスの作成を通して、以下の点について深く学べたように思います。
- API連携
- 定期実行処理
- ツール周り
- セキュリティ対策
- データベース周り
API連携
Nuxt.js とRails APIモード、iPhoneアプリ(Flutter)とRails APIモードでAPI連携を行なっています。また、API連携では、Firebase AuthenticationのJWT認証でリクエストを処理しています。
当初は、Railsでフロントエンドとバックエンドを兼ねて実装していましたが、iPhoneアプリのサポートを決定した際に、バックエンドをRails APIモードで実装し直し、フロントサーバのNuxt.jsとiPhoneアプリからRails APIモードにリクエストする方式で変更しました。
もともと、Webと連携したiPhoneアプリを自分で作れることが目標の一つだったため、API連携の実装を学べて良かったです。
定期実行処理
本サービスでは、Twitter検索からイベント情報を抽出し、ユーザーにイベント一覧を表示します。そのため、Railsのバックエンド側で定期的にイベント情報を抽出する処理が必要になっています。また、Twitter検索からイベント情報のツイートを抽出する以外にも、イベント詳細情報やフォロー情報、ツイートやリツイート情報といった取得処理も定期的に実行し、データベースに取得情報を保存する処理を行う必要があります。
これらの依存関係のある処理を正常時・異常時ともに上手に実行する方法を検討したり、サーバに負荷がかからないように定期実行処理の間隔を調整したり、いろいろと学ぶことが多かったように思います。
定期実行処理の起動自体は、HerokuのWorker DynoでClockworkを用いてスケジューリングしています。
ツール周り
今回は開発環境としてDockerを使うとともに、本番環境にも同じDockerコンテナを使ってデプロイすることができました。
その他、自動テストの一部として、スナップショットテスト用にStorybookやリクエスト・レスポンステスト用にOpenAPIを活用しました。OpenAPIはOpenAPI Generatorを使ったコードの自動生成機能なども活用してみたいです。
また、クラッシュ検出やパフォーマンス測定用にSentryやSkylightを導入しています。
セキュリティ周り
サービスをリリースして公開するといことで、CSRFやXSS、SQLインジェクションの脆弱性がないか最低限の対策を実施しました。
自作サービスでは、ログインユーザーのフォロー情報を取得する必要があるため、ログインユーザー毎のアクセストークンを保持し、データベースに格納しています。当初は、生のアクセストークンを保存していましたが、念の為暗号化したトークンを格納しています。
データベース周り
ユーザーからイベント情報をAPIに対してリクエストすると、データベースからイベント情報を取得し、ユーザー側にイベント情報のレスポンスを返却します。イベントに関連する友達の情報やツイートの情報も同時に取得するとN+1の問題が発生しました。
N+1の問題を解消するために、preloadを使った関連データの読み込みやユーザー情報やツイート情報をフロントサーバ側から非同期に取得することで、データベース周りのパフォーマンスを改善することができました。
サービスに含めなかった機能
当初は、フォローしている友達がシェアしたイベント一覧の表示機能以外に、以下の2つのイベント一覧の表示機能を初期リリースに含めることを検討していました。
Twitterのリスト毎にイベント一覧の表示
今後やりたいことの節においても後述しますが、Twitterのリスト後毎にイベントを抽出すると、より求めるセグメントのイベント情報を発見しやすくなると考えています。ただし、実装工数の観点と初期リリースには必ずしも必要がないことから、初期リリースの機能から見送りました。
友達の友達からのイベント一覧の表示
Twitterの友達の友達がシェアしたイベントを抽出すると、思いがけないイベントを発見する可能性があります。ただし、こちらも、実装工数の観点と初期リリースに必ずしも必要がないこと、友達の友達まで大量のフォロー情報を取得することによる実現可能性の点を考慮し、初期リリースの機能から見送りました。
今後やりたいこと
現状では、最低限の機能でサービスのリリースを行いました。このサービスを作成する上で、自分の興味の方向に近い技術系のイベントを見逃してしまう問題という課題を解決するために、Twitterの友達がシェアしたDoorkeeperやconnpassなどのイベントを発見することで課題解決を図りました。ただし、イベントがあることは認知していても実際にイベントに参加登録することを忘れたり、Doorkeeprやconnpass以外の有用なイベント情報を発見することができない問題もまだまだ残っています。これらの問題を解決するような機能の追加を検討しています。
その他、自作サービスの運用を通して、サービス運用の知見を蓄えて、今後の改善に繋げていけたらと思っています。
機能の追加
今後の機能追加としてこれらを予定しています。
Twitterのリスト毎にイベント一覧の表示
現状では、フォローしている友達がシェアしたイベント一覧を表示しています。フォローしている友達が属しているセグメントがそれぞれ異なるため、異なるセグメントのイベント情報が混在して表示される可能性が高くなっています。そこで、Twitterのリスト毎にイベント情報を抽出すると、より求めるセグメントのイベント情報を発見しやすくなると考えています。
connpassやDoorkeeper以外の技術イベントの発見
connpassやDoorkeeperはAPIが提供されているため、Twitter検索からこれらのイベント情報のツイートを抽出し、イベント詳細情報をAPIから抽出することができます。ただし、その他のパターン化されていない有益なイベント情報は自動的に発見することできません。これらのパターン化されていないイベント情報を何らかの方法でデータベースに格納し、APIとして提供することで本サービスからイベント情報を発見できるようにすることも検討しています。
iPhoneアプリの強化
イベント情報を発見するために、WebアプリやiPhoneアプリを起動する必要があります。よりイベント情報を発見しやすくするために、プル型だけでなく、プッシュ型の情報配信も有用であると思います。そこで、プッシュ通知によるイベント情報の配信も検討しています。その他、Apple Watchやウィジェットといった配信形態も検討しています。
運用の知見を蓄える
サービスの運用を行う上で、メトリクス評価やパフォーマンス測定などを行い、サービスの改善を図りたいと思います。
振り返り
プラクティスのフェーズ
FJORD BOOT CAMPの「Webサービスを作って公開する」のプラクティスのフェーズ毎に要した期間を調べてみました。サービス案の検討からリリースまでの間で結構な期間(約8ヶ月)を要したことが分かります。
- サービス案の検討 (7/20 〜 9/23 )
- ペーパープロトタイプを作る(9/23〜9/25)
- 自分で作るWebサービスのリポジトリを作る(10/2)
- Webサービスを作る(10/26〜4/20)
- 自分で作ったWebサービスのデザインレビューを受ける(4/20〜5/11)
- 自分で作ったWebサービスのコードレビュー(6/5〜6/10)
サービス案の検討では、5案考案しましたが、サービスの課題設定や実現可能性、類似の競合サービスにより4案ボツになりました。ただし、サービスについていろいろと考えるキッカケになったことと、本サービスを作ることができたので、ボツ案も無駄ではなかったように思います。
Webアプリ
実際に10月くらいから実装を行い、3月中旬まで主要な機能の実装を行いました。11月の後半部分は、FJORD BOOT CAMP内のLTの準備で、コミット数が落ち込んでいます。また、3月中旬から4月中旬は、iPhoneアプリを実装していたため、コミット数が落ち込んでいます。
4月中旬以降は、デザインレビューやコードレビューでおよそ1ヶ月くらいかかりました。合計コミットは約700コミットになりました。

https://github.com/mh-mobile/event_follow/graphs/contributors?from=2020-09-28&to=2021-06-29&type=c
iPhoneアプリ
主要な実装期間は、3月中旬から4月中旬の1ヶ月くらいかかりました。また、コミット数は約170コミットになりました。
Flutterの実装経験がほとんどなかったため、最近主流の実装方法を調べたり、既存のサンプルコードを読み解いて理解に努めました。手探り状態でしたが楽しかったです。
ソースコードが荒削りで、テストケース自体がまだほとんどないため、今後改善していきたいと思います。*5

おわりに
自作サービスの実装期間が大分長期化しましたが、WebアプリとiPhoneアプリを実装することができ納得感を持ってサービスをリリースすることができたと思います。また、これまで学習したことがないNuxt.jsやRails APIモード、Flutterといった技術スタックをサービスに取り入れた*6ことで、自作サービスを通じてより多くの技術を学び成長することができたと思います。
今後もサービスをより良くするために改善していきたいと思います。ぜひ技術イベントを見つける際に活用していただけると嬉しいです。また、何かバグを見つけた際は、GitHubのIssueなどでバグ報告をお願いします。
*1: iPhoneアプリに関しては、個人的にサポート対象に選定したため、FJORD BOOT CAMPのデザインレビューやコードレビューの対象外です
*2:今後、Andorid・iPad向けに提供する可能性はあります。
*3:イベントをツイートした友達が1人以上
*4:connpassかDoorkeeperのイベントサイト
*5:iPhoneアプリ(Flutter)に関しては、そもそもFJORD BOOT CAMPのデザインレビューやコードレビューの対象外なため、レビューは受けていません。
*6:FJORD BOOT CAMPのシステム開発のプラクティスでは、Vue.jsやRailsの技術スタックを主に学習します。
2021年2月発売の気になる技術書
Web配信の技術 ―HTTPキャッシュ・リバースプロキシ・CDNを活用する
2021年2月13日発売
AWSではじめるインフラ構築入門 安全で堅牢な本番環境のつくり方
2021年02月10日発売
Google Cloudではじめる実践データエンジニアリング入門
2021年2月20日発売
Webサービスチューニングコンテスト ISUCONのススメ
2021年2月5日発売
再発見の発想法
2021年2月20日発売
【特典付き】仕組みと使い方がわかる Docker&Kubernetesのきほんのきほん
2021年02月01日発売
https://book.mynavi.jp/ec/products/detail/id=120304
みんなのVue.js
2021年02月18日発売
無料の技術書あれこれ
- Pro-Git
- JavaScript Primer
- JavaScript Promiseの本
- JavaScript Plugin Architecture
- Real World HTTPミニ版
- プロフェッショナルIPv6(無料版)
- OpenSSLクックブック(電子書籍のみ)
OpenSSLクックブック(電子書籍のみ)www.lambdanote.com
- 個人Webサービスシステム構成事典 v2
- WANTEDLY TECH BOOK 8(電子版のみ)
- WANTEDLY TECH BOOK 7
- WANTEDLY TECH BOOK 6
- WANTEDLY TECH BOOK 5
- WANTEDLY TECH BOOK 4
- WANTEDLY TECH BOOK 3
- WANTEDLY TECH BOOK 2(電子版のみ)
- WANTEDLY TECH BOOK (電子版のみ)
- Linux標準教科書
Linux標準教科書 ダウンロード LinuCレベル1対応 | Linux技術者認定試験 リナック | LPI-Japan

