Dockerについて part1

#Dockerのimageとは

Dockerのimageとはある実行環境のスナップショットみたいなもので、これを共有することで環境構築を簡単にすることができる。

dockerのimageはdockerhubというサイトから様々なものをインストールすることができる。


#Dockerfileとは

自分でimageを作成するときに使うファイル。vimとかでも書くことができる。

```Dockerfile.ci
  1 FROM ubuntu
  2 COPY hello.txt /tmp/hello.txt
  3 CMD ["cat", "/tmp/hello.txt"]
  4 
```
こんな感じのファイル。

###catコマンドとは

一番よく使われるのはファイルの閲覧。
$ cat fileA
このようにして使う。

#buildとは
docker buildコマンドではDockerfileからdockerimageを作成する。

次のコマンドをみてみよう

```
$ docker build -t hello .
```
-t hello オプションは「Docker Imageを hello という名前にする」という意味です。
. はdocker build 実行時のコンテキストの指定です。 . は COPY コマンドを実行する際にどのディレクトリを起点とするかを指定します。(入門dockerより）

###docker images
インストールしたり作成したimageはdocker imagesというコマンドで確認することができる。

```
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello               latest              98d66d64d276        13 seconds ago      64.2MB
ubuntu              latest              ccc6e87d482b        28 hours ago        64.2MB
nginx               latest              c7460dfcab50        7 days ago          126MB
ruby                latest              fb53c5f433da        10 days ago         842MB
python              2.7                 426ba9523d99        2 weeks ago         896MB
```

docker buildで作成したdocker imageを実行してみよう

```
$ docker run hello
hello docker !
```

#実行中のコンテナを確認しよう

docker container ls -aで確認することができる。

```
$ docker container ls -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
429c028bcf74        ubuntu              "echo hoge"         16 seconds ago      Exited (0) 15 seconds ago                       stoic_greider
```


#実行中のコンテナを削除しよう

docker image prune -aで削除することができる。

```
$ docker image prune -a
WARNING! This will remove all images without at least one container associated to them.
Are you sure you want to continue? [y/N] y
Deleted Images:
```

#Dockerhubから作成したimageの取得をしよう

$docker pull yokotadaiki/hello

これでdocker run yokotadaiki/helloを実行できるようになった。


#Dockerfileの基本的なコマンド

基本的なコマンド¶
個人的に、Dockerfileを本番のワークロードで使用する場合以下の7つのコマンドを覚えるだけで十分だと考えています。

FROM , COPY , RUN , CMD , WORKDIR , ENV , USER
（入門dockerより）

##FROM

ベースとなるDocker Image を指定します。
DockerはベースとなるDocker Image の上に COPY や RUN のようなコマンドを重ねてDocker Image を作成します。
ベースとなるDocker Image は公式で提供されているImageを使用するのが一般的です。

また、ここで設定したイメージを「ベースイメージ」と呼びます。

##ENV

Docker内で使用する環境変数を定義します。
NODE_ENV のようなDockerの起動時にデフォルトで定義されていてほしい環境変数を定義すると良いでしょう。

```
ENV NODE_ENV=production
```

##WORKDIR
Dockerfileでコマンドを実行する際に基準となるディレクトリを設定します。
このとき存在しないディレクトリを指定すると自動的にディレクトリが作成されます。
デフォルトだと / が設定されているため、最悪の場合既存のディレクトリを上書きしてしまいコンテナが起動しなくなります。

```
WORKDIR /scripts
```

##COPY

Docker内へホストのファイル/ディレクトリをコピーします。
COPY は基本的に2つの引数を設定します。1つ目はホスト側のディレクトリ、2つ目はDocker側のディレクトリです。
ホスト側のディレクトリは docker build . で指定したディレクトリです。この場合 . を指定しており、カレントディレクトリが参照されます。
Docker側は WORKDIR で定義されたディレクトリを参照します。

##RUN

Docker内でコマンドを実行します。
ここでコンテナへ依存するライブラリやパッケージのインストールやユーザーの設定などの処理を実行します。

```
RUN npm install \
  && groupadd app \
  && useradd -g app app \
  && chown -R app:app /scripts
```

##USER

作成したDocker Image を起動時にログインするユーザーを指定します。
デフォルトは root が設定されているため、セキュリティリスクを回避するために別のユーザーを指定するのが良いでしょう。

```
USER app
```

##CMD

Docker起動時にデフォルトで実行されるコマンドを定義します。
Dockerはここで設定したコマンドがフォアグラウンドで実行されている間が生存期間になります。
そのため、プロセスの処理が走っている間はフォアグラウンドで実行するように記述します(バックグラウンドで起動するとDockerが終了してしまう)。

```
CMD ["npm", "run", "start"]
```

##EXPOSE

コンテナ起動時に公開することを想定されているポートを記述します。
EXPOSE を記載することで他の人から「このDockerはポートをどの使用するのか」がわかりやすくなるため、記述すると丁寧でしょう。

##VOLUME

Data Volumeを作成するためのコマンドです。Volumeについては後の章で説明します。
永続的なデータや共有するためのデータ、更新頻度の激しいファイルを扱うために使用されます。
基本的に永続的なデータはDockerで管理することは推奨されないため、基本的にログのような更新頻度の激しいファイルで使用すると良いでしょう。

```
VOLUME ["/app/log"]
```

#Docker Containerとは

Docker Image がスナップショットだとしたらDocker Container はその 「スナップショットから起動したプロセス」 です。

より具体的にいうと docker run を実行するとDocker Image をもとにしてDocker Containerが作成され、隔離された環境が作成されます。
Docker Container は Docker Imageを元にして作成され、リソースの許す限り立ち上げることができます。

意識する点として、Docker Container は1つのコマンドをフォアグラウンドで動かすように設計されていることです。
Docker Containerは1つのコマンドを隔離された環境で実行し、そのコマンドの実行がフォアグラウンドで終了するまで生存します。

#Dockerのライフサイクル

Dockerコンテナは大きく5つの状態に分けられる。

###image

指定したdocker imageからコンテナを起動する。

###RUNNING

Docker Containerが起動した状態です。
Dockerfileの CMD もしくは ENTRYPOINT で指定したコマンドがフォアグラウンドで動いている間がRUNNINGの状態です。
例えば docker run -P nginx のようにnginxを起動した場合、nginxが起動してアクセスを待ち受けてる間はRUNNINGの状態となります


###STOPPED

起動したContainerが終了した状態です。
正常終了・異常終了、どのような形であっても終了したContainerはSTOPPEDへ遷移します。

###PAUSED

Containerが停止した状態です。
ユーザーが docker pause <CONTAINER ID> を実行すると、現在の状態を保持して一時停止します。
docker unpause <CONTAINER ID> で一時停止したコンテナIDを指定することで再開することが可能です。
ユーザーが明示的に指定しない限りこの状態へは遷移しません。

###DELETED

Docker Container は明示的に削除を行わない限り停止した状態で残り続けます。
docker rm <CONTAINER ID> で明示的に削除するとDELETEDの状態へ遷移し、削除されます。


#ここまでの重要なポイント

・コンテナは隔離された環境で実行される
・Dockerコンテナは1つのコマンドを実行してプロセスを実行する
・基本的に「1コンテナ1プロセス」
・ライフサイクルは実行されたコマンドがフォアグラウンド上で実行されている期間
・バックグラウンドでプロセスが立ち上げられていてもフォアグラウンドでコマンドが終了したらコンテナは停止する


#Networkを利用する

dockerコンテナは、１コンテナ１プロセスが基本だった。
そのため、railsとmysqlなど複数のコンテナを協調して動作させるにはnetworkを利用する必要がある。

$ docker network ls
このコマンドで現在dockerが管理しているnetwork一覧を出力することができる。


```
$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
3d295ce0594e        bridge              bridge              local
0539a1b864dd        host                host                local
30c3217049fb        none                null                local
```

次は新しいBRIDGEネットワークを作成しよう。

```
$ docker network create myapp
6dbd1262d023114e349e2f12e843044327cc2d870bd41cc4d74103ad68d24725
```

##作成したnetworkにnginxを参加させる
通信を受けるためのサーバーとしてnginxを構築します。
$ docker run --name nginx --network=myapp -d nginx

##AmazonLinux2を起動し、Nginxコンテナへ接続する
Bridgeネットワークの場合、同一ネットワークのコンテナにはコンテナ名で名前解決が可能です。

Nginxと疎通できるか myapp ネットワーク内にAmazonLinux2 イメージでコンテナを起動し、 curl を実行してみましょう。

```
$ docker run --network=myapp -it amazonlinux:2 curl nginx:80
```

#ここまでの重要ポイント
・1プロセス1コンテナ、復数プロセスはネットワークを通して通信を行う。
・Bridgeを基本的に使用する。


#Volumeとは
ボリュームはデータを永続化するための機能です。

Docker Containerは基本的にエフェメラルなもので、コンテナ上で作成されたファイルはコンテナのライフサイクルの終了と共に消えてしまいます。
Volumeはデータ保持・永続化のために設計されており、コンテナのライフサイクルとは独立してファイルの管理を行います。

ここは難しい。。。


#デバッグ(dockerの動作確認方法)

そもそも、dockerは実行されるたびに新しいコンテナが立ち上がる。そのため、dockerimageが1つでも複数のコンテナが立ち上がっているという状況がある。

コンテナの状態を確認するコマンドとして

```
$ docker container ls -a
```

としてきた。

ちなみに実行中のコンテナだけ確認したいときは

```
$ docker container ls
```

とすれば良い。


###コンテナの停止~imageの削除
```
###コンテナの停止
$ docker stop $(docker container ls -q) 

###コンテナの削除
$ docker rm $(docker container ls -aq)

###imageの削除
$ docker rmi $(docker images -q)
```

#Dockerfileのベストプラクティス

例えばPHPの環境を構築するのにCentOSのベースイメージで、phpenvを入れて、MySQLを入れて、、といったことは非推奨です。
DockerはいままでのVMとは思想が異なります。1コンテナ1プロセスになるように設計を行いましょう。
復数のプロセスを使用したい場合はそれぞれコンテナに分け、オーケストレーションツールを使用してコンテナを協調させて動かしましょう。

##.dockerignoreを使う

Dockerのビルド時に無視するファイル/ディレクトリを指定することができます。
".git" のようなコンテナ内に不要な情報、 "node_modules" のような上書きされると困るものを記述します。
.dockerignore は基本的に .gitignore と同じ書き方が可能です。

ここも難しい。

#オーケストレーションツール

複数のDockerを動かすためのツール。
docker-composeもその中の1つだと思う。

#docker-composeについて
docker-composeはローカルでDockerのオーケストレーションを行うためのツールです。
DockerのビルドからNetworkやVolumeの管理をコードベースで定義して行ってくれます。

docker-composeはDockerの構成をyamlを定義し、そのyamlを元に起動します。
例えばnginxを起動し、ホストの8080ポートへコンテナの80ポートをフォワードする設定は以下のyamlになります。

例えば以下のようなファイル

```yaml
version: '3.7'

services:
  nginx:
    image: nginx
    ports:
      - 8080:80
```
docker run -p 8080:80 nginx とほぼ同じ動きをします(異なる点としては、docker-composeでは専用のNetworkを作成・使用する点です)。
単純なnginxの起動であれば素のdockerコマンドで問題ありませんが、ここにPHP, MySQL...と増えていくとその威力を発揮します。

つまり、数が増えるほどymlファイルにまとめて記載してdocker-composeでまとめてコンテナを立ち上げた方が簡単じゃない？って話だと思う。


#Lalavelのサンプルで雰囲気を掴む

dockerでLalavelをのサーバーを起動する。

```
$ git clone https://github.com/y-ohgi/introduction-docker.git
$ cd introduction-docker/handson/laravel
$ docker-compose up
```

この状態はrailsでいうrails sを実行したのと同じ。

#docker-compose.ymlを読む。

```docker-compose.yml
version: '3.7'

services:
  nginx:
    build:
      context: .
      dockerfile: docker/nginx/Dockerfile
    volumes:
      - ./public:/var/www/html/public:ro
    ports:
      - 8080:80
    environment:
      PHP_HOST: app

  app:
    build:
      context: .
      dockerfile: Dockerfile
    env_file:
      - .env.example
    # volumes:
    #   - .:/var/www/html:cached

  mysql:
    image: mysql:5.7
    volumes:
      - ./mysql:/var/lib/mysql:delegated
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
    ports:
      - 13306:3306
```

###version

docker-composeのバージョンを指定する。
特にこだわりがなければ最新のものを。

###services
起動するコンテナの定義を行います。
このdocker-compose.yamlでは nginx , app , mysql の3つが定義されています。

###build

docker buildの実行情報を記述します。
ここで定義された情報を元にDockerをビルドし、そのビルドしたイメージ使用してコンテナを起動します。
image もしくは build どちらかを記述する必要があります。
コマンドの場合、 docker build -f docker/nginx/Dockerfile . と同一です。


###volumes

ボリュームのマウントを行います。 コマンドの場合、 -v $(pwd)/public:/var/www/html/public:ro <IMAGE ID> オプションと同一です。

###ports

ポートの開放を行います。
左にホストのポートを、右にコンテナのポートを指定します。
コマンドの場合、 -p 8080:80 オプションと同一です。

###command

Dockerfileで定義されている CMD の上書きを行います。

#Docker-composeのコマンド集

```
1.カレントディレクトリに存在する docker-compose.yaml を参照してdocker-composeの起動
$ docker-compose up

2.カレントディレクトリの docker-compose.yaml に紐付いているContainerとNetworkを削除
$ docker-compose down

3.Imageも削除
$ docker-compose down --rmi all

4.volumeを削除
$ docker-compose rm
```

#プロダクションへの導入

新規開発なら最初からDockerで開発していきましょう。

既存のVMからの移行であればステップを踏んで行くのが良いでしょう。

1.ローカル環境のDocker化
 まずはローカル環境での導入から行います
 チームの開発環境をDockerで統一し、Dockerに慣れていくところから始めていきましょう

2.テスト/CIへの導入
 次にテスト/CI環境への導入を行います
 また、CIを導入してないのであればDockerとの相性が良いのでこの機会に導入するのもありでしょう。

3.ステージングへの導入
 本番へいきなり導入する前にステージング環境でDockerの動作を確認しましょう

4.本番への導入
 最後に本番環境への導入を行いましょう


#tools

全てのcontainerを停止、削除します。関連するvolumeとnetworksも削除します。
$ docker-clean stop

Dockerのリソース全てを削除する。起動中のcontainerも削除される点に注意。
$ docker-clean all


#開発スタート
あとは、コンテナが起動している時は、以下のようにexec webに続いてrailsのコマンドを実行していけば、scaffoldやmigrationの実行が可能です。

$ docker-compose exec web bin/rails g scaffold User first_name:string last_name:string

マイグレーションの場合は、以下のような感じです。
$ docker-compose exec web bin/rails db:migrate

コンテナを終了させたい場合は、以下のコマンドになります。
$ docker-compose down

Gemを追加したときは、イメージを再構築する必要があるので、以下のコマンドを実行してください。
$ docker-compose build web