docker

コンテナ開発の始め方

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


対象

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

お品書き

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

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


VM と コンテナ

https://qiita.com/niisan-tokyo/items/88a53a1b4aa7ad60723e


コンテナの目的

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


サーバの中のプロセス

$ 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

※最後の2つは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オプションにより、コンテナ内のディレクトリとホストマシンのディレクトリを共有ボリュームにすることができる

例3) 現在のディレクトリをコンテナの/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


作業用コンテナがあるとなおよし

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

  • 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イメージの作成

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


エントリーポイント

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

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

docker run -d -p 8080:80 nginx

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


docker のネットワーク


コンテナ間の通信

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

telnet.jpg


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


開発のまとめ

  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が使えるので、環境変数をここで設定しておくことができる

COMPOSE_PROJECT_NAME=sample

BASE_DIR=../:/var/www

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


まとめ

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