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

FjordBootCampの歩み方 ~Tips編~

 

これは「フィヨルドブートキャンプ Advent Calendar 2020」の22日目の記事です。

adventar.org

 

2020年の10月にフィヨルドブートキャンプの歩み方をテーマとしたLT会が開催されました。私はバグ報告テンプレートの活用について初LTを行ったのですが、当初はFjordBootCamp Hacksといったタイトルでお役立ちTipsを話そうかなと考えていました(笑)

speakerdeck.com

 

今回、フィヨルドブートキャンプ初のアドベントカレンダーが始まり、ちょうど良い機会なので、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で言及された日報はどこに力をい入れて書くのがよいか の内容が大変参考になると思います。こちらの内容をテンプレートとして使っている方も多くいます。

 

"日報"はどこに力を入れて書くのがよいか🤔

 

いまの気持ち:(感情)

あなたが書かなければ誰にも伝わらない。"相手に伝わるように"テキストで書くには訓練が必要だ。

 

やったこと(事実)

今日も一日がんばった!詳しく書きたいだろうけど、みんなは詳細を読みたいかな?

 

わかったこと:(意見, 解釈)

もちろん、感情以外も適切に伝わるようにテキスト出力するには訓練、訓練、訓練

 

次にやること:(計画,表明)

考えることは大事だけれど、明日への自分の置き手紙(明日の自分に適切に伝わるようにかけるかな?)

 

・シャウトアウト:(感謝)

チームに感謝。仲間に感謝。よい振る舞い、助かったことをフィードバックして増えたらうれしくない?

 

 

speakerdeck.com

 

youtu.be

 

また、疑問点なども書いておくと、メンターやブートキャンプ生の方がコメントを残してくれるので、日報を活用するとよいでしょう。

 

私は最低15分は毎日何かしら学習をするようにしています。学習して日報を書くとGitHub同様の草が生えるので、モチベーションの維持に役立っています。*1

 

f:id:mh_mobile:20201222081652p:plain

メンターコメント

メンターさんのコメントを見ることができます。

他の人に対する指摘は自分に当てはまることも多く、大いに参考になるので、積極的に見るとよいでしょう。

メンターさんのコメントは、ユーザーページからメンターさんのコメントを開くと確認することができます。 

 

f:id:mh_mobile:20201222085449p:plain

 

有益なコメントなどは個人の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の掛け合いが面白く、密かに更新を楽しみにしています。聴いたことない人はぜひ、聴きましょう(笑)。

 

yancanfm

yancanfm

  • yancanfm
  • Technology
  • USD 0

podcasts.apple.com

 

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を勉強している方は活用すると学びがあると思います。

達人に学ぶDB設計 徹底指南書 輪読会

最近では、「達人に学ぶDB設計 徹底指南書」の輪読会が毎週土曜日の22:00〜23:00の時間帯で開催されるようです。こちらも気になる方は参加してみるとよいでしょう。

地域の勉強会・オンライン勉強会

多くの方が積極的に勉強会に参加されています。

今はコロナ禍でオンライン勉強会が活発ということもり、いろいろな勉強会に参加しやすい状態かと思います。積極的に参加してみるとよいでしょう。

BootCampアプリ

ブートキャンプ後半のシステム開発のプラクティスでこれまで学習してきたEラーニングシステムのアプリの機能追加や修正を行います。

普段使ってきたシステムを自分達で改修したりするので、人一倍学びが多いと思います。

システム開発のプラクティスが終わった後にでも、PRを投げてみたり、独自に機能追加してみるのも学びがあり楽しいかもしれません😄

 

github.com

 

OSS活動

Railsのプラクティスでポリモーフィック関連を学習する中で、Railsガイドの関連付けのサンプルコードが分からない箇所があり、Q&Aで質問をしました。

 

このQ&Aの質問がきっかけでサンプルコードの間違いに気づくことができ、Railsガイドの修正を行うPRを投げるきっかけになりました。初めてのPRがRailsリポジトリにマージされて嬉しかったです。

 

github.com

 

オープンソースリポジトリにPRを投げることで、他の人にも影響を与えることができるとともに、小さな貢献でも自信につながるので、今後もやってみたいと思います。

 

また、ブートキャンプにはオープンソース開発に参加するの発展編のプラクティスもあるので、ぜひチャレンジしてほしいと思います。 

Twitter

フィヨルドブート生やメンター、アドバイザーの方などが日々Twitterで有益な情報を発信しているので、Twitterを有効に活用すると良いと思います。

 

ここでは、以下の4点を紹介したいと思います。

  • フォロー
  • リスト
  • Nuzzel
  • 検索

フォロー

ブートキャンプのユーザー一覧画面で各人が公開しているTwitterアカウントを参照することができるので、ここからフォローするとよいでしょう。 フィヨルドブートキャンプの関係者以外にも、RailsのコミュニティやJavaScriptのコミュニティなどの自分の関心のあるコミュニティの人物をフォローするとよいと思います。 

 

自分は200人程度しかフォローできていませんが、フィヨルド生やRails、フロントエンド、モバイル系のコミュティの方を中心にフォローしています。

 

また、#fjordbootcampのハッシュタグをみるのもよいでしょう。

twitter.com

リスト

 公開リストや非公開のリストを作ることができます。

現時点では、フィヨルドブートキャンプ関係者の公開リストはおそらくないと思いますが、興味の対象ごとにリストを作っておくことで、効率よく情報収集することもできます。

 

Nuzzel

Twitterでフォローしている人のつぶやきを全て見る(全て見たいかは別として)ことは時間的に厳しいです。

 

有益なつぶやきの指標としては、RT数やいいね数をもとに、IFTTTなどのツールを使って、一定数の閾値を超えた場合はSlackに通知する方法も使えると思います。

 

フォローしている人がシェアしてくれたリンクを効率的に収集する方法としては、Nuzzelを使うのがオススメです。このツールは、同じリンクを複数人がシェアした数が多いほど、アプリ画面の一覧の上位に表示してくれます。

 

nuzzel.com

 

www.danshihack.com

 

一週間の期間でフィルターをかけると、その間に話題になった記事が上位に表示されるようになります。以下の画像では、ちょうどフィヨルドブートキャンプのアドベントカレンダーの記事が表示されています。

 

また、常時Twitterを見るのは大変厳しいので、週末とかに時間をとって確認するのが時間効率の点でオススメです。とはいえ、私は 朝、昼、晩の3回くらいの頻度で見ている気がします。。

f:id:mh_mobile:20201222113442p:plain

 

その他、フォロー先のフォローしている人たちがシェアしたリンクを表示するFriends of Friendsの機能も、話題の記事や思いがけない記事を発見するのに便利です。

 

f:id:mh_mobile:20201222114254p:plain

 

同様に、先ほど紹介したTwitterのリスト毎にリンクを収集することもできます。

f:id:mh_mobile:20201222114944p:plain

 

検索

 Twitterで検索すると、情報収集に良いこともあります。

例えば、特定の勉強会のスライドを収集するなどです。他にも、Twitter検索コマンドは色々あるので、知っておくとよいでしょう。

検索例

2019年8月29日から2019年8月31日まで開催されたbuilderscon tokyo 2019のツイートを対象に検索した例です。ハッシュタグは #builderscon です。

(
url:www.icloud.com/keynote/ OR
url:slideshare.net OR
url:speakerdeck.com OR
url:dropbox.com OR
url:slidelive.jp OR
url:docs.google.com OR
url:xd.adobe.com/view OR
url:slides.com
)
since:2019-08-29 until:2019-08-31
(#builderscon)

 

qiita.com

 

Slack編

フィヨルドブートキャンプのSlackもまた、日々お役立ち情報が飛び交っています。ただ、無料枠の1万メッセージの制限があるので、generalのチャンネルだと、既に1ヶ月前のメッセージが表示されない状況です。

 

これまで、Slackのメッセージをストックする方法については、あれこれ議論もありましたが、とりあえず自分で気になるメッセージはストックするとよいでしょう。

私の場合は、気になるメッセージや記事があった場合は、個人のSlackのワークスペースのチャンネルに投稿したりしています。

検索

ここでは、簡単にSlackのお役立ち情報を検索する方法について紹介したいと思います。

基本的には、以下のSlackの公式ページに記載されている内容になります。

 

slack.com

スレッド内のメッセージも通常検索でヒットするので、たまに検索してみるのはオススメです。フィヨルド生が分報などで書いてるお役立ち情報など思わぬ発見があるかもしれません。

リアクション検索

お役立ちなメッセージは、大抵リアクション(絵文字)が付きますので、リアクションで検索すると便利です。いい話のリアクションで検索したい場合は has::iihanashi:  や神のリアクションで検索したい場合は has::kami: といったように、has::絵文字コード: を入力します。

 

f:id:mh_mobile:20201221221344p:plain

 

f:id:mh_mobile:20201221221314p:plain

リンク検索

has:link を入力すると、リンク付きのメッセージを検索することができます。

任意のメンバーのメッセージ検索

from:@表示名で、任意のメンバーのメッセージを表示することができます。

メンターさんやアドバイザーさん、その他の方のメッセージを追う際に役に立ちます。

個人Slack

有料版の個人Slackのワークスペースを契約して使っているのですが、プラクティス毎にチャンネルを作ったり、多くのアプリを連携することができ、メリットがあると感じています。

ラクティス用のチャンネル作成

今は、自作Webサービスのプラクティスをやっていますが、ペーパープロトタイプのスクラップや気づきなどのメモなどに活用しています。

 

f:id:mh_mobile:20201221223230p:plain

 Slackアプリの導入

GitHubのアプリやTrelloのアプリなど自由にインストールすることができます。特定のチャンネルに通知を飛ばすとステータスを確認する際に便利です。 

また、フィヨルドブートキャンプのSlackには、Colaさんが導入されています。平日の11頃にインタビューされたメンバーの回答がシェアされ、話題のきっかけに大いに役立っているようです。

atengagement.com

 

実は、私の個人Slackにも導入しており、 平日毎日質問が来るので、少し考えるきっかけになったりしています(笑)。

※ 最近、回答した内容が一望できるページができたので、後で見返す際に便利です。 

メールの転送

ブートキャンプのコメント通知やお知らせ通知がメールに届くのですが、SlackのEmailアプリを導入して、任意のチャンネルにメール転送しています。支払いの領収書の確認など意外と便利です。*4

 

f:id:mh_mobile:20201222220610p:plain

Slack API

Slack APIを使って、任意のチャンネルにメッセージを投稿することができます。*5

iOSショートカット機能やCLI、後述するAlfred WorkflowなどからSlack APIを呼び出すと簡単にメッセージを投稿できて便利です。

SlackのURLスキームの活用

PC版やモバイル版のSlackアプリには、URLスキームが定義されています。

api.slack.com

 

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検索や自動化プログラムのワークフローを実行して、作業を効率化するアプリです。 

 

www.alfredapp.com

 

このアプリを使うと、作業の生産性を向上させることができます。

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}にキーワード文字列が置換された状態で検索されます。

 

f:id:mh_mobile:20201222125943p:plain

 

同様に、日報画面のURLやローカル開発環境のWebページのURLを定義すると、素早くWebページを開くことができ、時短になります。 

SlackのURLスキーム 

SlackのURLスキームの活用の節でも前述しましたが、SlackのURLスキームをWeb検索で定義すると、素早く任意のSlackのワークスペースのチャンネルに遷移できます。

 

以下は、フィヨルドのSlackのチャンネル毎にWeb検索のURLスキームを定義した例になります。b-wakaran と入力して候補を確定させれば、wakaranチャンネルに遷移します。

 

チャンネルが多い場合など、チャンネルを探して選択する手間が省けて意外と便利です。*6

f:id:mh_mobile:20201222020438p:plain

 

f:id:mh_mobile:20201222020640p:plain

Alfred Workflow

最後にAflred Workflowを紹介したいと思います。*7

 

ワークフローを自動化するツールには、有名所で主に以下があります。

  • IFTTT(Web)
  • Zapier(Web)
  • iOSショートカット(モバイル)
  • Alfred Workflow(Mac

他にもたくさんあると思いますが、これらのツールを使うと身の回りの作業を自動化することができます。

 

iOSショートカットに関しては、ちょうど1年前のアドベントカレンダーで記事を書いたので、こちらも参考にしてください。この記事の中で、IFTTTでSwichBotを動かす例も書いています。

qiita.com

 

また、ワークフローの自動化を考える際には以下の書籍が役に立つかもしれません。

ワークフローは、UNIXのパイプの考え方に近い気がします。

www.ohmsha.co.jp

 

他にも、まだ読んだことはないですが、レビューの評判が大変よいのでこちらのPythonの書籍も参考になるかもしれません。

www.oreilly.co.jp

 

今回は、Alfred Workflowを使って、Mac上で作業を行うスクリプトを実行し、自動化プログラミングを行う方法について紹介します。

 

まずは、ワークフローを自作する前に3つの便利なワークフローを紹介したいと思います。

GitHub

GitHubと連携したワークフローを使うと、自分のリポジトリに素早くアクセスすることができます。リポジトリ名の後にprojectsやissuesなどを入力して、projectやissue画面に遷移することもできます。

 

f:id:mh_mobile:20201222140959p:plain

github.com

  

Twitter 

Mac版のTwitterアプリを使って、新規ツイートの作成やキーワード検索などを行うことができます。

 

f:id:mh_mobile:20201222160232p:plain

 

github.com

 

Slack

Slackのワークスペースと連携することで、指定のチャンネルにメッセージを直接投稿したり、チャンネルに直接遷移することができます。

分報的な使い方もできなくはないです。。

こちらのワークフローは認証が必要なため、個人Slackに導入しています。

 

f:id:mh_mobile:20201222161242p:plain

 

Alfred標準では、アクションを実行した後にウィンドウが閉じてしまうため、再度入力が必要となり面倒です。↑の方向キーを押して履歴を遡るか、AlfredのAdvance設定のShow latest query if within 5 minutes にチェックを入れるすることで、検索クエリを維持することができます。

f:id:mh_mobile:20201222162259p:plain

 

github.com

 

自作ワークフロー 

 

ワークフローで実行するスクリプトは、Alfred標準では以下のスクリプトを使うことができます。

など...

 

ただし、外部のスクリプトBashZshなどのシェルを使って呼び出すことも可能なので、GoやRust、JavaScriptなど個人の好きな言語を使ってスクリプトを実行できます*8

 

ワークフローを作成するフレームワークとして、AlfyというNode.jsのフレームワークがあります。これを使うと簡単にリスト型(検索結果を一覧表示)のワークフローを自作することもできます。同様に、RubyやGoなどのフレームワークも存在はしているようです。*9

 

github.com

 

フィヨルドブートキャンプでは、主にRubyJavaScriptを中心に学ぶため、これらのプラクティスを終了した生徒なども、RubyJavaScriptで作ったスクリプトでワークフローを自作することができます。ぜひ、ワークフローの自作にチャレンジして欲しいと思います。

 

今回、自作するワークフローですが、「フィヨルドブートキャンプ Part Advent Calendar 2020 」14日目の佐野真潮さんの日報を書こう!の日報自動投稿プログラムにインスパイアされ、この日報自動投稿プログラムをお借りして、ワークフローを自作してみました。*10*11

mashoo1101.hatenablog.com 

自作した4つのワークフローは以下になります。

  • 日報自動投稿プログラム
  • 自分の日報一覧取得
  • みんなの日報一覧取得
  • ドキュメント検索

日報自動投稿プログラムのワークフローに関しては、ログイン情報の部分と投稿する日報のテンプレートの文章を除いてオリジナルのコードを使用しています。その他の3つのワークフローに関しては、日報自動投稿プログラム内の認証処理の部分を流用しています。

日報自動投稿プログラムのワークフロー

まずは、日報自動投稿プログラムをワークフローから実行できるように作ってみました。

キーワードのfjord-create-wip でワークフローを起動するように、keyword inputオブジェクトを配置しています。また、Push NotificationのオブジェクトをRun Scriptオブジェクトと接続することで、投稿完了後に、投稿しました!の通知がMac上にポップアップされます。

 

f:id:mh_mobile:20201222163621p:plain

ワークフローが起動されると、Run Scriptオブジェクト内部に定義したスクリプトのaction.rbが実行されます。

 

f:id:mh_mobile:20201222164051p:plain

 

action.rbのスクリプト自体は、ワークフロー用のフォルダ上に格納されています。

 

gist.github.com

 

スクリプト上では、ログインユーザーのログイン名とパスワードをAlfred上で環境変数を取得するように変更を加えています。

 

環境変数の設定自体は、ワークフローの作成画面の右上の[x]を押下すると、環境変数の設定画面が表示されるので、ここにログイン名とパスワードを追加します。*12*13

 

f:id:mh_mobile:20201222170652p:plain

 

環境変数を設定後に、実際にfjord-create-wipのキーワードを入力すると、以下のようになります。

 

f:id:mh_mobile:20201222171244p:plain

実際にワークフローを実行するとスクリプトが実行され、投稿しましたの通知がMacの画面上にポップアップされます。その後、BootCampアプリの自分の日報一覧画面で日報がWIPとして作成済であることを確認できます。

 

f:id:mh_mobile:20201222171638p:plain

 

ワークフローのソースコードは以下のGitHubリポジトリにアップしています。releaseタブからalfredworkflowファイルをダウンロードし、alfredworkflowファイルをダブルクリックすることでワークフローを自分のAlfredにインストールすることができます。

 

実際にワークフローを使用する際には、ログイン名やパスワードが必要になります。

 

github.com

  

自分の日報一覧取得用のワークフロー

ワークフローの基本的な流れは以下になります。

  1. BootCampアプリにログインする。
  2. 自分の日報一覧のページをリクエストする
  3. 2のレスポンスをスクレイピングして、日報一覧の情報を取得する
  4. 日報一覧の情報をAlfred WorkflowのScritp FilterのJSON形式に変換し標準出力する
  5. Alfredに表示されるリスト上の日報を選択すると、該当の日報画面に遷移する。

1の手順までは、日報自動投稿プログラムと同じ処理になります。2で自分の日報一覧のページをリクエストしていますが、APIではない通常のWebページのレスポンスであるため、レスポンスのHTMLをスクレイピングして自分の日報を取得する必要があります。

 

今回は、Nokogiriを使ってHTMLをパースし、日報のタイトルやサブタイトル部分(ユーザー名や日報の日時など)を取得しています。

 

また、日報一覧はページングされたレスポンスとして返ってきますが、APIのレスポンスではないのと、複数のページングのリクエストを処理することが面倒という理由で、最新の1ページ分の日報一覧のみを取得します。

 

f:id:mh_mobile:20201222000002p:plain

 

環境変数にログイン情報と日報取得対象のユーザーIDを設定後、実際にfjord-my-reportsのキーワードを入力すると、以下のように自分の日報一覧が表示されます。

 

f:id:mh_mobile:20201221235555p:plain

 

github.com

 

みんなの日報一覧取得用のワークフロー 

ワークフローの基本的な流れは以下になります。

  1. BootCampアプリにログインする。
  2. みんなの日報一覧のページをリクエストする。
  3. 2のレスポンスをスクレイピングして、日報一覧の情報を取得する。
  4. 日報一覧の情報をAlfred WorkflowのScritp FilterのJSON形式に変換し標準出力する。
  5. Alfredに表示されるリスト上の日報を選択すると、該当の日報画面に遷移する。

 

2のリクエストページ以外は、自分の日報一覧取得用のワークフローと同じになります。みんなの日報一覧の特徴としては、WIP状態の日報まで取得されるのと、誰の日報か一目でわかるようにユーザーアイコンをダウンロードして表示するようにしています。既にユーザーアイコンが取得済の場合はアイコンは再取得されません。

 

WIPの日報を表示したくない場合もあるため、nowipの引数がAlfredの検索バーで入力された場合、WIPではない日報一覧を取得するようにします。ただし、既存の日報一覧のレスポンスからWIPの日報をフィルタリングするので、WIPでない日報一覧は通常に比べると件数が少なくなります。

 

f:id:mh_mobile:20201222183709p:plain

環境変数にログイン情報を設定後に、実際にfjord-all-reportsのキーワードを入力すると、以下のようになります。

 

f:id:mh_mobile:20201222180849p:plain

fjord-all-reports nowipのキーワードを入力すると、以下のようになります。

f:id:mh_mobile:20201222181037p:plain

 

ここまで作って気づいたのですが、通常検索でWIPなしの日報を表示する方が便利だなと思いました(笑)

 

github.com

ドキュメント検索のワークフロー

BootCampアプリ上では、以下のドキュメント種別の内容を検索することができます。

 

  • すべて
  • お知らせ
  • ラクティス
  • 日報
  • Q&A
  • Docs

 

f:id:mh_mobile:20201222182248p:plain

 

ただし、上記の画像には、提出物の項目はありません。すべての種別で検索した場合のみ、提出物のコメントなども検索結果に表示されるようです。おそらく、カンニング防止を考慮しての仕様かなと少し思いました。🤔

 

また、検索結果は、更新順で表示されます。

以下の画像は、すべてのドキュメント種別をGitHubのキーワードで検索した例になりますが、更新順で提出物、日報、Q&Aが表示されることを確認できます。

f:id:mh_mobile:20201222183316p:plain

 

上記で説明した検索結果をワークフローで取得します。

ワークフローの基本的な流れは以下になります。

  1. BootCampアプリにログインする。
  2. 検索したいドキュメント種別に応じて、検索ページをリクエストする。
  3. 2のレスポンスをスクレイピングして、日報一覧の情報を取得する。
  4. 日報一覧の情報をAlfred WorkflowのScritp FilterのJSON形式に変換し標準出力する。
  5. Alfredに表示されるリスト上の日報を選択すると、該当のドキュメント種別の画面に遷移する。

 

基本的な流れは、日報一覧取得のワークフローと同様ですが、2ではドキュメント種別に応じてリクエスト対象のURLを組み立てる必要があります。また、3でドキュメント種別に応じてHTMLのパース対象の項目に違いがあるので注意が必要になります。そのため、検索対象のドキュメント種別に応じて、パース処理などを行うインスタンスを生成して切り替えるようにします。
 

f:id:mh_mobile:20201222015854p:plain

 

実際にワークフローを実行する際のコマンドは、以下になります。

 

fjord-search <ドキュメント種別の略語> <検索キーワード>

※ ドキュメント種別の略語は必須

※ 検索キーワードはオプション

 

すべてのドキュメントを検索したい場合:

fjord-search a <検索キーワード>

 

f:id:mh_mobile:20201222185710p:plain

 

お知らせを検索したい場合:

fjord-search i <検索キーワード>

 

f:id:mh_mobile:20201222185841p:plain

 

ラクティスを検索したい場合:

fjord-search p <検索キーワード>

 

f:id:mh_mobile:20201222190521p:plain

 

日報を検索したい場合:

fjord-search r <検索キーワード>

 

f:id:mh_mobile:20201222190201p:plain

 

Q&Aを検索したい場合:

fjord-search q <検索キーワード>

 

f:id:mh_mobile:20201222190409p:plain

 

Docsを検索したい場合:

 fjord-search d <検索キーワード>

 

f:id:mh_mobile:20201222190021p:plain

 

github.com

 

おわりに  

少し長くなりましたが、半分はAlfredの記事になってしまいました。

 

最後の日報一覧取得のワークフローなどは検索実行毎にログインを行うため、実用的でないかもしれません。今後、BootCampアプリのAPIがオープン化されて、自由に日報などのデータを取得できるようになることを期待したいと思います。その際は、ネイティブアプリとかも作れるといいなと勝手に妄想しています。

 

最後に、この記事のTipsが少しでも誰かに役立ったのであれば幸いです。

 

それでは、引き続き2021年もよろしくお願いします🙇‍♂️

*1:日報を書き忘れることは頻繁にあります。

*2:ラジオ参加は視聴するだけの参加方式です

*3:ただ、私は16時の雑談にはまだ1回しか参加できていません。。

*4:Slackのメール連携は有料プランのみです。

*5:Slack APIトークンが必要です。

*6:アイコンはチャンネル毎に分類した方が分かりやすいかもしれません

*7:Alfred Workflowを使うには、有料版のpowerpackの購入が必要です

*8:前述したGitHubやSlackのワークフローではPHPが使われるいるようです。

*9:評判のよいフレームワークかは分かりません。

*10:ワークフローを自作するのは、今回初めてです

*11:日報自動投稿プログラムを使用する許可は事前に頂いています

*12:APIキーなどの秘密情報もここで環境変数として設定することが多いですが、GitHubなどにワークフローのソースコードをアップする際には、秘密情報をアップしないように注意が必要です。

*13:環境変数の設定項目のDont' Exportにチェックを入れるとよいでしょう