コンテナ開発の始め方

社内向けの講演のために作ったスライドですが、わざわざGoogle プレゼンテーションにするまでもなかったし、特段秘密の情報もなかったので、Qiitaスライドの方に作りました
多分ちょいちょい追記します

---

## 対象

- まだコンテナ開発していない人
- コンテナ開発がうまくいかない人
- 既存プロジェクトをコンテナ化したい人

---

## お品書き

- コンテナの概念のおさらい
- コンテナでの開発
- docker のネットワーク
- docker-compose を使った開発の効率化

---

# コンテナの概念のおさらい

---
#VM と コンテナ
https://qiita.com/niisan-tokyo/items/88a53a1b4aa7ad60723e

![](https://camo.qiitausercontent.com/db04577e8708dd39bf6acec877d791aeeea90931/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3130323138322f66633738623434622d346266622d653433652d616264612d3466316430613063353166622e706e67)
 
---
## コンテナの目的

### ある前提となる状態のもとで、特定のコマンド(**のみ**)を実行したときの動作をシミュレートすること

---
## サーバの中のプロセス

```
$ ps -A
  PID TTY          TIME CMD
    1 ?        00:00:00 init
    2 ?        00:00:00 kthreadd
    3 ?        00:00:04 ksoftirqd/0
    5 ?        00:00:00 kworker/0:0H
    6 ?        00:00:51 kworker/u30:0
    7 ?        00:00:13 rcu_sched
    8 ?        00:00:00 rcu_bh
    9 ?        00:00:00 migration/0
   10 ?        00:00:00 khelper
   11 ?        00:00:00 kdevtmpfs
   12 ?        00:00:00 netns
...
```

---
## コンテナの中のプロセス (PHP-FPM)

```
# ps -A
PID   USER     TIME   COMMAND
    1 root       0:00 php-fpm: master process (/usr/local/etc/php-fpm.conf)
    6 www-data   0:00 php-fpm: pool www
    7 www-data   0:00 php-fpm: pool www
    8 root       0:00 sh
   13 root       0:00 ps -A
```

※最後の２つはdocker exec で侵入した自分自身のプロセスなので、それを除くとfpmのプロセスしか存在していない

---
# コンテナでの開発

---
## コンテナ開発で考えること

- そもそも対象がコンテナで開発すべきものか
- コンテナを動かすコマンドを知る(Dockerを使う)
- 役割に応じたコンテナの分離
- 適切なイメージの使用
- 適切なイメージの作成
- コアとなるコンテナの把握

---
### そもそも対象がコンテナで開発すべきものか

- コンテナは動きを再現するもの
- サーバを作るプロビジョニングツールの起点としてはコンテナで動かしてもいいが、プロビジョニングの対象はVMであるべき
- アプリケーションはほとんどがコンテナ開発して差し支えない

---
### コンテナを操作するコマンドを知る(Docker)

まずはこれだけ知っておけばいいってやつ

- docker run
- docker build
- docker ps

---
#### docker run

コンテナを実行する。

```
docker run [<OPTIONS>] <IMAGE_NAME>[:<TAG_NAME>] [<COMMAND>] 
```
イメージがローカルにない場合はdockerhubなど、外部レジストリのイメージを pull しつつコンテナを実行する

---
例1) コンテナをそのまま実行する

```
docker run hello-world
```
※結果は標準出力に出る

例2) PHPのalpineイメージを、コマンドラインで操作できるようなコンテナを実行する

```
docker run -it --rm php:alpine sh
```
---
#### 共有ボリューム(ディレクトリ)

`-v`オプションにより、コンテナ内のディレクトリとホストマシンのディレクトリを共有ボリュームにすることができる

例３) 現在のディレクトリをコンテナの`/var/www`との共有ボリュームにする

```
docker run -it --rm -v `pwd`:/var/www php:alpine sh
```

※厳密にはコンテナ内部の出力を永続化するためのものだけど、開発なので気にしない

---
#### docker ps
実行中のコンテナを一覧表示する

```
docker ps
```

`-a`オプションを付けると停止中のコンテナも一緒に表示する

```
docker ps -a
```

---
#### docker build

コンテナイメージを作成する

```
docker build -t <IMAGE_NAME>[:<TAG_NAME>] <DIR_NAME>
```

自分は、基本的にはDockerfileのあるディレクトリ上で実行している

例) 自分がphpの作業用コンテナをビルドするときのコマンド

```
dockder build -t niisan/php .
```

---

### 役割に応じたコンテナの分離

Dockerが使えるようになったら、次はコンテナの分離を考える

---

基本的に、一つのコンテナに付き一つの動作を担当する 
よくあるNginx + php-fpm + mysql + redis とかであれば、システムを4つのコンテナに分離できる

![system.jpg](https://qiita-image-store.s3.amazonaws.com/0/102182/0c0630fe-5dbc-8939-b61a-a035123368b6.jpeg)

---
#### 作業用コンテナがあるとなおよし
作業用コンテナとは、直接動作には関係ないが、パッケージの依存性解決など、いくつかの作業を実施するのに利用されるコンテナ

- nodeのyarn でnode_module のインストール
- composer install
- etc...

---
### 適切なイメージの使用
役割に応じてコンテナを分離したら、各コンテナにあったイメージを探す

- Dockerhubから公式のイメージを落とすのが手っ取り早い
 - mysql, redisなど、適当な環境変数を設定するだけで動くものは、イメージそのまま使える
 - 予めバージョン指定 (ex.] mysql:5.7) しておくと、バージョンブレイクで困らない
- nginx + php-fpm だと、nginx側に追加設定が必要なので、公式イメージをベースにカスタマイズしたイメージを自作する必要が出てくる

---
mysql イメージを直接使った例

```
docker run -d -e MYSQL_ROOT_PASSWORD=my-secret-pw -p 3306:3306 mysql:5.7
```

- 環境変数`MYSQL_ROOT_PASSWORD`を設定することで、id: root, pass: my-secret-pw でアクセス可能なmysql5.7のコンテナが立ち上がる
- これはポートフォワード`-p 3306:3306`しているので、外部から接続可能

---
### 適切なイメージの作成
composerを使うためのイメージなど、公式にないイメージなどは自作する

イメージの自作は

1. Dockerfileを作る
2. `docker build` でイメージを作る

で行う

---
例) composer入のphpイメージの作成

```Dockerfile
FROM php:alpine

RUN apk update && apk add unzip bzip2-dev
RUN docker-php-ext-install mysqli pdo_mysql
RUN curl -sS https://getcomposer.org/installer | php; mv composer.phar /usr/local/bin/composer ; mkdir /var/www

RUN docker-php-ext-install bz2

WORKDIR /var/www

CMD ["sh"]
```

---
### コアとなるコンテナの把握
運用フェーズで特に必要になるが、コンテナ群で置き換え不可能なコンテナ (コアとなるコンテナ) が何かを把握しておく

![system2.jpg](https://qiita-image-store.s3.amazonaws.com/0/102182/4bf5c9d8-f343-9c90-3c1b-ab0e3781a41f.jpeg)

---
#### エントリーポイント
アプリケーションのコアコンテナの中で、エントリーポイントになっているコンテナはどれかも把握する
例えば、 nginx -> php-fpm をコアとするアプリでは、ユーザーのエントリーポイントはnginxになる。

外部からnginxにブラウザなどでつなげるために、こちら側のポートから、コンテナの内部のポートにポートフォワードを設定する

```
docker run -d -p 8080:80 nginx
```
この設定は、Docker for Macであれば、 `localhost:8080`によって、コンテナ内部の80番ポートにアクセスできる

---
# docker のネットワーク

---
## コンテナ間の通信

これはどうやって実現できるか？

![telnet.jpg](https://qiita-image-store.s3.amazonaws.com/0/102182/5c15273a-c40d-9c50-7423-6e37d197f3a8.jpeg)

---
## dockerのネットワークを使った解決

dockerはdockerサーバ自体が提供しているネットワークが存在している。
このネットワーク上でaliasをつければ良い

例 ) php:alpineから、mysqlコンテナをdbという名前で他のコンテナからアクセスできるようにする

```
docker run -d -e MYSQL_ROOT_PASSWORD=my-secret-pw --name db  mysql:5.7
```

```
docker run -it --rm --link db:db php:alpine sh
```
---
php:alpineの中からdbをpingした様子

```
/ # ping db
PING db (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.122 ms
64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.093 ms
64 bytes from 172.17.0.3: seq=2 ttl=64 time=0.118 ms
64 bytes from 172.17.0.3: seq=3 ttl=64 time=0.080 ms
64 bytes from 172.17.0.3: seq=4 ttl=64 time=0.144 ms
64 bytes from 172.17.0.3: seq=5 ttl=64 time=0.086 ms
```

---
## docker の ネットワーク追加

dockerはデフォルトの内部ネットワークだけでなく、ユーザー定義のネットワークもできちゃう

「sample」という名前のネットワークを構築するコマンド

```
docker network create -d bridge --subnet 172.26.0.0/24 sample
```

---

「sample」上にmysqlを設置し、dbという名前でネットワーク内からなら繋げられるよう設定

```
docker run -d --network sample --network-alias db -e MYSQL_ROOT_PASSWORD=my-secret-pw mysql:5.7
```

php:alpineコンテナを同じネットワークに乗っける

```
docker run -it --rm --network sample php:alpine sh
```

---

php:alpine 内部から、dbにpingを打ってみる

```
# ping db
PING db (172.26.0.2): 56 data bytes
64 bytes from 172.26.0.2: seq=0 ttl=64 time=0.337 ms
64 bytes from 172.26.0.2: seq=1 ttl=64 time=0.234 ms
64 bytes from 172.26.0.2: seq=2 ttl=64 time=0.087 ms
64 bytes from 172.26.0.2: seq=3 ttl=64 time=0.087 ms
```
接続していることがわかる

---
複数のネットワークを使って、複雑なネットワーク構成のシステムを組むこともできる

![microservice.jpg](https://qiita-image-store.s3.amazonaws.com/0/102182/2ee8bbe4-7c1e-4b75-0682-981378d2fcf9.jpeg)


---
開発のまとめ

1. dockerコマンドの基本的なところを使えるようにしておく
2. 対象のシステムを役割ごとに分離する
3. 分離した役割ごとにコンテナのイメージを設定する
4. 各コンテナを立ち上げる
5. 必要があればネットワークの設定をする

---
# docker-compose を使った開発の効率化

---
## 不便なコンテナ開発

- コンテナを一つ一つ起動しなければならない
- ネットワーク設定を考えなければならない
- コンテナ起動コマンドのスニペットがぶくぶく太っていく

=> 「操作より設定」の概念を導入して解決

---
## docker-compose

- 設定ファイルを通して、コンテナを立ち上げるのに必要なコマンドを簡略化する
- ネットワークの設定も自動で設定してくれる
- 設定ファイルをリポジトリ管理することで、構成管理を透明化できる
- .env対応
- kubernetesのpodみたいなもん
- ECSのタスクみたいなもん

---
### 設定ファイル (docker-compose.yml)

一例としてlaravelのプロジェクトでniisan-tokyoがよく使っている設定です

```docker-compose.yml
version: '3'

services:

  workspace:
    build: workspace/
    volumes:
      - ${BASE_DIR}

  php-fpm:
    build: ./php-fpm
    volumes:
      - ${BASE_DIR}
    expose:
      - "9000"

  nginx:
    build:
      context: ./nginx
      args:
        - PHP_UPSTREAM=php-fpm
    ports:
      - "8080:80"
      - "444:443"
    volumes:
      - ./logs/nginx/:/var/log/nginx
      - ./nginx/sites/:/etc/nginx/sites-available
      - ./nginx/ssl/:/etc/nginx/ssl
      - ${BASE_DIR}

  db:
    image: mysql:5.7
    environment:
      - MYSQL_DATABASE=homestead
      - MYSQL_USER=homestead
      - MYSQL_PASSWORD=secret
      - MYSQL_ROOT_PASSWORD=root
    volumes:
      - mysql:/var/lib/mysql

volumes:
    mysql:
        driver: "local"
```
---
### docker-compose.ymlの説明

- version
- services
- image
- build
- environment
- ports
- volumes
- depends_on

---
#### version

docker-compose.ymlの記法のバージョン
docker-compose自体のバージョンによって使えたり使えなかったりする

例

```
version '3'
```
---
#### services

それぞれが動作するコンテナを意味している。
システムを分離できたら、それらを全部書き込んでいく

```
services:
  workspace:
    ...
    ...
  
  php-fpm:
    ...

  db:
    ...
```
---
#### image, environment, volumes

- imageはコンテナのイメージをそのまま使う場合に使用
- environmentは起動時に必要な環境変数 ( 配列形式で指定 )
- volumesは共有ボリュームの設定 ( 配列形式で指定 ) 

```
  db:
    image: mysql:5.7
    environment:
      - MYSQL_DATABASE=homestead
      - MYSQL_USER=homestead
      - MYSQL_PASSWORD=secret
      - MYSQL_ROOT_PASSWORD=root
    volumes:
      - ../mysql:/var/lib/mysql
```

---
#### build, ports, depends_on

- buildは自前のイメージを使う場合に、Dockerfileの存在するディレクトリを指定
- portsはポートフォワードするときのポートを設定
- depends_onは自分が立ち上がる時に一緒に立ち上がっているべきコンテナ( サービスを指定 )

---
```
  nginx:
    build:./nginx
    ports:
      - "8080:80"
      - "444:443"
    volumes:
      - ./logs/nginx/:/var/log/nginx
      - ./nginx/sites/:/etc/nginx/sites-available
      - ./nginx/ssl/:/etc/nginx/ssl
      - ${BASE_DIR}
    depends_on:
      - php-fpm
      - db
      - cache

```

---
## docker-composeコマンド

まずは以下を覚えておけば良い

- up
- down

---
### docker-compose up

サービスを立ち上げる
ターゲット指定した場合、depends_onに設定されたサービスが有る場合はそれらも一緒に立ち上げる

例) 設定されているコンテナ全部立ち上げる

```
docker-compose up
```

例) nginx コンテナをバックグラウンドで動作させる

```
docker-compose up -d nginx
```

---
### docker-compose down

立ち上がっているサービスを停止させ、コンテナを除去する
やることやって、作業を終了するときなどに使う

```
docker-compose down
```
---
### .env

docker-compose は.envが使えるので、環境変数をここで設定しておくことができる

```.env
COMPOSE_PROJECT_NAME=sample

BASE_DIR=../:/var/www
```

※`COMPOSE_PROJECT_NAME`の設定はプロジェクトごとにやっておくとサービス名などがかぶらないので便利

---
# まとめ

- コンテナ開発を始めよう
- システムを分離して役割に応じてコンテナを立てよう
- ネットワーク機能を使ってコンテナ同士をつなげよう
- docker-compose を使ってコンテナ開発を楽に使用