Docker最速（拙速？）入門

# この記事の内容

世の中に遅れること250週（もう５年前からあるのね…）、
ようやっと[Docker](https://www.docker.com/)がどういうものなの分かってきました。
ということで、「Dockerって何？」という、数日前の私に向けた説明を書いてみます。
もちろん入門記事や詳しい解説などは世の中に既にたくさんあるので、
この記事では最速で「ここまで理解すれば後は自力で調べられるようになる」的な記事を目指します。

Dockerに関する多くの記事では、Dockerが従来の仮想環境とどう違うのかについて説明されますが、
この記事では逆に、主に **Dockerが従来の仮想環境と基本的に同じである** ことを説明しています。
Dockerとは何なのかと思ってこの記事を見るような人であれば、あとは自分で調べられると思います。

# 前提知識・想定読者
この記事ではDockerは、仮想環境を構築するもの、という位置づけです。
VirtualBoxやVMware、Hyper-Vなどの仮想環境を使ったことがあれば、感じはつかめるかと思います。
Linuxコマンド等についての基本的な知識は前提としています。

# 検証動作環境
AmazonLinuxで動作を検証しましたが、
この記事では「この通りに追えば同じ結果が得られる」ということは重視していません。
目的は大雑把な概要の理解ですので。

# 触ってみる
## インストール
さっそくインストールしていきます。
AmazonLinuxであればyumを使って、リポジトリ追加後インストール可能です。

```console:Dockerのインストール・起動
# リポジトリを追加
$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

# インストール
$ sudo yum install docker-ce

# 起動
$ sudo service docker start
```

Dockerをインストール・起動できました。
ここまでで解説することは特にありませんが、
Dockerは、「ユーザが自分のhome下にファイルを持ってきて軽く試せるもの」ではなく、
「管理者がシステムにインストールしないと試せない」というものです。
また以降では`docker`コマンドを使っていきますが、これはroot権限が必要です。

## 仮想環境にCentOSを立ち上げる
とりあえずDockerを使ってCentOS7の仮想環境を作ってみます。

```console:CentOSのダウンロード
# CentOSの起動に必要なものをダウンロードしてくる
$ sudo docker pull centos:7
```

このコマンドは約70MBのダウンロードを伴って完了します。
70MBというのは、OSのインストールメディアにしては遥かに小さい量ですね。
次に起動・接続します。

```console:CnetOSを起動、接続
# CentOSを "my_centos" という名前で起動
$ sudo docker run -it -d --name my_centos centos:7

# my_centosに接続（インタラクティブシェルを立ち上げ）
$ sudo docker exec -it my_centos bash

[root@3d4bd7b19efd /]#    # ←別プロンプトになり、仮想環境に入ったことがわかる
```

CentOSを起動して、そこに接続しました。
プロンプトの表示が変わり、仮想環境のCentOS内にログインできたのがわかります。
`3d4bd7b19efd` は適当に割り振られたホスト名です。
なお、この **起動と接続はほぼ一瞬で完了します** 。

また用語として、立ち上げた仮想環境"my_centos"のことを「コンテナ」、
それを立ち上げる元になった"centos:7"を「イメージ」と言います。
（dockerは「コンテナ型仮想化」という技術を使っているというのを聞いたことがあるかもしれませんが、
ここではそのような知識は不要です。）

次に仮想環境内でいくつかコマンドを叩いてみます。

```console:いくつかコマンドを実行してみる
# lsすると、実環境とは別のファイルシステムが見える
[root@3d4bd7b19efd /]# ls
anaconda-post.log  bin  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

# sshはインストールされていない
[root@3d4bd7b19efd /]# ssh localhost
bash: ssh: command not found

# yumでパッケージのインストールができる
[root@3d4bd7b19efd /]# yum -y install wget
（省略）
Complete!

# exitで仮想環境を抜ける
[root@3d4bd7b19efd /]# exit

[user-hoge@ip-XXX-XX-XX-XX ~]$    # 仮想環境の外に戻ってきた
```

`ls`で見えるファイルシステムはホスト環境（仮想環境の外側）のものとは別のもので、
どうやら仮想環境として立派にファイルシステムを持っているようです。
また、`ssh`がインストールされていないなど、かなり最小限のOSであることがわかります。
とは言え、`yum`でパッケージインストールなどをして自由に環境構築することができます。

## 一旦まとめ
以上でDocker上で仮想環境を構築する所までが完了し、
「Dockerでは仮想環境が作れる」というこの記事の本質部分はもう説明完了です。

Dcokerと言ったら「コンテナ型仮想化」「マイクロサービス」とか、そういう話が多いんですが、
まずは「仮想環境を作るもの」とだけ言って貰えればだいぶ分かりやすいと思うのは私だけでしょうか…？
操作としては上記のようにコマンドライン上から環境を起動して、
そこにログインして環境の構築等をするのが基本です。

実際、私は上記だけでもかなり便利だと思いました。
仮想環境上でパッケージのインストール等いろいろ試行錯誤して、不要になったらぶっ壊してやり直す。
このスクラップ＆ビルドがすごく手軽に、速くできるというのは大きいです。

# イメージ化でセーブポイント
## イメージの作成
多くの仮想環境では、現在の環境全体を保存しておくことができます。
環境全体のセーブポイントを作って、いつでもその状態に戻ってこれるということですね。
この機能はもちろんDockerにもあります。

```bash:コンテナのイメージ化（仮想環境から出た後、ホスト側での操作）
# コンテナの一覧を表示
$ sudo docker ps -a
CONTAINER ID    IMAGE       COMMAND       CREATED             STATUS          PORTS    NAMES
a9f4485b7e45    centos:7    "/bin/bash"   About an hour ago   Up 6 minutes             my_centos

# 現在の "my_centos"コンテナを、"my/centos7"という名前のイメージにする
$ sudo docker commit my_centos my/centos7
sha256:5a00eab57015e7c94f6f526f843a50f779cb1167c3693354f5e69ee08378c7c5

# "my_centos"コンテナは不要なので削除
$ sudo docker rm -f my_centos
```

`ps`コマンドは動作中のコンテナの情報を表示するコマンドです。
`-a`をつけると停止中のコンテナも表示されます。今はまだ停止しているコンテナはありませんが、
基本的に`-a`をつけることの方が多いと思うので、セットで覚えておいてください。
また、STATUSに"Up"と書いてあり、これはコンテナが起動中であることを表しています。
先程`exit`しましたが、これはあくまでもインタラクティブシェル(bash)を終了しただけであり、
**コンテナはバックグラウンドで動作** しています。

次に`commit`コマンドでコンテナのイメージ化を行っています。
この`commit`コマンドがセーブポイントの作成コマンドです。
作成したイメージには名前をつけることができますので、今回は"my/centos7"としました。

また最後に、イメージ化の元になったコンテナ"my_centos"を削除しました。
別に消さなくてもいいんですが、名前の使い回しと、削除コマンドの紹介のためです。
`rm`コマンドがDockerの **コンテナを削除する** コマンドです。
`-f`は起動中のコンテナでも強制的に削除するという意味です。
紛らわしいですが、 **イメージの削除は `rmi <イメージ名>`** というコマンドで行います。
この辺は最初は混同してしまいがちですが、コンテナとイメージはしっかり区別しましょう。

さて、気づいたでしょうか？この`commit`は、起動したままの"my_centos"コンテナに対して行っています。
OSイメージの保存はその仮想環境を停止させなければできないのが普通ですが、Dockerではそれができます。
また、ここでは一度`exit`してから`commit`していますが、別ターミナルから`commit`してやれば、
インタラクティブシェルから出る必要すらありません。
これにより、まさに爆速で環境のスクラップ＆ビルドが可能になります。

## イメージからコンテナを起動する
次に今作成したイメージから新たにコンテナを作ってみます。
その前にまず、きちんとイメージが保存されていることを確認します。

```bash:Dockerイメージの一覧を表示
$ sudo docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
my/centos7          latest              5a00eab57015        About a minute ago  302MB
centos              7                   e934aafc2206        2 weeks ago         199MB
```

`images`は、Dockerが管理しているイメージの一覧を表示します。
さきほど作成した"my/centos7"と、最初にpullしてきた"centos"の２つがあるので、予想通りですね。
ちなみにDockerイメージの名前は"(作者名)/(イメージ名)"とする決まりがあり、
"/"のないイメージ名は、公式に配布されるものなど、「偉い」イメージだけに許されます。
（もちろんローカルで試す分には何でも構いませんが）
また、"centos:7"というのは `<イメージ名>:<TAG>` という指定の仕方をしていたのでした。
TAG省略時は"latest"を指します。

次に保存した"my/centos7"イメージからコンテナを起動します。

```bash:
# "my/centos7"イメージから"my_centos"コンテナを作成
$ sudo docker run -it -d --name my_centos my/centos7
98413eb9e3af3ea828768be868b0e96188694fa3217c1878a27e985a598950d3

# コンテナ一覧
$ sudo docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                           PORTS                     NAMES
98413eb9e3af        my/centos           "/bin/bash"         8 seconds ago       Up 7 seconds                                               my_centos

# コンテナ"my_centos"に入る
$ sudo docker exec -it my_centos bash

# 先程インストールしたwgetがちゃんと入っている
[root@4b4375a3778b /]# wget
wget: missing URL
Usage: wget [OPTION]... [URL]...
```

最初に`pull`してきた"centos:7"からコンテナを起動したのと同様の操作で
イメージからコンテナを起動できたことがわかると思います。
ここからもわかる通り、"centos:7"イメージも、何も特別なイメージではなく（名前以外）、
普通に、誰かが上記のように作成したイメージに過ぎないということです。
そしてこのように、誰かが作成したイメージは[DockerHub](https://hub.docker.com)に公開されており、
コマンドラインから`search`コマンドで検索することができ、`pull`してくることができます。

## Dockerを使った環境構築の流れ
ここまでのまとめとして、Dockerを使った環境構築の流れについてまとめます。

1. 核となるイメージをダウンロードして来る　（`pull`）
2. イメージからコンテナを作成する　（`run`）
3. コンテナ内で環境構築を行う　（`exec bash`してコンテナ内で操作）
4. 適宜コンテナをイメージ化しておく　（`commit`）
5. 何か間違いなどに気づいたら、途中のイメージからコンテナを作成し、3.へ。

核となるイメージは、"centos:7"のようなクリーンOSイメージでも良いし、
DockerHubに公開されている、特定の用途に特化したイメージでも構いません。

Dockerを使うことで、以上のような流れを、本当に気軽に行うことができます。
また様々なイメージが公開されていますので、
何か新しいサービスなどを試してみたいときには、環境構築を自分でやらずとも、
他の人が作ったイメージからコンテナを作成すればすぐに試すことが可能になります。

# 少しだけ実践
ここまでで、私が言いたかった「Dockerは仮想環境を作るもの」という話は完結しています。
ですがこれだけでは、なぜDockerがここまで騒がれるのか実感が湧きにくいので、
少し実践的な例をやってみたいと思います。

題材として[tomcat](http://tomcat.apache.org/)(HTTPサーバ)を立ち上げてみます。
立ち上げて、動いていることを確認するだけなので、tomcatについては何も知らなくて大丈夫です。

## tomcatのインストール
コンテナ内でtomcatをインストールして起動してみます。

```bash:tomcatをインストールして、サービス起動
# my_centosにもう一度接続
$ sudo docker exec -it my_centos bash

# tomcatをダウンロードして解凍
[root@3d4bd7b19efd /]# wget http://www-eu.apache.org/dist/tomcat/tomcat-9/v9.0.7/bin/apache-tomcat-9.0.7.tar.gz
[root@3d4bd7b19efd /]# tar xvf apache-tomcat-9.0.7.tar.gz

# javaをインストールする
[root@3d4bd7b19efd /]# yum -y install java

# tomcatを起動
[root@3d4bd7b19efd /]# apache-tomcat-9.0.7/bin/startup.sh
（省略）
Tomcat started.
```

tomcatが立ち上がりました。
tomcatはデフォルトでは8080番ポートで接続できるので、ブラウザからアクセスしてみます。 [^1]

```
http://XXX.XXX.XXX.XXX:8080
```

はい、接続できませんね。
まぁ仮想環境内で動いているサービスですから、
ホストのポート8080にアクセスしてもダメなのはごもっともです。
ということで、仮想環境内のポートへアクセスできるよう設定してみましょう。

[^1]: 「XXX.XXX.XXX.XXX」はEC2インスタンスのIPアドレス。セキュリティグループの設定は完了しているものとします。

## ホストのポートをコンテナ内のポートにマッピングする

```console:コンテナ内のポートに接続できるようにする
# 仮想環境から出る
[root@3d4bd7b19efd /]# exit

# "my_centos"コンテナを"my/tomcat"という名前でイメージ化
$ sudo docker commit my_centos my/tomcat
sha256:5a00eab57015e7c94f6f526f843a50f779cb1167c3693354f5e69ee08378c7c5

# "my/tomcat"イメージから"tomcat"コンテナを作成
# その際、ホスト側のポート18080を仮想環境内ポート8080にマッピング
$ sudo docker run -it -d -p 18080:8080 --name tomcat my/tomcat
98413eb9e3af3ea828768be868b0e96188694fa3217c1878a27e985a598950d3

# コンテナの一覧を表示。tomcatが起動中(Up)で、ポートがマッピングされているのがわかる。
$ sudo docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                     NAMES
98413eb9e3af        my/centos           "/bin/bash"         6 minites ago       Up 6 minites                                  my_centos
9c14f4176b34        my/tomcat           "/bin/bash"         4 seconds ago       Up 3 seconds        0.0.0.0:18080->8080/tcp   tomcat

# bashは立ち上げず、直接tomcatサービス起動のシェルスクリプトを実行
$ sudo docker exec tomcat /apache-tomcat-9.0.7/bin/startup.sh
Tomcat started.
```

まず、ここまで作った環境をセーブするために`commit`を行っています。
今回はポートをマッピングするのが目的ですが、
一度`run`したコンテナにポートマッピングの設定だけを追加することはできないようです。
なので、一度イメージ化して、そこから新たなコンテナを起動するという手順を踏みます。

さて、本題のポートのマッピングですが、`-p 18080:8080`という部分で行っています。
書式としては、`-p (ホスト側のポート):(コンテナ内のポート)`となります。
複数のポートマッピングが必要な場合は以下のように並べればOKです。
`-p 18080:8080 -p 18081:8081`

最後に、インタラクティブシェル(`bash`)は立ち上げず、直接実行スクリプトを叩いています。
これでコンテナ内部でtomcatサーバが立ち上がります。

さてこれでコンテナ内の8080ポートへ、ホストの18080ポートで接続できるようになりました。
早速試してみましょう。
手元のWindowsマシンのブラウザから、接続を行ってみます。 [^1]

```
http://XXX.XXX.XXX.XXX:18080
```
![tomcat.png](https://qiita-image-store.s3.amazonaws.com/0/243204/eaa48dd1-7862-efe8-bca1-29b7f8b65677.png)

バッチリアクセスできていますね。

## Dockerでサービスを動かす利点
ポートをマッピングすることで、Docker内で動いているtomcatに接続できました。
さてここで少し考えてみると、ブラウザからこのtomcatにアクセスして人にとっては、
このtomcatが **Docker内で起動されているかどうかは、全く意識されません** 。
また、この記事では全く触れていませんが、
実はDockerは、ホストOS上で動作させた場合と **ほぼ同じ速さ、負荷で動作** [^2] します。

[^2]: 普通のDockerの記事はむしろここが一番焦点として語られますが、この記事は自分で動かすことを主眼にしているため、豪快に無視しています。まして、どうしてこのような高パフォーマンスを出せるのか、などは完全に範囲外です。

これが何を意味しているかというと、
**Dockerで構築した環境は、実験環境ではなく、そのまま本番環境になりうる** ということです。
普通の仮想環境であれば、その環境自体を動かす負荷が高いので、
「中で作った環境をホスト環境に改めて構築する」という作業が必要になるかと思います。
しかし、負荷が高くなく速度も問題ないのであれば、そんな面倒なことはしたくないわけです。
ということで、Dockerで作ったコンテナをそのままWebアプリなどとして公開するということが考えられます。

また、コンテナがそのまま使えるということは別の利点もあります。
コンテナ内では、ホストOSとは別のファイルシステムが用意されます。
つまり、 **サービス１つにつき１つのファイルシステムを持てる** ということです。
これは別のサービスによって環境が汚されてしまったり、
逆にそのサービスが外の環境に影響を与えることがない、ということを意味しています。
なので最早、ホスト上にいくつものサービスを起動させておくよりも、
１つだけのサービスを持ったコンテナを複数起動させた方が管理がし易いという流れになるわけです。

# おわりに
以上、Dockerを試す手順について簡単にまとめました。
Dockerにはすごい要素がいっぱいありますが、
まずはただの仮想環境だと思えば理解が簡単になると思ったので記事にしてみました。
初めは便利だけど難しそうなイメージでしたが、この記事くらいの理解でも、
かなり恩恵を受けられるかと思いますので、是非試してみて頂ければと思います。

# 主に参考にさせて頂いたページ
[さくらのナレッジ Docker入門](https://knowledge.sakura.ad.jp/13265/)