Raspberry Piを使ったネットワーク&アプリケーション構築Part1

株式会社LITALICOでエンジニアをやっている @koheiyamaguchi0203 です。
この記事は『LITALICO Engineers Advent Calendar 2019』の12日目の記事になります。

# 目的
- 同一ネットワーク上に存在するRaspberry Pi4台とMac1台とルータ3台でいい感じに通信します。
  - いい感じな通信を具体化すると以下になります。
    - 2台のRaspberry PiでRubyアプリケーションが動作しています。
    - 1台のRaspberry Piでmariadbサーバが動作しています。
    - 1台のRaspberry PiでLoad Balancerが動作しています。
    - MacからSinatraアプリケーションにリクエストを送ると、リクエストに応じたHTMLを返します。
    - ページを返すときにDBからデータを取得し、それをHTMLに反映します。
- これを読んだ人が以下の状態になること。
  - クラウドサービスが裏側でどういう風に、どう物理的につながっているのか？を理解できる。また、それをイメージしやすくなる。
  - ネットワークを物理的に構築することに興味を持てる。

# どうしてこれを書くのか？
インフラ構築の勉強をする中で「EC2って何？」「IGWって何？」「Route Tableって何？」とよく分からないけど、なんか動くぞ？？みたいなことが多いです。
クラウドが抽象化していい感じにやっているところをできる限り自前でやることで、クラウドをなんとなーく使いこなしている状態を脱することができるのではなかろうか？という考えがあり、Raspberry Piを使ってソフトウェアを0から入れてみたり、物理的な配線、ルータの設置などをしつつ、簡単なアプリケーションを作ってみることになりました。

もう少し補足すると、社内で同じようなことをやっている研修があります。だが、数人(メンター1とメンティー4ぐらい)でやっており、私が何もせずとも進んでいく研修になっていました。一人でその研修をやってみたくなり、メンターに週に一回ぐらい分からんかったことや自分がどう構築したのか？などを壁打ちしよう！と思いました。

そのプロセスが自分の勉強がてら、他に同じような悩みを持っている人に届けば良いなぁと思っています。

# 対象者
- クラウドを用いたインフラ構築しかやったことがない。それも雰囲気でやっている人。
- アプリケーションを作ったことがある。
    - フロントエンドもサーバサイドも一定の経験があると良いと思います。
- LinuxやTCP/IP、mariadb、ルータについて詳しくない人。
  - 私も詳しくないので、この辺について詳しくなりたいと思っています。そのプロセスを書くので、そのような人には参考になるかもしれないです。
  - 全てを説明していると、大変なので、この辺が分かっていれば、大丈夫だと思う！みたいなテンションで書いています。

# やったことの要約
- 今回は以下の写真のような論理ネットワークを構築しました。
<img width="934" alt="スクリーンショット 2019-12-11 17.08.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208805/c1b3a887-28e3-1359-7abc-79a149d77d10.png">

- こっちは物理的な結線を表したものです。
<img width="1171" alt="スクリーンショット 2019-12-11 20.16.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208805/74f38640-bc00-1137-3cc1-014c2fee0ec6.png">

- 実際にできるようにしたこと
  - Clientから各Raspberry Piにsshできるようにした。
  - ClientからLoadBalancer(以下LB)にHTTP通信をできるようにした。
  - ClientからAPサーバにHTTP通信をできるようにした。

本当はClientからはLBにしかHTTP通信できないようにして、LBが二台のAPサーバに通信を振り分けるようにしたかったのですが、時間の都合上、この記事を書いた時点では出来ていないです。できるようにしたあとに追記しようと考えています。

# 使った物リスト
個数は実行者の環境によりますが、概ねこのくらい必要になります。

- microSD(8GB~16GBあると良いんじゃなかろうか？) × 4
- 有線LAN × 10(以上)
- 有線LAN to USB 2.0 Type A × 4
- USB 2.0 Type A to microUSB × 4
- ルータ * 3
- Raspberry Pi zero * 2
- Raspberry Pi zero w * 2
- Mac × 1
- microUSB × 4
- スイッチングハブ × 1
- 電源タップ(環境に応じて変わる)

# Rapberry Piのセットアップ
やることは以下。

- Rasbian Buster Liteをダウンロードする。
- SDカードをフォーマットする。
- SDカードにRasbianを焼く。
- SDカード内の一部を編集する。
- SDカードをRaspberry Piに刺す。
- Raspberry Piにsshする。

## Rasbian Buster Liteをダウンロードする
まずはRasbianというOSをダウンロードします。
今回はRasbian Buster Liteをダウンロードします。
この[ミラーサイト](http://ftp.jaist.ac.jp/pub/raspberrypi/raspbian_lite/images/raspbian_lite-2019-09-30/)からダウンロードできます。
```2019-09-26-raspbian-buster-lite.zip```とあり、これをクリックします。そうすれば、Raspbianのimageファイルをzip形式でダウンロードできます。

## SDカードをフォーマットする
私は[このツール](https://www.sdcard.org/downloads/formatter/)を使ってフォーマットしました。
使い方は分かると思うので、頑張りましょう。

## SDカードにRasbianを焼く~Raspberry Piにsshする
[この記事]([https://desertbot.io/blog/ssh-into-pi-zero-over-usb])がわかりやすかったです。私はこの記事の通りにやることで、Raspberry Piのセットアップができました。

## APサーバを用意する
- Raspberry Pi × 2にRubyをインストールします。
    - RubyのインストールはRaspberry Piの種類によりますが、相当時間がかかります。その上Cライブラリがなくて、コンパイルが失敗します。エラーが出たら、エラー文を読み、どのライブラリをインストールする必要があるのか判断して、インストールしてください。頑張りましょう。
- sinatraの環境を整えます。
    - これは適当にググれば誰でもできると思います。
- 外部ホストのDBサーバと通信できるようにします。
    - https://qiita.com/NoriIka/items/cccaf60eacee6fb6951b
    - ↑の記事が分かりやすいです。ただ、CentOSとはFWの設定コマンドなどが違うので、読み替えると良いと思います。
    - **ufwなどを使ってFWを設定するときに、22番を開放しないでlogoutすると、sshできなくなるので、本当に気を付けましょう。**
    - mariadb/mysqlに関連するライブラリをAPサーバが動作するホストにインストールしないと不都合が起きると思います。エラーになったら、エラー文を読み、必要なライブラリをインストールしましょう。頑張りましょう。
 
## DBサーバを用意する
- Raspberry PiにmariaDBをインストールします。
- APサーバと通信できるようにします。
    - 「APサーバを用意する」というところにおすすめ記事を書いたので、それを読みましょう。
    
## LBを用意する
- Raspberry PiにNginxをインストールします。
- HTTPリクエストをAPサーバにいい感じに振り分ける設定を書く(これがまだできてないので、後日追記します)。

# ネットワーク
再掲となりますが、このようなネットワークを構築しました。
<img width="934" alt="スクリーンショット 2019-12-11 17.08.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208805/c1b3a887-28e3-1359-7abc-79a149d77d10.png">

どうしてこのようなネットワークが構築されるのかについて以下の順で説明していきます。

- IPについて
- Routerの機能について
    - DHCP
    - ルーティングテーブル
- NAPTについて

## IP
ISO参照モデルで言うところのL3 ネットワーク層に当たるプロトコルです。
「インターネット」を構成する中でIPはなくてはならない仕組みです。
IPの役割は物理的/論理的なネットワーク間を超えた通信を可能にすることです。図にします。
<img width="963" alt="スクリーンショット 2019-11-11 1.11.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208805/f440de1e-ab9f-1d78-8998-cfefa767173d.png">
図の2つの四角は物理的にも論理的にも別のネットワークです。L1(物理層)/L2(データリンク層)だけではネットワークを超えた通信をすることはできないのですが、L3(ネットワーク層)のプロトコルであるIPによってそれは実現されています。
詳しいことはまだ調べきれていないので、ここでは説明できません。後日、L2/L3について調べることがあった場合に、まとめてみたいと思います。

よくIP Addressという言葉を聞くと思いますし、上の写真でもIP Addressという単語があります。これが何なのかについて説明します。
IP Addressとはネットワークに接続されているノード(ホストとルータの総称)に割り振られるユニークな32ビットの正整数です。
ノード間で通信を行う際には通信内容とは別に送信元、送信先の情報が必要です。なぜならば、情報をどこに渡すのかがわからないと、情報を送信できないし、情報を送信しても、送信元がないと、受け取った側(送信先)はどこに返事をして良いのかわからないからです。
この送信先と送信元を一意に特定するためにIP Addressが用いられます。
IP Addressは32ビット正整数なので、実際には00001010000000000000000100000001となります。これだと人間が識別しづらいため、8bitずつに分けて、その分けた値を10進数にすることでIP Addressを表現しています。上で書いたIP Addressを8bitずつ10進数に直すと、このような流れで変換されます。

- 00001010000000000000000100000001
- 00001010.00000000.00000001.00000001
- 10.0.2.2

IP Addressはネットワーク部とホスト部に分けることができます。
IP Addressはパケットの送信先、送信元を指定するために使われます。ネットワーク部はホストがどのネットワークに属するかを示す部分であり、ホスト部はそのネットワーク内でユニークな値になります。どう分けているかというと、CIDRという仕組みがあります。詳しくは他の書籍や記事を読んでください。ざっくり言えば、どこまでがネットワーク部で、どこからがホスト部なのかを指定する仕組みです。

## Routerの機能について
Routerには「接続されているノード(ここではRaspberry PiとMac、Router)にIPアドレスを割り振る」「送られてきたパケットをどこに流すかを決める」機能があります(他にもあります)。

**DHCP**
ルータにはDHCP(Dynamic Host Configure Protocol)という機能があります。この機能によってルータに接続されているホスト(MacとRaspberry Pi)にIP Addressなどを割り振る設定が行われます。
このDHCPという仕組みのおかげでMacとRaspberry PiにIP Addressを割り振っています。
具体的な仕組みについて以下に書いていきます。
まず、ネットワークに繋がれたDHCPクライアントはそのネットワークに対してBroadcast通信を行います。その通信を受け取ったDHCPサーバはDHCPクライアントに「この設定を使うように」というレスポンスを返します。
そのレスポンスを受け取ったDHCPクライアントは再びBroadcast通信を行い、「レスポンスに書かれていた設定を使って良いですか？」とDHCPサーバに確認を取ります。
どうして確認を取るのかというと、ネットワーク内には複数のDHCPサーバがある場合があります。他のDHCPサーバが同一ネットワーク内のホストに同じ設定を割り振っている場合、ネットワークとしておかしくなってしまう(例えば、同じIP Addressを持っているホストが2つになってしまう)ので、再度確認を取っています。
最後にDHCPサーバがその設定を使うことを許可するレスポンスをDHCPクライアントにすることで、ホストにIP Addressが割り振られます。

**ルーティングテーブル**
ルーティングテーブルとは「ルータに到達したパケットをどこに流すかを判断する対応表」のことです。ネットワーク図をもとに説明します。
まず、ルーティングテーブルにはネットワークアドレスとゲートウェイと呼ばれるカラムがあります。これはルータによって名称は違えど、意味するものは同じです。
ネットワークアドレスはルータに到達したパケットの宛先IP Addressと照らし合わせられます。そこでルーティングテーブルのネットワークアドレスとパケットの宛先IP Addressのネットワーク部がマッチする場合において、ゲートウェイに指定されているIP Addressを持つノードにパケットが送信されます。
つまり、Clientから宛先アドレス10.0.2.2を持つパケットを送ったときには以下のようにパケットは流れます。

- Client→RouterB
- RouterB→RouterA
- RouterA→RouterC
- RouterC→LB
- LB→RouterC
- RouterC→RouterA
- RouterA→RouterB
- RouterB→Client

ただし、BroadStationを使っている場合は上手くいきません。理由としてはBroadStationのWAN側ポートの挙動がおかしいのでは？という推測がありますが、BroadStationの仕様を把握していないので、推測の域をでません。もし、ちゃんと通したいのであれば、中古で安い業務用ルータを買うべきです。会社で使われていなかったRTX 1200というルータを使って同じ構成を試したところ、上手くできたので、この理論が正しいことは証明済みです。

ちなみに、Client(Mac)やRaspberry Pi、RouterB、RouterCにもルーティングテーブルは設定されています。
ClientとRaspberry Piでルーティングテーブルを確認するには以下のコマンドを叩く。

```terminal
$ netstat -rn
Routing tables

Internet:
Destination        Gateway            Flags        Refs      Use   Netif Expire
default            10.0.2.2           UGSc          118        0     en0
...略
127.0.0.1          127.0.0.1          UH             10    49670     lo0
```
ClientやRaspberry Piから送信されるパケットはこのルーティングテーブルを見て、どこに送信されるのか決まります。Destinationは宛先であり、テーブルの一行目はDestinationがdefaultとなっています。このルーティングテーブルに当てはまらない場合にdefaultで設定されているGatewayにパケットが送信されることを意味します。
AWSでもDefault Gatewayを設定するなどと書かれているが、それはこれのことです。
要するに、ルーティングテーブルに当てはまらないパケットが到達した場合はdefault gatewayにパケットを送信しています。

RouterBのルーティングテーブルは以下のようになっています。確認はルータの管理画面orコンソール(BroadStationはコンソールに入れないので、管理画面から確認してください)からできます。

<img width="261" alt="スクリーンショット 2019-12-11 18.37.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208805/6099e1e0-8c54-01e3-116e-857efa8c6db6.png">

RouterCはこうなります。
<img width="262" alt="スクリーンショット 2019-12-11 18.52.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208805/c56d8f07-526a-5e90-328e-8d0ae44ee532.png">


上の箇条書きに付け加えて、具体的にパケットの流れを説明すると、以下のようになります。

- Client→RouterB
    - Clientで宛先IP Address10.0.2.2がルーティングテーブルに指定されているか確認します。
    - マッチしないので、default gatewayにパケットが送信されます。要するにRouterBにパケットが送信されます。
- RouterB→RouterA
    - RouterBのルーティングテーブルに10.0.2.2がマッチするか確認します。
    - マッチしないので、default gatewayとして指定されているRouterAにパケットが送信されます。
- RouterA→RouterC
    - RouterAのルーティングテーブルに10.0.2.2がマッチするか確認します。
    - 10.0.2.2はRouterAのルーティングテーブル(10.0.2.0/24のところ)にマッチします。なので、192.168.11.3にパケットを流す処理を行います。
- RouterC→LB
    - RouterCのルーティングテーブルに10.0.2.2がマッチするか確認します。
    - 10.0.2.2はRouterCのルーティングテーブル(10.0.2.0/24のところ)にマッチします。なので、local、要するに10.0.2.0/24ネットワークに属するホストにパケットを流します。ルータはどのポートにパケットを流せば、宛先IP Addressを持つホストに到達するのか知っているので、特定のホストにパケットを流すことができます。
- LB→RouterC
    - 送信元IP Addressが宛先パケットになり、LB→Clientへパケットを返します。やっていることは上述した内容と同じなので、詳しくは書きません。
- RouterC→RouterA
- RouterA→RouterB
- RouterB→Client


ここまではClient→APサーバにパケットを送信することができるみたいに書いていますが、途中で書いたようにルータにBroadStationを使うとできません。もし、Client→APサーバにパケットを送信したいなら、良いルータを買いましょう。
*パケットを送信したいというより、pingを通したいが正しいです。別にNAPTの設定を行い、Portを指定した通信を行えば、APサーバにパケットを送信はできます。

## NAPTについて
NAPTとは「Network Address And Port Translation」の略です。1つのIP Addressで複数のホストを外部に公開するときに使われる仕組みです。
IP AddressとPortを変換して、ネットワークに属するホストに1つのIP Addressを共有する仕組みのことです。

<img width="356" alt="スクリーンショット 2019-12-11 20.02.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208805/6141fa88-8229-05c6-4ec8-65fa46eec036.png">

上は実際に私が構築したネットワークに存在するNAPTの設定です。この設定はRouterCにされています。具体的にどういう処理になるかを以下に書きます。

- Clientが192.168.11.3:80にHTTP通信を行う。
- RouterCはこの通信を10.0.2.2:80に流す。

こうすると、1つのIP Address(192.168.11.3)を使って複数のホストを外部に公開することができます。

どうしてこれを使ったのか？というと、BroadStationの仕様によってClientからAPサーバにpingを通せずにいたからです。しかし、ClientからRouterCのWAN側IPまではpingが通せることが分かっていました。
であれば、NAPTを使えば、TCPによる通信ができだろうと目処を付けました。案の定出来たので、良かったです。

# 感想
- 本当に多くのL3~L7の知識と実践ができて、得られたものが多かったです。さらにマシンにOSをインストールし、bootするという経験もなかったので、これも楽しかったです。普段であれば、AWSが勝手にやってくれていることが何なのか？をある程度理解できている実感があります。この記事には書けていないこともたくさん得るものはありました。それはちゃんと学べたもの、これから学ぶ必要があるという気付きも含めて、得たものです。
  - sshについての仕組み
  - TCPについての仕組み/詳細
  - IPについての仕組み/詳細
  - L2の仕組み
- 本来であれば、L7についての研修だったのですが、思いがけずに低いレイヤについて学習してしまいました。これからはL7の話を中心にしていきます。特にHTTP周りの話が多くなると思います。それもいつか記事に出来たらと思います。
- ただ、お金と場所が必要になるので、普通の人はVMやコンテナ技術、あるいはAWSなどのクラウドサービスを使ってやればいいと思いますし、L2/L3の話ならば、cisco packet tracerとか使えばいいと思います。物理でしか得られないのは「楽しさ」「イメージのしやすさ」ぐらいだと思います。これが重要な人は物理で触ってみると良さそうです。私はこれがとても重要な人間なので、良い勉強になっています。
- ちなみに、総額で2万以上は掛かっています。これからルータを[こういうやつ](https://www.amazon.co.jp/dp/B000P5FXDE)に買い替えていこうと考えているので、更にお金は掛かりますし、なんなら、Raspberry Piもこれから増えるかもしれないので、まだまだお金がかかるかもしれないです。お金欲しいです。

# 次回
- 以下のことを実現したいと思っています。
  - Cacheやらなんやらを試す。
  - Webサーバを追加する。
  - DNSを導入する。
  - 静的ファイル配信サーバを導入する。
  - Reverse Proxyを導入する。
- もっと先の話としてはクラスタ構成とかコンテナ技術の実験、Elixirの分散処理の話とかを実験してみたいと思っています。

明日は明日は、@yoshikitanaka0707 さんの「『障害のない社会を作る』ためにアクセシビリティに向かい合う」です。お楽しみにです。