<Vagrant+VirtualBox(CentOS7)+Docker>コンテナにnginxを入れて起動してみた

#TL;DR
今回の流れは大まかに以下です。

1. VagrantでVirtualbox(CentOS7)を立ち上げ
2. Shellスクリプトで、Dockerのプロビジョニング
3. ホスト(ローカルのMac)からVMのCentOSに立ち上げたDockerコンテナのnginxにアクセス

VMマトリョーシカの初体験であります。


#VagrantでVitrualboxにCentOS7を入れて立ち上げる
Vagrantfileは下記の内容で進めました。

```ruby
# -*- mode: ruby -*-
# # vi: set ft=ruby :

## Version constraint
Vagrant.require_version ">= 1.3.5"

## Plugin constraint
unless Vagrant.has_plugin?("vagrant-hostsupdater")
  raise 'Missing plugin `vagrant-hostsupdater`! Install it by `vagrant plugin install vagrant-hostsupdater` command before vagrant up.'
end

# VM config
Vagrant.configure("2") do |config|
  config.ssh.insert_key = false
  config.vm.box = "centos/7"
  config.vm.box_version = "2004.01"
  config.vm.hostname = "vagrant-test.localhost.com"
  config.vm.network :private_network, ip: "192.168.0.1"
  config.vm.provider :virtualbox do |v|
      v.name = "centos7_vagrant_test"
      v.customize ["modifyvm", :id, "--memory", 2048]
      v.cpus = 1
      v.check_guest_additions = false
      v.functional_vboxsf     = false
      v.gui                   = false
  end

  # VM NFS
  config.vm.synced_folder ".", "/home/cwd/src", id: "home", :nfs => true, :mount_options => ['nolock,vers=3,udp,actimeo=2']

  # VM provisioning
  config.vm.provision "shell", run: "always", inline: <<-SHELL
    echo Hello, World
  SHELL
  config.vm.provision "shell", run: "always", :path => "./provision/local/scripts/docker-init.sh"

end
```
詳しい項目の詳細については、
こちらの記事も参考にご覧ください。
参考：[「Vagrant+VirtualBox」ローカルで仮想環境を立ち上げてみる](https://qiita.com/ichi_zamurai/items/7c89fde212a86883d0f0)

##「vagrant up」でvm立ち上げ
```
vagrant up
```
###ポイント
- `Vagrantfile`があるディレクトリに移動（cd）し、`vagrant up`をする
- [vagrant-hostsupdater](https://github.com/agiledivider/vagrant-hostsupdater)を入れておくとmacの`/etc/hosts`を自動で更新設定してくれるので便利なのでその判定をする
    - `vagrant halt`vm停止後も、`/etc/hosts`から追加したホストを削除してくれる。
- `config.vm.provision`で`run: "always"`にしないと、provisioningは初回しか走らないので、必要に応じて設定する
- `:path`は、Vagrantfileがあるディレクトリを基準に相対パスで`.sh`シェルスクリプトのファイルを指定する


#Shellスクリプトで、Dockerのプロビジョニング
Shellスクリプトとは、**コンピュータに対する命令をまとめて書くファイル**で、
Linuxコマンドとか、自分でインストールしたコマンドとかで命令を書いて、バッチを作れるやつ。

試行錯誤を重ね、初めて自分で書いてみたものが下記。（なかなか大変だったこりゃ。）
（※何度も自分のローカルで動作確認したけど、もしかしたらエラー出て動かないかも・・・？）

```shell
#!/bin/bash

# Directory Definition
FIRST_OUTPUT=~/docker-compose
LAST_OUTPUT=/usr/local/bin/

# Install jq command if necessary
jq --version >/dev/null 2>&1
if [ $? -ne 0 ]; then
    sudo yum -y install epel-release
    sudo yum -y install jq --enablerepo=epel
fi

# Install wget command if necessary
wget --version
if [ $? -ne 0 ]; then
    sudo yum -y install wget
fi

# Download the latest Docker
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
wait
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
wait
sudo yum install -y docker-ce docker-ce-cli containerd.io
wait

# Download the latest docker compose
DOCKER_COMPOSE_LATEST_VERSION=$(curl -L https://api.github.com/repos/docker/compose/releases/latest | jq .name -r)
wget "https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_LATEST_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -O ~/docker-compose
wait

# Make a directory & Move the installed docker-compose to the appropriate folder
sudo mkdir -p $LAST_OUTPUT
sudo mv ${FIRST_OUTPUT} ${LAST_OUTPUT}/docker-compose

# Change Owner & Mode for the Docker Compose
sudo chown root:root ${LAST_OUTPUT}/docker-compose
sudo chmod +x ${LAST_OUTPUT}/docker-compose

# Start Docker
sudo systemctl start docker
sudo systemctl status docker

# Run containers using docker-compose
cd /home/cwd/src/provision/local
sudo ${LAST_OUTPUT}/docker-compose up -d --remove-orphans

```
###やっていることの解説
- 最初に`docker-compose`をインストールする場所を定義
- `CentOS`はまっさらなPC状態で、使いたかった`jq`コマンドと`wget`コマンドが入ってなかったので、最初にインストール
    - `$?`には直前のコマンドの実行結果が返ってきて、成功すると`0`が入るからそれで判定が可能。
    - `jq`コマンドは、JSONパースしてくれて、`name`属性だけを`-r`つまり`""`を取り除いて取得してくれるってやつ。
        - これでversionの名前がキレイにとれる。
- 最新版のDockerをインストール
- 最新版のDocker-composeをインストール：OSとハードウェア的に互換あるやつ
    - `uname -s`はkernel nameを出力してくれる
    - `uname -m`はmachine hardware nameを出力してくれる
- インストールした`docker-compose`を仮置きしてたので、最終的に置きたい場所のためにフォルダ作成
    - そして移動
- `docker-compose`のオーナーを`root`に変更し、全ての人に実行権限だけを付与するように変更
- `Docker`をスタートして、一応statusなんかをコンソールに出しちゃって
- `docker-compose.yml`のあるディレクトリに移動し、docker-composeのバイナリファイルがあるディレクトリを指定しながら、`docker-compose up`。
    - `-d`はバックグラウンド実行。いわゆるデーモンみたいな
    - `--remove-orphans`は、`docker-compose.yml`にかかれていないサービスのコンテナを削除するってオプション
                          
###改善点
ってな感じで、自分で適当にバッチを書いてみたが、
もっとよく書けたり無駄があったり、するから改善の余地は多々ありそうw
あとは、ログ出力`echo`とか`1>&2`など、標準出力、標準エラー出力も意識した書き方をしないといけないと思っている。


#Docker-compose.yml
```yml
version: "3.8"

services:
  web:
    image: nginx:latest
    # build: ./local/docker
    container_name: web
    ports:
      - 8080:80
    stdin_open: true
    tty: true
    privileged: true
```
最初Dockerfileを使う予定だったけど、一旦は難易度をさげて、通常のDocker Hubのimageを使う
`tty: true`でコンテナが起動し続ける

#いざ、nginxにアクセス！！
```
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS                  NAMES
12exxe38xxx7   nginx:latest   "/docker-entrypoint.…"   5 minutes ago   Up 5 minutes   0.0.0.0:8080->80/tcp   web
```
` 0.0.0.0:8080->80/tcp `
と立ち上がったので、
さっそく、ブラウザからアクセスしたい。

###IPアドレスにポート番号でアクセス

Vagrantfileに設定したipアドレスにポート番号をつけて、
`http://192.168.0.1:8080/`
にアクセス!!!!!!!

![スクリーンショット 2021-03-09 15.55.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1075243/f9fe14ef-bcb1-8e75-8b19-5f0cdc011f82.png)
アクセスできた♪

###ホストネームにポート番号でアクセス

次に、ホストネームでアクセスする！！！
`http://vagrant-test.localhost.com:8080/`
![スクリーンショット 2021-03-09 15.55.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1075243/e49aab17-cd33-884a-e202-fa988a78aae6.png)


できた。。
ホスト名にポートつけるのかっこ悪いしなんだか気持ち悪いなあ、、、
でもまだ解消方法が分からないので次回挑戦。

>ポート番号として 80 番を使用する場合だけは例外で、ポート番号を省略した場合は 80 番が指定されたものとして扱われます。そのため本来であれば「http://www.example.com:80/ 」のようにアクセスする代わりに「http://www.example.com/ 」のようにポート番号を省略してアクセスすることができます。
参考元：[80番以外のポート番号を使用した場合のWebサーバへのアクセス](https://www.adminweb.jp/apache/install/index6.html#section1)

ここまでくるのに、1週間以上はかかったなあ。
大変でしたが、つながると気持ちいいですね！！



#ハマったポイント
##Vagrantfile
###フォルダ階層につまづき・・・
`config.vm.synced_folder`で、どうやってフォルダがマウントされるかという実感が持てず、
そもそもマウントされていないんじゃないかってところで何時間も調査したが、
単純にvagrantのvirtualbox内の階層を理解していなかっただけだった。

```
vagrant ssh
```
で、ログインしたときに最初にたどり着くディレクトリが
`/home/vagrant`
で、そこからスタートするのかとかマジで知らんしって状態だったんだけど、
自分が指定したのは
`config.vm.synced_folder ".", "/home/cwd/src",`

つまり、
`/home/cwd/src/`
であって、
ログイン後に一回`cd ../`して`ls -la`をすれば、自分の階層を見つけられたのだった。
ど素人すぎて、階層の蟻地獄にハマりました。


##Docker-compose
###アクセスがリセットされてしまう問題。

```
curl: (56) Recv failure: Connection reset by peer
```
これでずっと苦しむ。
ymlファイルに、以下のプロパティを設定したら、上記エラーがでて、nginxがhtmlを返さなくなってしまった。

```
command: tail -f /dev/null
user: $USER_ID:$GROUP_ID
```
原因が完全には理解していないものの、
おそらく
`tail -f /dev/null`すると常にコマンドが実行状態となり、忙しくて返って来なくなる
`user`はユーザー権限の問題と衝突してダメ
という予想です。

```
sudo docker exec -it web /bin/bash
curl localhost:8080
```
このコマンドで、dockerコンテナに入り込んで、コンテナのnginxが起動してるか確認ができる。

##Nginxのdefault.conf
###無駄にdefault.confを理解もしないでいじり倒してしまった
`defalt.conf`の設定も、よくわからないネットで拾ってきたものをマウントさせて使ってしまったから、ダメだった。
nginxのインストールしたてホヤホヤのものをそのまま使えば、よかったのに。

```
curl: (56) Recv failure: Connection reset by peer
```
が出続けた。

`listen`の設定がすごく大事だった。

```
#default.confの一部
server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;
```

###リクエスト＆レスポンスの流れ
1. まずは、localhostは127.0.0.1に解決される。
2. 次に、Port Forwardingがなされ、`127.0.0.1:8080 -> 172.17.0.x:80`
    -  「ホストの全インターフェースの8080ポート宛通信をコンテナの80ポートに転送する」
3.  `172.17.0.x:8000 -x-> 127.0.0.1:8000`
    -  ホストマシンからのリクエストがコンテナのIPアドレス 172.17.0.xの80ポートに届いたが一方で、アプリ側は `127.0.0.1:8080`でしかlistenしておらず、`172.17.0.x:80`ではListenしてないみたいになっていた。
4.  `0.0.0.0`でサーバを建てると、そのホスト(今回の場合、コンテナ)が持つ全てのインターフェースでLISTENできる


#まとめ

VMマトリョーシカは、泥沼でした。
深海のような水圧で、押しつぶされながらも、なんとか呼吸し、水面に脱出できました。

∈( 'Θ' )∋ .。○⚪︎

以上、ありがとうございました。