Dockerでのローカル開発環境構築の入門

# dockerとは
dockerはコンテナ型の仮想環境を作成、配布、実行するためのプラットフォーム（仮想化方式）

## 仮想化には2種類

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2367608/90c7079a-75f5-035c-9541-39362afb9c6f.png)

出典：https://cn.teldevice.co.jp/column/10509/

### ホスト型仮想化（VMWare, VirtualBox）
- アプリケーションを実行するためにはまずゲストOSを起動させなければならず、CPUやメモリ、ディスクなどのリソースも多く消費する

- インフラ（データセンター）を仮想化ソフトウェアを使って細かく切り出し、webを通じてクラウド化してサービス提供している代表的なものがAWS（例:EC2など）


### コンテナ型仮想化（Docker）

- dockerはゲストOSを使わないことが大きな特徴。（ホストマシンのカーネルを使っている。docker enjineの上でコンテナを動かしている。）

- dockerは設定がコード化されているので作成した環境を容易に共有できるのが大きなメリット。

- コンテナはホストOSから見ると、カーネル部分をホストOSと共有するため、メモリなどリソース使用量が少ない


# dockerの主なコンポーネント

Dockerを理解するには、３つのコンポーネントの理解が必須。

- Docker イメージ（image）
- Docker レジストリ（registry）
- Docker コンテナ（container）

## Docker イメージ（image）
- Dockerイメージ（image）は、Dockerコンテナの実行に必要な概念としてのパッケージのことを指す。
- 環境構築時には、リモート（Docker レジストリ）に存在するimageを取ってきて、自分のdockerホスト内にimageを置く。

## Docker レジストリ（registry）
- Docker レジストリはリモートにある使用可能なimageを格納している場所。
- 代表的なregistryは``docker hub``と呼ばれるもの
- dockerレジストリからimageを自分のdockerホスト内に引っ張ってきて使用。
- docker hubには、自分で自作したimageを公開することも可能。（開発時は公式マークのついた公式のimageを使うことが多い）

**※Docker Hub とは....**
公式 Docker イメージを含む、様々な Docker イメージが公開・共有したり、共同作業（コラボレーション）するための場所。簡単に言うと、GitHubがソースコードを共有したり共同作業したりできるのと、同じような位置づけで、dockerを使っている人なら誰でも利用可能。

## Docker コンテナ（container）
- Dockerイメージ（image）を基に構築される実際のアプリケーションの実行環境
- アプリケーション開発時には複数のimageから構築された複数のコンテナを同時に起動してその環境で開発（後に紹介する[docker-compose](#docker-compose)が代表例）

- コンテナが起動した状態では、基本的には何も意識せずにローカルで普通に開発ができる

# dockerの基本的な使い方
Dockerイメージを使う方法は、以下の２種類。

- Docker Hub (  https://hub.docker.com/  )等に代表されるDocker レジストリ（registry）から ``$ docker pull``コマンドでpullする
-  dockerfileを作成して、自分でimageを作成（``$ docker build``）する

アプリケーション開発時には、これらを組み合わせて環境を作る。

## imageをpullで取得してコンテナを起動する方法

Docker Hubにあるnginxリポジトリからコンテナイメージを取得するのは、``$ docker pull <REPOSITORY>`` コマンドで行う。

```bash
$ docker pull nginx                                                                                            
Using default tag: latest
latest: Pulling from library/nginx
a076a628af6f: Already exists
0732ab25fa22: Pull complete
d7f36f6fe38f: Pull complete
f72584a26f32: Pull complete
7125e4df9063: Pull complete
Digest: sha256:10b8cc432d56da8b61b070f4c7d2543a9ed17c2b23010b43af434fd40e2ca4aa
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest
```

再度イメージの確認をするとREPOSITORYが"nginx"という名前のイメージが取得できている。


```bash

$ docker images
REPOSITORY                         TAG       IMAGE ID       CREATED       SIZE
nginx                              latest    f6d0b4767a6c   2 weeks ago   133MB


```

このコマンドは実行したタイミングでhttps://hub.docker.com/_/nginx にある最新のものを取得している。

### コンテナの起動
コマンドは以下。

```bash
$ docker container run <image名>
```

## docker buildコマンドにてimageをカスタマイズする場合

dcokerfileに書かれた設定の通りにimageをbuildします。

```bash
$ docker image build
```

# dockerの基本構成

こちらの図は Railsのモノリシックなアプリケーションの運用を前提にしたローカルでのdockerの基本構成。[docker-compose](#docker-compose)と呼ばれるものを使って構築。

<img width="1800" alt="image.png (255.1 kB)" src="https://img.esa.io/uploads/production/attachments/10655/2021/03/12/78280/72a5318d-8a39-4dbd-932d-4f688b5faad9.png">


## 基本的な動作・手順($ docker-compose up)
1.Dockerをインストール
2. アプリケーションのソースコード配下に、dockerfile、docker-compose.ymlを用意
3. ローカルでdocker-compose upコマンドを実行
4. コマンドを実行すると、まずdocker-compose.ymlの設定を見にいく
5. docker-compose内のオプションでimageが指定されていれば、imageをdocker hubからpullしてくる。
6. オプションにbuildが入っていれば、dockerfileの設定を見にいき、設定通りカスタマイズしてimageを作成する。
7. imageが作成されたらそのままimageを元にコンテナを作成・起動する。
8. 完了するとアプリケーションの実行環境が準備できている。
9. 普通にローカル上のファイルを編集して開発していくと、ホストからの書き込みがコンテナ内に自動で反映される（逆も然りでコンテナ内でのソースコードの変更は即時ホスト側に反映させることも可能）。docker-compose.ymlに設定を書くことでこの自動反映を実現。

## dockerのネットワーク

<img width="1500" alt="image.png (196.0 kB)" src="https://img.esa.io/uploads/production/attachments/10655/2021/03/12/78280/d959c048-e023-4ec5-9368-d471f81c79ba.png">

port指定・・・順番にホスト側:コンテナ側で書く

## docker-compose

- 一つ一つimageを作成し、image一つ一つに対して、コンテナを作成・起動する方法だと、実際のアプリケーションの開発時はかなり面倒

- そこで、複数のimageをいっぺんにbuildし、それを元に一気にコンテナを作成・起動ができるのが「docker-compose」

- 設定ファイルをアプリ配下に作成した後、基本的にはコマンドを1回実行するだけで、一発で環境が構築できる

```bash
$ docker-compose up
```
docker-compose upコマンドは、imageの作成と、作成したimageからコンテナの作成・起動を全て一気に実行できるコマンドです。

## 設定ファイルの作成例
公式: https://docs.docker.com/compose/rails/
### Dockerfile
最低限の設定。各自それぞれ細かいところの設定は違うと思うので、あくまでご参考に。

```Dockerfile
# 公式:https://docs.docker.com/compose/rails/
# rubyのimageを指定
FROM ruby:2.5.3
# node関連のライブラリーをinstall
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - \
    && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
    && apt-get update -qq \
    && apt-get install -y nodejs yarn \
    && mkdir /myapp
# docker内での作業ディレクトリを指定
WORKDIR /myapp
RUN gem install bundler
# ライブラリ関係のファイルをコピー
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
COPY package.json /myapp/package.json
COPY yarn.lock /myapp/yarn.lock
# ライブラリのインストール※ここでbundle installしておけばdocker compose upでコンテナ立ち上げ時に生成物のディレクトリをボリュームマウントすることができる
RUN bundle config set --local path 'vendor/bundle' && \
    bundle install
# ライブラリのインストール※ここでbundle installしておけばdocker compose upでコンテナ立ち上げ時に生成物のディレクトリをボリュームマウントすることができる
RUN yarn install
# docker内の作業ディレクトリへローカルのファイルをコピー
COPY . /myapp
# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000
# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]

```


### docker-compose.yml
最低限の設定。各自それぞれ細かいところの設定は違うと思うので、あくまでご参考に。
DBはmysql

```docker-compose.yml
# 公式:https://docs.docker.com/compose/rails/
version: '3'
services:
  db: # DBコンテナ
    image: mysql:5.7 # docker hubからpullしてくるデータベースイメージ
    command: mysqld --character-set-server=utf8 --collation-server=utf8_general_ci
    volumes:
      - mysql_data:/var/lib/mysql # volumeにマウントして永続化
    environment:
      MYSQL_ROOT_USER: root # docker内ではrootで接続
      MYSQL_ROOT_PASSWORD: password
    ports:
      - "4306:3306" # DBクライアントツールを使う場合はポートに4306を指定
  web: # webコンテナ
    build: . # dockerfileを参照しimageをbuild
    environment: # webコンテナ内で使う環境変数
      REDIS_URL: "redis://redis:6379"
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" # pumaでサーバー起動
    volumes:
      - .:/myapp:cached # ホストからの書き込みをコンテナ内に反映
      - bundle:/myapp/vendor/bundle # gemをvolumeで管理する：docker-compose exec web bundle installでgemを管理
      - /myapp/tmp # volumeにマウント（webコンテナのみで使う）
      - node_modules:/myapp/node_modules # volumeにマウントして永続化
    ports:
      - "3000:3000"
    depends_on:
      - db
      - redis
    stdin_open: true # binding.pryを動かすための記述
    tty: true # binding.pryを動かすための記述
  redis: # redisコンテナ
    image: redis:latest # docker hubからpullしてくるredisのイメージ
    ports:
      - 6379:6379
    volumes:
      - redis:/data # volumeにマウントして永続化
volumes: # 永続化
  mysql_data:
  redis:
  bundle: # gemをvolumeで管理する：docker-compose exec web bundle installでgemを管理
  node_modules:
```

# ボリュームやマウントなど
アプリケーションの実行はコンテナで行うが、ソースコードの編集などはエディターなどを使ってホスト側（ローカル）で行う前提でボリュームの便利さを解説。

# ボリュームとは
公式：
https://matsuand.github.io/docs.docker.jp.onthefly/storage/volumes/
https://matsuand.github.io/docs.docker.jp.onthefly/storage/

- ボリュームは、コンテナのライフサイクルとは独立した、永続データを保管するディレクトリのこと。
- コンテナを削除しても永続化されていて、ボリュームを指定して明確に削除しない限りいつでも参照可能で、docker上のボリュームのデータはリアルタイムで更新される。
- ボリュームとして保存することを設定したディレクトはボリュームに保存されるので、コンテナ側からホスト側に同期するファイルの対象からは除外することができるため、dockerコンテナ内だけに存在してほしいディレクトリなどは名前付きボリュームに指定することでホストとコンテナ間の同期対象（ファイル共有対象）から除外してコンテナからホストへの同期書き込みを効率化するという裏技も使える。（そうすると、docker内でコマンド実行（$ docker compose runなど）して生成したファイルのうち、名前付きボリュームに指定したものがホスト側に同期されず、ローカルではファイルの存在を確認できなくなる）


<img width="1844" alt="image.png (150.6 kB)" src="https://img.esa.io/uploads/production/attachments/10655/2021/03/07/78280/cd0eaaf2-d2d3-4190-b9eb-2b37540abf66.png">

## マウントとは
- 「ボリュームをコンテナにマウントする」とは、コンテナの外にあるボリュームのデータをコンテナから参照できるようにすることを指している。（docker-compose.ymlに設定を書くだけでできる）

- PCの例で言うと、記憶装置（外付けHDD）をPCに接続するとHDDにあるファイルを参照できるようになるが、裏側ではPC上にデータを参照するためのファイルが作られて（マウントしている）、OSからアクセス（参照）できるようになる。それと同じ原理。

### マウントの設定（データの永続化）
主によく使う3パターン。
その中でも、データを永続化する方法には大きく分けると、
``ディレクトリをマウントして利用する方法（ローカル上のソースコードを同期する。バインドマウント)``と
``ボリュームをマウントして利用する方法（ボリュームマウント）``
の2つ。

#### 1.ディレクトリをマウント（同期）する方法（バインドマウント）
##### dockerコンテナにホストをマウント（ホストマシンでのソースコードの変更をdockerコンテナに同期するための設定）

- こちらはバインドマウントと呼ばれる方法で、ホスト側のディレクトリをコンテナ内のディレクトリと共有している。
- ホスト側のカレントディレクトリのpathを左側に指定してるいる点がこの後のボリュームマウントと違う点。
- 逆にコンテナ側のソースコードをvi等で修正して場合もホスト側のソースに反映されていることが確認できると思うので、ホスト/コンテナの双方向の動機を行っており、バインドマウントしているパスについては常に同期される状態となる。

``[ホスト側パス]:[コンテナ側パス]``

```docker-compose.yml
web:
  volumes:
    - .:/myapp:cached # ホストからの書き込みをコンテナ内に反映
```

#### 2.ボリュームを利用する方法（ボリュームマウント）
##### dockerコンテナにボリュームをマウント（ボリューム名つき）

Docker の管理下でストレージ領域を確保してボリュームを作り、dockerコンテナ内から参照する方法。

``[ボリューム名]:コンテナ側パス``

```（例）docker-compose.yml

web:
  volumes:
    - node_module:/myapp/node_modules  // [ボリューム名]:コンテナ側パスの形になっている

volumes:
  node_module:
```

- 名前付きでdockerのボリュームゾーンにボリュームを作ることが可能となる。
- 名前付きでボリュームが作られるが、コンテナ上で何かコマンドを打ったりアプリを動かす場合には、dockerコンテナが勝手にこのボリュームを参照する（名前を指定して明示的にボリュームを参照するようにする必要はない）ので、コンテナ内で作業をする場合は特に意識することはない。
- 他のサービス（コンテナ）からそのボリュームを参照したい場合は、ボリューム名を指定することでマウントすることも可能。

```docker-compose.yml
volumes:
  - node_modules:/myapp/node_modules # volumeにマウントして永続化
```

##### dockerコンテナにボリュームをマウント（無名）
``[コンテナ側パス]``
名前なしでボリュームゾーンにボリュームを作る。（ユニークなIDが自動でつく）このサービス（コンテナ）内でしか使えない。（例:webコンテナ内のみで使える）

```docker-compose.yml
volumes:
  - /myapp/tmp # volumeにマウント（webコンテナのみで使う）
```

ボリュームに保存することでコンテナを削除してもデータは保持されるので、毎回bundle installしなおしたり、DBセットアップをしなおしたりする必要がなくなる。
ただ、ボリュームマウントをしていなければ、ボリュームは作成されなくなるため、バインドマウントでホスト/コンテナ間を双方向に同期していれば、コンテナを削除してもホスト側のソースは残っているので、ボリュームを作成していないからといって、毎回bundle installをしないといけないわけではない。そういう意味ではボリュームにnode_modulesやvendor/bundle配下をボリュームにする行為はコンテナからホストへの同期のための書き込みの負荷を減らす行為だと考えることができるので、node_modulesなどの直接ソースを編集しないが、容量が大きいファイル群はボリュームにしておくと良い。
##### docker run -vとdockerfileのVOLUMEの違い

docker-composeを使わない場合のマウントの仕方であるが、こちらの記事が参考になったので掲載しておく

https://karuta-kayituka.hatenablog.com/entry/2019/05/04/114056

##### ボリュームの確認

- typeがbindの場合は、Sourceにホスト側が指定されているし、typeがvolumeの場合は、Sourceにボリューム側のpathが指定されている。
- Destinationにはdockerコンテナ側が指定されてマウントされていることを以下のコマンドで確認できる
- バインドマウントの場合は、ホスト側を編集するとdocker側にも修正が反映されていることが確認できるし、逆にコンテナ側のソースコードを修正するとホスト側のソースコードにも反映されることが確認できる
- ボリュームマウントの場合は、docker側のコードをボリュームとして作成している場合はdocker側のコードが変わればボリューム側も自動で更新され、常に最新の状態がボリュームに保存されることになる

```bash
$ docker container ls
$ docker container inspect {CONTAINER ID}
"Mounts": [
            {
                "Type": "bind",
                "Source": "/host_mnt/Users/kenkentarou/ahms_app",
                "Destination": "/app",
                "Mode": "delegated",
                "RW": true,
                "Propagation": "rprivate"
            },
            {
                "Type": "volume",
                "Name": "f84401f3a5c73ab99ca496f812c01edfc95b3c6609bced14763437676376475c64",
                "Source": "/var/lib/docker/volumes/f84401f3a5c73ab99ca496f812c01edfc95b637246286482144da63835cfd35c64/_data",
                "Destination": "/app/tmp",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            },


```

## ボリュームの参照・削除

```bash
$ docker volume ls
$ docker volume inspect {ボリューム名}
[
    {
        "CreatedAt": "2021-07-20T11:28:33Z",
        "Driver": "local",
        "Labels": {
            "com.docker.compose.project": "app",
            "com.docker.compose.version": "1.29.2",
            "com.docker.compose.volume": "gem_data"
        },
        "Mountpoint": "/var/lib/docker/volumes/app_gem_data/_data",
        "Name": "app_gem_data",
        "Options": null,
        "Scope": "local"
    }
]
```

ただ、このmountpointのpathをmacのターミナル上から確認しても存在は確認できない。Docker for MacのDocker EngineはVM(Virtual Machine)の上で動いているので、MountpointはローカルのMacのPATHじゃなくて、VM上のPATHにあるためである。
ググると以下のコマンドでVM上で動いているdocker enjineに入ることができるよう

```bash
$ docker run -it --privileged --pid=host debian nsenter -t 1 -m -u -n -i sh
```
入って、lsコマンドなどで見ると確認できる。


※EC2上でdockerを動かせばボリュームはホスト上のpathでlsコマンドなどで存在はシンプルに確認できる

```bash
$ docker-compose rm -v サービス名
サービス名を指定しなかったらdocker-compose.ymlに書かれているものすべて消去
```


```bash
$ docker volume rm -f [volume名]
// volumeを指定して削除
```


https://www.memotansu.jp/docker/454/#toc3
https://qiita.com/kompiro/items/7474b2ca6efeeb0df80f


# コマンドリファレンスまとめ
参考: https://docs.docker.jp/engine/reference/commandline/toc.html
## docker-composeを使った一般的な環境構築

### コンテナの作成・起動

```bash
$ docker-compose up -d
// 初回のimgae構築時は時間がかかる（5〜10分）
// バックグラウンドで起動する場合は-dオプション
// うまくいかない時はまずdocker for macの「Restart Docker」(dockerの再起動)を行う
```

### キャッシュを使用しせずにimageをbuild

```bash
$ docker-compose build --no-cache
```


### ログを見る場合（バックグラウンドでコンテナを起動している場合）

```bash
$ docker-compose logs
```

### binding.pryを使える状態でサーバーを起動

```bash
$ docker-compose up -d
// バックグラウンドで起動

$ docker-compose ps
// Railsが動いているコンテナのIDもしくはコンテナ名を調べる

$ docker attach コンテナのIDもしくはコンテナ名_1
// コマンド実行後は何も起こらないがアクセスするとログが流れてくるようになる
// attachすると、binding.pryが記述している場所に来るとデバッグモードが起動する
```

### ローカルからコンテナに対してコマンドを実行する

```bash
$ docker-compose run {サービス名(コンテナ)} {実行したいコマンド}
// コンテナを新しく作って実行するコマンド
// runの後ろに--rmをつけるとコンテナを停止した段階でコマンド実行プロセスを削除するような挙動も可能 
 
$ docker-compose exec {サービス名(コンテナ)} {実行したいコマンド}
// 起動しているコンテナに対して実行するコマンド（新しくコンテナを作りなおさず実行）   
```

### webコンテナの中に入る
```bash
$ docker-compose exec web /bin/bash
// dbコンテナに入る場合はwebをdbに変更
```

### コンテナを停止

```bash
Ctrl+C
```
or

```bash
$ docker-compose stop
```


### コンテナとネットワークを停止+削除

```bash
$ docker-compose down
```

### コンテナとネットワークに加えてvolumeも削除

```bash
docker-compose down -v
```

### dcoker上に存在する全てのdocker imageを削除する
```bash
$ docker rmi $(docker images -q)

// docker imagesコマンドはローカルのホスト上にあるイメージの一覧を表示するコマンド。
```

### そのプロジェクトのdocker imageを全削除する

```bash
$ docker-compose down --rmi all
```

### そのプロジェクトのイメージもvolumeも全削除

```bash
docker-compose down --rmi all -v
```

### 起動中のコンテナの一覧を見る

```bash
$ docker-compose ps
```

### ユーザが作成した全てのネットワークを一覧表示

```bash
$ docker network ls

// Docker エンジンの daemon が把握している全てのネットワーク一覧を表示
```

### ネットワークの情報を表示

```bash
$ docker network inspect [オプション] ネットワーク [ネットワーク..]
```

コンテナのIDを指定することでコンテナのIPアドレスや所属するネットワークのIPアドレスも見れる

```bash
$ docker container ls
$ docker container inspect {containerのID}
```

### マウントの確認

```bash
$ docker inspect $(docker-compose ps -q <コンテナ名>)
```

### imageの一覧を確認
```bash
$ docker images
```

### 未使用のvolumeを一括削除

```bash
$ docker volume prune
```
### 未使用のimageを一括削除

```bash
$ docker image prune
``` 
### 未使用のcontainerを一括削除

```bash
$ docker container prune
```
### 未使用のnetworkを一括削除

```bash
$ docker network prune
```


# 参考
https://docs.docker.jp/
http://docs.docker.jp/v1.9/docker-hub/official_repos.html
https://matsuand.github.io/docs.docker.jp.onthefly/docker-hub/official_images/
https://www.docker.com/products/docker-desktop#/tutorials