ざっくりDockerことはじめ：実務でDockerfileからコンテナを作ろう

## 0.この記事の要約

### Dockerコンテナの、実務に振った超基本的な使い方まとめ

今や実務でも趣味でも使わないことがない、Dockerコンテナ。

主にバックエンドやインフラを触るのであれば
ほぼ確実に出てくる便利な仕組みです。

そんなDockerコンテナについて超簡単な説明記事となります。
もともと仲間内で書いたものですが、せっかくなので公開です。


### 対象読者

以下のような方が対象になります

* 今までなんとなーく使ってきた

* 初めて実務で触るから、今一度基礎的な使い方を勉強したい！

要は雰囲気でDockerを使ってきた方々がメインです。
私が実務で使用した、Dockerの使い方をここでおさらいしましょう。

### ほぼ使い方と考え方のまとめ

実務の開発でさっさと使いたいけど、どうすればいいんだって方向けです。

**チュートリアルを装ってはいるものの、かなり端折った記事になります**

要所要所参考にしつつ、別のチュートリアル記事をご参照頂いた上で
理解を深めたほうが良いかもしれません。

<u>**あくまで技術の付箋としてご利用ください。**</u>

![Heli Container.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235750/d0449b94-9629-d0dd-de68-740cde52f358.png)
突然のイラスト。

### ご注意

<u>インストール方法や、アーキテクチャには殆ど触れません。</u>

一般にDockerの仕組みを知っておいたほうが良いと言えます。
その例として、DockerはホストOSに強く依存する機能を
コンテナ内で実行することが不可能ですが…
なぜ不可能なのかは、この記事には記載しません。

Dockerアーキテクチャを別途ご参照ください。

## 1. Dockerって何？

### A.仮想マシン（VM）の一種です。

仮想化マシン（以下 VM）とは…

* ざっくり書けば…
  
  **<u>コンピュータの中にて動作するコンピュータをソフトウェアで再現する</u>**

* もうちょっと踏み込むと…？
  
  **<u>OSの中で、別のOSを動かす</u>**

ものを仮想マシンといったりします。

### ここでの仮想マシンは?

Windowsの中でUbuntuを動かしたりする技術のこと。

**一般には…**

仮想マシンを動かすOS = **ホストOS**

仮想マシンの中で動いてるOS = **ゲストOS**

といいます。

## 2. Dockerで”仮想マシン”を動かすには？

### Docker上では：仮想マシン ＝ コンテナ

Dockerでの仮想化技術は、 **コンテナ型** といいます。

ゆえに、**Dockerで実際に動く仮想マシン** を  **コンテナ** とも言えます。

つまり、コンテナを作って実行することが、Dockerで仮想マシンを動かす事になるのです
（端折りすぎてだいぶ乱暴な説明です）

### コンテナ作るやり方概要

それでは自分でDockerコンテナを作ってみるまえに、全体の流れを確認します。
自前のDockerコンテナを作成するには、ざっくりと下記画像の手順を実行します。

![Docker説明.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235750/bbd85479-e724-0b37-69c2-8dd626cac652.png)

要するに…

1. **Dockerfileを作る**

2. **Dockerfileからイメージをビルドする**

3. **コンテナを実行する**

の手順を踏む必要があるのです。

では、実際にDockerfileを作るところから追っていきましょう。

## 3. 実際にDockerfileを作ってみよう

まずは下記のステップに則って作ってみましょう。

### Dockerイメージの設計図＝Dockerfileを作る

開発業務において、開発中のプログラムをDocker上で動かしたい場合
最初にDockerfileを作成し、イメージをビルドします

Dockerfileは一定の記法がありますが
おおよそCUI（コマンド）でゲストOSの操作を行うため
**Dockerfileの大半を占めるのはだいたいOSのCUIコマンドと言えます。** 

なので

* Windows Server Coreなどのベースイメージの場合
  
  → PowerShell,Batch

* Ubuntu,AlpineなどのLinux系ベースイメージの場合
  
  → ash,bash,shなどの ShellScript

となります。

Shell芸人の方なら非常に相性がよいと言えます。

### 実際のDockerfileを見てみよう

広告＆マルウェアブロック用DNSサーバコンテナの

Dockerfileを例に挙げて見てみましょう。
実際のDockerfileソースはこちら↓

  [adblock-dns-v2/Dockerfile at master · MisoChan/adblock-dns-v2 · GitHub](https://github.com/MisoChan/adblock-dns-v2/blob/master/Dockerfile)
↑のファイルにコメント（＝”##”）解説したものが下記になります↓

```dockerfile
## FROM: ここでベースとなるイメージを指定
FROM alpine:latest 

## WORKDIR: ビルド時やコンテナで動かす時のホームディレクトリ
WORKDIR /work

## EXPOSE: Dockerに対して、UDP:53番ポートを開けたほうが良い、と教えている
##         TCPポートなら 80/tcp と書く（← の場合はTCP:80番ポート）
EXPOSE 53/udp

## ENV…コンテナに環境変数をセットしている
ENV DNSMASQ_SETTINGS_FILE "/etc/dnsmasq.conf"
ENV DNS_PRIMARY_SERVER "1.1.1.1"
ENV DNS_SECONDARY_SERVER "8.8.4.4"
ENV MAL_RESPONSE_SERVER "0.0.0.0"
ENV MAL_RESOLV_SERVER_v6 "::"
ENV CACHE_SIZE="7500"

## COPY：ホストOSから、Dockerイメージへファイルコピー
##       パスの記載順は ホストOS側 → ゲストOS側 へコピーとなる
COPY ./dnsmasq.conf ./dnsmasq_tmp.conf
COPY ./malware_adblock.sh ./malware_adblock.sh
COPY ./static ./static

## RUN：ゲストOSのコマンドをここで実行している(ここではAlpineLinuxのash)
##      ﾜﾝﾎﾟｲﾝﾄ：ワンライナーで実行している理由
##      RUNは複数書くことも可能だが
##      RUNの数だけビルド時の中間イメージ生成され
##      ディスク容量を圧迫してしまう事をさける為、ワンライナーで書く。
RUN apk update && apk upgrade && \
    apk add bash dnsmasq && \
    rm -rf /var/cache/apk/* && \
    mkdir -p /etc/default/ && \
    rm -rf /var/cache/apk/* && \
    sh ./malware_adblock.sh ${MAL_RESPONSE_SERVER} ${MAL_RESOLV_SERVER_v6} && \
    echo -e addn-hosts=/work/hostnames.txt >> ${DNSMASQ_SETTINGS_FILE} && \
    echo -e conf-file=/work/domains.txt >> ${DNSMASQ_SETTINGS_FILE} && \
    echo -e cache-size=${CACHE_SIZE} >> ${DNSMASQ_SETTINGS_FILE}  && \
    echo -e server=${DNS_PRIMARY_SERVER} >> ${DNSMASQ_SETTINGS_FILE} && \
    echo -e server=${DNS_SECONDARY_SERVER} >> ${DNSMASQ_SETTINGS_FILE} && \
    cat ./dnsmasq_tmp.conf >> ${DNSMASQ_SETTINGS_FILE}

## CMD:最終的にイメージからコンテナを実行するときのコマンドを指定
##     似たようなコマンドにENTRYPOINTがある
CMD ["dnsmasq","--no-daemon"]
```

基本的には前述した通り

**だいたいゲストOS（ベースイメージ）側のコマンドが羅列しているだけ**ですが…

ファイルコピーや通信ポート、環境変数など

**ホストOSとどうしても歩調を合わせたいときにだけ**Dockerfile特有の記法が出てくる

ことを頭の片隅に入れておくと良いでしょう。

## 4. Dockerfileをビルドしよう

ここではDockerfileを元に、Dockerイメージのビルドをしてみましょう。

### 基本のDockerfileビルドコマンド

キホンのビルドコマンドは下記の通りです。

（日本語部分はよしなに書き換えてください）

```shell
$ docker build ファイルパス
```

### よく使うDockerfileビルドオプション

実際には下記オプションを使用しビルドを行ったりします

* <u>Dockerイメージを後で呼び出しやすいようにタグ付け</u>
  
  * ``-t イメージ名:バージョン`` : タグ付け構文。イメージ名とバージョンはお好きなもので。
    
    バージョンを付与しない場合は、自動的にlatest になる

* <u>ゴミファイルの生成を抑える</u>
  
  * ``--no-cache`` : キャッシュを使用しない
  
  * ``--force-rm=true`` : ビルド時に使う中間生成コンテナをビルド終了後常に消す

結果として↓ のようになります。（日本語はよしなに書き換えてください。）

```
$ docker build --no-cache --force-rm=true ファイルパス -t イメージ名:バージョン
```

例に出したDockerfileを``dns-adblock``という名前でイメージビルドするなら…

```bash
$ docker build --no-cache --force-rm=true ./ -t dns-adblock:latest
```

となります。

## 5. コンテナを動かそう

いよいよDockerイメージが完成したので
実際にコンテナを実行する準備が整いました。

### 基本的な動かし方

基本的には下記コマンドを実行するとコンテナが生成され、起動します。

```bash
$ docker run イメージ名:バージョンタグ
```

実際にはすでにあるDockerfileからイメージを作って起動
が大半の使い方かもしれません。

### 実務頻出オプションについて

Dockerコンテナを動かすことができたものの
何もオプションがない状態では、コンテナ外との通信受付（ホストOSやその外部からのアクセス）などができず
指定の通信ポートにアクセスしようとしても動かない。

なので、以下のオプションを付加して使用する場合がほとんどです。

* --name: コンテナに名前をつける
  
  指定無しの場合は適当な名前が自動的に付与されます。

* `-itd`：``-it -d``の複合技。
  
  * ``-it`` : 疑似TTY（Terminal）を生成しDockerコンテナの標準出力に接続する
    
    → docker runコマンド終了時にコンテナが終了せず、維持し続ける
    
        （ただしDockerfileのCMDに書かれたコマンドが終了するまで。）
  
  * ``-d``：デーモンモードとして裏で動き続ける
    
    `docker run` コマンド終了時、コンテナを動かしたまま抜け出します
    
    （見た目は通常のコマンドの振る舞いと同じ）

* -p ホスト側アクセス受付ポート：コンテナへのフォワード先ポート 
  
  * ホストOSやその外側で受け付ける通信ポートと、コンテナ自体のポートを繋げる
    
    X:Y と書いた場合… ホストのポート X → コンテナポートY へポートフォワードする

これら上記のコマンドを付加した結果として↓のようになります

```bash
$ docker run -itd --name="コンテナ名" -p ホスト側アクセス受付ポート：コンテナへのフォワード先ポート イメージ名:バージョンタグ
```

前章の例に出したDockerイメージを実行するなら

```bash
$ docker run -itd --restart=always --read-only --name="malware_block_dns" -p 53:53/udp malware_block_dns:latest
```

となるはずです。

### コンテナ一覧の表示方法

イメージからデプロイされたコンテナの一覧を表示するには

```bash
$ docker ps -a
```

### コンテナの起動・停止方法

すでにあるコンテナを開始するには

```bash
$ docker start コンテナ名orハッシュ 
```

コンテナを一時停止するには

```bash
$ docker stop コンテナ名orハッシュ 
```

### コンテナの消し方

動かした後の後片付けとして消去するには

```bash
$ docker rm コンテナ名orハッシュ 
```

が挙げられます。

しかし、コンテナを一時停止しないと消去することができないので

```bash
$ docker rm -f コンテナ名orハッシュ 
```

を使って強制削除することが殆どです。

### さらに凝ったことをやりたくなったとき

例えば以下の事をやりたくなったら、関連ワードを調べると良いでしょう。

* **Dockerコンテナの中に直接Terminal接続して入りたい**
  
  **→ Docker exec**

* **コンテナ同士で通信させたい**
  
  **→ Docker Network**

* **Dockerコンテナ内データを永続化したい、ホストOSのディレクトリをマウントしたい**
  
  **→ Docker Volume**

* **複数のコンテナをひとまとめに起動・停止したい**
  
  **→ Docker Compose**

など、多岐にわたる知識があなたを待っております。
が、今回は動かすだけの記事なので割愛。

## 6. おわりに

Docker自体は開発において、今やどこでもある時代。
至極真っ当な開発現場であればDockerは頻出する技術とも言えます。

未だにホストOS型特有の、数GBにも登る仮想マシンファイルをやり取りする現場もありますが
そんな巨大ファイルのやり取りから、数KBのDockerfileのやり取りに切り替えれば
<u>仮想環境自体もGit管理ができ、巨大ファイルのDLを待たずに済む</u>ようになります。

Dockerに代表されるコンテナ型VMの使用方法を覚えることで
サクッと構築し、サクッと動かして、より快適に、効率よく開発を行い
浮いた時間は更に楽しい事へ手を伸ばせるような
そんな幸せのヒントになれば幸いです。

## Q. そもそもなぜ仮想マシンは必要なの？

### A.どこでも同じ環境でプログラムを動かしたい

なぜVMやコンテナを使うのか。
一番の理由は

**環境の差による動作不良を避けるため。**

です。

実務にて開発を行ったり、サーバー構築する際どうしでもつきまとうのが…

**システム環境の差。**

開発者個人のコンピュータや、サーバーは、当たり前ですが
CPU、メモリ、OS等 完全に同一ではなく、何らかの違いがあるわけです。
この違いによってソフトウェアは動かなくなることが多々あります。
俗に言うおま環（お前の環境が悪い）が発生するわけですね。

これを解消するためにVMをコピーし共有、その中でプログラムを動かせば
**同じ環境を再現できるので、おま環を克服する**のが目的だったりします。