仮想化方案についてまとめた上でDockerを業務で利用するところまで考えてみる

# 経緯
- 社内のお仕事ではホスト型仮想化についてはしばしば利用されていますが，社内におけるコンテナ型の仮想化についての理解が浅いと危機感感じたので，メリットデメリットについてまとめて勉強会でもしてみようと思いました．
- コンテナ型仮想化，というかDockerに触れることでその強みと弱みを認識してもらいつつ，これまでのレガシーな方式と比較してみるきっかけしてもらえればと思って作りました．
- 自社の仕事のやり方が古かったり良くなかったりして「未だにそんなことやってるの」というご指摘があるかもしれませんが，それにすら気がつけていない可能性があるので，世の中の流れから取り残されることがないよう，そのような批判やご指摘を受けることも織り込んだ上，敢えて外に資料用意しました．
- 諸々間違いがありそうな場合はご指摘ください
- Apache + PHP + Postgres(MySQL)のような構成の業務Webアプリを作る仕事が多い前提

---

# 読んでほしい人
 - 開発環境を構築する機会が多い人
 - これからどのようなアプリケーションが組み上がるのかの情報無しでとりあえずインフラ構築を投げられた人
  - サーバやインフラ用意するにあたりOSやCPUやメモリ，ネットワーク帯域など色々検討しなければならないが，その答えがもらえない中で仕事をしなければならない状況のかわいそうな人 
 - 開発環境と本番環境の差異でハマったことがある人
 - 開発環境と本番環境の差異に辟易している人
 - インフラ面倒なのでアプリケーションの開発に注力したい人
  - アプリケーションを作りたいけどマシンのセットアップとかやりたくない・嫌いな人
 - 仕事を早く終わらせたい人
  - ソフトウェア資産などを再利用したい人 
 - コードのデプロイでファイル編集したりファイルをコピーしたりしている人
  - CI/CDとまで言わないでももう少しかっこよくしたい人 
 - これからDocker使ってみようかなと思っている人 

---

# 仮想化とは

>仮想化とは、ソフトウェアによって複数のハードウェアを統合し、自由なスペックでハードウェアを再現する技術で、限られた数量の物理リソース（CPU、メモリ、ハードディスク、ネットワーク等）を、実際の数量以上のリソース（論理リソース）が稼働しているかのように見せかけることです。
> 仮想化とは
> https://www.fsi.co.jp/solution/vmware/knowledge/virtualization.html

これによって仕事が楽になったり，余剰計算機リソースを有効に活用できるようになったり，システムの可用性が上がったり様々な良いことがあります

---

# 仮想化の種類

- ホスト型
- ハイパバイザ型
- コンテナ型

大きく3つの仮想化方案を例として挙げて，一般的なサーバとの比較とともにメリットデメリットをまとめる

---
## 仮想化しないという選択肢（一般的なサーバ）

ハードウェアに直接インストールされたOS上でアプリケーションが動作するもの

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310137/93fe5346-9ab9-12a9-ab77-65dc9a65bcd8.png)

- 利点
 - 単純明快アプリが動く
 - アプリケーションの稼働に際したオーバーヘッドが少ない

- 欠点
 - アプリケーション同士でシステムの設定を共有する場合などに悪影響が生じる場合もある
 - 計算機リソースが有効に活用できない（例えば1日に1回，1時間しか稼働しないアプリケーションでは，のこりの23時間は待機状態でサーバが起動することになる ）


---

## ホスト型仮想化

ハードウェアに直接インストールされたOSを上で動作する仮想環境上でゲストOSを稼働させる方式

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310137/ea5accb4-9d1a-236e-9a59-a73edd518591.png)

- 利点
 - 仮想化を利用していなかったサーバでも導入が容易
 
- 欠点
 - ゲストOS上のアプリケーションが稼働する際，ハードウェアを利用する場合などに，ゲストOSとホストOSを経由するため比較的オーバヘッドが大きい


- 製品やアプリケーション
 - VMware Workstation/Player/Fusion
 - VirtualBox
 - Hyper-V
 - Windows7以降のXPモードとか

---

## ハイパバイザ型仮想化
ハードウェアにハイパバイザと呼ばれる仮想環境管理ソフトウェアを稼働させる方式

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310137/239834ec-4330-f87d-c5f9-fe9f970600da.png)

- 利点
 - ホストOSを利用しないため，ハードウェアを直接ゲストOSにアタッチすることが可能
 - ゲストOS上のアプリケーションが稼働する際のオーバーヘッドがホスト型に比べて少ない
 - ハイパバイザでは，割と容易に仮想環境を管理できる機能が備えられていることが多い

- 欠点
 - サーバ上では既存のホストOSが利用できないため，ハイパバイザをインストールしたサーバを新たに構築する必要がある


- 製品やアプリケーション
 - Vmware ESXi
 - Xen
 - VMware vSphere
 - Hyper-V



---

## コンテナ型仮想化

ホストOS上にプロセスとして実行されるコンテナエンジンが稼働し，アプリケーションとしてコンテナを実行する
仮想マシンでなくプロセスを仮想化する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310137/5912b2cf-bab6-ad3c-fd44-016aa7a61782.png)

- 利点
 - 仮想環境をコンテナとして用意すれば良いので軽量
 - ゲストOSを介さないので起動が早い

- 欠点
 - 異なるホストOS向けのコンテナは利用できない（LinuxOS上でWindowsOS用のコンテナは動かない）
 - ハードウェアを利用する場合，ホストOSとの兼ね合いがあるので少しめんどくさい
 - OSをホストと共有する点で，仮想マシンを用いる方式よりも自由度は低くなる
 - アプリケーションのレイヤでプロセスとして稼働してくれるが，後述するDockerイメージが割と大きいのでホスト側のストレージが小さいと運用に苦心する．
 - 保存すべきファイルやデータはきちんと「永続化」しておかないとコンテナ削除した時にまとめて消える．

- 製品やアプリケーション
 - Docker


---
# Dockerを使ってみる

このように様々な仮想化の方式がありますが，今回はコンテナ型仮想化を用いているDockerを利用してWebアプリケーションの開発環境の構築などに活用する方法を紹介します

- 上で挙げた図のコンテナエンジンの部分がdockerサービスに当たる
- コンテナ仮想化はプロセスとして仮想化する方式
 - 「Dockerイメージ」と呼ばれるコンテナのテンプレートのを利用する
 - DockerHubのリポジトリから取得して利用できるほか，既存イメージをベースとして自身でイメージを作成することも可能です
 - その場合Dockerfileを利用してイメージ作成手順を記述します．これによってセットアップ手順をレシピ化することが可能です


イメージやDockerファイルを利用したコンテナ仮想化を志向することにより，

 - 誰が起動しても（概ね）同じ結果になる
 - 開発環境と本番環境をはじめからDockerを前提に構築しておけば差異が少ないためリリースの手間も少なくなる
 - 必要なときに開発環境のコンテナ起動すれば良い
  - 大きなインスタンスに開発環境を備えて待機しておくのではなく，リポジトリなどに保管しておいて所定の手順で都度起動・デプロイするようにすればインスタンスの運用費用も減る． 

といった利点が生まれてくることになります．
DockerイメージはOSレベルからアプリレベルまで様々な粒度で用意されており，利用者のニーズに合わせて構築が可能です．



---

## 例

- Webサーバを利用したい場合：
 - Apacheのイメージのみ利用する
 - OSのイメージを利用してDockerfileにApacheのインストール・セットアップ手順を記述する
　
- Webサーバに加えてPHPを利用したい場合：
 - Apache＋PHPのイメージを利用する
 - OSのイメージを利用してDockerfileにApacheとPHPのインストール・セットアップ手順を記述する

## Dockerの活用
- 開発環境も本番環境もはじめからDockerを用いて構築する前提としていれば全く同じ環境を構築しやすい
- Dockerfileにレシピ化してあれば，個別に環境構築する際などにサーバ設定の取りこぼしたりするのを防げる

---
## Dockerのインストールとサービスの起動

サーバ上（手元のサーバやVPSなど）にDockerをインストールする手順について紹介します

下記で詳しく解説されていますので抜粋して紹介します．

> Docker入門（第二回）～Dockerセットアップ、コンテナ起動～
> https://knowledge.sakura.ad.jp/13795/

### インストール
CentOSを前提にした場合，下記のコマンドによりインストールが可能です．

```console
# yum install -y yum-utils device-mapper-persistent-data lvm2
# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# yum install docker-ce
```

### サービスの起動
起動後，ステータスの確認と再起動時のサービス自動起動設定を併せて行っています

```console
# systemctl start docker
# systemctl status docker
# systemctl enable docker
```

※ちなみにDockerにはCE（コミュニティエディション）とEE（エンタープライズエディション）がある

> DockerとDocker-CEの違いについて
> https://qiita.com/s-suefusa/items/cb3c4044da3b3657dbd0

---

## イメージを取得してコンテナを起動する

Webサーバのイメージを取得して，イメージの起動を行ってみます．

### イメージの取得

NginxというWebサーバのイメージを取得してきて，イメージが追加されたことを下記の通り確認します．

```console
# docker pull nginx
# docker images
```

### コンテナの起動

取得したNginxイメージを使用してコンテナを起動します．
起動する際に，ホストとコンテナ側のポートの関連付けを行っており，下記の例ではホスト側8181番をコンテナ側80番に関連付けしています．

```console
# docker run -d --name nginx-container -p 80:80 nginx
```

### コンテナIDの確認とコンテナの停止

コマンド：`docker stop [CONTAINER ID]`

```console
# docker ps -a                                                                                                                                       
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
65ee0bf845dc        nginx               "/docker-entrypoint.…"   2 minutes ago       Up 2 minutes        0.0.0.0:8181->80/tcp   nginx-container

# docker stop 65ee0bf845dc

# docker ps -a  
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS               NAMES
65ee0bf845dc        nginx               "/docker-entrypoint.…"   3 minutes ago       Exited (0) 7 seconds ago                       nginx-container
```

### コンテナの削除

コマンド：`docker rmi [CONTAINER ID]`

```console
# docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                          PORTS               NAMES
65ee0bf845dc        nginx               "/docker-entrypoint.…"   4 minutes ago       Exited (0) About a minute ago                       nginx-container

# docker rm 65ee0bf845dc                                                                                                                             
65ee0bf845dc

# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
 
```

### イメージの削除

削除済みのコンテナで利用していたイメージはサーバ本体のストレージ上に残るので，これも削除してみます．

コマンド：`docker rmi [IMAGE ID]`

```console
# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               latest              f35646e83998        12 days ago         133MB

# docker rmi f35646e83998                                                                                                                            
Untagged: nginx:latest
Untagged: nginx@sha256:ed7f815851b5299f616220a63edac69a4cc200e7f536a56e421988da82e44ed8
Deleted: sha256:f35646e83998b844c3f067e5a2cff84cdf0967627031aeda3042d78996b68d35
Deleted: sha256:9ae13393c37dce86ebd3ea923033503f2cb8f4d6b28fb554827c518a2d171535
Deleted: sha256:423bc419c558f70051d849a661a7a287b61af2037c4ce24f7bbe433e9fb63f39
Deleted: sha256:4cd04e685e3a8e5697bb91e2e6c6b477bc8c4f9a43f05578af3c0a788f011756
Deleted: sha256:611e1562bc2f489d72961d8e2e37f3097d64d9c5212a68c26aab2ad971c98f6d
Deleted: sha256:d0fe97fa8b8cefdffcef1d62b65aba51a6c87b6679628a2b50fc6a7a579f764c

# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
```

### コンテナ内に入る
コマンド：`docker attach [CONTAINER ID]`

-dオプションとかDockerfileの記述方法によっては入れないかも

### Webサーバを見てみる

http://(サーバのIPアドレス)

にアクセスするとWebサーバが起動していることが確認できます．

## ホストOSとコンテナ間のファイル共有方法

ここまでイメージの取得や起動方法について述べましたが，Web開発においてはサーバを構築した後に表示するファイルやデータを配置する必要がありますが，その際にコンテナとホストでファイルの交換を行う方法について下記にまとめます．

### ホストOSのディレクトリをコンテナ側にマウントする方法

コンテナ起動時に「-v」オプションを指定することで，ホスト側のディレクトリをコンテナ側にマウントすることが可能です．

先程のnginxの例だと下記のように指定することでマウントできます

`docker run -v /host_path:/container_path -d --name nginx-container -p 80:80 nginx`


```console
# docker run -v /root/html:/usr/share/nginx/html -d --name nginx-container -p 80:80 nginx
```

予めhtml/index.htmlを作っておく

```html:index.html
<!DOCTYPE html>

<html>
<body>

<h1>title</h1>
<p>hoge</p>

</body>
</html>
```

※ちなみにnginxのドキュメントルートは`/usr/share/nginx/html`

これでホスト側のindex.htmlを編集するとコンテナ側のサーバで公開されるようになりました．


### SSH／SCPによる方法

ホストOSからはコンテナ名で名前解決ができるので，SSHによるログインを行うこともできます．


### サーバ建てる方法
sambaだとかFTPサーバなどをコンテナ内にセットアップすることファイル交換ができるようになる方案もあるような気がします．

色々ありますが，Webサーバの場合だとコンテナ構築時（あるいは構築後）にリポジトリからソース持ってきて配置するようなかたちでファイルの配置を人手で行うことなく，リリースのスクリプトも用意しておくと美しい気がします．

---

## 複数のコンテナを利用したい場合
複数のコンテナを利用する場合，個別にコンテナを起動するのは面倒なのでdocker-composeなどを利用することによりこの手間を省けます

> Docker Compose 概要
> Compose とは、複数のコンテナを定義し実行する Docker アプリケーションのためのツールです。Compose においては YAML ファイルを使ってアプリケーションサービスの設定を行います。コマンドを１つ実行するだけで、設定内容に基づいたアプリケーションサービスの生成、起動を行います。
> https://docs.docker.jp/compose/overview.html

Laravel+DockerであればLaradockなんかは全部乗っけで利用できますが，全部乗っかりすぎてて機能を把握するのが大変じゃないかなと思うところです．

---

## docker-composeのインストールと使い方

以下のコマンドでインストールできます．

※注意
>ダウンロードコマンド内での Compose 最新リリース番号の利用は上に示したコマンドは一つの例ですから、すでに古いリリース番号になっているかもしれません。 最新版であるかどうかは GitHub 上にある Compose リポジトリのリリースページ を確認してください。

> Releases ・ docker/compose
> https://github.com/docker/compose/releases

```console
# sudo curl -L https://github.com/docker/compose/releases/download/1.27.4/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
# sudo chmod +x /usr/local/bin/docker-compose
# docker-compose --version
docker-compose version 1.27.4, build 40524192
```

これで終わりです．簡単！

---

## docker-compose.ymlの記述例

docker-composeを利用すると複数のコンテナを用いる場合の構築作業や起動処理の手間が省けます．
docker-compose.ymlというファイルを作成してYAMLで複数のコンテナを用いる場合の設定を行います．

今回は先程dockerコマンドで起動したnginxコンテナ一つの場合の設定について記載します

以下の通りのディレクトリ構造を前提として進めていきます．

```console
任意のディレクトリ
├ docker-compose.yml
├ Dockerfile
└ /html
    └ index.html #Nginxのドキュメントルート
```

docker-compose.ymlは下記のようにYAMLで記述します

```yaml:docker-compose.yml
version: '3'   #おまじない
services:　　　
  nginx:　　　　#コンテナの名前
    build: .　　#このコンテナのDockerfileのある場所
    ports:
      - "80:80"　#ホストの80番をコンテナの80番ポートに紐付ける設定
    volumes:
      - ./html:/usr/share/nginx/html　#ドキュメントルートをホストにマウントする設定
```

Dockerfileではnginxイメージをベースにする記述を追加します

```dockerfile:Dockerfile
# base imgae（docker imagesコマンドで表示されたREPOSITORYを指定）
FROM  nginx:latest
```

ドキュメントルートとなる `./html` にはWebページに表示されるhtmlファイル `index.html` を配置します

---

## docker-composeを利用する場合のコンテナ起動・停止処置

### 起動
起動したいコンテナ群のdocker-compose.ymlファイルが有るディレクトリで下記のコマンドを実行する

```console
# docker-compose up
```

バックグラウンドで起動する場合は「-d」オプションを付与します．

```console
# docker-compose up -d
```

### 停止

```console
# docker-compose down
```

## 【参考】複数のコンテナを利用する場合のdocker-compose.ymlの設定方法


Webアプリケーション向けのApache+PHP+MySQLを用いる環境について，下記にdocker-compose.ymlの記述例を示します．（phpmyadmin付き）

docker-compose.ymlにはコンテナの種類やDockerfileのパス，コンテナ起動時の設定やオプションを記述することができますが，docker-compose.yml内で参照する場所にそれぞれのファイルを配置する必要があります．dockerfileなどのディレクトリ構成については下記リポジトリを参照してください．

> docker-apache-php-mariadb
> https://github.com/nc30mtd/docker-apache-php-mariadb

```yaml:docker-compose.yml
version: "3"
services:
  #Web 
  template-web:
    container_name: template-web
    build: ./setup
    privileged: true
    volumes:
      - ./html:/var/www/html
    ports:
      - 80:80
    depends_on:
      - template-db
    tty: true
    stdin_open: true

  # MySQL
  template-db:
    image: mariadb:latest
    restart: always
    container_name: template-db
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: templatedb(ここ気をつけて)
      MYSQL_USER: templatedbuser(ここ気をつけて)
      MYSQL_PASSWORD: templatedbpass(ここ気をつけて)
      MYSQL_START_TIMEOUT: 1200000
      TZ: 'Asia/Tokyo'
    volumes:
      - ./database/data:/var/lib/mysql
      - ./database/sql:/docker-entrypoint-initdb.d
      - ./database/log/mysql:/var/log/mysql
    ports:
      - 3306:3306

  # phpmyadmin
  template-phpmyadmin:
    image: phpmyadmin/phpmyadmin
    environment:
      PMA_ARBITRARY: 1
      PMA_HOST: template-db(ここ気をつけて)
      PMA_USER: templatedbuser(ここ気をつけて)
      PMA_PASSWORD: templatedbpass(ここ気をつけて)
      PMA_PORT: 3306
    links:
      - template-db
    ports:
      - 8080:80
    volumes:
      - ./phpMyAdmin/sessions:/sessions
    depends_on:
      - template-db
```



---

## docker-composeを利用する場合のベストプラクティス

Webサーバ＋PHP＋DBのよくあるWebアプリケーションを構成する場合

- 一つのコンテナに何でもかんでも全部詰めない
 - おそらくなんでも詰め始めるとDockerfileが複雑になるはず
 - あとから見ても良くわからない．再利用しにくい．
 
- 全部自分で作らない
 - 世の中には自分よりすごい人達が，より良いイメージやコンテナを作ってくれている
 - 巨人の方に乗れ
 
- せめてWeb（＋PHP）とDBくらいに分けてコンテナ作る
 - DBアプリのバージョンアップに際した検証などを行う場合，DBコンテナだけ新しいバージョンのDBアプリを稼働させてアプリ側だけメンテすれば良い
 - うまくプリミティブな処理の組み合わせで疎結合にシステムを構成することができると，開発が大変だが改修時などの手間が少ない．（デグレも少ないはず）
 - 戦場は多くても収束した後に戦線が広がりにくい（補給線が伸び切らない）

など，イメージやコンテナの組み合わせを考えるだけで比較的仕事がサクサク進む環境を組み上げることができる

---

# 出典・参考
https://www.fsi.co.jp/solution/vmware/knowledge/virtualization.html
https://qiita.com/r-tominaga/items/8ac588d603802572185f
https://qiita.com/Qiita/items/4ff5873776992f0511d6
https://docs.docker.jp/compose/overview.html
https://qiita.com/s-suefusa/items/cb3c4044da3b3657dbd0