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
FjordBootCampの歩み方 ~Tips編~
これは「フィヨルドブートキャンプ Advent Calendar 2020」の22日目の記事です。
2020年の10月にフィヨルドブートキャンプの歩み方をテーマとしたLT会が開催されました。私はバグ報告テンプレートの活用について初LTを行ったのですが、当初はFjordBootCamp Hacksといったタイトルでお役立ちTipsを話そうかなと考えていました(笑)
今回、フィヨルドブートキャンプ初のアドベントカレンダーが始まり、ちょうど良い機会なので、2019年10月の参加からおよそ1年の間にブートキャンプで学んできた中で得たものや工夫していることなどのTipsを以下の4つのパートに分けて紹介したいと思います。
- ブートキャンプ編
- Twitter編
- Slack編
- Alfred(作業効率化)編
ブートキャンプ編
ブートキャンプ生は、プログラマー向けEラーニングシステムのBootCampアプリを使って、プラクティスや日報、Q&Aなどのやりとりをブートキャンプ生同士やメンターの方達と日々行っています。また、SlackやDiscord、Remoなどのオンラインツールを使ったコミュニケーションも活発に行われています。
ここでは、以下の点について紹介したいと思います。
- Q&A
- 日報
- メンターコメント
- 提出物
- ふりかえりミーティング
- Webサービス進捗報告会
- ミートアップ
- オンライン合同会社説明ドリンクアップ
- Discord
- Slack
- Podcast
- LT会
- 輪読会
- 地域の勉強会・オンライン勉強会
- BootCampアプリ
Q&A
ブートキャンプのプラクティスを学習する中で、分からないことを質問できる場所になります。ブートキャンプ生やメンターなどの方が自由に質問や回答を行うことができます。解決したQ&Aはストックされるので、過去のQ&Aを参照することも大変勉強になります。
曖昧な質問には、回答が付きにくいので、より具体的に内容を記載する方がよいでしょう。また、自己回答したり、Slackで質問して解決した内容などもQ&Aに転載することができます。
日報
学んだことや今ハマっていることなどを自由に書くことができます。人によって内容はバラバラですが、今年の9月に角谷 信太郎さんによる講演のFJORD BOOT CAMP AS GATEで言及された日報はどこに力をい入れて書くのがよいか の内容が大変参考になると思います。こちらの内容をテンプレートとして使っている方も多くいます。
"日報"はどこに力を入れて書くのがよいか🤔
・いまの気持ち:(感情)
あなたが書かなければ誰にも伝わらない。"相手に伝わるように"テキストで書くには訓練が必要だ。
・やったこと(事実)
今日も一日がんばった!詳しく書きたいだろうけど、みんなは詳細を読みたいかな?
・わかったこと:(意見, 解釈)
もちろん、感情以外も適切に伝わるようにテキスト出力するには訓練、訓練、訓練
・次にやること:(計画,表明)
考えることは大事だけれど、明日への自分の置き手紙(明日の自分に適切に伝わるようにかけるかな?)
・シャウトアウト:(感謝)
チームに感謝。仲間に感謝。よい振る舞い、助かったことをフィードバックして増えたらうれしくない?
また、疑問点なども書いておくと、メンターやブートキャンプ生の方がコメントを残してくれるので、日報を活用するとよいでしょう。
私は最低15分は毎日何かしら学習をするようにしています。学習して日報を書くとGitHub同様の草が生えるので、モチベーションの維持に役立っています。*1
メンターコメント
メンターさんのコメントを見ることができます。
他の人に対する指摘は自分に当てはまることも多く、大いに参考になるので、積極的に見るとよいでしょう。
メンターさんのコメントは、ユーザーページからメンターさんのコメントを開くと確認することができます。
有益なコメントなどは個人のSlackにストックして、後から見返せるようにしたりしています。 後述するSlack APIを使うと、個人Slackの任意のチャンネル宛にメッセージを投稿することもできます。
提出物
プラクティス終了後は、他の人の提出物を見ることができます。
実装はただ1つの正解があるわけではないので、他の人のソースコードをみることで、効率良い書き方や他の実装方法などの気づきを得るきっかけになったりします。ぜひ、プラクティス完了後に見るとよいでしょう。
※ 後半のWebサービスを作って公開するのプラクティスの提出物は、プラクティスを完了していなくても、他の人の提出物を閲覧することができます。
ふりかえりミーティング
毎週水曜日の14:00からdiscordのfjordbootcampサーバのミーティングのボイスチャンネルでシステム開発のふりかえり会が実施されています。自身が担当したissueの進捗状況の報告やissueの見積もりなどが実施されます。まだシステム開発のプラクティスに入ってない生徒もラジオ参加可能*2なので、開発現場で行われている様子を拝見したい人はラジオ参加してみるとよいでしょう。
Webサービス進捗報告会
ふりかえりミーティング終了後の15:00からふりかえりミーティングと同様の場所で、自作Webサービスの進捗会が実施されています。今週実施したissueの進捗状況や困っていること、来週実施するissueなどを共有します。こちらもふりかえりミーティング同様にラジオ参加可能なので、参加してみるとよいでしょう。
ミートアップ
毎月、中旬〜下旬あたりに開催されます。今年の3月くらいまではフィヨルドのオフィスでオフライン開催されていましたが、コロナ禍の影響もあり現在はRemo上で行われています。現役のフィヨルドブートキャンプ生や卒業生、メンターの方などが集まるので、積極的に意見交換して参加するのがよいでしょう。
オンライン合同会社説明ミートアップ
複数社の会社説明会がオンラインのRemo上で開催されています。カジュアルにお酒を飲みながら、企業の方に質問したり、話したりできます。
最近では、8月、9月、12月に開催されました。
就職を考えている方やそうでない方も、参加してみるとよいでしょう。
Discord
以前はWhrebyやZoomで16時頃に雑談や質問が行われていましたが、最近ではDiscordが使われるようになりました。個人的に以前よりも外部から参加者が見えやすくなったため、参加しやすくなったようにも感じました。*3
他にも、もくもく会の作業部屋などがあるので、活用してみるとよいでしょう。
Slack
Slackには、さまざまなチャンネルがあります。
- #技術
- #wakaran
- #wakatta
- #kyushu
- #kansai
など...
特に、wakaranチャンネルとwakattaチャンネルができてから、よりコミュニケーションが活性化されたように感じます。このチャンネルの作成はメンターのりほやんさんの発案なので、かなり功績が大きいかと思います。
私もwakaranチャンネルとwakattaチャンネルにはすごくお世話になっています。
wakaranチャンネルに書くと回答がすぐにので、wakaranことがあれば、積極的に書くとよいでしょう。 おそらく、気づいた心優しい人たちが回答してくれるはずです。
Podcast
メンターのりほやんさんがやっているPodcastにyancan.fmがあります。りほやんさんとりさきゃんさんの二人のRubyistの掛け合いが面白く、密かに更新を楽しみにしています。聴いたことない人はぜひ、聴きましょう(笑)。
LT会
3ヶ月に1回くらいの頻度で開催されています。
10月にLT会vol.5がオンライン開催されました。LT会で登壇すると、話す内容について深く掘り下げたり、発表のリハーサルなどを行ったりと、LT会の発表内容を準備する過程で得られる物が多く、参加してすごくよかったと思います。来年の1月後半にもLT会が開催されるので、(可能であれば登壇側で)ぜひ参加してほしいと思います。
輪読会
パRails輪読会
今年の7月にパーフェクトRuby On Railsの増強改訂版が出版されたこともあり、8月から毎週日曜日の8:30〜10:30の時間帯でパーフェクトRuby On Railsの輪読会が開催されています。パーフェクトRuby On Railsの著者(かつ フィヨルドブートキャンプの顧問)の五十嵐さんも参加なさっています。
朝8時半という早い段階ですが、本当に素晴らしいと思います。私は1回だけ参加したのですが、かなり濃厚な体験だったと記憶しています。ただ、朝が弱いので、参加できておりません。
また、HackMDを使った輪読会ノートがWeb上に公開されています。
フィヨルド生のちろるさんのツイートのスレッドで過去の輪読会ノートを閲覧することができます。パーフェクトRuby On Railsを勉強している方は活用すると学びがあると思います。
第1回フィヨルド輪読会無事に終わりました!!🎉
— ちろる@fjordbootcamp (@chiroru_choooco) 2020年8月9日
色んな人の目線があって、自分では読み流してしまう気付かない点などについても知ることができ、とても勉強になり有意義な時間でした🙏✨ありがとうございました!
【輪読会ノート】https://t.co/kgiM6gEq0o
達人に学ぶDB設計 徹底指南書 輪読会
最近では、「達人に学ぶDB設計 徹底指南書」の輪読会が毎週土曜日の22:00〜23:00の時間帯で開催されるようです。こちらも気になる方は参加してみるとよいでしょう。
地域の勉強会・オンライン勉強会
多くの方が積極的に勉強会に参加されています。
今はコロナ禍でオンライン勉強会が活発ということもり、いろいろな勉強会に参加しやすい状態かと思います。積極的に参加してみるとよいでしょう。
BootCampアプリ
ブートキャンプ後半のシステム開発のプラクティスでこれまで学習してきたEラーニングシステムのアプリの機能追加や修正を行います。
普段使ってきたシステムを自分達で改修したりするので、人一倍学びが多いと思います。
システム開発のプラクティスが終わった後にでも、PRを投げてみたり、独自に機能追加してみるのも学びがあり楽しいかもしれません😄
OSS活動
Railsのプラクティスでポリモーフィック関連を学習する中で、Railsガイドの関連付けのサンプルコードが分からない箇所があり、Q&Aで質問をしました。
このQ&Aの質問がきっかけでサンプルコードの間違いに気づくことができ、Railsガイドの修正を行うPRを投げるきっかけになりました。初めてのPRがRailsリポジトリにマージされて嬉しかったです。
オープンソースのリポジトリにPRを投げることで、他の人にも影響を与えることができるとともに、小さな貢献でも自信につながるので、今後もやってみたいと思います。
また、ブートキャンプにはオープンソース開発に参加するの発展編のプラクティスもあるので、ぜひチャレンジしてほしいと思います。
Twitter編
フィヨルドブート生やメンター、アドバイザーの方などが日々Twitterで有益な情報を発信しているので、Twitterを有効に活用すると良いと思います。
ここでは、以下の4点を紹介したいと思います。
- フォロー
- リスト
- Nuzzel
- 検索
フォロー
ブートキャンプのユーザー一覧画面で各人が公開しているTwitterアカウントを参照することができるので、ここからフォローするとよいでしょう。 フィヨルドブートキャンプの関係者以外にも、RailsのコミュニティやJavaScriptのコミュニティなどの自分の関心のあるコミュニティの人物をフォローするとよいと思います。
自分は200人程度しかフォローできていませんが、フィヨルド生やRails、フロントエンド、モバイル系のコミュティの方を中心にフォローしています。
また、#fjordbootcampのハッシュタグをみるのもよいでしょう。
リスト
公開リストや非公開のリストを作ることができます。
現時点では、フィヨルドブートキャンプ関係者の公開リストはおそらくないと思いますが、興味の対象ごとにリストを作っておくことで、効率よく情報収集することもできます。
Nuzzel
Twitterでフォローしている人のつぶやきを全て見る(全て見たいかは別として)ことは時間的に厳しいです。
有益なつぶやきの指標としては、RT数やいいね数をもとに、IFTTTなどのツールを使って、一定数の閾値を超えた場合はSlackに通知する方法も使えると思います。
フォローしている人がシェアしてくれたリンクを効率的に収集する方法としては、Nuzzelを使うのがオススメです。このツールは、同じリンクを複数人がシェアした数が多いほど、アプリ画面の一覧の上位に表示してくれます。
一週間の期間でフィルターをかけると、その間に話題になった記事が上位に表示されるようになります。以下の画像では、ちょうどフィヨルドブートキャンプのアドベントカレンダーの記事が表示されています。
また、常時Twitterを見るのは大変厳しいので、週末とかに時間をとって確認するのが時間効率の点でオススメです。とはいえ、私は 朝、昼、晩の3回くらいの頻度で見ている気がします。。
その他、フォロー先のフォローしている人たちがシェアしたリンクを表示するFriends of Friendsの機能も、話題の記事や思いがけない記事を発見するのに便利です。
同様に、先ほど紹介したTwitterのリスト毎にリンクを収集することもできます。
検索
Twitterで検索すると、情報収集に良いこともあります。
例えば、特定の勉強会のスライドを収集するなどです。他にも、Twitter検索コマンドは色々あるので、知っておくとよいでしょう。
検索例
2019年8月29日から2019年8月31日まで開催された
builderscon tokyo 2019
のツイートを対象に検索した例です。ハッシュタグは #builderscon です。
Slack編
フィヨルドブートキャンプのSlackもまた、日々お役立ち情報が飛び交っています。ただ、無料枠の1万メッセージの制限があるので、generalのチャンネルだと、既に1ヶ月前のメッセージが表示されない状況です。
これまで、Slackのメッセージをストックする方法については、あれこれ議論もありましたが、とりあえず自分で気になるメッセージはストックするとよいでしょう。
私の場合は、気になるメッセージや記事があった場合は、個人のSlackのワークスペースのチャンネルに投稿したりしています。
検索
ここでは、簡単にSlackのお役立ち情報を検索する方法について紹介したいと思います。
基本的には、以下のSlackの公式ページに記載されている内容になります。
スレッド内のメッセージも通常検索でヒットするので、たまに検索してみるのはオススメです。フィヨルド生が分報などで書いてるお役立ち情報など思わぬ発見があるかもしれません。
リアクション検索
お役立ちなメッセージは、大抵リアクション(絵文字)が付きますので、リアクションで検索すると便利です。いい話のリアクションで検索したい場合は has::iihanashi: や神のリアクションで検索したい場合は has::kami: といったように、has::絵文字コード: を入力します。
リンク検索
has:link を入力すると、リンク付きのメッセージを検索することができます。
任意のメンバーのメッセージ検索
from:@表示名で、任意のメンバーのメッセージを表示することができます。
メンターさんやアドバイザーさん、その他の方のメッセージを追う際に役に立ちます。
個人Slack
有料版の個人Slackのワークスペースを契約して使っているのですが、プラクティス毎にチャンネルを作ったり、多くのアプリを連携することができ、メリットがあると感じています。
プラクティス用のチャンネル作成
今は、自作Webサービスのプラクティスをやっていますが、ペーパープロトタイプのスクラップや気づきなどのメモなどに活用しています。
Slackアプリの導入
GitHubのアプリやTrelloのアプリなど自由にインストールすることができます。特定のチャンネルに通知を飛ばすとステータスを確認する際に便利です。
また、フィヨルドブートキャンプのSlackには、Colaさんが導入されています。平日の11頃にインタビューされたメンバーの回答がシェアされ、話題のきっかけに大いに役立っているようです。
実は、私の個人Slackにも導入しており、 平日毎日質問が来るので、少し考えるきっかけになったりしています(笑)。
※ 最近、回答した内容が一望できるページができたので、後で見返す際に便利です。
メールの転送
ブートキャンプのコメント通知やお知らせ通知がメールに届くのですが、SlackのEmailアプリを導入して、任意のチャンネルにメール転送しています。支払いの領収書の確認など意外と便利です。*4
Slack API
Slack APIを使って、任意のチャンネルにメッセージを投稿することができます。*5
iOSショートカット機能やCLI、後述するAlfred WorkflowなどからSlack APIを呼び出すと簡単にメッセージを投稿できて便利です。
SlackのURLスキームの活用
PC版やモバイル版のSlackアプリには、URLスキームが定義されています。
SlackのURLスキームは slack:// で定義されており、以下のteamとidのクエリ付きでslack://channelを呼び出すと、指定のワークスペースのチャンネルに直接遷移することができます。
slack://channel?team={TEAM_ID}&id={CHANNEL_ID}
後述する、AlfredアプリのWeb検索機能を使うと、すぐに任意のチャンネルに遷移することができ便利です。
TEAM_IDとCHANNEL_IDの調べ方ですが、特定のSlackのワークスペースをPC版のSlackで開くと、URLにTEAM_IDとCHANNEL_IDが含まれるので、そこから調べることができます。
https://app.slack.com/client/{TEAM_ID}/{CHANNEL_ID}
Alfred(作業効率化)編
AlfredはホットキーやキーワードでWeb検索や自動化プログラムのワークフローを実行して、作業を効率化するアプリです。
このアプリを使うと、作業の生産性を向上させることができます。
Alfredには、無料版と有料版(powerpack)があります。
無料版と有料版(powerpack)には主に以下の違いがあります。
無料版
- アプリ起動
- ファイル検索
- Web検索
有料版(無料版の機能を含む)
ここでは、無料版のWeb検索機能と有料版のワークフローについて取り上げたいと思います。
Web検索
ショートカット用のキーワードや検索キーワードを使って、素早くWebページを開くことができます。
例えば、
- ブートキャンプのQ&Aや日報の検索
- 日報画面を開く
- ローカル開発環境のWebページを開く
など...
日報の検索などは、
https://bootcamp.fjord.jp/searchables?document_type=reports&word={query}
のリンクのように、document_typeパラメータにreports、wordパラメータに検索キーワード用の{query}を定義します。
以下の画像では、fjord-rのキーワードで日報の検索ができるように設定しています。
Alfredの検索バーで fjord-r キーワード と入力すると、{query}にキーワード文字列が置換された状態で検索されます。
同様に、日報画面のURLやローカル開発環境のWebページのURLを定義すると、素早くWebページを開くことができ、時短になります。
SlackのURLスキーム
SlackのURLスキームの活用の節でも前述しましたが、SlackのURLスキームをWeb検索で定義すると、素早く任意のSlackのワークスペースのチャンネルに遷移できます。
以下は、フィヨルドのSlackのチャンネル毎にWeb検索のURLスキームを定義した例になります。b-wakaran と入力して候補を確定させれば、wakaranチャンネルに遷移します。
チャンネルが多い場合など、チャンネルを探して選択する手間が省けて意外と便利です。*6
Alfred Workflow
最後にAflred Workflowを紹介したいと思います。*7
ワークフローを自動化するツールには、有名所で主に以下があります。
他にもたくさんあると思いますが、これらのツールを使うと身の回りの作業を自動化することができます。
iOSショートカットに関しては、ちょうど1年前のアドベントカレンダーで記事を書いたので、こちらも参考にしてください。この記事の中で、IFTTTでSwichBotを動かす例も書いています。
また、ワークフローの自動化を考える際には以下の書籍が役に立つかもしれません。
ワークフローは、UNIXのパイプの考え方に近い気がします。
他にも、まだ読んだことはないですが、レビューの評判が大変よいのでこちらのPythonの書籍も参考になるかもしれません。
今回は、Alfred Workflowを使って、Mac上で作業を行うスクリプトを実行し、自動化プログラミングを行う方法について紹介します。
まずは、ワークフローを自作する前に3つの便利なワークフローを紹介したいと思います。
GitHub
GitHubと連携したワークフローを使うと、自分のリポジトリに素早くアクセスすることができます。リポジトリ名の後にprojectsやissuesなどを入力して、projectやissue画面に遷移することもできます。
Mac版のTwitterアプリを使って、新規ツイートの作成やキーワード検索などを行うことができます。
Slack
Slackのワークスペースと連携することで、指定のチャンネルにメッセージを直接投稿したり、チャンネルに直接遷移することができます。
分報的な使い方もできなくはないです。。
こちらのワークフローは認証が必要なため、個人Slackに導入しています。
Alfred標準では、アクションを実行した後にウィンドウが閉じてしまうため、再度入力が必要となり面倒です。↑の方向キーを押して履歴を遡るか、AlfredのAdvance設定のShow latest query if within 5 minutes にチェックを入れるすることで、検索クエリを維持することができます。
自作ワークフロー
ワークフローで実行するスクリプトは、Alfred標準では以下のスクリプトを使うことができます。
など...
ただし、外部のスクリプトをBashやZshなどのシェルを使って呼び出すことも可能なので、GoやRust、JavaScriptなど個人の好きな言語を使ってスクリプトを実行できます*8
ワークフローを作成するフレームワークとして、AlfyというNode.jsのフレームワークがあります。これを使うと簡単にリスト型(検索結果を一覧表示)のワークフローを自作することもできます。同様に、RubyやGoなどのフレームワークも存在はしているようです。*9
フィヨルドブートキャンプでは、主にRubyやJavaScriptを中心に学ぶため、これらのプラクティスを終了した生徒なども、RubyやJavaScriptで作ったスクリプトでワークフローを自作することができます。ぜひ、ワークフローの自作にチャレンジして欲しいと思います。
今回、自作するワークフローですが、「フィヨルドブートキャンプ Part Advent Calendar 2020 」14日目の佐野真潮さんの日報を書こう!の日報自動投稿プログラムにインスパイアされ、この日報自動投稿プログラムをお借りして、ワークフローを自作してみました。*10*11
自作した4つのワークフローは以下になります。
- 日報自動投稿プログラム
- 自分の日報一覧取得
- みんなの日報一覧取得
- ドキュメント検索
日報自動投稿プログラムのワークフローに関しては、ログイン情報の部分と投稿する日報のテンプレートの文章を除いてオリジナルのコードを使用しています。その他の3つのワークフローに関しては、日報自動投稿プログラム内の認証処理の部分を流用しています。
日報自動投稿プログラムのワークフロー
まずは、日報自動投稿プログラムをワークフローから実行できるように作ってみました。
キーワードのfjord-create-wip でワークフローを起動するように、keyword inputオブジェクトを配置しています。また、Push NotificationのオブジェクトをRun Scriptオブジェクトと接続することで、投稿完了後に、投稿しました!の通知がMac上にポップアップされます。
ワークフローが起動されると、Run Scriptオブジェクト内部に定義したスクリプトのaction.rbが実行されます。
action.rbのスクリプト自体は、ワークフロー用のフォルダ上に格納されています。
スクリプト上では、ログインユーザーのログイン名とパスワードをAlfred上で環境変数を取得するように変更を加えています。
環境変数の設定自体は、ワークフローの作成画面の右上の[x]を押下すると、環境変数の設定画面が表示されるので、ここにログイン名とパスワードを追加します。*12*13
環境変数を設定後に、実際にfjord-create-wipのキーワードを入力すると、以下のようになります。
実際にワークフローを実行するとスクリプトが実行され、投稿しましたの通知がMacの画面上にポップアップされます。その後、BootCampアプリの自分の日報一覧画面で日報がWIPとして作成済であることを確認できます。
ワークフローのソースコードは以下のGitHubリポジトリにアップしています。releaseタブからalfredworkflowファイルをダウンロードし、alfredworkflowファイルをダブルクリックすることでワークフローを自分のAlfredにインストールすることができます。
実際にワークフローを使用する際には、ログイン名やパスワードが必要になります。
自分の日報一覧取得用のワークフロー
ワークフローの基本的な流れは以下になります。
- BootCampアプリにログインする。
- 自分の日報一覧のページをリクエストする
- 2のレスポンスをスクレイピングして、日報一覧の情報を取得する
- 日報一覧の情報をAlfred WorkflowのScritp FilterのJSON形式に変換し標準出力する
- Alfredに表示されるリスト上の日報を選択すると、該当の日報画面に遷移する。
1の手順までは、日報自動投稿プログラムと同じ処理になります。2で自分の日報一覧のページをリクエストしていますが、APIではない通常のWebページのレスポンスであるため、レスポンスのHTMLをスクレイピングして自分の日報を取得する必要があります。
今回は、Nokogiriを使ってHTMLをパースし、日報のタイトルやサブタイトル部分(ユーザー名や日報の日時など)を取得しています。
また、日報一覧はページングされたレスポンスとして返ってきますが、APIのレスポンスではないのと、複数のページングのリクエストを処理することが面倒という理由で、最新の1ページ分の日報一覧のみを取得します。
環境変数にログイン情報と日報取得対象のユーザーIDを設定後、実際にfjord-my-reportsのキーワードを入力すると、以下のように自分の日報一覧が表示されます。
みんなの日報一覧取得用のワークフロー
ワークフローの基本的な流れは以下になります。
- BootCampアプリにログインする。
- みんなの日報一覧のページをリクエストする。
- 2のレスポンスをスクレイピングして、日報一覧の情報を取得する。
- 日報一覧の情報をAlfred WorkflowのScritp FilterのJSON形式に変換し標準出力する。
- Alfredに表示されるリスト上の日報を選択すると、該当の日報画面に遷移する。
2のリクエストページ以外は、自分の日報一覧取得用のワークフローと同じになります。みんなの日報一覧の特徴としては、WIP状態の日報まで取得されるのと、誰の日報か一目でわかるようにユーザーアイコンをダウンロードして表示するようにしています。既にユーザーアイコンが取得済の場合はアイコンは再取得されません。
WIPの日報を表示したくない場合もあるため、nowipの引数がAlfredの検索バーで入力された場合、WIPではない日報一覧を取得するようにします。ただし、既存の日報一覧のレスポンスからWIPの日報をフィルタリングするので、WIPでない日報一覧は通常に比べると件数が少なくなります。
環境変数にログイン情報を設定後に、実際にfjord-all-reportsのキーワードを入力すると、以下のようになります。
fjord-all-reports nowipのキーワードを入力すると、以下のようになります。
ここまで作って気づいたのですが、通常検索でWIPなしの日報を表示する方が便利だなと思いました(笑)
ドキュメント検索のワークフロー
BootCampアプリ上では、以下のドキュメント種別の内容を検索することができます。
- すべて
- お知らせ
- プラクティス
- 日報
- Q&A
- Docs
ただし、上記の画像には、提出物の項目はありません。すべての種別で検索した場合のみ、提出物のコメントなども検索結果に表示されるようです。おそらく、カンニング防止を考慮しての仕様かなと少し思いました。🤔
また、検索結果は、更新順で表示されます。
以下の画像は、すべてのドキュメント種別をGitHubのキーワードで検索した例になりますが、更新順で提出物、日報、Q&Aが表示されることを確認できます。
上記で説明した検索結果をワークフローで取得します。
ワークフローの基本的な流れは以下になります。
- BootCampアプリにログインする。
- 検索したいドキュメント種別に応じて、検索ページをリクエストする。
- 2のレスポンスをスクレイピングして、日報一覧の情報を取得する。
- 日報一覧の情報をAlfred WorkflowのScritp FilterのJSON形式に変換し標準出力する。
- Alfredに表示されるリスト上の日報を選択すると、該当のドキュメント種別の画面に遷移する。
基本的な流れは、日報一覧取得のワークフローと同様ですが、2ではドキュメント種別に応じてリクエスト対象のURLを組み立てる必要があります。また、3でドキュメント種別に応じてHTMLのパース対象の項目に違いがあるので注意が必要になります。そのため、検索対象のドキュメント種別に応じて、パース処理などを行うインスタンスを生成して切り替えるようにします。
実際にワークフローを実行する際のコマンドは、以下になります。
fjord-search <ドキュメント種別の略語> <検索キーワード>
※ ドキュメント種別の略語は必須
※ 検索キーワードはオプション
すべてのドキュメントを検索したい場合:
fjord-search a <検索キーワード>
お知らせを検索したい場合:
fjord-search i <検索キーワード>
プラクティスを検索したい場合:
fjord-search p <検索キーワード>
日報を検索したい場合:
fjord-search r <検索キーワード>
Q&Aを検索したい場合:
fjord-search q <検索キーワード>
Docsを検索したい場合:
fjord-search d <検索キーワード>
おわりに
少し長くなりましたが、半分はAlfredの記事になってしまいました。
最後の日報一覧取得のワークフローなどは検索実行毎にログインを行うため、実用的でないかもしれません。今後、BootCampアプリのAPIがオープン化されて、自由に日報などのデータを取得できるようになることを期待したいと思います。その際は、ネイティブアプリとかも作れるといいなと勝手に妄想しています。
最後に、この記事のTipsが少しでも誰かに役立ったのであれば幸いです。
それでは、引き続き2021年もよろしくお願いします🙇♂️
*1:日報を書き忘れることは頻繁にあります。
*2:ラジオ参加は視聴するだけの参加方式です
*3:ただ、私は16時の雑談にはまだ1回しか参加できていません。。
*4:Slackのメール連携は有料プランのみです。
*6:アイコンはチャンネル毎に分類した方が分かりやすいかもしれません
*7:Alfred Workflowを使うには、有料版のpowerpackの購入が必要です
*8:前述したGitHubやSlackのワークフローではPHPが使われるいるようです。
*10:ワークフローを自作するのは、今回初めてです
*11:日報自動投稿プログラムを使用する許可は事前に頂いています
*12:APIキーなどの秘密情報もここで環境変数として設定することが多いですが、GitHubなどにワークフローのソースコードをアップする際には、秘密情報をアップしないように注意が必要です。