【Windows】Virtualbox上のCentOSにDockerでNginxを立てExpressにアクセスした時のネットワークを図解してみた

## はじめに

アプリケーションの開発は普段やっているが、その際にはあまりインフラに関しては意識しないで開発している事が多かった。今回は少しネットワークがどうなっているか？が気になって図解してみたので、その備忘録を残す（理解に誤り等があればご指摘いただけると幸いです）。

## ネットワークの基本的な概念
いきなりどのようなネットワークになっているのか？を見ていく前に、ネットワークに関する重要そうな概念について理解を深めてみた。

### IPアドレス（IPv4）
ネットワーク内の住所。ネットワーク機器やPCなどネットワークにつながっている端末の場所を特定するための値。グローバルIPとプライベートIPがある。

表記は`192.168.56.2`のように10進数表記で4つの区切りで示される。コンピュータ内では2進数として扱われている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/f6d63235-51cf-df63-421e-167f31e1831e.png)

- 参考：[IPアドレスとは](https://www.cman.jp/network/term/ip/)

### サブネットマスク
ネットワークの範囲を定義するために使用する値。ネットワークを示す際にはこのサブネットマスクをIPアドレスとくっつけた表記がされる。具体的には、

`192.168.56.2/24`

のように`IPアドレス＋サブネットマスク`のような表記で、これをCIDR表記という。

サブネットマスクはネットワークの範囲（サブネット）を指定するためのものだが、仕組みとしては以下の図の通りで、ネットワーク部（同一ネットワーク内で固定で同じになるIPアドレスの部分）とホスト部（ネットワーク内で端末などでそれぞれ異なる値になるIPアドレスの部分）がどこになるか？を数値で表現するものになる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/8f4d3deb-c2f3-56b0-164a-2b1aeb969dcc.png)

なので上記の図のように、`192.168.56.0/24`というCIDRの場合、IPアドレスを2進数で表記した時の左から24桁（bit）分は固定部＝ネットワーク部になり、可変部＝ホスト部は2進数の残りの8桁（bit）分になる。つまり2の8乗＝256パターンのIPアドレスを指定できるネットワーク、という事になる。ただし、192.168.56.0と192.168.56.255はそれぞれネットワークアドレス・ブロードキャストアドレスになるので使用できず、実際に使えるIPの範囲は1～254の254通りになる（2^8-2=254）。

サブネットマスクでよく見るのは、以下の3パターン。

| CIDR形式 | IPアドレス形式 | IPアドレス形式を2進数にすると |
|:-:|:-:|:-:|
| /8 | 255.0.0.0 | 11111111 00000000 00000000 00000000 |
| /16 | 255.255.0.0 | 11111111 11111111 00000000 00000000 |
| /24 | 255.255.255.0 | 11111111 11111111 11111111 00000000 |

※サブネットをIPアドレス形式で書く場合、1になっている部分がサブネットマスクを意味するので上記の表のようなアドレスになる。もし`/1`なら`10000000 ...(0が24個)`なので10進数表記をするなら`128.0.0.0`というサブネットマスクになる。

サブネットを作成する具体的な場面としては、インターネットからアクセスできるパブリックなサブネットと、インターネットからアクセスできないプライベートなサブネットを作成することで、プライベートなネットワーク（サブネット）はセキュリティが高いというような状態が作れる事（役割をネットワークごとに区切る）。サブネットが違うネットワークは直接通信できないので、ルーターやネットワーク機器を使用してネットワークを切り替えて通信する必要がある。

AWSだと以下のような形で、VPC内にパブリックサブネットとプライベートサブネットがある構成がよく見られるだろう（パブリックサブネットaはIP10.0.1.1～10.0.1.254までの254通りのIPを持つサブネットで、プライベートサブネットaはIP10.0.2.1～10.0.2.254までの254通りのIPを持つサブネット）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/d393feb1-6392-1154-270e-f1e540d870df.png)

※サブネットというとサブのネットという意味で、VPCを作成する時にVPC自体はサブじゃなくね？となったが、VPCそのものもサブネットという言い方をし、VPC内にサブネットマスクで新しく区切って定義したネットワークもサブネットという言い方をするみたい。

- 参考：[サブネットマスクとは？](https://www.cman.jp/network/term/subnet/)
- 参考：[サブネットマスク計算（IPv4)](https://note.cman.jp/network/subnetmask.cgi)

## ローカル環境の全体構成のイメージ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/b6b8a36a-fcd2-59fe-d020-be51cc3958a6.png)

## 実際のアクセスの経路

起点はブラウザ（Chrome）で、終点はNode.js Expressのアプリケーションサーバー。その経路としては、

1. Chrome
1. Virtualbox内のDockerで立っているNignx
1. Virtualbox内のNode.js Express

という経路になる。

実際に上記の経路でアクセスした際のは以下の図の通り。

1. Chrome  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/d1053667-9ada-afb1-06d6-717d7293eefc.png)
1. Nginx  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/8d3c254d-4412-ae1d-d490-18091947d7f3.png)
1. Express  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/b38a636f-76e9-eb1d-bc65-5021474a0882.png)

以下では上記の経路の詳細を見ていきたいと思う。

## アクセスの経路を詳細に追っていく
### Chrome　→　Nignx
まず、`https://example.com/`というパスでアクセスしていたが、`example.com`というドメインは実際には存在しないドメイン。今回はWindowsの[hostsファイル](https://wa3.i-3-i.info/word16969.html)を利用して、なんちゃってDNSを利用してIPアドレスを解決している。

ファイルの中身は以下のようになっている（一部抜粋）。

```
user@DESKTOP-PS112J6 MINGW64 ~
$ cat /c/Windows/System32/drivers/etc/hosts
# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
...

# localhost name resolution is handled within DNS itself.
#       127.0.0.1       localhost
#       ::1             localhost

192.168.56.2       example.com
```

続いて`192.168.56.2`というIPアドレスが何者か？だが、VirtualBox上で`ip addr`コマンドを実行すると分かる。

```console
study@localhost:~/workspace/openid-connect (aws-iam-federation *)
$ ip addr
...
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:b1:62:72 brd ff:ff:ff:ff:ff:ff
    inet 192.168.56.2/24 brd 192.168.56.255 scope global noprefixroute enp0s8
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:feb1:6272/64 scope link 
       valid_lft forever preferred_lft forever
...
```

`192.168.56.2/24`というのがあるが、[上記のサブネットマスク](#サブネットマスク)で見たように、これはCIDR表記のIPアドレスで、`192.168.56.0～192.168.56.255`というIPアドレスの範囲のサブネット（192.168.56.0/24というサブネット）に属している1つのアドレス。では`192.168.56.0/24`というサブネットはどこから出てきたのか？（誰が作った者なのか？）という疑問が出てくるが、これはVirtualBoxのホストオンリーアダプターによって作成されたものになる（以下の図を参照）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/98ccaa7e-c83b-8900-f395-61c883a3ef46.png)

まず、IPアドレスのルール同様、192.168.56.0と192.168.56.255はそれぞれネットワークアドレス・ブロードキャストアドレスであり、利用できない。なので、実際に192.168.56.0/24というサブネットで利用可能なIPアドレスは192.168.56.1～192.168.56.254までになる。

そして、ホストオンリーアダプターとはホストOS（Windows）→ゲストOS（VirtualBox上のCentOS）との通信を可能にするもので、やっている事はどうやらゲストOSとホストOSを共通のネットワーク（今回だと192.168.56.0/24）に属させるというものと考えていいだろう（[ホストOSと接続するにはホストオンリーアダプターを使う](https://linuc.org/study/column/3196/#:~:text=%E3%83%9B%E3%82%B9%E3%83%88%E3%82%AA%E3%83%B3%E3%83%AA%E3%82%A2%E3%83%80%E3%83%97%E3%82%BF%E3%83%BC%E3%81%AF%E3%80%81%E3%83%9B%E3%82%B9%E3%83%88,%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%AA%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82)を参照）。そのゲストOS・ホストOS両方が属するネットワーク＝ホストOSの方でもIPアドレスが割り振られる、という事になるがそのIPアドレスが`192.168.56.1`というものになる。

それは実際にWindows上のcmdで`ipconfig`コマンドを実行すると確かめられる。

```console:windowsのcmd
C:\Users\user>ipconfig

Windows IP 構成

...

イーサネット アダプター VirtualBox Host-Only Network:

   接続固有の DNS サフィックス . . . . .:
   リンクローカル IPv6 アドレス. . . . .: fe80::e186:8563:7ab9:2c23%20
   IPv4 アドレス . . . . . . . . . . . .: 192.168.56.1
   サブネット マスク . . . . . . . . . .: 255.255.255.0
   デフォルト ゲートウェイ . . . . . . .:

...
```

`192.168.56.2`の方の正体についてはまだ触れていなかったが、これはゲストOS（CentOS）に割り振られた192.168.56.0/24内のIPアドレス。

つまり、192.168.56.0/24内には以下の2つのIPアドレスを持つ機器が接続されているのと同じ状態になっている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/8de0cbde-00cc-2d69-d5e6-9bbeaa7282ee.png)

したがって、Chrome　→　Nignxとアクセスする時には、IPアドレスでアクセス経路を見ると、

- Chrome = Windows = ホストOS = 192.168.56.1 → Nignx = VirtualboxのCentOS = 192.168.56.2

というアクセスになる。そのため、[実際のアクセスの経路](#実際のアクセスの経路)で見たNignxのアクセスログの方に192.168.56.1というのが出力されていた。

### Nignx
今回はDockerのNginxをdocker composeで立てていたが、DockerはDockerで独自のネットワークを持つ。が、今回はdocker-compose.yamlの方でportsを指定しているので、DockerのネットワークとCentOSのネットワークで通信できるようになっており、利用時には特に気にしないでいいようになっている。

```yaml:docker-compose.yaml
version: '3.9'
services:
  nginx:
    image: nginx:latest
    container_name: nginx
    ports:
      - 443:443
    volumes:
      - ./config/nginx:/etc/nginx/conf.d
      - ./data/nginx:/var/log/nginx
```

上記のように設定すると、公式の[Short syntax](https://docs.docker.com/compose/compose-file/compose-file-v3/#short-syntax-1)に以下のように記載されている通り、ホスト（CentOS）のあらゆるIP（127.0.0.1など）からDockerのネットワーク内のポート443（Nginx）にアクセスする事ができるので、ChromeからNignxにアクセスする際、`example.com(httpsのデフォルトのポートは443なので省略されている) → 192.168.56.2:443 → 0.0.0.0:443 → Docker内のNginx`という経路でアクセスできている。

> Specify the host IP address to bind to AND both ports (the default is 0.0.0.0, meaning all interfaces): (IPADDR:HOSTPORT:CONTAINERPORT). If HOSTPORT is empty (for example 127.0.0.1::80), an ephemeral port is chosen to bind to on the host. （両方のポートにバインドするホストIPアドレスを指定します（デフォルトは0.0.0.0、つまりすべてのインターフェイスを意味します）。(ipaddr:hostport:containerport) を指定します。HOSTPORT が空の場合 (たとえば 127.0.0.1::80) は、そのホストのエフェメラルポートがバインド先として選択されます。）

ちなみに、DockerのNginxのIPアドレスは以下のように確かめられる。

```console
study@localhost:~/workspace/openid-connect (aws-iam-federation *)
$ docker container exec -it nginx /bin/sh
# hostname -I
172.19.0.2 
```

### Nginx　→　Node.js Express（アプリケーションサーバー）
この経路はいわゆるリバースプロキシと呼ばれる部分で、Nginxに来たアクセスをNginxの設定（`nginx.conf`）の設定でプロキシ（他の所に中継）する事でExpressというアプリケーションサーバーへのアクセスが実現されている部分。

Nginxの設定の中身は以下のようになっている（locationディレクティブの設定は一部省略している）。

```conf:nginx.conf
server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate /etc/nginx/conf.d/ssl/server.crt;
    ssl_certificate_key /etc/nginx/conf.d/ssl/server.key;

    location / {
        # 省略
        proxy_pass http://192.168.56.2:3000;
    }

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
}
```

[`proxy_pass`](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass)というディレクティブがポイントで、これによりNginxに来たアクセス（リクエスト）はNginxを中継して`http://192.168.56.2:3000`というIP・ポートに届くようになる。

### 経路のまとめ
上記の詳細を踏まえて全体の流れを整理すると、結局Chromeからのリクエストは以下のようにしてExpress（アプリケーションサーバー）に届いていたという事が分かる。

1. Chrome：`example.com`にアクセス（ポートはhttpsデフォルトの443）
1. Windows：hostsファイルのDNSで`192.168.56.2`にアクセス（ポートは443）
1. VirtualBox：ホストオンリーアダプターのネットワーク内の192.168.56.1→192.168.56.2へのアクセス
1. CentOS：`192.168.56.2:443`へのアクセスは、Dockerのネットワークに中継されて、Dockerのネットワーク上に存在するNginxへアクセス
1. Nignx：プロキシの設定により自身に来たアクセスを中継し、`192.168.56.2:3000`にアクセス
1. Express:最終的にアクセス（リクエスト）が届く

## まとめとして

今回、普段あまり意識していないネットワーク（インフラの側面）についてアプリケーションエンジニアとして理解を深めてみて、色々な仕組みでアプリケーションサーバーにリクエストが届くようになっているんだなとしみじみ思った。本番環境だともっと複雑になると思うが、これをきっかけにインフラ面も理解を深めていきたいと思った。

上記の構成で実際に設定したコードは以下。

https://github.com/yuta-katayama-23/openid-connect-client-demo/tree/aws-iam-federation