No.6 新卒未経験エンジニアがDockerを使ってチョメチョメしてみた〜チュートリアル編〜


前回の予告通りDockerです！！
最近よく聞くし、使ってみたいけど何からやったら良いか分からないという方のために、新卒未経験エンジニアの僕がDockerを使ってみました！
て記事を書いたら安心して導入出来るんじゃないかな、と思います。
今回はDockerのチュートリアルを試してました。

## Dockerって何？
Dockerの公式イメージキャラクターはこちらのクジラくんです。
![docker.png](https://qiita-image-store.s3.amazonaws.com/0/205104/890462e9-4ad7-a3cc-3a61-ad9b7c3bec81.png)

上の四角いブロックみたいなの何？、て思うかもしれません。

そもそもDockerという言葉の意味をみなさまご存知でしょうか？
Dockerとは、「港湾労働者」という意味があります。
造船所に行くと「1番ドック」のように、船を泊めておく駐車場的なスペースを「Dock」と呼んでいます。（某有名海賊漫画で学びました。）
そのDockで働く人たちなので、Dockerです。
貿易に携わるDockerたちの仕事道具がコンテナ(Container)です。
コンテナには細かい規格や積み方のルールがあり、また、船による効率的な海上輸送を実現する重要な手段になっています。

「Docker」はまさにこのイメージの通りで、コンテナの中に自分の使いたいOSやミドルウェア、ファイルシステムを自由に入れることが出来ます。
いやー、なんとも秀逸な名前だなぁ、と思います。

## Dockerの仕組み
Dockerは「コンテナ型の仮想環境」を構築するツールです。
仮想環境には
・ホスト型
・ハイパーバイザー型
・コンテナ型
があります。
ホスト型、ハイパーバイザー型とコンテナ型とでは大きな違いがあります。
ホスト型、ハイパーバイザー型は仮想マシンレベルの仮想化を行い、コンテナ型はOSレベルの仮想化を行っています。
<img width="1283" alt="比較.png" src="https://qiita-image-store.s3.amazonaws.com/0/205104/9cc69030-18ec-96eb-a790-6de4bffa7081.png">

ホスト型・ハイパーバイザー型は、PCの中に仮想的にもう1台PCを立ち上げるようなイメージです。
そしてコンテナ型はホストOSがLinuxであれば、わざわざもう1台のPCを立ち上げるようなめんどくさいことはしなくて良いのです。
こうすることで、コンテナ型の大きなメリットが見えてきます。

・仮想マシンよりも起動が早いこと
　→サクサク動く！
・リソースの消費が少ないこと
　→サクサク動く！！
・起動する環境に依存せずに動かせる
　→サクサク開発！！！

案件ごとに異なる環境を構築できるため、特定のPC依存を回避出来ますし、ミドルウェア導入や新インフラ環境のテストが各自のPCで可能になります。
なんとまぁ素晴らしいことでしょう！！
僕自身説明下手なのとまだまだ理解が浅いので詳しく説明出来ないのですが、とりあえず多くのサイトで語られているのは上記のようなメリットです。
コンテナに限ったイメージでいえば、巨大なコンテナ船の中にたくさんのプールがあり、そのプールにはそれぞれ別のコンテナ船が浮かんでいるというような感じですかね。

## イメージとコンテナ
ではコンテナの中にどんなものを入れていくか、またどのように入れるか気になるところだと思います。
コンテナは「イメージ(image)」という、コンテナの設計書のようなものを元に作られます。
恒例の、僕のイメージをお伝えします！
旅行にいく前の夜を想像してみてください。
忘れ物をしたくない方は持っていくものをリスト化してそれを元にカバンに物を詰めていきます。
そのリスト化された項目が書かれた紙がイメージ、そのリストを元に作られた旅行カバンがコンテナです。
ちなみに僕は用意は当日の朝にする派です笑

AWsを使ったことある方は、AMIを想像してもらえればと思います。
イメージを実体化させたものがコンテナで、また、同じイメージから複数のコンテナを作成することが可能です。

## 何はともあれ事前準備
まずは Dockerをインストールしましょう！
[公式サイト](https://docs.docker.com/docker-for-mac/install/)からインストール出来ます。

![docker公式.png](https://qiita-image-store.s3.amazonaws.com/0/205104/63ba0b28-ebbf-48ce-9249-73dc6bd88979.png)

インストール出来て、うまく起動出来ればmacのメニューバーのところにクジラのロゴが現れます。
<img width="333" alt="dockerロゴ.png" src="https://qiita-image-store.s3.amazonaws.com/0/205104/31b30ef9-5a25-2611-cd6c-33045465ebe9.png">

## いざ実践！
参考にさせて頂いたのはこちらのサイトです。
[公式チュートリアルで始めるDocker](https://dev.classmethod.jp/tool/docker/start-docker-tutorials/)

### Docker起動確認
まず、きちんとDockerが起動出来ているか確認します。

```text:ターミナル
$ docker version

Client:
 Version:      17.09.0-ce
 API version:  1.32
 Go version:   go1.8.3
 Git commit:   afdb6d4
 Built:        Tue Sep 26 22:40:09 2017
 OS/Arch:      darwin/amd64

Server:
 Version:      17.09.0-ce
 API version:  1.32 (minimum version 1.12)
 Go version:   go1.8.3
 Git commit:   afdb6d4
 Built:        Tue Sep 26 22:45:38 2017
 OS/Arch:      linux/amd64
 Experimental: true
```

Dockerのバージョンが表示されたら起動確認OKです。
続いて以下のコマンドを打ってみてください。

### コンテナ作成
```text:ターミナル
$ docker run -it ubuntu bash

Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
ae79f2514705: Pull complete 
5ad56d5fc149: Pull complete 
170e558760e8: Pull complete 
395460e233f5: Pull complete 
6f01dc62e444: Pull complete 
Digest: sha256:506e2d5852de1d7c90d538c5332bd3cc33b9cbd26f6ca653875899c505c82687
Status: Downloaded newer image for ubuntu:latest

root@38f5848d06eb:/# 
```

「docker run」というコマンドはコンテナを起動する際に打つコマンドです。
その元となるイメージがここでは「ubuntu」を指定しているのですが、そんなものはないので、ネット上からpullしてきています。
オプションの「-it」は「-i」と「-t」を両方行う、という意味でそれぞれ
-i：interactiveの意味で、コンテナのシェルとinteractiveにコマンドをやり取りすると言う意味
-t：仮想端末を割り当て
です。
ubuntuの後についている「bash」はコンテナを起動して最初に動かすコマンドです。 
最後にrootユーザーになっているのは無事ubuntuを起動出来た証拠です。

### Commit！！
元のイメージをカスタマイズすることも出来ます。
試しにcurlコマンドを打ってみます。

```text:ターミナル
root@38f5848d06eb:/# curl 127.0.0.1

bash: curl: command not found
```

そんなコマンド知らねぇ、と怒られます。
DockerのImageは最小構成のミドルウェアしか入ってないため、必要なものを自分で追加する必要があります。

```text:ターミナル
root@38f5848d06eb:/# apt-get install -y curl

Reading package lists... Done
Building dependency tree       
Reading state information... Done
E: Unable to locate package curl
```

エラーになったのはapt-getのパッケージの取得を全く行っていないためだとか。
これを解決するにはupdateコマンドを叩いて必要なパッケージを取得してあげる必要があるようです。

```text:ターミナル
root@38f5848d06eb:/# apt-get -qq update
root@38f5848d06eb:/# apt-get install -y curl
・・・
・・・
root@38f5848d06eb:/# curl 127.0.0.1
curl: (7) Failed to connect to 127.0.0.1 port 80: Connection refused
```

Failedと出てはいますが、curlコマンドは通っているので無事にミドルウェアを追加出来たことを確認出来ました。
この変更をcommitしてあげる必要があります。
ここら辺はgitと同じ要領で行うことが出来ます。
psコマンドを使うことで先ほどのubuntuイメージを元に作成したコンテナのIDを確認出来ます。
一旦、exitコマンドを打ち、コンテナからexitしてからpsコマンドを打ちます。

```text:ターミナル
root@38f5848d06eb:/#　exit

exit

$ docker ps -a

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                    PORTS               NAMES
38f5848d06eb        ubuntu              "bash"              16 minutes ago      Exited (7) 11 seconds ago                       friendly_keller
```

コンテナIDが38f5848d06eb だということが分かったので、そのIDを指定してcommitします。

```text:ターミナル
$ docker commit 38f5848d06eb kosuke/curl:1.0

sha256:43f98db0a2f026ed6c06c8f7c5f424a375dada8e0953c2644233b6b5deedfb7a
```

この時、最後の「kosuke/curl:1.0」は何でも良いのですが、「:」以下はTAGとして認識されます。
おそらくバージョン管理に利用したりするんだと思います。
なので、

```text:ターミナル
$ docker images

REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
kosuke/curl               1.0                 43f98db0a2f0        11 days ago         177MB
```

と表示されます。
これでubuntuにcurlがインストールされたイメージが作成されました！！

### イメージからコンテナ起動
では作成したイメージを元にコンテナを起動してみましょう！

```text:ターミナル
$ docker run -it kosuke/curl:1.0 bash

root@f9b24c878481:/# curl 127.0.0.1
curl: (7) Failed to connect to 127.0.0.1 port 80: Connection refused
```

今度は何もしなくてもcurlコマンドが通ったかと思います。
これはcurlが入ってますよ、という設計書（イメージ）を元に作成したコンテナだからです。

### Dockerfileを作ってみよう！
Dockerイメージ作成時のコンフィグファイルです。
つまり、手順書のようなものでこいつを共有することでどんな複数に人が同じ環境を作り出せるようになるのです。
大事なやつです。

```text:ターミナル
$ mkdir DockerTest
$ cd DockerTest
$ vim Dockerfile

FROM ubuntu:14.04
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y vim

```

FROMでベースImageを指定し、RUNで自動実行するスクリプトを指定します。

### Dockerfile
作成したDockerfileを指定してイメージを作成してみます。

```text:ターミナル
$ docker build -t kosuke/testimage:1.0

Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM ubuntu
 ---> 747cb2d60bbe
Step 2/4 : RUN apt-get update
 ---> Using cache
 ---> 8a13d832a50e
Step 3/4 : RUN apt-get install -y curl
 ---> Using cache
 ---> 95ff1ddebfac
Step 4/4 : RUN apt-get install -y vim
 ---> Using cache
 ---> fe1f32a75803
Successfully built fe1f32a75803
Successfully tagged kosuke/testimage:1.0
```

-tオプション以降は例によって何でも良いです。
作成したイメージを確認します。

```text:ターミナル
$ docker images

REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
kosuke/testimage          1.0                 fe1f32a75803        11 days ago         230MB
```

はい！
無事作成出来ました！

### コマンド実行してみる
Dockerfile内でCMDキーワードを使うことでdocker runした際に、任意のコマンドを実行出来ます。
今回はpingコマンドを実行させてみることにしましょう！

```text:ターミナル
$ vim Dockerfile

FROM ubuntu:14.04
RUN apt-get update && apt-get install -y curl vim
CMD ping 127.0.0.1 -c 30
```

ちなみにRUNのコマンドは「&&」でつなげてまとめることが出来ます。
Dockerfileを編集出来たらbuildしていきます。

```text:ターミナル
$ docker build -t kosuke/testimage:1.1 .

Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM ubuntu
 ---> 747cb2d60bbe
Step 2/4 : RUN apt-get update && apt-get install -y curl vim iputils-ping
 ---> Using cache
 ---> ac0b52a45fdf
Step 3/4 : CMD ping 127.0.0.1 -c 30
 ---> Using cache
 ---> 6bd251e733b3
Step 4/4 : ENTRYPOINT ping
 ---> Running in 9426e3bb8666
 ---> a6c3e8e65687
Removing intermediate container 9426e3bb8666
Successfully built a6c3e8e65687
Successfully tagged kosuke/testimage:1.1
```

ちなみに、Dockerfileの記述で過去に同じ指示をしている場合、Imageがキャッシュ化されていますので、Buildは高速に終わります。
イメージを作成出来たら早速コンテナを起動させて、pingコマンドが実行されるか確認してみましょう。

```text:ターミナル
$ docker run -t kosuke/testimage:1.1

PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.058 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.067 ms
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.071 ms
64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.068 ms
64 bytes from 127.0.0.1: icmp_seq=5 ttl=64 time=0.068 ms
```

はい！何とか無事pingコマンドが実行されました！！
また、Dockerfileの中身を以下のように変えても同じような結果が得られます。

```text:ターミナル
$ vim Dockerfile

FROM ubuntu
RUN apt-get update && apt-get install -y curl vim
ENTRYPOINT ["ping"]

$ docker run -t kosuke/testimage:1.2 .
$ docker run -t kosuke/testimage:1.2 127.0.0.1 -c 5

PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.037 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.067 ms
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.056 ms
64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.066 ms
64 bytes from 127.0.0.1: icmp_seq=5 ttl=64 time=0.067 ms
```

エントリーポイントを用いることで、Dockerを起動時にあらかじめ指定したコマンドを実行することが出来ます。
ここで一つの疑問が。
CMDとエントリーポイントは何が違うのか・・・

長くなるので割愛しますが簡潔にまとめますと
CMD：docker run時の引数でCMD commandのcommand部分を全て上書きして実行
エントリーポイント：docker run時の引数がENTRYPOINT ["command"] のcommand の引数として実行
という違いがあります。
詳しくは以下の記事で紹介されていますので興味があればご参考にしてください。
[[docker] CMD とENTRYPOINT の違いを試してみた](https://qiita.com/hihihiroro/items/d7ceaadc9340a4dbeb8f)

コンテナの起動とストップは以下のコマンドで行います。

```text:ターミナル
$ docker ps -a

CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS                        PORTS               NAMES
352373b0b5aa        kosuke/testimage:1.2      "/bin/sh -c 'ping ..."   12 minutes ago      Exited (0) 11 minutes ago                         priceless_shirley
4038641f5e36        kosuke/testimage:1.1      "/bin/sh -c 'ping ..."   23 minutes ago      Exited (127) 23 minutes ago                       ecstatic_perlman
3ab31c3cc95f        kosuke/testimage:1.0      "/bin/sh -c 'ping ..."   27 minutes ago      Exited (127) 27 minutes ago                       sad_darwin

$ docker stop 352373b0b5aa
352373b0b5aa

$ docker start 352373b0b5aa
352373b0b5aa
```

最後にコンテナの削除とイメージの削除を行ってみます！
・コンテナの削除

```text:ターミナル
$ docker ps -a

CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS                        PORTS               NAMES
352373b0b5aa        kosuke/testimage:1.2      "/bin/sh -c 'ping ..."   12 minutes ago      Exited (0) 11 minutes ago                         priceless_shirley
4038641f5e36        kosuke/testimage:1.1      "/bin/sh -c 'ping ..."   23 minutes ago      Exited (127) 23 minutes ago                       ecstatic_perlman
3ab31c3cc95f        kosuke/testimage:1.0      "/bin/sh -c 'ping ..."   27 minutes ago      Exited (127) 27 minutes ago

$ docker stop 352373b0b5aa
352373b0b5aa

$ docker rm 352373b0b5aa
352373b0b5aa 
```
起動中のコンテナを削除をする時は一旦stopさせてから削除するかオプションで-fをつけて削除します。

・イメージの削除

```text:ターミナル
$ docker images

REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
kosuke/testimage          1.2                 6bd251e733b3        2 weeks ago         230MB
kosuke/testimage          1.1                 a62c9ca758f3        2 weeks ago         230MB
kosuke/testimage          1.0                 a62c9ca758f3        2 weeks ago         230MB

$ docker rmi 6bd251e733b3

Untagged: kosuke/testimage:1.2
Deleted: sha256:0c19b849e34f7711501cfacd34dd63dcb523cb78ffb4068da1ba61dc92ebfd18
```

コンテナが起動中の場合、イメージの削除が出来ないのでその場合はコンテナを停止させてから、イメージの削除を行います。

## まとめ
イメージの作成からコンテナの起動、コンテナの削除とイメージの削除までを一連の流れで確認してみました。
実際に手を動かしてやってみると割と分かり易く、イメージも湧きやすかったです。
最初の「Dockerって何？」のところのイメージがあったから分かりやすかったからかもしれません！
やはり、身近な例に例えてイメージをするのが良いのかもしれません。
現在、Dockerで開発環境の構築に勤しんでいるので出来次第、また記事にして投稿しようと思っているのでお楽しみに！！