生成AI時代の学習の10ステッププロセスを再考する

この記事はフィヨルドブートキャンプ Advent Calendar 2025の23日目の記事です。 昨日は、LEFさんのRails の find はどう動く? Active Recordの内部実装を見てみよう! - LEFログでした。

この記事では、SOFT SKILLSで紹介されている「学習の10ステップ」を、生成AI時代の視点で再整理します。

はじめに

『SOFT SKILLS ソフトウェア開発者の人生マニュアル』という書籍には、スキルを素早く習得するための「学習の10ステッププロセス」が紹介されています。この書籍は第一版が2016年、第二版が2022年の2月頃に発売されました。 ChatGPTが登場した2022年11月以前に書かれた書籍であるため、現在のような生成AI(以下、AI)については考慮されていません。 そこで、現在のようなAI時代に合わせて、AIを活用した10ステッププロセスを考えてみるというのが本記事の内容になります。

bookplus.nikkei.com

学習の10ステッププロセス

まず10ステッププロセスについて簡単に書きます。プロセス前半の①〜⑥までのステップが準備フェーズで、 プロセス後半の⑦〜⑩が実行フェーズになります。

準備フェーズではざっくりと学習内容のカリキュラムを作り、実行フェーズでは作成したカリキュラムに沿って学習を進めます。

準備フェーズ

①全体像をつかむ

学習範囲を決める前に学習テーマの全体像をざっくりと知る必要があります。Webサイトで検索して記事を流し読みしたり、 書籍の目次や最初の章など読んで対象の大きさを知ります。ここでは深掘りはせずにざっくりと調べる程度でよいと述べられていました。

②学習範囲を決める

ざっくりと学習対象となる全体像の地図を手にしたら、学習テーマをサブテーマに分解して、 学習対象を絞り込みます。書籍では、デジタルカメラ撮影を学習テーマとした場合に、常識的な時間内にデジタルカメラ撮影のすべてを網羅的に学ぶことは不可能なので、たとえばポートレイト写真の撮り方を知りたいならそれを学習範囲に指定します。

③成功の基準を決める

何ができるようになったらできたと言えるかを先に決めます。 たとえば、Rubyで主要な言語機能を利用したCLIアプリをかけるようになるなどです。

④参考資料を探す

Webサイトの記事や書籍、動画などの学習範囲に関連した資料をできるだけたくさん収集します。 ⑥のステップで最良の参考資料を絞り込むため、ブレーンストーミングのアイデア出しのように質を問わずに できるだけ収集することがよいと述べられています。

⑤学習プランを立てる

④で収集した参考資料をもとに学習プランを立てます。これは、RubyCLIアプリを作ることが学習範囲の場合、rbenvやlsコマンド、wcコマンドなどのサブテーマ(プラクティス)に分割します。分割したサブテーマをどの順番に学習するかも学習プランを立てる時に考慮します。

ここで分割したサブテーマごとに、実行フェーズの⑦〜⑩を繰り返します。

⑥参考資料を絞り込む

⑤で作成した学習プランのサブテーマに役立つ参考資料を、④で収集した資料の中から絞り込みます。

実行フェーズ

準備フェーズの学習プランの各サブテーマごとに実行します。

⑦ある程度使えるようにするための方法を学ぶ

Hello Worldのやり方だったり、インストールのセットアップ方法などが例に挙げられます。 収集した参考資料からやり方を探し出して実行します。

⑧遊び倒す

基本的な使い方を学んだら、自由に触って学びます。 ここでいろいろ触って思い浮かんだ疑問を⑨で収集した参考資料をもとに解消します。

⑨役に立つことができるところまで学ぶ

⑧のステップで浮かんだ疑問などを解消します。 ここでは、②で決めた成功の基準と照らし合わせて、目標を達成したかも判断します。 目標を達成していない場合は、参考資料などで学んだ後に ⑧〜⑨のステップを繰り返すことになるかと思います。

⑩教える

最後にブログやLTの発表、日報などで学んだことをアウトプットします。 学んだことをアウトプットすることを通じて、学習範囲の内容を自分が理解できているかや記憶の定着などに効果があると考えています。

最近読んだ最高の勉強法という書籍に記載されている学習法に、精緻的質問や自己説明というテクニックがあります。 精緻的質問はなぜこうなるのか(Why)や、どのようにするか(How)などを自分に質問します。また、自己説明は学習内容の理解について自分に説明します。

これらのテクニックを意識してアウトプットすることで学習効果を高めることもできる気がします。また、この書籍には精緻的質問や自己説明以外に、アクティブリコールや分散学習、インターリービング、ファインマンテクニックといったテクニックついても記載されているので、個人的にはぜひ読んで欲しいなと思います。

www.kadokawa.co.jp

10ステッププロセスとフィヨルドブートキャンプの学習プロセスとの類似性

ここまで学習の10ステッププロセスについて簡単に書きました。 この10ステッププロセスについて書籍を改めて再読し気付いた点になりますが、この学習プロセスはフィヨルドブートキャンプのカリキュラムで実施しているプロセスとほぼ同じなのではないか、という点です。

10ステッププロセスにおける準備フェーズは、ブートキャンプにおけるカリキュラムに相当するのではないかと考えています。 フィヨルドブートキャンプのカリキュラムは運営者が作成したものですが、生徒が効率的に学習してスキルアップできるよう、学習範囲・プラクティス・参考資料を含めたカリキュラム全体を、推奨される学習順に沿って設計しています。

たとえば、下記の添付図はRailsエンジニアコースのカリキュラム(①の全体像)の一部です。

(学習内容の全体はこちらで見ることができます)

このカリキュラムのGit & GitHubRubyが学習範囲で、学習範囲内の項目が⑤の学習プランのサブテーマ(プラクティス)に相当します。それぞれの学習範囲の最初のプラクティスでは、カリキュラムの全体像における学習範囲の位置が地図のように分かるようになっています。

また、各プラクティスでは、実行フェーズの⑦を実行するための参考資料や⑧〜⑨を実行するための参考資料は十分に厳選して取り揃えられています。その他にプラクティスごとに終了条件が決められており、メンターによる提出物のレビューを通して、⑨の役に立つところまで学ぶを実行し、目標を達成することができます。

最後にフィヨルドブートキャンプの学習システムの日報の仕組みによって学んだことをアウトプットすることもでき、⑩の教えるも達成できるようになっています。その他にも、Q&Aや輪読会、LTの発表などアウトプットする機会が準備されています。

2021年頃の少し古いブログ記事ですが、フィヨルドブートキャンプの雰囲気を感じることができると思います。

bootcamp.fjord.jp

フィヨルドブートキャンプでは、これまで文系出身の方や他分野の未経験の方を即戦力のWebエンジニアとしておよそ数年で育てることができるのか?と疑問に感じることもありましたが、10ステッププロセスのスキル習得のプロセスと本質的には同じであると感じています*1。このことからフィヨルドブートキャンプの学習プロセス(学習システム)の強みを発見することができた気がします。

独学における10ステッププロセス

基本的には、この学習の10ステッププロセスは独学でスキルを素早く学習するためのプロセスです。 フィヨルドブートキャンプの卒業後やフィヨルドブートキャンプの Railsエンジニアコース/フロントエンドコースのカリキュラム外のスキルを習得したい場合には、自分で①〜⑥の準備フェーズのカリキュラムを自分で作成する必要があります。また、メンターも不在になるため、自分でメンターを探すか、代替策を探して、実行フェーズにおける⑨のフィードバックも得られるとありがたいです。

現在はAI活用の時代になっているため、AIを活用して効率化する方法を考えたいと思います。 しかし、フィヨルドブートキャンプでは、提出物・チーム開発・自作サービスへのAI活用を禁止(成果物をAIに作ってもらうこと)はされているため、 フィヨルドブートキャンプでのこれらのAI活用を推奨するものではありません。

bootcamp.fjord.jp

フィヨルドブートキャンプにログイン可能な人のみ閲覧可能)

AIツールの整理

自分自身はそこまでAIツールは使い倒してはいないですが、AIツールは様々であるため、一旦 ここで想定するAIツールを整理したいと思います。

以前読んだ2024年に出版された書籍には、AIツールは3つの種類で分類されています。

gihyo.jp

類型 概要 代表的なAIツール
対話型 人と自然言語で対話しながら、思考整理・理解支援・意思決定を助けるAI ChatGPT、Claude、Gemini、Perplexity、NotebookLM、Dia(AIブラウザ)、DeepWiki、CodeWiki
補完型 人のコーディング作業中にリアルタイムで候補や続きを提示し、生産性を高めるAI GitHub Copilot、Gemini Code Assist、Cursor
エージェント型 目的を与えると、タスク分解から実行までを自律的に行うAI Claude Code、Codex、Gemini CLIGitHub 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サイトから簡単にインポートできます。

chromewebstore.google.com

⑤学習プランを立てる

ここも前ステップで収集した参考資料の内容をもとに対話型のChatGPTなどと相談しながら決めるとよいかもしれません。 最近はNotebookLMとGeminiが相互に連携することができるようになりました。この連携機能が利用できるようになると、 NotebookLMで収集した参考資料をもとにGeminiでNotebookLMのドキュメントを取り込み、Geminiと相談して学習プランを立てることもできるかもしれません。

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

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

この際、学習プランのプラクティスの終了条件も検討するとよいと考えています。

ところで、立てた学習プランの内容はどこに記載するとよいか考えています。個人的には、Notionを使っているので、Notionで準備した学習テーマ用のページに学習プランの表を作成して、表の中に各プラクティスのページへのリンクを貼るのもよいと考えています。もしくは、独学用の学習システムを構築するか、OSSbootcampアプリをフォークして活用させていただくのもありかもしれません。

⑥参考資料を絞り込む

NotebookLMで参考資料を収集した場合は、⑤の学習プランで立てたプラクティスに関連した資料以外を削除します。 理想的には、ここで収集した資料とプラクティスの内容を関連づけしたいところです。

実行フェーズ

⑦ある程度使えるようにするための方法を学ぶ

⑥の参考資料をもとに、最初の一歩として使えるようになるための方法を実行します。 基本的には公式サイトのドキュメントを参照したり、簡単なチュートリアルを実行したりすることで学ぶことになるかと思います。

内容がわからないことがあった際には、公式サイトのドキュメントの内容を対話型のChatGPTに貼り付けて質問したりして解決を図ることもできます。ただし、内容を貼り付ける作業が煩わしいので、個人的にはDiaのようなAIブラウザを使って、公式サイトやチューリアルサイトを開き、直接ブラウザのチャットUIで要約したり、質問したりするのが便利で活用しています。

以下は、bootcampアプリのREADMEをDiaブラウザで開いて、チャットUIでREADMEのインストール手順について質問している例です。

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

⑧遊び倒す

⑦である程度使えるようになったら、つぎは実際にコードを書いたり、アプリを動かしてみます。 この際に使えるAIツールとしては、補完型のGitHub Copilotやエージェント型のClaude Codeなどになるかと思います。

補完型やエージェント型の活用例としては、

  • ライブラリのAPIを呼び出す簡単なサンプルを複数生成してもらうことでライブラリの挙動を把握する
  • プロトタイプのコードを生成してもらうことでアプリの動作を把握する

といったことが考えられるかと思います。これらの過程でいろいろ疑問が思い浮かぶと学習のきっかけになるかと思います。

⑨役に立つことができるところまで学ぶ

ここでも、⑧で疑問に感じた点をエージェント型のチャットUIで質問すると、疑問点が解消する可能性があります。 また、実装したコードをエージェント型のClaude Codeなどでコードレビューしてもらったり、リポジトリ内のソースコードを調査してもらったり、AIツールを活用することでフィードバックを得ることができます。これらは独学でスキルを習得する際などのメンター不在の環境においてはフィードバックを得ることができ大変助かります。

その他にも、ソースコードの調査には前述のDiaブラウザを使った対話による質問をすることもできます。また、OSSGitHubリポジトリが公開されている場合は、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のブログ でした。

今年のアドベントカレンダーは以下になります。

adventar.org

adventar.org

目次

はじめに

コロナ禍でリモートワークになり、デスク周りの作業環境を快適にしようと、ディスプレイやキーボード、高額の椅子を購入しました。 たしかに、デスク周りの作業空間は快適になったのですが、ARグラスを今年入手してから物理的なデスク環境に 縛られずに作業空間がさらに拡張された気がします。 ここでは、ARグラスでどのような作業環境が変わったのか、いろいろ試した内容を共有したいと思います。

過去と現在の作業環境

簡単に前後の作業環境を比較します。

コロナ禍の作業環境

主にデスクでの作業を前提として、アイテムを揃えました。

MacBookクラムシェルモードで、ディスプレイ2枚と接続して使用します。 傾斜台はデスク上に角度をつけるとキーボードが打ちやすかったり、iPad proでメモが取りやすいです。 オットマンは足を伸ばせるので、アーロンチェアの背もれたに寄りかかりながらの作業が快適です。

デスク周り

現在の作業環境

コロナ禍の頃の作業環境も残ってはいますが、使用頻度は下がっています。

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 SwitchPlayStationなどのゲーミング端末の画面をミラーリングできます。 一般的な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 SwitchPlayStationなどのゲーミング端末の画面をミラーリングできます。 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

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を閲覧できるようになっています。 ただし、NetflixAmazon Primeの視聴はDRM制限により、残念ながらSD画質でしか再生ができません。その他にも、Google Playからのアプリのインストールはできなかったり、充電・発熱問題など色々と制限があります。それでも、空間ディスプレイを活用できるデバイスとして価値を感じています。

XREAL Beamの画面

https://www.xreal.com/jp/beam/

Nebula for Mac(ベータ版)

macOSの作業環境を拡張できる仮想ディスプレイを作成できるソフトウェアです。 最大3画面の仮想ディスプレイを横並びに配置できます。ディスプレイの空間の位置や距離、サイズなどを 調整することができます。

https://www.xreal.com/jp/app/

MacBookの作業環境

MacBook上で複数画面で作業できるようにNebula for Macをセットアップします。 また、スマホなどの他の端末からSSH接続できるようにTailscale SSHをセットアップします。

SSH接続以外にも、VSCodeのリモートトンネル機能(vscode.dev経由で接続)やリモートデスクトップなどを使ってリモートアクセスができますが、詳細は割愛します。

code.visualstudio.com

vscode.dev

ただし、リモートデスクトップについてはいくつか試してみたので、簡単に紹介します。

1つ目は、macOSに標準で搭載されている画面共有アプリ(Apple Remote Desktop)です。 もし、メインマシンとサブマシンがどちらもmacOSであれば、画面共有アプリを使うことで簡単にリモートアクセスができます。メインマシンの設定画面の一般 > 共有 > 画面共有をONにすることで、サブマシンからリモートアクセスすることができます。 サブマシンのMacBookからメインマシンのMacBookに画面共有アプリで接続した感じでは、遅延の問題もあまりなく使うことができました。 また、macOS Sonomaの画面共有アプリは、画面共有アプリが刷新され、Apple Silicon Macでは高パフォーマンス接続が可能となっているようです。

support.apple.com

2つ目は、主にゲームプレイ向けのリモートデスクトップツールのParsecです。

parsec.app

これも、メインマシンのMacBookにParsecのアプリをセットアップすることで、同じアカウントでログインしたサブマシンのMacBookAndroid GalaxyのDeXなどのParsec(クライアント側)アプリを通して、リモートアクセスができます。

サブマシンのMacBookからリモートアクセスすると、低遅延で画面表示が画面全体にフィットするため、非常に使いやすく感じました。 サブマシンのDexからリモートアクセスした場合も、問題なく表示はできましたが、DeX側のタッチ操作をなぜか受け付けず使えなかったです。*4

Nebula for Macをセットアップ

MacBookにARグラスを接続すると、MacBookの画面がミラーリングされます。 この時に、頭を動かすと、ミラーリングされた画面も一緒に頭に追従されます。

複数の画面(2画面と3画面)を使用するには、Nebula for Macというソフトウェアを使用します。 このソフトウェアを使用すると、ARグラスを単体で使用することとは異なり、複数の画面が横並びに固定配置され、 頭を動かしても画面は追従されません。

Nebula for Macの設定画面

Nebula for Macの調整画面

SSHサーバを有効化する

MacBookSSHサーバを有効化します。設定の共有→リモートログインを有効化することでSSHを使ってアクセスすることができます。 ただし、ここではTailscale SSHを使って簡易的にSSHでアクセスできるようにします。

Tailscale SSHのセットアップ

Tailscaleは、簡易的にVPNネットワークを構築できるサービスです。同じネットワークに参加している端末同士でアクセスできるようになります。 Tailscale SSHは、現在ベータ版の機能になりますが、ユーザー自身で公開鍵認証のセットアップをしなくとも、Tailscaleのネットワークに参加している 端末からSSHアクセスできる機能を提供します。

tailscale.com

上記のドキュメントに記載の手順でTailscaleをインストールします。

Tailscaleをインストール後、以下のコマンドでTailscale SSHの機能を有効化します。

$ tailscale up --ssh

Tailscale SSHのセットアップ

便利なMacアプリ

Rectangle

macOSのウィンドウのリサイズや複数のディスプレイ間でウィンドウを移動するのに便利です。

rectangleapp.com

CatchMouse

macOSの複数のディスプレイ間でカーソルを移動するのに便利です。

github.com

iPhone15(iPad pro)の作業環境

iPhoneスマホでも、ターミナルエミュレータを使って、簡易的な開発作業ができます。 ARグラスの登場前はスマホの画面が小さすぎて、使う気にはなれなかったのですが、ARグラスを使うことで小さな画面も ARグラス上に投影されるため、無理なく作業できるようになったと感じます。

また、iPhone15でDP Altモード対応のUSB-Cが搭載されたことにより、ARグラスと直接有線接続できるようになりました。 ここでは、iOSのターミナルエミュレータを使って、MacBookSSH接続して作業する方法を記載します。

iSH Shellをインストール

iOSで使用できるターミナルエミュレータとしてiSH Shellというアプリがあります。 これは、iOSのローカル上にAlpine Linux環境を構築できます。

iSH Shell

iSH Shell

  • Theodore Dubois
  • 開発ツール
  • 無料
apps.apple.com

MacBookにTailscale SSH接続する

リモートマシンのMacBookSSH接続することも可能 *5 ですが、ここではTailscaleを使ったSSH接続について紹介します。 TailscaleでSSH接続するには、リモートマシンのMacBook上でTailscaleをインストールし、Tailscale SSHを有効化する必要があります。

また、クライアントであるiPhoneにもTailscaleのアプリをインストールします。

Tailscale

Tailscale

  • Tailscale Inc.
  • ユーティリティ
  • 無料
apps.apple.com

TailscaleでSSH接続する前に、iOSのTailscaleアプリを起動し、Tailscaleのネットワークに参加します。

Tailscaleアプリ

iSH Shellのターミナルを起動し、通常のSSH接続を行います。

この時、Tailscaleのログイン画面が起動し、ログインが成功すると、MacBookSSH接続することができます。

iSH ShellのSSH接続

Tailscale SSHの認証

Tailscale SSHの接続完了

FBC Stackを動かす

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

github.com

FBC StackのWebアプリの起動

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

FBC StackのWebアプリにアクセス

Tailscaleのサービスには、Tailscaleネットワークの内外からhttps/httpでアクセス可能なTailscale FunnelやTailscale Serveなどの機能も提供されているので、興味がある方は調べてみてもいいかもしれません。

tailscale.com

tailscale.com

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モードのデスクトップがミラーリングされます。

Sumusung DEXの起動

Samsung DEXの表示

Samsung DeXのタッチパッド

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

DeXのHDMI出力

※ どうやら、FjordBootCampでは、12/21(木)〜1/7(日)の期間で年末学習キャンペーンを実施中のようです。

UserLAndをセットアップ

以下のGoogle Playからアプリをインストールします。

play.google.com

ターミナルエミュレータのUserLAndを使うと、UbuntuLinuxをインストールすることができます。 UserLAndやUbuntuのセットアップ手順の詳細は割愛します。

UserLAndのセットアップ後

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でセットアップする手順と同じで、調査すれば分かるので詳細は割愛します。

github.com

開発環境のBootcampアプリを起動

MacBookにTailscale SSH接続する

Androidでも、Tailscaleのアプリをインストールします。

play.google.com

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

Tailscale SSH接続

屋外(野外)で作業する

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

野外で作業

必要なアイテム

XREAL Air 2 pro

明るい場所でも作業しやすくなるため、調光機能がついている XREAL Air 2 proがおすすめです。

MacBook

持ち運び可能なノートパソコンとしてMacBookを利用します。 MacBookの内蔵ディスプレイとXREAL Airのディスプレイの2つのディスプレイも同時に利用することもできますが、基本はクラムシェルモードで利用したいので、MacBookは閉じた状態で内蔵ディスプレイは利用しません。

MacBookクラムシェルモードで使用する条件として、以下があります。

  • 電源で給電されていること
  • 外部ディスプレイが接続されていること

support.apple.com

HDMI ダミープラグ or BetterDisplay

内蔵ディスプレイを閉じた状態のMacBookにXREAL Airを接続すると、MacBookの画面がミラーリングされます。 ただし、この接続状態では Nebula for Macのソフトウェアが正常に動作しません。これを回避するために、外部ディスプレイが接続されている状態と認識されるように、HDMIダミープラグかBetterDisplayを使って、仮想ディスプレイを作成します。

ディスプレイエミュレータプラグ ヘッドレス ゴーストエミュレーター フェイクディスプレイ (ヘッドレス-1920x1080-4096x2160 @60Hz

MacBookHDMI端子がない場合は、仮想ディスプレイを作成できるBetterDisplayというソフトウェアを使いのもOKです。 有料版のソフトウェアですが、仮想ディスプレイの作成は無料でも可能です。

github.com

これで、MacBookクラムシェルモードでXREAL Airを接続して、Nebula for Macを使用できる状態になります。

キーボード

Bluetooth接続でHHKB Hybrid Type-Sを使用しています。 キータッチの押し心地がいいのと、マルチペアリングでMacBookiPadiPhoneAndroidなどに繋がるので 大変重宝しています。

最近発売された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 AirMacBook、電源などが入れば、なんでもいい気がします。 ただし、割と重量が重いので、耐久性があるリュックがいいかもしれません。

ポータル電源が必要な場合は、サイズが大きめのUber Eatsのような配達バッグがあると役立つのではないでしょうか。

ロゴ入り配達バッグ(ブラック) - Delivery Bag with Logo (Black) – Ubereatskit Japan - ウーバーイーツ 配達バッグ 購入

Povo 2.0

屋外でインターネットを使用するには、テザリングなどができる環境が必要です。 Povo以外についてほとんど使ったことないので他の代替品を知らないのですが、Povoは24時間(とはいえ、2日間)で使い放題のオプションがあるので、たまに屋外(旅行中でも)で作業したいときに低価格で利用できて重宝します。

povo.jp

もしかすると、スペースXが提供する通信衛星サービスのスターリンクを使うのもありかなと思ったのですが、電波法により野外での利用に制限があるようですね。

www.starlink.com

作業場所

  • 公園(ベンチ)
  • 河川敷
  • キャンプ場

河川敷にて

屋内で作業する

Nintendo Switch のリングフィット(RubyKaigi 2023参加中のホテルにて)

RubyKaigi 2023のイベントにて

必要なアイテム

自宅以外の屋内なら、屋外で必要なアイテムと基本変わらないかもしれかもしれません。 自宅で作業なら、自宅のWi-Fiに接続できるので、テザリング用のPovo 2.0は不要です。 (もちろん、屋内でWi-Fiが完備されている場合は、そちらで代用できます)

作業場所

作業場所としては、以下があります。

作業スタイル

作業スタイルとしては、以下があります。

  • クッション(ごろ寝)
  • 椅子(背もたれ、壁)
  • スタンディング
  • お風呂(自宅)

ニトリのゲーミング座椅子や最近発売された回転ゲーミング椅子などは持っていないのですが、ARグラスでの作業と相性がよさそうで 少し気になっています(笑)

www.nitori-net.jp

www.nitori-net.jp

お風呂で作業するのは、割りと良かったです*6。ただし、電子機器を扱うので、取り扱いには注意する必要があります。 先日、Atcoder(競プロ)のABCのコンテストにお風呂から参加したのですが、問題が難しく動揺したせいか、風呂フタが傾いてiPhone端末とケーブルを浴槽に落としてしまうことがありました。MacBookやARグラスを落としてしまった際には、かなりショックが大きいことかと思います。*7

あと、屋内で作業する際に、屋外の項目でも記載しているリュック🎒をかけて作業するのも、作業場所やスタイルを変更する際に便利かもしれません。 たまに別の場所に移動して作業したり、スタンディングで作業したり、気分によって変更することができます(笑)

まとめ

いろいろと書いたので、雑にまとめると以下になります。

  • ARグラスの登場により、小型のデバイスであるスマホMacBook(クラムシェルモード)を作業環境として使いやすくなった。
  • ARグラスを使うことで、自宅のデスク環境と比べて、さまざまな作業場所や作業スタイルを選択できるようになった。
  • 3DoFの空間ディスプレイをサポートするNebula for Macのソフトウェアを使うことで、物理的なディスプレイが不要で、場所によらず複数の画面(2画面と3画面)を利用できるようになった。
  • DP Altモード対応のUSB-C対応デバイスとARグラスを簡単に接続できることで、屋内や屋外にかかわらず、大画面で読書や動画視聴ができるようになり、以前に比べて読書や動画視聴の体験が向上した

普段は、メインの作業マシンとしてMacBook を使っていますが、以下の組み合わせで結構便利になったと感じています。

おわりに

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

目次

はじめに 

mhと申します。 Fjord Boot Camp(以下、FBC*1と記載)で2021年の6月頃に卒業し、早いもので卒業してから1年半くらい経ちました。 卒業した時にEvent Follow という技術イベント発見というサービスを開発し、Heroku上で今も元気に 動いています。よろしければ、ぜひ使ってみてください😄。

eventfollow.app

リリース時のブログ:「EVENT FOLLOW」という技術イベント発見サービスをリリースしました!

今回は FBCの卒業生が作成したサービスの技術スタックデータベースのサービスをリリースしましたので、そのサービスのご紹介になります。 特に卒業制作のサービスというわけではなく、ちょうど解決したいFBC関連のサービスがあったので、 このアドベントカレンダーをリリース日に設定し、開発を行いました。

fbc-stack.vercel.app

github.com

サービス概要

FBC Stackは、FBC卒業生が自作したサービスの技術スタックの情報が有効に蓄積されていない問題を解決したい、FBC卒業生の自作サービスの技術スタックデータベースです。ユーザーは 卒業生の自作サービスの技術スタックを見ることができ、卒業生のWebサービス一覧のドキュメントから個別に自作サービスを発見することとは違い、採用している技術スタックのツールから卒業生の自作サービスを発見できるのが特徴です。

ここでは、卒業生のWebサービス一覧のリンクを参照していますが、実際はFBCのサービス内に同様のドキュメントがあります。 こちらのドキュメントには卒業生が卒業式の時に発表したサービスのデモ動画があります。

機能紹介

サービスでできる機能は以下になります。

  • トップ画面で卒業生のサービス一覧を表示
  • サービス詳細画面でサービスの技術スタックを表示
  • みんなのツールを選択し、各サービスで採用されているツールを一覧表示(現在はアルファベット順)
  • ツール一覧をキーワードで検索
  • ツール詳細画面でツールを採用しているサービスを一覧表示

卒業生のサービス一覧画面

表示するサービスの数がそこまで多くないため、全てのサービスを表示しています。

卒業生のサービス一覧

サービスの詳細画面

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

サービスの詳細画面

ツールを採用しているサービス一覧

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

ツールを採用しているサービス一覧

ツール一覧画面

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

ツール一覧画面

ツールの検索画面

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

ツールの検索画面

Amazon EC2を採用しているサービス一覧

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

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を見ました。

bootcamp.fjord.jp

speakerdeck.com

このLTを見て、どうやったら、サービスを知ってもらえるかを考えたことがきっかけでもあります。 LTの最後に紹介されている卒業生のサービス一覧は、FBCのサービス内のドキュメントの方の自称メンテナー(笑)として度々更新しているので多少愛着があります。このドキュメントをメンテナンスしていく中で、今後卒業生が増え、自作サービスも蓄積されていくので、その卒業生のサービス一覧のデータを有効に活用していけないかと感じました。

bootcamp.fjord.jp

この2点を雑にまとめると、

  • 卒業生の自作サービスの技術情報を蓄積することで、自作サービスのトレンドを把握するとともに、 FBC内外の人にも自作サービスを知ってもらう情報を提供できるのではないかと考えた次第です。

どう解決するか

技術情報の蓄積として着目したのが、サービスのリリースブログの広報でもよく使われる技術スタックの情報になります。 FBCでも、リリースブログの他、卒業式のデモの時やGitHubリポジトリに必ずといって記載される内容となっています。

現在は卒業生のWebサービス一覧でGitHubリポジトリやサービスURL、リリースブログなどは一覧で網羅されていますが、 自作サービス間でのつながり(共通点)を見つけることはできません。

そこで、サービスの技術スタックのツールを登録することで、採用しているツール経由でサービスを見つけやすくなると考えました。

FBC内の在校生や卒業生の得られる期待としては、

  • 自分の知らない新しいツールを発見できる。
  • 自分が検討してい技術スタックに近しいサービス(卒業生)を探せる。
  • 検索したツールを使っているサービスを探せる。

例えば、Firebase Authenticationで検索すると、それを使っているサービスを探せます。 ここから、サービスの採用している技術スタックを確認できたり、GitHubリポジトリソースコードFBC内での提出物や日報を見て参考にすることもできるかと思います。

FBC内のメンター(アドバイザー)や運営の方の得られる期待としては、

  • 在校生の自作サービスの技術のトレンドを把握できる。
  • 誰がどういったツールを使っているかを知ることで、他の生徒にアドバイスができる。

FBC外のスクールを検討している方の得られる期待としては、

  • どういったサービスを作れるようになるのか知ることができる。
  • どういった技術を使っているのか知ることができる。

FBC外の企業の採用担当の方の得られる期待としては、

  • どういった技術を使ってサービスを作ったかを把握できる。
  • FBCの技術トレンドを把握できる。

技術スタックの類似サービス

もちろん、技術スタックを調査する類似サービスはいくつか存在します。

技術スタックを調査できる同様のサービスとしては、StackShareWappalyzarwhat 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

サービスの技術スタックの情報やツール情報を登録した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のシステム構成

  1. サービスの技術スタックのMarkdownやロゴデータを登録します。
  2. GitHubにプッシュします。
  3. GitHubでPRを作ります。この時点でGitHub ActionsのビルドとVercelのプレビューデプロイが行われます。
  4. PRがマージされたらVercelの本番環境にデプロイされます。

システム構成

FBC Stackに掲載している自作サービス

現在掲載している自作サービスは以下の基準で掲載しています。

  • リリースブログで広報して、自作サービスをリリース済み

→ これは、卒業生のリリースブログの広報を優先したいからです。

  • 卒業生がスクールで作成したサービスと異なる自作サービス

→ 例えば、自分が今回作ったFBC Stackのサービスです。

  • FBCのBootcampアプリ

→ これは、卒業生全員がチーム開発で実質開発に携わったサービスなので、Bootcampアプリの技術スタックを登録しています。

開発をした上での気づき

  • Bootcampアプリの開発は、1年半前にチーム開発で携わっていましたが、Bootcampアプリの技術スタックを調査するなかで、ReactやDockerがされているなど、使用されている技術自体の変遷を感じ取ることができました。現在は、VueとReactが混在しているようで、Vue.jsからReactに置き換えが進んでいることを感じ取れましたし、こういった技術スタックの変遷も、バージョン管理してみれると過去の経緯も把握しやすくなるかと思いました。

  • 自作サービスに自作gemを組み込んでいる人がいることを知りませんでした。

  • 自作サービスの採用している技術スタックのツールから、自作サービスのシステム構成がある程度把握できたように思いました。

    • フロントエンド中心のサービス
    • MPA型のサービス
    • フロントエンドとバックエンドの分離型のサービス
    • インフラをAWSでがっつり作ったサービス
  • サービスのデプロイ先も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内外の方に少しでもお役に立てれば幸いです! あと、メリークリスマス🎄

*1:Fjord Boot Camp内で呼び方は最近表記揺れが多いですが、本サービスではFBCとします

手を動かして学ぶネットワーク実験環境入門

これは、「フィヨルドブートキャンプ Part 2 Advent Calendar 2021」25日目の記事です。

adventar.org

adventar.org

はじめに

今年は、仮想環境のコンテナ周りを学習していましたが、仮想環境のコンテナを隔離する技術のNamespaceについて知ることができました。 そのNemespaceのうちの一つにNetwork Namespaceというコンテナのネットワーク環境を隔離するLinuxカーネルの機能があります。

ある日、このNetwork Namespaceを使ってTCP/IPのネットワークを手を動かして学ぶ方法が書いてある Linuxで手を動かして学ぶTCP/IPネットワーク入門 という 書籍を読んで実際にネットワークを作ってみました。すると、書籍を読んで理解した気になるのとは違って、実際に手を動かすことで、 ネットワークのパケットやルーターの動きなどをより深く理解できるようなりました。また、ネットワーク関連のコマンドやiptablesコマンドなどのLinuxコマンドにも 触ることになったので、Linuxを慣れ親しむ上でも有益になりましたし、ネットワーク技術を今後学ぶ上での知見(ヒント)を多少得ることができました。

f:id:mh_mobile:20211225111826j:plain

www.amazon.co.jp

この書籍では、ルーターやブリッジ、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のブリッジ以外に vethc881c6avethaaf049aveth6a8c5f9といった仮想インターフェースを確認できます。

$  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.2172.17.0.3172.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という名称の仮想ブリッジに接続されているのでしょう。

f:id:mh_mobile:20211225195026p:plain

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

実際に作成するネットワーク構成は以下になります。

f:id:mh_mobile:20211225195042p:plain

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

f:id:mh_mobile:20211225205629p:plain

参考までに、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を定義します。

f:id:mh_mobile:20211225205834p:plain

同様に、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)の準備

MacLinux VM上でネットワーク環境の構築を行います。 Mac上でLinux VMを動かす方法としては、VirtualBoxを使った方法が有名だと思いますが、 Intel MacmacOS Montereyで動作しなかったり、M1 Macに対応していないため、別の方法を 使うのが良いかと思います。

手軽にLinux VMを動作させる方法には、主にMultipass と Limaの2つがあります。今回は、私がよく使うMultipass を使った方法について書きます。

multipass.run

github.com

ちなみに、Mac上のLinux VMについては、以下の記事も参考になるかと思います。

tech.mirrativ.stream

Multipassを使った仮想環境の準備

HomeBrewでインストール

$ brew install --cask multipass

UbuntuVMの起動

$ multipass launch 20.04 --name tcpip --mem 5GB --disk 50GB
Launched: tcpip

*1

UbuntuVMにログイン

$ 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-veth0ns1-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を作成します。 また、仮想ブリッジに、ns1ns2ns3のペアとなる仮想インターフェースを割り当てます。

$ 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

ただ、正直この設定がよく理解できていません。

*2

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

iptablesDestination 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という便利なツールを使うことができます。

*3

github.com

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の作者の方のツイートですが、参考になるかと思います。

github.com

ホスト用の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 コマンドもあります。

*4

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も入手したということもあり(笑)、仮想環境を存分に使いこなしつつ、今後も作って壊してを高速に回して楽しんで学習していきたいと思います。

参考資料

ネットワークを学ぶ上で参考になる資料(読んで面白かった書籍など)もあわせて載せておきます。

gihyo.jp

www.ohmsha.co.jp

aws.amazon.com

gihyo.jp

Dockerのブリッジネットワークの構成については、以下の記事がめちゃくちゃ分かりやすいので、ぜひ読んでほしいと思います。

tech-lab.sios.jp

Dockerのiptabesの仕組みについては、Software Desigin 2021年9月号とSoftware Desigin 2021年10月号の体系的に学ぶDockerネットワークのしくみiptablesの回が参考 になるかと思います。

gihyo.jp

gihyo.jp

tinetを使う上では、以下のYouTube動画やtinetのリポジトリ上のexamplesにある設定例が参考になるかと思います。

www.youtube.com

github.com

参考コード

今回使ったスクリプトや設定ファイルは、以下のリポジトリにアップしています。

github.com

*1:nameオプションには、VMの名称を指定します

*2:networking - Port forward with iptables - Server Faultも参考にしています。

*3:tinetでは、ルーティングテーブルを変更するため、Dockerコンテナが特権モードで動作します。

*4:tinetのコマンドを実行すると、実行されたコマンドが標準出力に表示されます。

「EVENT FOLLOW」という技術イベント発見サービスをリリースしました!


 

f:id:mh_mobile:20210702084927p:plain

 

はじめに 

FJORD BOOT CAMP(フィヨルドブートキャンプ) Webサービスを作って公開する という最終プラクティスで作成した自作サービスをこの度Webアプリ、iPhoneアプリとしてリリースしましたので、そのサービスのご紹介になります。

Speaker Deck

こちらは、FJORD BOOT CAMP内で自作サービスを発表した際のスライドになります。

本ブログでは、こちらのスライドと同様の構成でサービスの紹介を行います。

speakerdeck.com

Webアプリ

リリースしたWebアプリのサービスURLです。

eventfollow.app

iPhoneアプリ

リリースしたiPhoneアプリApp StoreのURLです。

EVENT FOLLOW

EVENT FOLLOW

  • ビジネス
  • 無料

apps.apple.com

自作サービスの紹介

本サービスのコンセプトは、興味のある技術イベントを見逃さない技術イベント発見サービスになります。これまで、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を使って認証を行なっています。 

f:id:mh_mobile:20210702084543p:plain

イベント一覧の表示

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

f:id:mh_mobile:20210702083000p:plain

開催日のステータス表示


また、Webアプリでは、ページネーションでイベント一覧を表示し、1ページにつき最大10件づつイベントを表示します。

iPhoneアプリの場合は、最下部にスクロールしたタイミングで、最大10件づつページの追加読み込みを行います。

イベントのソート機能

  • 以下の4つの種別にもとづきイベント一覧をソートできます。
    • Friend数
    • 新着順
    • 投稿順
    • 開催が近い順

f:id:mh_mobile:20210702085459p:plain

イベントの絞り込み機能

  • 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+

f:id:mh_mobile:20210702085939p:plain

イベント情報を投稿した友達一覧の表示 

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

f:id:mh_mobile:20210702090144p:plain

イベントを投稿した友達のツイートの表示

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

f:id:mh_mobile:20210702090502p:plain

 

イベントデータの定期実行処理

イベント一覧や友達一覧、ツイート一覧などのデータをユーザー側に表示するために、バックエンド側でTwitterからのイベントデータを処理する定期実行処理を開始します。

定期実行処理では、主に6つの実行処理で構成されます。

  • ツイート取得
  • イベント情報取得
  • フォロー取得
  • リツイート取得
  • 引用リツート取得
  • 過去のイベント削除
ツイート取得

Twitter APIのsearch APIを使って、connpassやDoorkeperなどのイベントのリンクが含まれるツイート情報を取得し、データベースに一時的に保存します。

イベント情報取得

データベースに保存されたツイートに含まれるリンクから、イベント種別*4を判定します。特定のイベント詳細のAPIを使って、イベント詳細情報を取得し、データベースに保存します。

フォロー取得

ログインユーザーのTwitterのフォロー情報を取得し、データベースに保存します。

リツイート取得

データベースに保存されたツイートのリツイートTwitter APIリツイート取得用のAPIを使って取得します。

引用リツート取得

データベースに保存されたツイートの引用リツイートTwitter APIのsearch APIを使って取得します。

過去のイベント削除

24時に終了したイベント情報を一括で削除します。

 

f:id:mh_mobile:20210702150334p:plain

定期実行処理

 

技術スタック

Webアプリの場合は、フロントエンドにNuxt.js、バックエンドにRails APIモードを使っています。

github.com

フロントエンド

  • Nuxt.js
    • Nuxt Compositon API
  • Firebase Authentication

バックエンド

アプリケーションサーバ

  • Puma 5.2.2

データベース

キャッシュサーバ

  • Redis

ツール

  • Storybook
  • Sentry
  • Skylight
  • OpenAPI
    • API仕様のドキュメント
    • APIのリクエスト・レスポンスのバリデーション

インフラ

  • Docker Compose(開発環境)
  • HerokuのDockerによるデプロイ(本番環境)
    • Nuxt.jsのコンテナ
      • Webプロセス
    • Railsのコンテナ
      • Webプロセス
      • Workerプロセス
        • Twitterのツイートやイベント収集などの定期実行処理用
  • Github Actions
    • デプロイ
    • ReviewDog
    • Slack連携
    • 定期的なヘルスチェック
    • Lint
  • Dependabot 

iPhoneアプリのクライアントはFlutter実装で、バックエンドはWebアプリのバックエンドと同じRails APIモードを使用しています。

github.com

Flutter 2.0.1

インフラ構成図

本番環境のインフラ構成は以下のとおりです。

f:id:mh_mobile:20210702092736p:plain

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

github.com

サービスURL

eventfollow.app

iPhoneアプリ

リポジトリURL

github.com

サービスURL(App Store
EVENT FOLLOW

EVENT FOLLOW

  • ビジネス
  • 無料

apps.apple.com

作って学んだこと

今回の自作サービスは、バックエンド側で様々なイベント情報を取得し、それらのイベント情報を加工して表示するシンプルな構成だったと思います。サービスとしてシンプルな構成ではありますが、この自作サービスの作成を通して、以下の点について深く学べたように思います。

  • 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を導入しています。

セキュリティ周り

サービスをリリースして公開するといことで、CSRFXSSSQLインジェクション脆弱性がないか最低限の対策を実施しました。

自作サービスでは、ログインユーザーのフォロー情報を取得する必要があるため、ログインユーザー毎のアクセストークンを保持し、データベースに格納しています。当初は、生のアクセストークンを保存していましたが、念の為暗号化したトークンを格納しています。

データベース周り

ユーザーからイベント情報を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コミットになりました。

f:id:mh_mobile:20210702091146p:plain

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

f:id:mh_mobile:20210702091329p:plain

https://github.com/mh-mobile/event_follow_mobile/graphs/contributors?from=2020-09-28&to=2021-06-29&type=c

 

おわりに

自作サービスの実装期間が大分長期化しましたが、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日発売

gihyo.jp

AWSではじめるインフラ構築入門 安全で堅牢な本番環境のつくり方

2021年02月10日発売

www.shoeisha.co.jp

Google Cloudではじめる実践データエンジニアリング入門

2021年2月20日発売

gihyo.jp

Webサービスチューニングコンテスト ISUCONのススメ

2021年2月5日発売

nextpublishing.jp

再発見の発想法

2021年2月20日発売

www.sbcr.jp

【特典付き】仕組みと使い方がわかる Docker&Kubernetesのきほんのきほん

2021年02月01日発売

https://book.mynavi.jp/ec/products/detail/id=120304

みんなのVue.js

2021年02月18日発売

https://www.amazon.co.jp/dp/4297119021

無料の技術書あれこれ

  • Pro-Git

progit-ja.github.io

jsprimer.net

azu.github.io

azu.github.io

  • Real World HTTPミニ版

www.oreilly.co.jp

  • プロフェッショナルIPv6(無料版)

professionalipv6.booth.pm

OpenSSLクックブック(電子書籍のみ)www.lambdanote.com

techbookfest.org

  • WANTEDLY TECH BOOK 8(電子版のみ)

techbookfest.org

techbookfest.org

techbookfest.org

techbookfest.org

techbookfest.org

techbookfest.org

  • WANTEDLY TECH BOOK 2(電子版のみ)

techbookfest.org

  • WANTEDLY TECH BOOK (電子版のみ)

techbookfest.org

Linux標準教科書 ダウンロード LinuCレベル1対応 | Linux技術者認定試験 リナック | LPI-Japan