Dockerを少し整理したい

## はじめに

職場で本格的に使うことになるので Docker とはどういうものなのかを簡単に整理。

## 参考

[【図解】Docker の全体像を理解する -前編-](https://qiita.com/etaroid/items/b1024c7d200a75b992fc)
[【図解】Docker の全体像を理解する -中編-](https://qiita.com/etaroid/items/88ec3a0e2d80d7cdf87a)
[【図解】Docker の全体像を理解する -後編-](https://qiita.com/etaroid/items/40106f13d47bfcbc2572)
[【AWS Black Belt Online Seminar】 CON142 Docker 入門](https://www.youtube.com/watch?v=CGfRsyQW1rE)
[docker-compose で Django 開発環境を構築する](https://zenn.dev/dsonoda/articles/dbe14ca8af617ed85b1f)

## 何故 Docker を使うのかその 1

A. 同一の仮想環境をインスタンスに作成・再現することができるから

これを実現するのが Docker Image とそれを作成する DockerFile の仕組み。

### Docker Image と DockerFile

Image は 1 コンテナの設計図といったもので例えば

- Python
- AWS CLI
- Django

という 3 つのライブラリ・ミドルウェアからなる Image があるとする。
このとき各ライブラリ・ミドルウェアはレイヤー単位で扱われ、この時点でこのコンテナは 3 レイヤーから成り立っていることになる。
ここに Nginx をインストールしてこのコンテナを Commit すると、このコンテナは 4 レイヤーからなる Image を生成する。
取得した Image の確認は以下のコマンド。

```cmd

docker images

```

Image は DockerHub からも DL できるが、公式が作成したもの……つまり信用できる Image でないと環境を再現するという Docker の特徴からそこに例えばウイルスを仕込まれていた場合、大変なことになってしまう。
そこで、大抵は公式の Image から自分で Dockerfile を作成し Image を作成することになる。

```dockerfile

# どのImageを元にImageを作成するか、以下はPythonの3.8のImageを使う
FROM python:3.8
# コンテナの中にユーザーを追加するためのコマンド。特定のユーザーのみにコンテナを扱う権限をもたせたいときに使う。
# rootはデフォルト(なのでrootの場合は記述しなくてもよい)
USER root

RUN apt-get update
RUN apt-get -y install locales && \
    localedef -f UTF-8 -i ja_JP ja_JP.UTF-8

# 環境変数
ENV LANG ja_JP.UTF-8
ENV LANGUAGE jp_JP:ja
ENV LC_ALL ja_JP.UTF-8
ENV TZ JST-9
ENV TERM xterm

# RUNはインストールコマンド、他にもCMDでコンテナ作成後に実行するコマンドを指定することができる
RUN apt-get install -y vim less
RUN pip install --upgrade pip
RUN pip install --upgrade setuptools

RUN python -m pip install jupyterlab
RUN python -m pip install pandas
RUN python -m pip install numpy

```

Dockerfile を用意したら以下のコマンドで実行する。
-t オプションを使うと-t 以下にイメージ名をつけられる。
`.`以下で Dockerfile があるディレクトリを指定して実行する。
通常 build コマンドで読み込む Dockerfile は指定したディレクトリ(ビルドコンテキストというらしい)の Dockerfile になる。

```cmd

docker build -t ~ .

```

Dockerfile を指定して build する場合は以下

```cmd

docker build -f 指定するDockerfile

```

なお、build した image について変更をしたあとでそれを反映して build し直したい場合 Dockerfile を修正してから行う。
これは build cache が残っているからで以下のコマンドでそれを無視してやれば Dockerfile の修正なしでも一応できる。

```cmd

docker build -no-cache -t docker-while

```

あとはコンテナを作る。
pull(イメージ取得)、create(コンテナ作成)、start(コンテナ実行)コマンドを順に実行していくことになるが基本的には image が既にある場合は run コマンドでそれを一括で行う。

```cmd

docker run イメージ名

```

あとは特定のコンテナの中に入ってコマンドを実行するみたいな時は以下のコマンドを実行する。

```cmd

docker exec -it 起動中のコンテナ名 bash

```

他のコマンドは compose のところと重複するので割愛。

## 何故 Docker を使うのかその 2

A. サービスやアプリケーションのマイクロサービスアーキテクチャ化という思想にマッチしているから

これでコンテナという仮想環境をインスタンスに作れることがわかったがこれだけだと Docker の真価は発揮できてるとは言えない。
Docker が今日持て囃されてるのはマイクロサービスアーキテクチャの思想が進み、それが開発の上でトレンドになっているところに大きな要因があるからだ。
マイクロサービスとはなんぞやというと、簡単に言えばサービスやアプリケーションをモノリシック(1 つの大きな塊)に構築するのではなく、マイクロに分割してそれらを連携させることで構築するという思想のこと。
例えば先程までだと 1 つのコンテナに

- Django
- Nginx
- PostgreSQL

この 3 つが入っていればアプリケーションはできるが、これだと例えばバグや障害が発生したときに何が要因なのか特定しづらくなる。
なので、せっかく Docker は簡単に環境を構築できるんだからこの 3 つの image をそれぞれのコンテナに分割して、それらを連携させればいいじゃないというところに何故 Docker を使うのかという理由の大半があるのではないかと私は思っている。
で、それを実現するのが Docker Compose という仕組み。

### Docker Compose

Docker Compose はその名の通りちょっとしたオーケストレーションを行えるような仕組み。
簡単に言うと先程の Dockerfile は 1 つのコンテナに対してアレコレ……ということしかできなかったが、Docker Compose では Web サーバー、DB サーバー、Application サーバーのコンテナをそれぞれ作成してまとめて起動して……なんてことができる仕組み。

例えば Dockerfile は以下のようになる。

```dockerfile

FROM python:3
# pythonの標準出力をバッファにため込まないための環境変数設定
ENV PYTHONUNBUFFERED 1
# serviceディレクトリを作成
RUN mkdir /service
# serviceディレクトリに移動
WORKDIR /service
# requirements.txt(事前に作成)をserviceディレクトリに置く
COPY requirements.txt /service/
# pip  installでパッケージをインストール
RUN pip install -r requirements.txt
# buildコンテキストの内容を全て/service内に置く
COPY . /service/


```

[引用:【図解】Docker の全体像を理解する -中編-](https://qiita.com/etaroid/items/88ec3a0e2d80d7cdf87a)

先程と比べるとより具体的な操作になっていることがわかる。
つまり、PythonImage を取得して、コンテナにディレクトリを作成して事前に作成してビルドコンテキストに置いた requirements.txt をコピーしたあとそれを元にパッケージをインストールする……ということをしている。
次に肝となる docker-compose.yml を作成する。

```yml
# docker-composeのversion指定
version: "3"
# 起動するサービスコンテナを書いていく、複数コンテナを記述するのでインテンドを区別することに注意
services:
  # dbという名前のコンテナを起動
  db:
  # イメージはdockerhub上のpostgres:latestを使用
  image: postgres
  # webという名前のコンテナを起動
  web:
  # カレントディレクトリにあるdockerfileからimage作成
  build: .
  # コンテナ起動時に実行されるコマンドを指定
  command: python3 manage.py runserver 0.0.0.0:8000
  # currentディレクトリを/appディレクトリにbind mountする
  volumes:
    - .:/app
  # コンテナの8000番を公開
  ports:
    - "8000:8000"
  # webサーバーコンテナを立ち上げる前にdbサーバーコンテナを立ち上げるようにする、依存関係を整理しないのとコンテナが正しく起動しない
  depends_on:
    - db
```

[引用:【図解】Docker の全体像を理解する -中編-](https://qiita.com/etaroid/items/88ec3a0e2d80d7cdf87a)

あとは

```cmd

docker-compose run web django-admin.py startproject test .

```

これで Web コンテナにおいて Django の startproject を実行することができる。
run コマンドは特定のコンテナを起動するコマンド。
docker-compose.yml に基づいて一連のコンテナを起動したい場合は-d オプションをつけてバックグラウンドでも起動するようにして以下のコマンドを実行する。

```cmd

docker-compose up -d

```

ちなみに Volume はローカルの特定フォルダをコンテナ内にもマウント(外付け HDD みたいな感じ)するための記述。
Docker Compose では大体`docker-compose down`コマンドを使って終了するが、それはコンテナを削除して終了するコマンドなので次に`docker-compose up -d`で起動しても Dockerfile と docker-compose.yml 以外のことはすべてなかったことになるので、永続化させたいものはローカルにおいてそれをマウントさせることで実現する。

#### 補足

Docker Compose では前述の Volume の他、`docker network ~`で本来設定していくところもコンテナごとではなく一括で行うことができる(docker-compose.yml の ports でポート開放をしている部分)。
そもそも、Docker のコンテナ間の通信は

- Bridge(デフォルトのネットワークの 1 つであり、同じネットワーク内に存在するコンテナ同士で IP アドレスを指定して通信する仕組みで`-p`オプションでポートを開けば外部からのアクセスも可能。DNS なしなのでコンテナ名では通信できない。)
- Host(デフォルトのネットワークの 1 つで Docker Host、つまり Docker Machine の IP で通信するシステム。この場合、`-p`オプションを使わずともホストの IP に接続すれば外部からのアクセスも可能)
- None(デフォルトのネットワークの 1 つであり、ここに接続するとそのコンテナはネットワーク・インターフェースを喪失する。)
- 自分で作成した独自のネットワーク(Bridge での起動だが、`docker network create`コマンドでネットワーク名を定義して作成すると、コンテナ名を指定して対象コンテナと通信可能になる)

という仕組みで成り立っているそうな。

## コンテナオーケストレーション

補足の部分で Docker Host という単語が出てきたが、それは Docker Engine が動作している仮想マシン(Docker Machine)のことを指す。
ここまでの工程は私達は自前の PC に Docker(正確にはこれが Docker Engine ということらしい)をインストールして docker コマンドを打ってコンテナを作成している。
なので、この時点でその PC は Docker Host であるといえる。
ただし、Docker Machine を複数立ち上げて連携したい(プロビジョニングしたい)……というユースケースもあるわけで、それを`docker-machine`コマンドを使って実現していくという話になる。
しかしこれも人力で一つずつやるのは大変なのでコンテナオーケストレーションを行ってくれるサービスを使うことになる。
特に近年ではクラウドインフラを利用して行うことが多いので

- AWS ECS
- Kubernetes 及びそれを利用した AWS EKS
- Docker Swarm
- etc……

といったサービスを使うことになる。
Docker Compose が Docker Image をオーケストレーションして`docker network ~`などを行っていたように、これらのコンテナオーケストレーションを行えるサービスでは

- 複数台の Docker Machine 同士をネットワークで接続・連携させる
- 上記の各 Docker Machine 及びその中で動作するコンテナの管理

ということが行える(つまり Docker Compose は単一ホストのみ利用できるということになる)。
コンテナオーケストレーションサービスで連携させると例えばリクエスト A と D はマシン 1、B はマシン 2、C はマシン 3……と負荷分散をさせるようなこともできる。
で、AWS の試験とかでもよく聞かれるノードとタスクという概念がこのサービスにある。

- 複数台ある Machine のうち、Task を各 Machine に振り分ける(これを Service という)役割を持つ Machine は Manager ノードという
- Manager ノードから Task を分配されて、それを実行する Machine を Worker ノードという

サービスによって使われている言葉とかは微妙に違う可能性はあるが概ね以上のようなことを言っているはず。

例えば

```cmd

docker service create --replicas 3 nginx

```

というコマンドがあったとする。
これは Manager ノードで nginx イメージからコンテナを 3 つ起動する起動するというサービスを作成するというコマンドになる。
これを実行すると、同時に nginx イメージからコンテナを起動するという Task が 3 つ発生するというのが上記の仕組み。
あとは Manager ノードがリソース等条件に応じて Task を Worker ノードに分配するというのがコンテナオーケストレーションサービスの超基本的な役割となるらしい。
ちなみに同一ホスト内であるならばサービスの作成も docker-compose.yml で可能とのこと。

## 終わりに

前述の通り、実務でも使うことになるのと、AWS試験でもECS等で頻出の概念なのでもくもく会で紹介して頂いた記事からDockerがなぜ使われてどういうサービスなのかということの基本を自分なりに洗い出してみました。
まずはdocker-compose.ymlについて実際に使っていって慣れていくことが大切だなと。