-pオプションと決別する

## 概要
この記事では読者諸賢に以下の情報を提供できるかもしれません。

- `docker run`時に`-p`オプションを付けなくても良い開発環境を提案する。
- chromebookで`hosts`の編集と同様の効果を実現できる。
- chromebook内のネットワーク階層について理解が深まる。
- JupyterをNGINXでプロキシする際に必要な設定について概略を掴める。

## はじめに
### この記事は
「コンテナrunしたけどポートフォワーディングしてなかった！」というときの対応と改善策をchromebookで検討しました。
だいぶニッチな需要&`-p`オプション付ければ済む話なのですが、何かのお役に立てば幸いです。

### 動機
「とりあえず動かしてみるか」ぐらいの軽い気持ちでコンテナを立ち上げ、内部でごちゃごちゃいじった後に「`-p`オプションつけ忘れた…」ってことが何度がありました。
で、その場しのぎで対処できるんですが、なんか毎回無駄なことしてるなぁと。
あと各種サービスの開発用のポートって`8000`とか`8080`とか`8787`とか`8888`とか同じようなのが多くないですか？
これも変更すればいい話なのですが、複数コンテナで何も考えずに`-p 8080:8080`とかしちゃうと競合で怒られたり、「今ローカルの8080はどのコンテナと繋がってるんだ…？」と混乱したりすることもあったため、この際ポリシーを決めよう！と試行錯誤した結果をまとめます。

### 方針
という訳で今回のゴールは、
**`-p`オプションつけなくてもさくっとコンテナの動作確認できるようにする！**
です。
そのための手段が煩雑になってしまっては本末転倒なので、以下に挙げた方法以外の、とにかくお手軽に目的を達成できる方法を検討します。(結果としてお手軽ではなくなりましたが…)

- `iptables`を設定する記事( [ここ](https://www.scriptlife.jp/contents/programming/2016/09/07/docker-port-forward/)や[ここ](http://sagantaf.hatenablog.com/entry/2018/11/18/155131))を見かけましたが、普段使わないコマンドを叩くのはちょっと敷居が高い…(スキル不足です、すみません…)

- コンテナをイメージ化して`-p`オプションつけてrunする、というのも煩雑すぎて却下	


### 環境

- PC: Acer Chromebook 11 CB311-8H-C5DV
- 仮想環境: crostini(LXD)
	- Debian GNU/Linux 9 (stretch)
- Docker: Docker version 19.03.8, build afacb8b7f0


## 前提条件
chromebookのアーキテクチャはざっくりと以下のような感じです。
![chromebook0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378557/68f23b75-55a4-532d-8a02-b9b85db052aa.png)

### ①chromebook
今回の主役
### ②crostini
chrome OSにビルドインされているLXDベースの仮想環境（のプロジェクト名)。
正確にはdocker同様コンテナ型の仮想環境で、デフォルトではpenguinコンテナ一つが有効化されている状態だが詳細は割愛。(図の中で入れ子が多くなって見づらくなる…)
以下では便宜上、penguinコンテナのことをcrostiniと呼びます。
【参照：[Chromium OS Docs \- Running Custom Containers Under Chrome OS](https://chromium.googlesource.com/chromiumos/docs/+/master/containers_and_vms.md)】

chromebookからは`100.115.92.202`または`penguin.linux.test`(または`penguin.termina.linux.test`)でアクセスできます。

#### 確認方法
##### LXDのコンテナ一覧
```
crosh> vmc start termina
(termina) chronos@localhost ~ $ lxc ls
+---------+---------+------------------------------+------+------------+-----------+
|  NAME   |  STATE  |             IPV4             | IPV6 |    TYPE    | SNAPSHOTS |
+---------+---------+------------------------------+------+------------+-----------+
| penguin | RUNNING | 172.21.0.1 (br-a0c17ef02fe7) |      | PERSISTENT | 1         |
|         |         | 172.19.0.1 (br-e5939d775b77) |      |            |           |
|         |         | 172.18.0.1 (br-d7b930feccbc) |      |            |           |
|         |         | 172.17.0.1 (docker0)         |      |            |           |
|         |         | 100.115.92.202 (eth0)        |      |            |           |
+---------+---------+------------------------------+------+------------+-----------+
(termina) chronos@localhost ~ $ 
```
##### chromebookのhosts(開発者モードのみ)
```
crosh> shell
chronos@localhost / $ tail /etc/hosts
# In case you want to be able to connect directly to the Internet (i.e. not 
# behind a NAT, ADSL router, etc...), you need real official assigned 
# numbers.  Do not try to invent your own network numbers but instead get one 
# from your network provider (if any) or from your regional registry (ARIN, 
# APNIC, LACNIC, RIPE NCC, or AfriNIC.)
#

#####DYNAMIC-CROSDNS-ENTRIES#####
100.115.92.202 penguin.linux.test
100.115.92.202 penguin.termina.linux.test
chronos@localhost / $ 
```

### ③containers
dockerコンテナたち。
crostiniが導入された初期は起動に[一手間必要でした](https://qiita.com/azumag/items/a834f8cce08a65570033)が、いつの間にか普通に使えるようになりましたね。
私の環境ではコンテナを走らせた順に、`172.17.0.2`、`172.17.0.3`、...とIPアドレスが割り振らていくようです。



以上、このような環境を前提として検討を進めて参ります。

## `-p`オプション設定時の挙動
という訳で、まずはちゃんと`-p`オプションを設定した場合の挙動を確認してみましょう。
図にするとこんな感じです。
(見づらい…。8000とか8080の赤い四角はcrostiniのポートだと思ってください…。)

![chromebook1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378557/865385c0-8402-ce62-489a-31d385923fd5.png)

この場合、container1へは[penguin.linux.test:8000](http://penguin.linux.test:8000/)で、container2へは[penguin.linux.test:8080](http://penguin.linux.test:8080/)でアクセスできることになります。

## 対処法① socatでプロキシ
`-p`オプションつけ忘れたって時に、手っ取り早く上のような構成を実現するにはsocat一択ではないでしょうか。
socatとは、(以下google翻訳)
> **Socat**は、2つの双方向バイトストリームを確立し、それらの間でデータを転送するコマンドラインベースのユーティリティです。
> [socat（1）：多目的リレー\-Linux manページ](https://linux.die.net/man/1/socat)

という、あらかた何でもプロキシできるやつです。
例によって図示するとこんな感じ。

![chromebook2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378557/3832b001-43ca-a0e3-60ec-aa275b75d35e.png)


**crostini内で8000ポートと172.17.0.2:8000を繋ぐ**

```
socat tcp-listen:8000,reuseaddr,fork \
tcp-connect:172.17.0.2:8000
```

## 対処法② サブドメインでコンテナ指定しNGINXで振り分ける

socatによる対処は非常にお手軽なんですが、これを2,3回やった頃に「面倒くせぇ！」となりました。
そして「そもそも`-p`オプションつけなくてもよくしよう！」と思い立ちました。
ということでNGINXを使います。
例のごとく図示。(plantUMLが楽しくて…)

![chromebook3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378557/0369eaf8-7c29-0b23-fe58-635b7d63c223.png)

1. NGINXはcrostini内で80番ポートをリッスン
2. NGINXは[X.penguin.linux.test]()(Xは2以上の数字)というリクエストを受けると、内部で[172.17.0.X]()のコンテナにフォワードする

### NGINX設定
上の要件を実現する最小設定はこんな感じです。
ついでに[X.test]()でも同じ挙動となるようにしました。

```nginx
server {
        listen 80 default_server;
        listen [::]:80 default_server;

        server_name ~^(?<subdomain>.+)\.penguin\.linux\.test$ 
                    ~^(?<subdomain>.+)\.test$;              

        location / {
                proxy_pass http://172.17.0.$subdomain;
        }

}
```

### hosts設定
後はブラウザから[X.penguin.linux.test]()にアクセスするときに全て[100.115.92.202]()で名前解決するようにすればよいです。
windowsなどであれば直接hostsファイルをいじればよいでしょうが、**chromebookではhostsを編集できません。**[^1]
仮にhostsを編集するとしても、[X.penguin.linux.test]()と[X.test]()を必要なだけずらずらと書くのはなんかダサい…
という訳で、DNSサーバをたてることにします。([後述](#dnsmasq%E8%A8%AD%E5%AE%9A))
[^1]: 開発者モードにすれば編集はできますが、再起動で変更内容はもとに戻ってしまいます。

## websocketもプロキシする等
ここまでで最低限のやりたいことは実現できましたが、まだ不十分な箇所があります。

anaconda3コンテナでJupyterを起動する場合を例に(というかこれを使いたかったので)見ていきましょう。
以下コマンドでコンテナを走らせます。

```
docker run -it \
        --name container_name \
        -v /home/username/apps/anaconda/mnt:/opt/notebooks \
        continuumio/anaconda3:2020.02 \
        /opt/conda/bin/jupyter notebook \
        --notebook-dir=/opt/notebooks \
        --allow-root \
        --ip='0.0.0.0' \
        --port=80 \
        --no-browser \
        --NotebookApp.token='' \
        --NotebookApp.password='' \
```
簡単に説明すると、
`-v`:作成するファイルはホスト側で永続化したいので、適当にディレクトリを作成してマウントさせます。

`--port=80`:`jupyter notebook`のオプション。80番ポートを使用する。最初alpineイメージ(anaconda3:2020.02-alpine)で実行しようとしたらroot権限が必要だと怒られました。[^2] 
しかしそもそも`sudo`コマンドが無く、apkでsudoをaddするためにはroot権限が必要…[^3]
[^2]: [公式イメージ](https://github.com/ContinuumIO/docker-images/blob/master/anaconda3/alpine/Dockerfile)の時点で実行ユーザがanacondaになっている。
[^3]: `-u root`オプションを付けてrunすればいけた気がしますが、公式イメージのポリシーに反している気がして…。加えて、以前alpineでapk addしてもリポジトリが見つからないみたいなエラーに遭遇してうんざりしたこともあり、普通に[debian slim](https://hub.docker.com/layers/debian/library/debian/buster-slim/images/sha256-119b2663a32b7a2944363f352ed34cd442a04ffb9fc2023452d55d0eea8149b4?context=explore)とかでいいかなぁと最近思ってます。

そもそもanacondaパケージの容量がすごすぎてベースイメージalpineで軽量化しても霞んで見えるので、使い慣れたdebianベースに変更しました。
こちらではデフォルトでrootなので問題なく80番ポート使えます。[^4]
[^4]: [Dockerfile](https://github.com/ContinuumIO/docker-images/blob/master/anaconda3/debian/Dockerfile)もだいぶすっきりですね！alpineイメージ版と統一感がないのは気にしないことにします😇

　
`--allow-root`: `jupyter notebook`のオプション。root権限で実行する際に必要。

`--ip='0.0.0.0'`: `jupyter notebook`のオプション。デフォルトでは`localhost`になっている。今回外部(コンテナの外)からアクセスするので、全てのIPを許可する。
`'0.0.0.0'`でも`'*'`でもいいっぽい。[^5]
[^5]:  [dockerhubのUsage](https://hub.docker.com/r/continuumio/anaconda3)では`'*'`、[GitHubのUsage]([https://github.com/ContinuumIO/docker-images/tree/master/anaconda3](https://github.com/ContinuumIO/docker-images/tree/master/anaconda3))では`'0.0.0.0'`になってる。記述中のPythonのバージョン的にGitHubの方が新しいので後者推奨？
　
`--NotebookApp.token='' \`　及び
` --NotebookApp.password='' \`: `jupyter notebook`のオプション。アクセス時にログインを無効化。


### 404 Not Found
さて、ブラウザから先程走らせたコンテナにアクセスして新しいノートブックを作成しようとすると、こんなエラーが表示されます。
![Screenshot 2020-05-27 at 22.37.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378557/ab29a072-5510-d619-e89a-8b4a548ce130.png)

ターミナルでは以下。

```
Blocking Cross Origin API request for /api/contents.  Origin: http://3.test, Host: 172.17.0.3
```
どうやらオリジンとホストが違うからブロックするよ、ということのようです。
[Host \- HTTP \| MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Host)
[Origin \- HTTP \| MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Origin)
という訳でchromeのDevToolsで404になったHTTPリクエストを確認すると、`Host`と`Origin`は一致しているので、Nginxがプロキシする際に`Host`ヘッダーを書き換えていることが分かります。

```
POST /api/contents HTTP/1.1
Host: 3.test
Connection: keep-alive
Content-Length: 19
Accept: application/json, text/javascript, */*; q=0.01
DNT: 1
X-Requested-With: XMLHttpRequest
X-XSRFToken: 2|1120b20b|51fbe92a334cbd816742728c7ff542b0|1590415173
User-Agent: Mozilla/5.0 (X11; CrOS x86_64 12871.102.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.141 Safari/537.36
Content-Type: application/json
Origin: http://3.test
Referer: http://3.test/tree?
Accept-Encoding: gzip, deflate
Accept-Language: ja,en-US;q=0.9,en;q=0.8,es;q=0.7
Cookie: _xsrf=2|1120b20b|51fbe92a334cbd816742728c7ff542b0|1590415173; username-3-test="2|1:0|10:1590417087|15:username-3-test|44:NGQ5MzU0YjhkNjlmNDY5YWEwMTExZDAwNmIyNTU2MDU=|b8b86f39cdc6962141a5e26c7462bd6ccd34142d822651ac4d0cd0250c3bb89a"; username-172-17-0-3="2|1:0|10:1590593650|19:username-172-17-0-3|44:NmM0NzJiYjQ3MzdhNDE1ODkxYjFjNzM0MjU1NjcxMmE=|81f00e786eb319318d06a51adbf25fbd88383033759cb4184161b31138c4ea12"
```
そこで`Host`ヘッダーを保持させる設定を追加します。

```diff
@@ -7,5 +7,6 @@ server {

        location / {
                proxy_pass http://172.17.0.$subdomain;
+               proxy_set_header Host $host;
        }
 }
```
[Module ngx\_http\_core\_module](http://nginx.org/en/docs/http/ngx_http_core_module.html#var_host)

これで新規作成ができるようになりました。

### connection failed
それではさっそく新しいノートブック開いてみましょう！
と開いたはいいものの、いっこうにkernelに繋がらず、しばらくするとこんな通知が表示されます。	
![Screenshot 2020-05-25 at 23.42.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378557/0e293308-e6d7-a7b7-28fc-2acafbd7dadd.png)


再度DevToolsでネットワークを確認すると、WebSocketのハンドシェイクで失敗してる様子。
![Screenshot 2020-05-30 at 16.53.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378557/e59cd8fa-e2e0-e746-2fdc-7b351e13e233.png)


という訳でWebSocketの設定を追加します。

```diff
@@ -1,3 +1,7 @@
+map $http_upgrade $connection_upgrade {
+  default upgrade;
+  ''      close;
+}
 server {
        listen 80 default_server;
        listen [::]:80 default_server;
@@ -6,7 +10,11 @@ server {
                    ~^(?<subdomain>.+)\.test$;

        location / {
+               proxy_http_version 1.1;
                proxy_pass http://172.17.0.$subdomain;
+
                proxy_set_header Host $host;
+               proxy_set_header Upgrade $http_upgrade;
+               proxy_set_header Connection $connection_upgrade;
        }
 }
```
参照

- NGINX 公式
	- [Using NGINX as a WebSocket Proxy](https://www.nginx.com/blog/websocket-nginx/)
	- [WebSocket proxying](http://nginx.org/en/docs/http/websocket.html)
- Qiita
	- [NginxのリバースプロキシでWebソケットを通す際の設定 \- Qiita](https://qiita.com/YuukiMiyoshi/items/d56d99be7fb8f69a751b)
		- 丁寧に解説してくれています。
- Jupyter公式
	- [Using a reverse proxy — JupyterHub 1\.2\.0dev documentation](https://jupyterhub.readthedocs.io/en/latest/reference/config-proxy.html)
		- NGINXの例があります。

これでJupyterの基本的な機能は使えるようになりました。
あとは使っているうちに、`X-Forwarded-For`などリバースプロキシで定番のヘッダーが実は必要だったなんてことがあるかもしれませんが、そん時はそん時対応することにします。

## IPアドレスとURLを固定化する

ここまでで当初思い描いた環境は整いました！🎉
しかし人間とは欲深いもので、さらなる便利さを求めてしまうのです…。
という訳で、anacondaコンテナを永続化することを想定して固定のIPとURLでアクセスできるようにしてみます。

### [docker] Static IPのアサイン

[前提条件](#③containers)で記載した以下のような挙動は、
 > 私の環境ではコンテナを走らせた順に、`172.17.0.2`、`172.17.0.3`、…とIPアドレスが割り振らていくようです。

ネットワークを指定せず、コンテナがデフォルトのbridgeネットワークに接続された場合です。
(参考：[Use bridge networks \| Docker Documentation](https://docs.docker.com/network/bridge/))

そして固定のIPをアサインするには、コンテナをユーザ定義のネットワークに接続させる必要があるようです。
> You can also choose the IP addresses for the container with `--ip` and `--ip6` flags when you start the container on a user-defined network.
> [docker run \| Docker Documentation](https://docs.docker.com/engine/reference/commandline/run/#connect-a-container-to-a-network---network)

という訳でまずはネットワークの作成。

```
docker network create --subnet=172.18.0.0/24 mynet
```
そしてanacondaコンテナ起動時に`--net`オプションと`--ip`オプションを追加します。

```
docker run -it \
        --name container_name \
        -v /home/username/apps/anaconda/mnt:/opt/notebooks \
        --net mynet --ip 172.18.0.254 \
        continuumio/anaconda3:2020.02 \
        /opt/conda/bin/jupyter notebook \
        --notebook-dir=/opt/notebooks \
        --allow-root \
        --ip='0.0.0.0' \
        --port=80 \
        --no-browser \
        --NotebookApp.token='' \
        --NotebookApp.password='' \
```

### [NGINX] バーチャルホスト追加
今回はとりあえず[jupyter.test]()で上のコンテナにプロキシするようにしてみました。

```diff
@@ -3,6 +3,20 @@ map $http_upgrade $connection_upgrade {
   ''      close;
 }
 server {
+       listen 80;
+
+       server_name jupyter.test;                   
+
+       location / {
+               proxy_http_version 1.1;
+               proxy_pass http://172.18.0.254;
+
+               proxy_set_header Host $host;
+               proxy_set_header Upgrade $http_upgrade;
+               proxy_set_header Connection $connection_upgrade;
+       }
+}
+server {
        listen 80 default_server;
        listen [::]:80 default_server;
```
無事固定URLでJupyterにアクセスできました🎊

## chromebookで簡易DNSサーバを立てる
最後にchromebookで名前解決をカスタマイズ(？)する方法をご紹介します。
変な表現になってしまいましたが、他のOSで言う"`hosts`を編集する"という作業に等しいです。
余談ですが、こういう微妙に痒いところに、頑張れば手が届くというのがchromebookの面白さだと思います！

さて、[先述の通り](#hosts%E8%A8%AD%E5%AE%9A)chromebookでは`hosts`を編集できません。
chromeブラウザの設定(chrome://about)をみても当たりなし…
拡張機能はどうかとそれっぽい名前のやつを2,3試しましたが、どれもURLでリダイレクトする実装になっていて今回の要件では使い物にならない…

それならいっそのことcrostiniでDNSサーバを建てたらいいのでは！？という訳で以下手順です。

### 問い合わせ先DNSサーバの設定
ずばり
設定のここ↓の
![無題の図形描画.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378557/4e9a8f80-e6be-516a-c4de-65ae00e41296.png)

↓ここの、
![無題の図形描画 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378557/1d71e0df-07ab-c1e1-1561-3df6bc15d4ba.png)


↓ここです！
![無題の図形描画 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378557/385515cd-100e-0ae5-835b-ddde1bb2d98c.png)



詳細：[How to Change the DNS Server on a Chromebook](https://www.howtogeek.com/204672/how-to-change-the-dns-server-on-a-chromebook/)

こんなところで設定できるとは…！正直感動しました。
ネットワーク毎の設定になりますが、普段使いのネットワークは限られますし、ネットワークの切り替えや再起動などでも設定は保持されるので、今の所不便はないです。

ここで、1つ目(優先DNS)にcrostiniのIPアドレス(`100.115.92.202`)、2つ目(代替DNS)に「自動ネームサーバー」のラジオボタンで設定されていたWi-FiのIPアドレス(=デフォルトゲートウェイ:`192.168.128.1`)を設定しています。

crostiniで建てるDNSサーバで解決できなかった場合には、これまでどおりに問い合わせることを意図しています。

### DNSMASQ設定
次にcrostiniでネームサーバーを建てるのですが、`hosts`の代替という限られた機能しか求めてないので軽量なソフトはないかと探したら**DNSMASQ**を発見しました。
軽量という割には多機能で色々できるようです[^6]が、今回は設定の簡単さという点で使ってみることにします。
[^6]: > **dnsmasq** は、軽量のDNS、TFTP、PXE、ルーター通知、およびDHCPサーバーです。[DNSMASQのmanページ](http://www.thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html#lbAH)

crostiniはdebianなので普通に`apt`でgetすると、`/etc/dnsmasq.conf`ファイルが作られます。
これを編集していくのが王道なんでしょうが、インストール直後は恐らく設定可能な全ての項目がコメントアウトされた状態([こんな感じ](https://oss.segetech.com/intra/srv/dnsmasq.conf))でうざいので、オリジナルは退避させ同名ファイルを新規作成します。

そして必要な設定だけを記述したのがこちら。
以下逐行解説。

```ini
listen-address=100.115.92.202
# interface=eth0
log-queries
log-facility=/var/log/dnsmasq.log
port=53
no-resolv
address=/test/100.115.92.202
```

#### `listen-address` or `interface`
この設定が**最も重要**です。
なぜなら、どちらも付けない状態で起動した場合、デフォルトでは`local-service`オプションを付けた場合と同じ挙動になり、ブラウザからの問い合わせに応答してくれません。
> **--local-service**
Accept DNS queries only from hosts whose address is on a local subnet, ie a subnet for which an interface exists on the server. This option only has effect if there are no  **--interface**,  **--except-interface**,  **--listen-address**  or  **--auth-server**  options. It is intended to be set as a default on installation, to allow unconfigured installations to be useful but also safe from being used for DNS amplification attacks.
[Man page of DNSMASQ](http://www.thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html#lbAM)

起動時のログでは以下のように出力されます。

```
DNS service limited to local subnets
```
Manの説明にある`local subnet`は多分同一ネットワークということでしょう。
実はブラウザを実行しているchromebookの本体(以下、ホスト)とcrositiniは、同一ネットワークに属していません。
以下のように、間にVM(バーチャルマシン)が挟まっているのです。
![chromebook5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378557/e8153259-0316-3c80-d406-c46c75518d75.png)



図の①：ホストのVMネットワーク

```
crosh> shell
chronos@localhost / $ ip addr show vmtap0
30: vmtap0: <BROADCAST,MULTICAST,ALLMULTI,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 1a:9a:71:4f:7e:00 brd ff:ff:ff:ff:ff:ff
    inet 100.115.92.25/30 brd 100.115.92.27 scope global vmtap0
       valid_lft forever preferred_lft forever
    inet6 fe80::189a:71ff:fe4f:7e00/64 scope link 
       valid_lft forever preferred_lft forever
```

図の②：VMのVMネットワーク
図の③：VMのLXDネットワーク

```
crosh> vmc start termina
(termina) chronos@localhost ~ $ ip addr show eth0                     
3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 22:4d:1b:2e:d7:de brd ff:ff:ff:ff:ff:ff
    inet 100.115.92.26/30 brd 100.115.92.27 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::204d:1bff:fe2e:d7de/64 scope link 
       valid_lft forever preferred_lft forever
(termina) chronos@localhost ~ $ ip addr show lxdbr0
4: lxdbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 92:42:1b:cd:ed:0b brd ff:ff:ff:ff:ff:ff
    inet 100.115.92.193/28 scope global lxdbr0
       valid_lft forever preferred_lft forever
    inet6 fe80::1c80:f1ff:fe84:6b28/64 scope link 
       valid_lft forever preferred_lft forever
```
で、ここからちょと奇妙な、というか自分でもまだ確証がない部分なんですが、crostiniではデフォルトでDNSの問い合わせ先がVMのLXDネットワークのIPアドレス(`100.115.92.193`)になってるんですね。

```
$ cat /etc/resolv.conf 
domain lxd
search lxd
nameserver 100.115.92.193
```
加えて、crostini内でのDockerコンテナも同様に、`100.115.92.193`がデフォルトの問い合わせ先になっています。
crostini内で`curl`や`wget`しても普通に通るしVMでネームサーバが動いているとは考えづらいので、VMが受信したDNSクエリはそのままホストで設定したDNSサーバに投げられているのだと思います。
実際、VMの`resolv.conf`では[ホストでのDNS設定](#%E5%95%8F%E3%81%84%E5%90%88%E3%82%8F%E3%81%9B%E5%85%88dns%E3%82%B5%E3%83%BC%E3%83%90%E3%81%AE%E8%A8%AD%E5%AE%9A)が反映されています。

```
crosh> vmc start termina
(termina) chronos@localhost ~ $ cat /etc/resolv.conf 
nameserver 100.115.92.202
nameserver 192.168.128.1
nameserver 0.0.0.0
options single-request timeout:1 attempts:5
```
この`resolv.conf`は、ホストで設定を変更したりネットワークを切り替えたりすると動的に更新されます。

この仮説を検証するため、次のような実験をしてみました。
1. 【準備】DNSMASQの設定で、`listen-address` や `interface`を削除し、 local subnetsのみで応答するようにする。
2. 【準備】ホストでのDNS設定に、`100.115.92.193`を追加する。
3. 【確認済み】ホストからcrostini(`100.115.92.202`)に直接問い合わせた場合は、ネットワークが異なるため応答がない。
4. 【**確認したい仮説**】ホストからVM(`100.115.92.193`)に問い合わせた場合、VMではホストのDNS設定が反映されているので、`100.115.92.202`に問い合わせを投げる。`100.115.92.193`と`100.115.92.202`は同一ネットワークなので、DNSMASQは応答を返す。

 はい、この実験結果は仮説通りの挙動となりました。
 ちょっとうまくまとまってないので読んでる方は🤔顔になるかも知れませんが…。chromebookユーザーは是非試してみてください。

さて話を戻して、この実験結果を踏まえれば結局`listen-address` や `interface`はいらないじゃん！となってしまいますが、さすがにここまで込み入った設定だと混乱してしまうので、ホストでのDNS設定は[最初](#%E5%95%8F%E3%81%84%E5%90%88%E3%82%8F%E3%81%9B%E5%85%88dns%E3%82%B5%E3%83%BC%E3%83%90%E3%81%AE%E8%A8%AD%E5%AE%9A)に戻しておきます。

そしてその場合にはやっぱりネットワーク外からの問い合わせを受け入れるということで、`listen-address` や `interface`の設定が必要です。
どちらもDNSMASQが問い合わせを受ける入り口の設定で、どちらか1つでも両方でも問題ありません。
今回はより明確になるように`listen-address`のみ有効化しています。

#### `log-queries`
 ログ出力する。

#### `log-facility`
ログの出力先。

#### `port=53`
DNSプロトコルは53番ポートを使うので設定。
DNSMASQもデフォルト53番を使うみたいですが一応明示しときます。

#### `no-resolv`
DNSMASQは応答を返せない場合、インストールされたマシンの`resolv.conf`を読んで上位のサーバに問い合わせるようになっています。
今回は前述の通り"`hosts`の編集"程度の機能のみ期待しているので、上位サーバへの問い合わせをさせないようにします。

### `address=/test/100.115.92.202`
今回実現したかったこと。testドメインのURLは全て`100.115.92.202`として名前解決します。


## おまけ
今回の設定ファイル等githubで公開しているので、よろしければご活用ください。

- [NGINX](https://github.com/arakaki-tokyo/dev_env/blob/develop/.dev/nginx/docker.conf)
- [DNSMASQ](https://github.com/arakaki-tokyo/dev_env/blob/develop/.dev/dnsmasq/dnsmasq.conf)
- [container_run.sh](https://github.com/arakaki-tokyo/dev_env/blob/develop/apps/anaconda/container_run.sh)
	- anacondaコンテナを走らせるだけのシェルスクリプトを作りました。
	- `-s`オプションで固定IPかどうかを選択できます。
	- `--name`オプションで任意のコンテナ名を設定できます。
- [flask app](https://github.com/arakaki-tokyo/dev_env/tree/develop/apps/flask)
	- Dockerコンテナに届くHTTPのリクエストを確認したくて簡単なflaskアプリを作ってみました。意外に役立ちました。