【初心者用】コンテナ実行環境デファクトスタンダード "Docker" をサクッと学ぶ


# はじめに

Dockerの操作は、はじめたての人にとっては少し難しく感じるかもしれません。
「ここを抑えておけばOK」目線で記載しました。困った時の辞書がわりに使用していただければと思います。

# 対象者

- Docker初心者

# 目次

1. まず、Dockerって何よ。
2. とりあえず、「Hello World!」
3. Dockerfileの作成を覚えよう
4. Docker Hubへ登録
5. よく使うDockerコマンド一覧

# 1. まず、Dockerって何よ。

Docker社が開発している、コンテナ型の仮想環境を作成、配布、実行するためのプラットフォームです。Dockerを理解する上では以下の3つがポイントになってきます。

- コンテナ
    - ホストマシン上の隔離される領域
    - コンテナの実体は Linux の Namespace という機能でほかと分離されたただの１プロセス
    - Dockerイメージ上に読み書き可能なレイヤを追加し、指定されたプロセスを隔離された状態やシステムリソース上で実行すること
    - コンテナはあくまでホストマシンの１プロセスであり、仮想サーバではない
- Dockerイメージ
    -  コンテナの実行に必要なパッケージで、ファイルやメタ情報を集めたもの
    -  コンテナを実行するときに必要なファイルシステム。読み込み専用。
    -  レイヤーで情報管理している。ベースは何か？何をインストールするか？環境変数は？ 等
- Dockerfile
    - 既存のイメージにレイヤーをさらに積み重ねるためのテキストファイル








# 2. とりあえず、「Hello World!」

今回は、Ubuntuの仮想環境を作成してそこでコマンドを入力してみたいと思います。
Docker Hub上の Dockerイメージをローカルにダウンロード

```bash
$ docker pull ubuntu:18.04 # ($ docker pull イメージ名:タグ名)
```


ローカルマシンに取得しているDockerイメージの一覧表示

```bash
$ docker images
```

イメージからコンテナ作成
※ホストにあるディレクトリをマウントする際はこの時に`-v`オプションで指定する


```bash
$ docker run -it --name test-ubuntu ubuntu:18.04 /bin/bash
# ($ docker run -it --name {任意のコンテナ名} {利用したいイメージ名} /bin/bash)
```

-  docker run オプション(run: pull, create, start同時に実行)
    - `-it`:ターミナルで操作可。コンテナ内でbashプロセスを新規で立ち上げ、手元の環境でDocker内入力できる。
    - `-d` : バックグラウンドで実行。付けなければ、すぐにコンテナ内に入る。
    - `--name` : コンテナに名前をつける。これは指定した方が良い。
    - `-v` : ホストマシンのファイルをコンテナ内でも使う。マウント。


更新できるか確認
```bash
root@9e139a6ea690:/# apt-get update
root@9e139a6ea690:/# exit
```

バックグラウンドでコンテナを起動 (-d) → コンテナ内に入らない

```
$ docker run -it -d --name test-ubuntu ubuntu:18.04 /bin/bash
```

コンテナが起動されているか確認
```
$ docker container ps
```

コンテナ内に入る
```bash
$ docker exec -it test-ubuntu /bin/bash
root@3ccfddbac17d:/# 
root@3ccfddbac17d:/# exit
# (docker exec コンテナ名 コマンド名)
```

コンテナの停止
```
$ docker container stop test-ubuntu
```

コンテナが停止されているか確認
```
$ docker container ps
```

イメージの削除

```
$ docker rmi 58db3edaf2be (-f)
```
※エラーが出たら 「-f」をつけて強制削除


<プラスα>

ポートフォワーディング (-p)
- ポートの紐付け
- コンテナのような仮想環境を利用する際には、ホスト側からアクセスするためにポートを紐づける必要

```
$ docker run -p 5000:5000 [コンテナ名] /bin/bash
```

ローカルにあるディレクトリをDocker内にマウントする方法

```
$ docker run -it -v {ホストマシンの任意のディレクトリ}:{Dockerコンテナ内の任意のディレクトリ} {利用したいイメージ} /bin/bash
```








# 3. Dockerfileの作成を覚えよう


今回は、先ほど利用したubuntuのイメージをベースに、Pythonの環境を構築してみようと思います。
※本来ならPythonのベースイメージを使用すれば楽ですが、ハンズオンということでこのようなやり方になっています。

Dockerファイルを効率的に作成するには以下のSTEPを踏めば良いと思います。

```
1. ベースにするDockerイメージを作成する
2. `docker run -it <docker-image> sh` でコンテナ内部で作業
3. 一行ずつ実行してうまくいったらメモ
4. 失敗したら`exit`して再度`docker run`
5. 全部うまくいったらDockerfile作成
```



(例) Python3の環境構築 + pip3でNumpyをインストール
まずは、ubuntuのベースイメージを利用してコンテナを立ち上げます

```bash
$ docker run -it --name python-container ubuntu:18.04 /bin/bash
root@7f1739b10280:/# 
```

ひとつずつコマンドを入力してメモをしていきます
```bash
root@7f1739b10280:/# apt update

root@7f1739b10280:/# apt-get install -y python3

root@7f1739b10280:/# python3 -V
Python 3.6.9

```

これをDockerfileにまとめると、、、

```Dockerfile
FROM ubuntu:18.04
RUN apt-get update \
    && apt-get install -y python3 
```

[参考]Dockerfileの中身

- `FROM` --- ベースとなるイメージ
- `RUN` --- docker build　時に実行。複数書いてOK
- `WORKDIR` --- 実行ディレクトリの変更。mkdir & cd の両方をやる。つまり移動されている。
- `COPY` --- ホストからコンテナへコピー
- `ADD` --- COPYとほぼ一緒。転送元に圧縮ファイルを指定すると自動的に展開してコピー。なので圧縮したファイルの時はこっちが良き。
- `CMD` --- 実行中のコンテナにコマンドと引数を提供。１ファイルに1つだけ。
- `ENTRYPOINT` --- コンテナ実行時に必ず実行したいコマンド指定
- `VOLUME` --- ディレクトリマウントポイント
- `EXPOSE` --- ポート公開



次に、このDockerfileからイメージ作成

```
$ docker image build -t python-env .
# ($ docker image build -t イメージ名:tag Dockerfileが置かれているディレクトリパス)
```


- イメージの詳細確認

```bash
$ docker inspect python-env #(docker inspect イメージ名)
```


# 4. Docker Hubへ登録

Dockerへログインします
Docker loginとは、Dockerのレジストリにログインするための手段です。レジストリにログインすることによって、レジストリの各機能が使えるようになります。

https://www.kagoya.jp/howto/cloud/container/dockerlogin/#:~:text=Docker%20login%E3%81%A8%E3%81%AF%E3%80%81Docker,%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%82%92%E6%8C%87%E3%81%99%E8%A8%80%E8%91%89%E3%81%A7%E3%81%99%E3%80%82


```
$ docker login
```

- Dockerhubにアップロードする際は、イメージ名を`アカウント名/イメージ名:tag`にする
- 最初から、Dockerhubにuploadする予定ならこの形式にしておくのがベター

```
$ docker tag python-env [アカウント名]/python-env
```

- ローカルからイメージをプッシュ

```
$ docker push [アカウント名]/python-env
```



# 5. よく使うDockerコマンド一覧

起動中のコンテナ確認

```
$ docker container ps
```

コンテナな停止

```
$ docker stop コンテナID
```

イメージ削除 (強制削除オプション)

```
$ docker rmi イメージID (-f)
```

コンテナ一覧

```
$ docker container ps -a
```

コンテナ削除

```
$ docker rm コンテナID
```

未使用オブジェクトを消す (prune)

```bash
$ docker container prune
$ docker network prune
$ docker image prune
```
# その他

### privileged

- コンテナに対して特権モードを付与
- 通常のコンテナではアクセスできないホストコンピュータへのアクセスが可能
- 特権モードが付与されたコンテナではコンテナ内から直接Docker deamonを実行したりホストに接続されているハードウェアにアクセス可能


### --device

- ホスト上のUSBデバイスなどのアクセスできるようにする
- privilegedでも実現可能だが特定デバイスにはこちらを使うのが良い

### sbin/init

- DockerはデフォルトでLinux系のコマンドに制限しているので、「systemctl」などのシステム系のコマンドが使えたり動作しない
- Ubuntuは通常「sbin/init」では起動せず「bin/bash」で起動

### PID1

- Dockerイメージは動かしたいアプリを「デーモン(バックグランドのサーバー)」として動かすのではなく、そのアプリを直接動かすのが基本
- 例えば、Dockerでcentosのコンテナ上にnginxをインストールした場合は、systemctl経由でnginxを起動させるのではなく、直接nginxを起動させるのが一般的
- Dockerは「起動時のコマンドが全てのプロセスの原点となる」
- デーモン(アプリ)が動いていてもPID1のプロセスが終了するとコンテナが停止してしまうので、一番重要なアプリをデーモンとしてではなくPID1としてt起動するのが基本


### Supervisorで複数の子プロセスを管理

- Dockerの基本は1コンテナに1プロセス
- ただし、結びつきが強い2つのアプリを1つのコンテナにまとめたい時もある
- その時は「supervisor」という他のプロセスを管理するソフトウェアを使うことが一般的

https://docs.docker.jp/engine/admin/using_supervisord.html


### docker-compose (編集中)

複数のコンテナで構成されるアプリケーションを、Dockerイメージのビルドや各コンテナの起動・停止などをより簡単に行えるようにするツール

```
$ docker-compose --version
```

```yaml
version: '3.7'

services:
  nginx:
    image: nginx
    ports:
      - 8080:80
```

`docker run -p 8080:80 nginx` とほぼ同じ動き
- 異なる点としては、docker-composeでは専用のNetworkを作成・使用


例
```yaml
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` : 起動するコンテナの定義
- `image` : コンテナを起動するDocker Image を指定
- `build` : docker buildの実行情報を記述 ( context, Dockerfile )
- `volumes` : ボリュームのマウント
- `ports` : ポートの開放
- `environment` : 起動するコンテナへ環境変数を定義
- `command` : Dockerfileで定義されている CMD の上書き

docker-composeのコマンド

起動
```
$ docker-compose up
```

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

# 終わりに

コンテナ実行環境のデファクトスタンダードであるDockerについて学ぶ機会になれば良いと思います。エンジニアならDockerは触れて当たり前の時代になってしまったのでしっかり学ぶことをお勧めします。

# 参考記事


- [実践 Docker - ソフトウェアエンジニアの「Docker よくわからない」を終わりにする本](https://zenn.dev/suzuki_hoge/books/2022-03-docker-practice-8ae36c33424b59)

※この記事がとても良いので是非読んでみてください

- [docker-compose - 入門 Docker](https://y-ohgi.com/introduction-docker/3_production/docker-compose/)