Dockerについて学んだこと

# Dockerについて学んだこと
### ■初めに
Dockerに関して勉強を行っており、学んだ内容をまとめておきたいと思い、この記事を作成しています。
初学者向けの内容となりますのでご容赦ください。

### ■Dockerとは
Dockerを用いることでコードを早く正確にユーザに届けられるようになります。

従来の開発から本番導入までの流れは、
1.Developperがコードを書く
2.インフラエンジニア：本番環境構築
のようになるかと思います。
このような手順の場合、環境起因により、開発・テスト環境では動くが、本番環境では動かないということが起こりえます。

Dockerでは開発チームがコードとライブラリをパッケージ化するため、インフラエンジニアに渡すことができるため、環境差異が発生しないメリットがあります。

##### ▶仮想化との違い
従来の技術として、仮想化がありますが、これとDockerとの違いとしては下記のようになる。
仮想化：1つのOS上に複数の仮想OSを置き、その上でアプリケーションを動かす。
Docker：1つのOSにDocker engineを起動している複数のコンテナを起動する。

仮想化に対して、Dockerのメリットとしては下記のようなものが挙げられる。
・リソースが軽い
　→OS使わないので、OSイメージとカーネル分が減り、プロセッサ及びメモリの消費が少ない。
・ストレージの使用量が減る
　→OSイメージの通常サイズは10GB程度なのに対し、Docker Imageのサイズは1~2GB程度
・起動時間短い
　→カーネルをロードする必要がない
・複数環境での運用が楽
　→Dockerが起動されていればどの環境でも使える。

##### ▶イメージとコンテナの違い
Dockerの勉強をしていると、イメージやコンテナという言葉が出てきますが、これらの違いとしては、イメージがテンプレート、コンテナがテンプレートから作成された実物となります。

##### ▶Docker環境
勉強用の作業環境を取得するにはローカル環境でDockerをインストールする方法とオンライン上のプレイグラウンドを用いる方法があります。
ローカル環境にインストールする場合には下記のページよりダウンロードを行う必要があります。
・[Dockerインストール](https://matsuand.github.io/docs.docker.jp.onthefly/get-docker/)

オンライン上のプレイグラウンドに関しては下記のようなものが挙げられます。
・[Play With Docker](https://labs.play-with-docker.com/)

### ■Dockerでの作業フロー
コンテナ起動までの流れとしては大雑把には下記のようになります。
1.コードを書く。
2.Dockerイメージをビルドする。
3.リポジトリにイメージをPushする。
4.本番環境でイメージをダウンロードする
5.コンテナを起動する。
##### ▶docker pull
まずは2までは既に作成したものを用いる方法としてdocker pullコマンドに関して見ていきます。
DockerHubなどにイメージがアップロードされているのでこれをダウンロードします。代表的なものとしてDockerHubを挙げましたが他にもイメージがアップされているレジストリはあるようです。

イメージをダウンロードするためのコマンドは下記のような形式になります。
```
docker pull [Image名]

=====nginxをpullする場合=====
$docker pull nginx
```
また、nginxの後ろにタグをつけてVersionを指定することも可能です。何も指定しない場合には最新版(latest)が指定されます。
```
$docker pull nginx:
```

##### ▶docker images,docker rmi
取得および作成したイメージの一覧は下記のコマンドにて確認できます。
```
$docker images

#####実行結果#####
REPOSITORY                    TAG       IMAGE ID       CREATED       SIZE
nginx                         latest    41b0e86104ba   5 days ago    142MB
```
不要となったイメージは下記のコマンドで削除を行うことができます。
```
docker rmi [Image ID|Image名]

=====nginxをpullする場合=====
$docker rmi nginx

=====Image確認=====
$docker images
#####実行結果#####
REPOSITORY                    TAG       IMAGE ID       CREATED       SIZE
```
この結果を見ることで確かにnginxのイメージが削除されていることがわかります。

##### ▶docker run
Docker Imageからコンテナを起動するには下記のコマンドを実行します。
```
docker run [Image名] [オプション等]
```
特にオプション等をつけなくても起動させることはできますが、より便利に使うために下記のようなオプションが用意されています。ここに記載したものはよく用いるオプションのみですので、他のオプションについては下記の公式ドキュメント等を参照ください。
[https://matsuand.github.io/docs.docker.jp.onthefly/engine/reference/commandline/run/](https://matsuand.github.io/docs.docker.jp.onthefly/engine/reference/commandline/run/)
|オプション|意味|使用例|
|----|----|----|
|--name|コンテナ名を指定|docker run --name "test" nginx|
|-d|バッググラウンド実行|docker run -d nginx|
|-it(シェル名指定)|コンソールに結果を出力|docker run -it nginx bash|
|-p host:container|ポートのマッピング|docker run -p 8080:80 nginx|
|-v host:container|ディレクトリの共有|docker run -v /test1:/test2 nginx|
|-e|環境変数を設定|docker run -e foo=bar nginx|
|-w|作業ディレクトリを指定|docker run -it -w=/tmp/work nginx bash|

例として下記に、起動時の設定と実行コマンドを載せておきます。
・用いるイメージ：nginx
・コンテナ名：my-nginx
・ポート：コンテナの80ポートをホストの8080にマッピング
・ディレクトリ：ホスト上の「/test1/test01」をコンテナ上の「/test2/test02」にマウント
・環境変数：TEST_03にtest3を設定する。
・作業ディレクトリ：「/test4/test04」
・コンソールに結果を出力するようにする。またシェルはbashを指定
```
docker run --name my-nginx -p 8080:80 -v /test1/test01:/test2/test02 -e TEST_03=test3 -w /test4/test04 bash
```
#####▶docker stop
実行中コンテナを停止させるには下記のコマンドを実行します。
```
docker stop [コンテナID|コンテナ名]
```


##### ▶docker ps,docker logs,docker inspect 
コンテナの一覧を取得するコマンドとしては下記のようなコマンドがあります。この場合動作中のコンテナのみが表示されます。
```
$docker ps 

#####実行結果#####
CONTAINER ID   IMAGE   COMMAND                  CREATED          STATUS          PORTS   NAMES
4c0723265a8e   nginx   "/docker-entrypoint.…"   33 minutes ago   Up 33 minutes   80/tcp  interesting_dhawan
```
停止中のコンテナを含めて全コンテナを見たい場合にはdocker psコマンドに下記のようにオプションをつけて実行します。
```
$docker ps -a
```

コンテナの実行ログを見る方法としては下記のものがあります。ここで出てくるコンテナIDおよびコンテナ名は上記のdocker psで実行した際に最左端、右端に出力されるCONTAINER ID、NAMEのことです。
```
docker logs [コンテナID|コンテナ名]
```
コンテナのメタデータを見るには下記のコマンドを用います。
```
$docker inspect [コンテナID|コンテナ名]

#####実行結果#####
[
    {
        "Id": "568fb7ebc68172c2b0859ef7ee6df05e9596485e410ad3e2084dc13a17984530",
        "Created": "2022-07-17T12:34:39.157175901Z",
        "Path": "/docker-entrypoint.sh",
        "Args": [
            "nginx",
            "-g",
            "daemon off;"
        ],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 2243606,
            "ExitCode": 0,
#######################################中略#######################################
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:11:00:02",
                    "DriverOpts": null
                }
            }
        }
    }
]
```

##### ▶docker rm
停止したコンテナは下記のコマンドを用いて削除を行うことができます。
```
docker rm [コンテナID|コンテナ名]

=====my-nginxを削除=====
$docker rm my-nginx

#####実行結果#####
my-nginx
```

##### ▶docker exec
バックグラウンド等で実行し、現在動作中のコンテナに入るには下記のコマンドを用います。この際、シェルを指定する必要があります。
```
docker exec -it [コンテナID|コンテナ名] [シェル名] 

=====my-nginxにbashを指定して入る=====
$docker exec -it my-nginx bash
```

##### ▶その他
docker rmi,docker rmコマンドにてそれぞれイメージの削除、コンテナ削除を行えることを上記で記載しましたが、これらの方法は個々に削除対象を指定しているため、一括での削除を行いたい場合に不便です。そのため下記に一括削除の方法を記載します。
停止中のコンテナと無名のイメージを削除
```
$docker system prune
```
起動中のコンテナを含めて全コンテナを削除
```
$docker rm -vf $(docker ps -a -q)
```
全てのイメージを削除
```
$docker rmi -f $(docker images -a -q)
```

### ■Docker Imageの作成
上記ではDokcer Imageを入手する方法としてDockerHubからリポジトリをpullするというものを挙げていました。
これ以外にもDocker Imageを入手する方法はいくつかあり、本節では下記2点を見ていきます。
・コンテナからイメージを作成する。
・Dockerfileに記述する。
##### ▶コンテナからイメージを作成
コンテナ内で新たにパッケージをダウンロードした際などには、起動した際に用いたイメージから作成されるコンテナとは異なる状態のコンテナになります。このような新たに追加が行われたコンテナを作成できるようなイメージを作る方法として下記のコマンドがあります。
```
docker commit [コンテナID] [新規作成コンテナ名]
```

##### ▶Dockerfileに記述
Dockerfileというものにイメージに関する設定を書き込むことでもイメージを作成することができます。Dockerfileは下記のようなものです。
```Dockerfile:Dockerfile
FROM centos:7
  
RUN echo "now building..."
RUN yum -y install httpd
RUN sed -i '/#ServerName/a ServerName www.example.com:80' /etc/httpd/conf/httpd.conf
EXPOSE 80
CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]
```
このファイルを見るとFROMやRUN、ADD等の命令と引数という構造でDockerfileが書かれていることがわかります。FROMやRUN、ADD等など命令がそれぞれ何を表しどのような使い方をするのかについては下記の表の通りです。こちらについてもよく用いるもののみを記載しているので、その他のものについては下記を参照ください。
[https://docs.docker.jp/engine/reference/builder.html](https://docs.docker.jp/engine/reference/builder.html)

|命令|意味|使用法|
|----|----|----|
|FROM|ベースイメージを指定|'FROM [Image名]' または 'FROM [Image名]:[タグ]'|
|RUN|コマンド実行|RUN [コマンド]|
|CMD|コンテナ起動時の実行コマンド|'CMD [コマンド]' または 'CMD ["実行バイナリ", "パラメータ1", "パラメータ2"]'|
|EXPOSE|ポートの解放|EXPOSE [ポート]|
|VOLUME|マウント|VOLUME ["/data"]|
|ADD|File/Directory追加|ADD [ソース] [送信先]|
|COPY|File/Directoryコピー|COPY [ソース] [送信先]|
|WORKDIR|作業ディレクトリ指定|WORKDIR [ディレクトリパス]|
|ENV|環境変数設定|ENV [key]=[value]|

このDockerfileの命令を見るとDocker runでのオプションと同じようなことができることがわかります。

このように作成したDockerfileを用いて、Imageをビルドするには下記のコマンドを用います。こちらについてもオプションを指定することができます。各種オプションは下記を参照ください。
[https://matsuand.github.io/docs.docker.jp.onthefly/engine/reference/commandline/build/](https://matsuand.github.io/docs.docker.jp.onthefly/engine/reference/commandline/build/)
```
docker build [Dockerfileのパス]
```
このビルドを行った際の、イメージはDockerファイルの各行毎にLayerを追加していくように作成されます。
このことはビルドを実行した際の出力を見ることで分かります。上記のDockerfileを実行すると下記のようになります。
```
$docker build .

#####実行結果#####
[+] Building 34.4s (8/8) FINISHED
 => [internal] load build definition from Dockerfile                                                               0.0s
 => => transferring dockerfile: 253B                                                                               0.0s
 => [internal] load .dockerignore                                                                                  0.0s
 => => transferring context: 2B                                                                                    0.0s
 => [internal] load metadata for docker.io/library/centos:7                                                        6.1s
 => [1/4] FROM docker.io/library/centos:7@sha256:c73f515d06b0fa07bb18d8202035e739a494ce760aa73129f60f4bf2bd22b407  9.1s
 => => resolve docker.io/library/centos:7@sha256:c73f515d06b0fa07bb18d8202035e739a494ce760aa73129f60f4bf2bd22b407  0.0s
 => => sha256:dead07b4d8ed7e29e98de0f4504d87e8880d4347859d839686a31da35a3b532f 529B / 529B                         0.0s
 => => sha256:eeb6ee3f44bd0b5103bb561b4c16bcb82328cfe5809ab675bb17ab3a16c517c9 2.75kB / 2.75kB                     0.0s
 => => sha256:2d473b07cdd5f0912cd6f1a703352c82b512407db6b05b43f2553732b55df3bc 76.10MB / 76.10MB                   6.9s
 => => sha256:c73f515d06b0fa07bb18d8202035e739a494ce760aa73129f60f4bf2bd22b407 1.20kB / 1.20kB                     0.0s
 => => extracting sha256:2d473b07cdd5f0912cd6f1a703352c82b512407db6b05b43f2553732b55df3bc                          2.1s
 => [2/4] RUN echo "now building..."                                                                               0.6s
 => [3/4] RUN yum -y install httpd                                                                                17.4s
 => [4/4] RUN sed -i '/#ServerName/a ServerName www.example.com:80' /etc/httpd/conf/httpd.conf                     0.5s
 => exporting to image                                                                                             0.6s
 => => exporting layers                                                                                            0.6s
 => => writing image sha256:56839962af6761a5790e0d01133ec26754ccba630d42a8d933fa1fa3858ed826
```
この結果を見るとDockerfileに記載したようにFROMで記載したベースイメージの上に層を重ねて言っている様子がわかるかと思います。またこれ以外にも下記のコマンドでもこの様子を確認することができます。

```
$docker history [Image ID|Image名]

=====上記で作成したImageを見る=====
$docker history 56839962af67

#####実行結果#####
IMAGE          CREATED         CREATED BY                                      SIZE      COMMENT
56839962af67   5 minutes ago   CMD ["/usr/sbin/httpd" "-D" "FOREGROUND"]       0B        buildkit.dockerfile.v0
<missing>      5 minutes ago   EXPOSE map[80/tcp:{}]                           0B        buildkit.dockerfile.v0
<missing>      5 minutes ago   RUN /bin/sh -c sed -i '/#ServerName/a Server…   11.8kB    buildkit.dockerfile.v0
<missing>      5 minutes ago   RUN /bin/sh -c yum -y install httpd # buildk…   209MB     buildkit.dockerfile.v0
<missing>      5 minutes ago   RUN /bin/sh -c echo "now building..." # buil…   0B        buildkit.dockerfile.v0
<missing>      10 months ago   /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
<missing>      10 months ago   /bin/sh -c #(nop)  LABEL org.label-schema.sc…   0B
<missing>      10 months ago   /bin/sh -c #(nop) ADD file:b3ebbe8bd304723d4…   204MB
```

##### ▶作成したイメージをpush
上記のような方法で作成したイメージをDockerHub上に公開する方法を見ていきます。
この際DokcerHubのアカウントが必要となるので下記より作成を行って下さい。
[https://hub.docker.com/](https://hub.docker.com/)
公開を行うためには、まずタグ付けを行う必要があります。下記のようにDockerHubで登録を行ったユーザー名を用いてタグを付けて下さい。
```
docker tag [Image ID] [DockerHubのユーザーネーム]/[リポジトリ名]

=====上記で作成したImageの場合=====
$docker tag 56839962af67 yusuke0614/my-centos
```
また、pushを行う前にDockerHubにログインを行う必要があるため、下記を実行しログインを行います。
```
$docker login
```
ここまで完了したら、DockerHubにリポジトリをアップロードできますので、下記のコマンドを実行します。
```
docker push [DockerHubのユーザーネーム]/[リポジトリ名]

=====上記で作成したImageの場合=====
$docker push yusuke0614/my-centos
```
### 複数のコンテナ管理(docker-compose)
複数のコンテナを起動したい、停止したい場合に、それぞれに対してdocker run、docker stopを実行するのは手間がかかります。これを解決するために一括して管理を行うのがDocker-composeです。この方法ではyamlファイル(docker-compose.yaml)に複数のコンテナの情報を書き込み、管理を行います。
ファイルは下記のようなものです。
```yaml:docker-compose.yaml
version: '3'
services:
  db:
    image: postgres
  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db
```
詳細に関して、ここでは説明を行いませんが、大雑把には、versionの箇所にdocker-composeのversionを記載し、servicesの箇所に起動するサーバの情報を記載しています。このファイルの場合だと、DBサーバとWebサーバをそれぞれ起動するように記載されております。またその配下に記載されている内容としては、Dockerfileに記載している内容と似ています。
詳細については下記を参照ください。
[https://docs.docker.com/compose/compose-file/](https://docs.docker.com/compose/compose-file/)


このようなyamlファイルを作成した後に下記のコマンドを実行することで、コンテナを起動できます。
```
docker-compose –f [yamlファイルパス] up
```
また起動したコンテナに関して、yamlファイルに記載されたコンテナを停止させるには下記のコマンドを実行します。
```
docker-compose -f [yamlファイルパス] dowm
```
これらの起動した複数コンテナのログをまとめて見る場合には下記のコマンドを実行します。
```
dokcer-compose -f [yamlファイルパス] logs
```

サーバのクラスタ化を行う場合など、Webサーバ等を複数起動したい場合があります。この複数起動した同じ役割を持つコンテナのことをレプリカといいます。このようなdocker-composeにてコンテナレプリカを複数起動する方法を見ていきます。
複数起動を実現するには上記のyamlファイルのポートに関する設定の変更を行う必要があります。具体的にはポートがそれぞれのコンテナで重複しないようにレンジ表記にする必要があります。
```yaml:docker-compose2.yaml
version: '3'
services:
  db:
    image: postgres
  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/myapp
    ports:
      - "3000-3004:3000"
    depends_on:
      - db
```
このように記載を変えることで、ホスト側のポートには3000,3001,3002,3003,3004が割り当てられるようになるため、5つのレプリカセットを作成することができるようになります。このようなdocker-composeファイルを用いて起動を行いますが、upコマンドを実行する際に--scaleオプションを追加し、レプリカ数を指定します。
```
docker-compose –f [yamlファイルパス] up –scale [コンテナ名]=[レプリカ数]
```
### ネットワーク
Docker-composeで定義されたコンテナ同士はデフォルトの設定で互いに通信可能です。しかし、他の場所(yamlで記載されたコンテナ以外のコンテナ)からはアクセスできないようになっています。これは、コンテナ起動時にネットワークネームスペースが作成され、そこにコンテナが割り当てられるためです。
コンテナを個別に立ち上げた場合には、特に設定を行わない限りはデフォルトで用意されているBridgeネットワークに繋がるように繋がるようにネットワークネームスペースが作られます。一方でdocker-composeから作成したコンテナ群たちは別のBridgenettoワークを個別に設定し、そのBridgeネットワークに繋がるようにネットワークネームスペースが作られます。
そのため、docker-composeで作成したコンテナと個別に作成したコンテナは通信できません。

これを解決し、接続するには下記の記事にあるような方法で、ネットワークの設定を行う必要があります。
[https://qiita.com/reneice/items/20e981062b093264cd0a](file:https://qiita.com/reneice/items/20e981062b093264cd0a)


ネットワークの一覧を確認するには下記のコマンドを実行します
```
$docker network ls

#####実行結果#####
NETWORK ID     NAME           DRIVER    SCOPE
8b49310155c1   bridge         bridge    local
d9fdce15afbe   host           host      local
84bc27f25415   none           null      local
8193ae150103   test_default   bridge    local
```
この結果を見るとDRIVER列にいくつかの種類があることがわかります。これらはネットワークのモードに対応しています。Dockerのネットワークには下記の4つのモードがあります。
・bridge
　→ホストの中のDockerBridgeネットワークからIPが振り分けられるため、IPレンジがホストとは別になる。
・host
　→ホストのIPレンジからIPが振り分けられ、またホストのポートを用いるので、-pオプションなどのポートのマッピングはできない。
・null
　→Bridgeにもホストにもリンクされず、IPが振り分けられない。そのため、接続不可の状態となる。
・overlay
　→複数ホストの場合に用いる。

新しいネットワークの作成するには下記のコマンドを実行します。
```
docker network create –driver bridge [ネットワーク名]
```
ネットワークの削除を行うには下記のコマンドを実行します。
```
docker network rm [ネットワーク名]
```

### ストレージ
コンテナではライフサイクルが短いこともあり、データはコンテナ内ではなく、ホスト上に保存を行います。
そのためにDocker runコマンドにおいて、-v で指定していたように、ホスト上のフォルダとコンテナ内のフォルダを指定し、マウントを行います。