Dockerfileを使用したコンテナ環境の作成ついて

# Dockerfileを使用したコンテナ環境の作成について
- dockerを使って、素早く環境構築したい
- dockerfileをもっとスラスラ描きたいという思いから、基本的なことをまとめてみました。
- 下記の書籍を参考にしました。
- [Docker/Kubenetes実践コンテナ開入門](https://gihyo.jp/book/2018/978-4-297-10033-9)
- 間違いなどありましたら、コメントにて教えて頂けると、嬉しいです。

## Dockerとは
- コンテナ型仮想化技術を実現、操作、管理するためのプロダクト
- コマンドラインで操作
- 良い特徴として
  - インストールの難しいツールなどを使う場合でも、ホストを汚さずにDockerコンテナとして実行できる。
  - 様々なライブラリやツールをコードで管理することができ、チームで開発環境を統一できる。
  - Kubernetesなどを使ってコンテナを管理することにより、より簡単にデプロイやスケールを実現できる。 などなど

## DockerイメージとDockerコンテナ
### Dockerイメージ
- Dockerコンテナを構成するためのファイルシステムや実行するアプリケーション、設定をまとめたもの。コンテナを作成するためのテンプレート。

#### DockerHub
- Docker社が管理しているDockerレジストリで、Docker公式のイメージはDockerHubで公開されており、そこからpull(取得）して使う。自分で作成したイメージをDockerHubにpushすることで世界中のユーザに使用してもらうことも可能です。

```docker
# イメージ pull
# タグ名は省略可能ですが、省略するとデフォルト(多くの場合latest)が指定されます。
# docker image pull [options] リポジトリ名[:タグ名]
docker image pull golang:1.12
# イメージ push
docker image push [options] リポジトリ名[:タグ]
```

### Dockerコンテナ
- Dockerイメージを基に作成され、アプリケーションが実行されている状態。Dockerによって作成されるゲストOS。

### Dockerによる大まかなアプリケーション実行までの流れ
1. Dockerイメージを定義...dockerfileを作成
2. Dockerイメージを作成...dockerイメージをビルド
3. 作成されたDockerイメージを使って、コンテナを作成実行
4. コンテナを操作する
5. コンテナの停止と開始、再起動と削除
6. イメージの削除

## 1.Dockerイメージを定義...dockerfileの作成
### Dockerfileとは
- Dockerイメージを作成するためのスクリプトのことです。イメージを作成するために必要な手順を記載していきます。

```dockerfile
# Dockerfile
FROM golang:1.12

RUN mkdir /echo_test
COPY main.go /echo_test

CMD ["go", "run", "/echo_test/main.go"]
```
```go
// コンテナ実行される、main.goファイル
package main

import (
  "fmt"
  "log"
  "net/http"
)

func main(){
  http.HandleFunc("/",func(w http.ResponseWriter, r *http.Request){
    log.Println("received request")
    fmt.Fprintf(w, "Hello Docker!!")
  })
  log.Println("start server")
  sv := &http.Server{Addr: ":8080"}
  if err := sv.ListenAndServe();err != nil{
    log.Println(err)
  }
}
```
- `FROM`や`RUN`と行ったキーワードを「インストラクション（命令)」と言います。
- Dockerfileではインストラクションを使って、イメージの構成を定義します。
- 上記のDocerfileでやっていること
  1. `FROM` で作成するDockeイメージのベースとなるイメージを指定しています。この場合、公式のgolang:1.12が指定されていて、golang:1.12のイメージがまだpullされていなければ、DockerHubからpullし、すでにpullされていれば既存のgolang:1.12のイメージを使用します。
  2. `RUN` でDockerイメージをビルドする時に、Dockerコンテナ内で実行するコマンドを定義しています。この場合、/echo_testというディレクトリを**コンテナ内**に作成しています。
  3. `COPY` で、ホストマシン上のファイルやディレクトリをDockerコンテナ内にコピーしています。この場合、Dockerfileと同じディレクトリ内にあるmain.goというファイルをコンテナ内の/echo_testディレクトリにコピーしています。
  4. `CMD` でDockerコンテナとして実行する際にコンテナ内で実行するプロセスをしています。 CMDはコンテナ起動時に1度実行されます。この場合、以下のようなコマンドを実行してgoのプロセスを実行します。

```
go run /echo_test/main.go
```
- Dockerfileに記載した各項目(この場合、`FROM`, `RUN`, `COPY`, `CMD`）が、それぞれ1つのレイヤーという単位で、そのレイヤーが重なって、イメージが作成されます。FROMで指定したイメージがベースとなるというのは、このような仕組みのためです。

### 2.Dockerイメージを作成...Dockerイメージをビルド
- Dockerfileを使用して、Dockerイメージをビルドします。

```docker
# docker image build -t イメージ名[:タグ名] Dockerfileのあるディレクトリ
docker image build -t example_go/echo:latest .
```
- `docer image build ` でイメージをビルドします。
- `-t` オプションを必ずつけたほうが良いです。`-t`オプションにより、イメージ名を指定することができます。
- `-t`オプション無しでもイメージの作成はできますが、イメージ名(REPOSITORY)とタグ(TAG)の値が`<none>`になります。ハッシュ値(IMAGE ID)での管理となり、わかりづらくなります。


```docker
$ docker image ls # docker imageの一覧を表示
REPOSITORY        TAG      IMAGE ID       CREATED             SIZE
<none>            <none>   a086e6cb87f0   53 seconds ago      774MB
example_go/echo   latest   806faed5d0a8   About an hour ago   774MB
```
- `docker image ls` コマンドで`example_go/echo`のイメージが作成されていることが確認できました。

### 3. 作成されたDockerイメージを使って、コンテナを作成実行
- 作成したイメージからコンテナを実行します。
- コマンドは`docker container run イメージ名`となりますが、いくつかオプション指定したほうが良いです。

```docker
# docker container run [options] イメージ名:[タグ名]
docker container run -d -p 9000:8080 example_go/echo:latest
```
- 上記のコマンドを実行後、`localhost:9000`にアクセスすると`Hello Docker!!`が表示されます。
<img width="294" alt="スクリーンショット 2019-05-20 0.54.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254591/4c96e5fc-d566-7b81-8d85-661c36d79b14.png">

- `-d` オプションで、コンテナをバックグラウンドで実行します。今回のようなサーバアプリケーションだとバックグラウンドで実行したほうが勝手が良いことが多いと思います。ただrailsなどをコンテナで実行する時に、あえて`-d`オプション無しで実行し、logを確認しやすくするようなこともあります。
- `-p`オプションでポートフォワーディングを設定します。今回のアプリケーションは8080のポートで実行されていますが、それはコンテナ内のことで、ポートフォワーディングの設定が無い場合、コンテナの外からポート8080にアクセスしても、コンテナ内のアプリケーションに到達できません。
- コンテナ外からきたリクエストをコンテナ内で実行しているアプリケーションに到達させるために、`-p {ホスト側ポート}:{コンテナ側ポート}` で、ホスト側の指定のポートにアクセスすると、コンテナ側の指定したポートでリクエストを送ることができます。
- また、`-p`オプションで、ホスト側ポートを指定しなかった場合、どのポートにアクセスするば良いのか調べる方法もあります。

```docker
$ docker container run -d -p 8080 example_go/echo:latest
2bdfce9e69c6b1ed81a7d6ea0e493e0867effa233d3f89aa8e27a793e1f29f90
$ docker container ls
CONTAINER ID  IMAGE  　　　　　　　　　  COMMAND                 CREATED        STATUS        PORTS 
 NAMES
2bdfce9e69c6  example_go/echo:latest  "go run /echo_test/m…"  3 seconds ago  Up 2 seconds  0.0.0.0:32769->8080/tcp  elated_roentgen
```
- PORTSに記載されている、`0.0.0.0:32769` がホスト側のポートになります。この場合、`localhost:32769`にアクセスすると、`Hello Docker!!`と表示されます。

### 4.コンテナを操作する
#### コンテナで任意のコマンドを実行する

```docker
# docker container exec [options] コンテナIDまたはコンテナ名 実行するコマンド
# 今回は例として、先ほど実行したコンテナに入ります。
docker container exec -it 2bdfce9e69c6 bash
root@2bdfce9e69c6:/go#
root@2bdfce9e69c6:/go# pwd
/go
root@2bdfce9e69c6:/go#
```
- `docker container exec` コマンドは実行しているDockerコンテナの中で任意のコマンドを実行できます。
- 標準入力を開いたままにする`-i`オプションと仮想ターミナルを割り当てる`-t`オプションを組み合わせて、対話的にシェルコマンド（bash)を実行しています。
- コンテナの中に入るときは`-it`をつけると覚えておくと便利です。

#### コンテナとホスト間でファイルのコピーをする

```docker
# docker container cp [options] コンテナIDまたはコンテナ名：コンテナ内のコピー元 ホストのコピー先
# コンテナ内の/echo_test/main.go を ホストの ~/以下にコピー
docker container cp 2bdfce9e69c6:/echo_test/main.go ~/
```

```docker
# docker container cp [options]  ホストのコピー元 コンテナIDまたはコンテナ名：コンテナ内のコピー先
# ホストの ~/.ssh/id_rsa をコンテナ内の/echo_test以下にコピー
docker container cp ~/.ssh/id_rsa 2bdfce9e69c6:/echo_test
```
- 上記のように`cp`コマンドでコンテナとホスト間でファイルのコピーができます。
- 例にあるように、ホストssh_keyをコンテナにコピーして、コンテナからGitHubのリポジトリにアクセスするなども可能です。

### 5.コンテナの停止と開始、再起動と削除
- 先ほど使用した、`docker container ls` コマンドでコンテナのIDやコンテナ名を確認することができます。
- 確認したコンテナIDやコンテナ名を引数にして下記のコマンドを実行することで、コンテナの停止と開始、再起動と削除ができます。
- コンテナの停止

```docker
docker container stop コンテナIDまたはコンテナ名
```
- コンテナの開始

```docker
docker container start コンテナIDまたはコンテナ名
```
- コンテナの再起動

```docker
docker container restart コンテナIDまたはコンテナ名
```
- コンテナの削除
- コンテナの削除はコンテナが停止している状態でないと基本的には実行できません。
- ただ`docker rm -f コンテナIDまたはコンテナ名`のように、`-f`オプションで強制削除することもできます。

```docker
docker container rm コンテナIDまたはコンテナ名
```
### 6.イメージの削除
- `docker image ls` または `docker images` コマンドでイメージの一覧を確認できます。
- そこで表示された対象のイメージのIDを引数に指定することで、イメージを削除します。

```docker
docker image rm イメージID
# または
docker rmi イメージID
```

## 最後に
- dockerfileだけでも簡単に環境構築ができます。
- 他にも便利なコマンドがありますので、[公式ドキュメント](https://docs.docker.com/engine/reference/builder/)や、`docker help`コマンドで確認できます。
- ComposeやSwarm、Kubernetesなどについても、まとめていきます。