Docker超入門編(インストールから開発環境構築まで)

# 環境
Ubuntu 20.04 LTS(WSL2)

# はじめに
本記事はdocker入門者向けに記載しました。なので、そこは「docker compose」
で一括で設定できるよ、など色々あると思いますがそういうことを理解している方は飛ばし飛ばしでみていただけたらと思います。
また間違っている点などあればご指摘いただければ幸いです。

# Dockerとは
Dockerは、コンテナ仮想化を用いてアプリケーションを開発・配置・実行するためのオープンプラットフォームである。

Dockerはコンテナ仮想化を用いたOSレベルの仮想化によりアプリケーションを開発・実行環境から隔離し、アプリケーションの素早い提供を可能にする。かつその環境自体をアプリケーションと同じようにコード（イメージ）として管理可能にする。Dockerを開発・テスト・デプロイに用いることで「コードを書く」と「コードが製品として実行される」間の時間的ギャップを大きく短縮できる(wiki参照)。

つまり一言で表すならば、「使い捨て可能な仮想環境」ということだろうか

Dockerを使う上で確実に理解しておきたいのが「イメージ」と「コンテナ」の概念である。以下にその説明をする

## コンテナとイメージ
***コンテナ***
コンテナとはアプリケーションを実行する分離された環境のことである。つまりホストの環境とは異なった環境であるという認識であれば問題ないと思う。

***イメージ***
イメージとは、コンテナを作成するための指示が記載された読み取り専用のテンプレートのことである。つまりコンテナを作成するためのものだという認識であれば問題ないと思う。

# Dockerのインストール
https://www.docker.com/products/docker-desktop/

インストールできたら、ターミナルでまずはdockerを使って"hello world"を出力してみよう

```
$> docker run Ubuntu-20.04 echo hello world
```
これで"hello world"が出力されれば成功である。
これはUbuntu-20.04の仮想環境を作り、その中で「echo hello world」を入力しているのと同じことをしている。

ここで使った仮想環境は「イメージ」として残るため、例えば
```
docker run -it Ubuntu-20.04 bash
```
としてやると、コンテナとして起動し、仮想環境内の操作を行える。runコマンドはイメージからコンテナ化する際に用いる。
オプションの「-it」はbashなどの対話モードの際に用いるもので例えば他の例でいうと
```
docker run Ubuntu-20.04 ls
```
としてUbuntu-20.04内のディレクトリ構成をみることなども可能。

先ほどのbashを用いて、このコンテナ内で
```
apt install ruby
```
を行うと、rubyのインストールができる。

ただ最初にも述べたようにDockerとは使い捨ての仮想環境であるため、上の例でいうと、コンテナを止めると中でインストールしたrubyもなくなってしまう。

そこで使用するのが***Dockerfile***である。

# Dockerfileからイメージの作成

```Dockerfile:Dockerfile
# ベースイメージ
FROM ruby:2.7.6

#カレントディレクリにあるGemfileをコピーしている
COPY Gemfile .
# RUNは実行したいコマンド
RUN apt update
# オプションのyはターミナルでYes/Noを聞かれた時に自動的にYesで返す
RUN apt install -y build-essential libmariadb-dev
RUN bundle install

```
上の例はrubyやbuild-essentialをインストールして、Gemfileの中身からbundle installを行う一連の処理が記述されている。

buildコマンドを使って以下のようにすればDockerfileからイメージの作成が行える。
```
$> docker build -t [イメージ名]:[タグ名] .
```

# コンテナのボリューム
ここまでこれば、イメージの作成とコンテナ内でのプログラムの動かし方が分かったと思う。
ただ、これまでの内容で例えばrubyで書いたファイルを実行したい場合、viなどでrubyファイルを作成する必要があった。
なのでここではホストのファイル構成をボリューム(マウント)してコンテナに渡す方法を記載する。

```
$> docker run -it -v $PWD:/opt/myapp -w /opt/myapp [イメージ名]:[タグ名] bash
```

上記のコマンドについて細かく説明していく。
```
-v $PWD:/opt/myapp
→現在のディレクトリをコンテナ内の/opt/myappディレクトリにマウントする

-w /opt/myapp
→ワークスペースを/opt/myappにする。(デフォルトで/opt/myappにくるようにする)
```

これでコンテナ内にはいってみると/opt/myapp内に、docker runを行ったディレクトリのファイルが入っている。
また、ホスト環境で編集したものがコンテナ内にも反映されているのが確認できると思う。

# ポートのマッピング
ホストとコンテナは別の環境かのようにネットワークが分離されている。
なので、localhostでアクセスできでもマッピングしていないとコンテナ上の環境にアクセスできない。したがってコンテナを起動する際にポートを指定してやる必要がある。

ここではlocalでポート番号4567とコンテナのポート番号4567をつなげてみる。
```
docker run -v $PWD/sinatra:/opt/myapp -w /opt/myapp -d -p 4567:4567 [イメージ名]:[タグ名] ruby mya
pp.rb
```

上記は「myapp.rb」というAPIをコンテナ内で実行する処理を記述している。
ここでつけているオプションの「-d」はコンテナ起動中に他のコマンドをうつようにできるものである。

ちなみに起動中のコンテナにアクセスするには
```
docker exec -it [コンテナのID]or[コンテナ名] bash
```

話は戻ってポートのマッピングの話だが、このままだとコンテナ内のアプリケーションサーバーのlocal adress:portを見ればわかるように、127.0.0.1:4567となり、自分自身のアクセスしか受け入れないようになっている(コンテナ内でlocalhostにアクセスはできるが、ホストなどのブラウザからは確認できない状態)。
なので

```
docker run -v $PWD/sinatra:/opt/myapp -w /opt/myapp -d -p 4567:4567 [イメージ名]:[タグ名] ruby myapp.rb -o 0.0.0.0
```

イメージをコンテナ化する際に、「-o 0.0.0.0」とlocal IPアドレスを0.0.0.0とすることで、どのからのアクセスを受け入れるようにすればよい。

# dockerhubからのイメージの取得
今回はMySQLを例にdockerhubに挙げられている公式のイメージの取得についてやってみる。

https://hub.docker.com/_/mysql

公式のページに書いてある通りの手順で行えばよい。なので
```
docker run --name my-db -e MYSQL_ROOT_PASSWORD=rootpassword -d --platform linux/x86_64 mysql:8.0.29
```
としてやれば、「my-db」という名前で、環境変数「MYSQL_ROOT_PASSWORD」が「rootpassword」のMySQLのイメージが取得できる。

## コンテナ起動時のMySQLの処理
アプリケーションを立ち上げた時の初期のデータベースやテーブルが欲しい、などコンテナ作成時にDBを設定したいケースについて記述する。

上記の公式のページの「Initializing a fresh instance」という項目によると
「/docker-entrypoint-initdb.d」というディレクトリ内にある.sqlファイルの処理が行われる。

```
$> docker run --name my-db -e MYSQL_ROOT_PASSWORD=rootpassword -d --platform linux/x86_64 -v $PWD/mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d mysql:8.0.29
```

上記の例について説明する。
まず「カレントディレクトリ/mysql/docker-entrypoint-initdb.d」以下に.sqlファイルを作り、行いたい操作を記述する。
そして上記のコマンドを実行することでホストの「$PWD/mysql/docker-entrypoint-initdb.d」以下のファイルがコンテナの「/docker-entrypoint-initdb.d」に置かれ、コンテナ作成時にsqlの処理が走る、という流れになっている。

# ネットワークの設定
上の二つの例では、rubyのAPIサーバとMySQLの二つのコンテナについて説明したが、dockerではこれら二つのコンテナを同一のネットワークで使うためには
ネットワークの設定をしてやる必要がある。

```
$> docker network create [ネットワーク名]
```

```
docker run -v $PWD/sinatra:/opt/myapp -w /opt/myapp -d -p 4567:4567 --net [ネットワーク名] [イメージ名]:[タグ名] ruby myapp.rb -o 0.0.0.0
```

```
docker run --name my-db -e MYSQL_ROOT_PASSWORD=rootpassword -d --platform linux/x86_64 -v $PWD/mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d --net [ネットワーク名] mysql:8.0.29
```

このようにイメージ名の前に「--net my-net」と、先ほど作成したネットワークを紐づけてあげる。

# docker composeについて
ここまでコンテナを起動するのにrunコマンドを使ってきた。
ただrunコマンドで環境構築する場合、たくさんのオプションをつけたコマンドをいくつも実行する必要がある。
そこでdocker composeを用いると楽になる。

```yaml:docker-compose.yaml
version: '3'

services:
  sinatra:
    build: ./sinatra
    command: ruby myapp.rb -o 0.0.0.0
    ports:
      - 4567:4567
    volumes:
      - ./sinatra:/opt/myapp
    working_dir: /opt/myapp
    depends_on:
      - mysql

  mysql:
    image: mysql:8.0.29
    platform: linux/x86_64
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
    volumes:
      - ./mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
      - ./mysql/conf.d/charset.cnf:/etc/mysql/conf.d/charset.cnf

```

これは今までrunコマンドで行ってきたことをYAML形式で書いただけである。
もう少し詳しく説明すると
| コマンド | 説明 |
| --| --|
|build| 配下にあるDockerfileからイメージの作成|
|image|どのイメージを使うかの指定|
|command|コンテナ内で実行するコマンド|
|ports|上記のポートのマッピング|
|volumes|上記のボリュームの話|
|working_dir|ワークスペースの指定|
|depends_on|コンテナの起動順の指定|

また、docker composeを使うメリットとして、同一のcompose内ではネットワークの設定をしなくてよいということがある！

## docker composeでのコンテナ起動、削除
```terminal
# 起動
$> docker-compose up

# 削除
$> docker-compose down
```

また注意点としてdocker composeを使うと、それぞれのコンテナはdocker-compose.yamlファイルで指定したサービス名でアクセスできるようになる。

## Dockerfileを書き換えた時の注意点
docker-compose upではイメージの再buildしてくれない。
なので

```terminal
$> docker-compose build
```
でbuildしてあげる必要がある。
その後、再び

```terminal
$> docker-compose up
```
してあげる必要がある。

# おまけ

```
# イメージ一覧の取得
docker images

# dockerのコンテナ一覧
docker ps

# 停止しているのも含めてdockerのコンテナ一覧
docker ps -a

# dockerでのログの確認
docker logs [コンテナID]or[コンテナ名]

# dockerでのログの確認(更新も含む)
docker logs -f [コンテナID]or[コンテナ名]
```

# 最後に
ここまで理解できればdockerのインストールから、最低限コンテナで開発を行えるようになったのかなと思います。
dockerの理解にはlinuxやネットワークの知識も必須になると思うので、そこらへんから勉強するのも良いと思います。
私も勉強不足なのでそこから勉強してきます💦

# 参考文献
https://www.udemy.com/course/docker-from-linux-and-networking/?utm_source=adwords&utm_medium=udemyads&utm_campaign=DSA_WebIndex_la.JA_cc.JP&utm_content=deal4584&utm_term=_._ag_89101504074_._ad_439843892593_._de_c_._dm__._pl__._ti_dsa-454026085647_._li_1009250_._pd__._&gclid=CjwKCAjwjMiiBhA4EiwAZe6jQ-jOwYDfKm2GVmVIi1iuayncu4bNUGfTjBBzu-_0jUUpGvMREk9PUBoCwFkQAvD_BwE