ようへいの日々精進XP

よかろうもん

Docker Compose チュートリアル

Docker Compose とは

Docker Compose とは複数のコンテナを利用して一つのサービスを提供する場合に YAML 一発で構築出来るようにするツール(という認識)で、以前は同様なツールfig というツールがあったと記憶しているがこの fig がそのまま Docker Compose に名前を変えたようだ。

Fig has been replaced by Docker Compose, and is now deprecated. The new documentation is on the Docker website.

なるほど、なるほど。


準備

Docker Compose のインストール

現状は Docker Compose 自体のインストーラーは存在せず、以下のようにインストールするしかないようだ。

curl -L https://github.com/docker/compose/releases/download/1.3.2/docker-compose-`uname -s`-`uname -m` > docker-compose
sudo cp docker-compose /usr/local/bin/
sudo chmod +x /usr/local/bin/docker-compose

ヘルプ

$ docker-compose --help
Define and run multi-container applications with Docker.

Usage:
  docker-compose [options] [COMMAND] [ARGS...]
  docker-compose -h|--help

Options:
  -f, --file FILE           Specify an alternate compose file (default: docker-compose.yml)
  -p, --project-name NAME   Specify an alternate project name (default: directory name)
  --verbose                 Show more output
  -v, --version             Print version and exit

Commands:
  build              Build or rebuild services
  help               Get help on a command
  kill               Kill containers
  logs               View output from containers
  port               Print the public port for a port binding
  ps                 List containers
  pull               Pulls service images
  restart            Restart services
  rm                 Remove stopped containers
  run                Run a one-off command
  scale              Set number of containers for a service
  start              Start services
  stop               Stop services
  up                 Create and start containers
  migrate-to-labels  Recreate containers to add labels

チュートリアル

サンプル各種

とりあえず rails

こちらを写経する。

まずは Dockerfile を作る。

FROM ruby:2.2.0
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev
RUN mkdir /myapp
WORKDIR /myapp
ADD Gemfile /myapp/Gemfile
RUN bundle install
ADD . /myapp

Gemfile を作る。

source 'https://rubygems.org'
gem 'rails', '4.2.0'

docker-compose.yml を作る。

db:
  image: postgres
  ports:
    - "5432"
web:
  build: .
  command: bundle exec rails s -p 3000 -b '0.0.0.0'
  volumes:
    - .:/myapp
  ports:
    - "3000:3000"
  links:
    - db

一旦、rails new する。

$ docker-compose run web rails new . --force --database=postgresql --skip-bundle

docker-compose.yml には bundle exec rails s -p 3000 -b '0.0.0.0' と指定されているので、rails new . --force --database=postgresql --skip-bundle で上書きするイメージで処理が終了するとカレントディレクトリに以下のようにファイルが展開されている。

$ ls -l
total 68
-rw-rw-r-- 1 vagrant vagrant  178 Jul 18 12:20 Dockerfile
-rw-rw-r-- 1 vagrant vagrant 1480 Jul 18 12:21 Gemfile
-rw-r--r-- 1 root    root    3819 Jul 18 12:25 Gemfile.lock
-rw-r--r-- 1 root    root     478 Jul 18 12:26 README.rdoc
-rw-r--r-- 1 root    root     249 Jul 18 12:26 Rakefile
drwxr-xr-x 8 root    root    4096 Jul 18 12:26 app
drwxr-xr-x 2 root    root    4096 Jul 18 12:26 bin
drwxr-xr-x 5 root    root    4096 Jul 18 12:26 config
-rw-r--r-- 1 root    root     153 Jul 18 12:26 config.ru
drwxr-xr-x 2 root    root    4096 Jul 18 12:26 db
-rw-rw-r-- 1 vagrant vagrant  183 Jul 18 12:21 docker-compose.yml
drwxr-xr-x 4 root    root    4096 Jul 18 12:26 lib
drwxr-xr-x 2 root    root    4096 Jul 18 12:26 log
drwxr-xr-x 2 root    root    4096 Jul 18 12:26 public
drwxr-xr-x 8 root    root    4096 Jul 18 12:26 test
drwxr-xr-x 3 root    root    4096 Jul 18 12:26 tmp
drwxr-xr-x 3 root    root    4096 Jul 18 12:26 vendor

ちょっと Gemfile を修正。

gem 'therubyracer', platforms: :ruby

JavaScript の Runtime である therubyracer をインストールするようにアンコメントして最後に docker-compose build を実行する。

$ docker-compose build

以下のように出力される。

db uses an image, skipping
Building web...
Step 0 : FROM ruby:2.2.0
 ---> 51473a2975de
Step 1 : RUN apt-get update -qq && apt-get install -y build-essential libpq-dev
 ---> Using cache
 ---> 76dbd0c53ce1
Step 2 : RUN mkdir /myapp
 ---> Using cache
 ---> c263028a071e
Step 3 : WORKDIR /myapp
 ---> Using cache
 ---> 3172fc182c6c
Step 4 : ADD Gemfile /myapp/Gemfile
 ---> 6b9bcc9f7ccb
Removing intermediate container a5e6dcd92778
Step 5 : RUN bundle install
 ---> Running in 1482b35b5d74
Don't run Bundler as root. Bundler can ask for sudo if it is needed, and
installing your bundle as root will break this application for all non-root
users on this machine.
Fetching gem metadata from https://rubygems.org/.......

(略)

Installing uglifier 2.7.1
Installing web-console 2.2.1
Your bundle is complete!
It was installed into /usr/local/bundle
 ---> f30b4667c9f9
Removing intermediate container 1482b35b5d74
Step 6 : ADD . /myapp
 ---> 396b0def9cdf
Removing intermediate container e711c5b5248e
Successfully built 396b0def9cdf

config/database.yml を以下のように修正。

development: &default
  adapter: postgresql
  encoding: unicode
  database: postgres
  pool: 5
  username: postgres
  password:
  host: db

test:
  <<: *default
  database: myapp_test

最後にコンテナを起動する。

$ docker-compose up -d

-d でデタッチモード。以下のように出力される。

$ docker-compose up -d
Creating composerails_db_1...
Creating composerails_web_1...

docker logs で確認。

$ docker logs composerails_web_1 
=> Booting WEBrick
=> Rails 4.2.0 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
[2015-07-19 07:06:16] INFO  WEBrick 1.3.1
[2015-07-19 07:06:16] INFO  ruby 2.2.0 (2014-12-25) [x86_64-linux]
[2015-07-19 07:06:16] INFO  WEBrick::HTTPServer#start: pid=1 port=3000

ブラウザで確認。

f:id:inokara:20150719161941p:plain

おお。


少しひねる

Sinatra + Redis でなんちゃって API システム

こちらを参考させて頂いて、チョー簡単 API システムを作ってみたいと思う。

Dockerfile を作成、というか先ほどの Rails のものをそのまま。

FROM ruby:2.2.0
RUN apt-get update -qq && apt-get install -y build-essential
RUN mkdir /myapp
WORKDIR /myapp
ADD Gemfile /myapp/Gemfile
RUN bundle install
ADD . /myapp

docker-compose.yml を以下のように。

redis:
  image: redis
  ports:
    - "6379"
web:
  build: .
  command: bundle exec ruby app.rb -o 0.0.0.0
  volumes:
    - .:/myapp
  ports:
    - "14567:4567"
  links:
    - redis
  environment:
    - REDIS_HOST=redis

アプリケーション(app.rb)は以下のような簡単スクリプト

#!/usr/bin/env ruby
#
require 'sinatra'
require 'redis'
require 'json'

r = Redis.new host: ENV["REDIS_HOST"], port:"6379"

get '/value/:key' do
  value = r.get params[:key]
  v = {
    key: params[:key],
    value: value
  }
  v.to_json
end

post '/data/:key' do
  msg = request.body.read
  r.set params[:key], msg
end

Gemfile は以下のように用意。

source 'https://rubygems.org'

gem "sinatra"
gem "redis"

docker-compose build でビルド。

$ docker-compose build

以下のように出力される。

Building web...
Step 0 : FROM ruby:2.2.0
 ---> 51473a2975de
Step 1 : RUN apt-get update -qq && apt-get install -y build-essential
 ---> Running in 8c1bc72000c3
Reading package lists...
Building dependency tree...
Reading state information...
build-essential is already the newest version.
The following package was automatically installed and is no longer required:
  libbison-dev
Use 'apt-get autoremove' to remove it.
0 upgraded, 0 newly installed, 0 to remove and 160 not upgraded.
 ---> 2ebb189c25b1
Removing intermediate container 8c1bc72000c3
Step 2 : RUN mkdir /myapp
 ---> Running in 3ae7afaa1941
 ---> f528a03f229b
Removing intermediate container 3ae7afaa1941
Step 3 : WORKDIR /myapp
 ---> Running in 114d4e12d4a6
 ---> 0cd25195c3ab
Removing intermediate container 114d4e12d4a6
Step 4 : ADD Gemfile /myapp/Gemfile
 ---> 33430f3a8848
Removing intermediate container c9333ca6b80b
Step 5 : RUN bundle install
 ---> Running in b3fac0dae84d
Don't run Bundler as root. Bundler can ask for sudo if it is needed, and
installing your bundle as root will break this application for all non-root
users on this machine.
Fetching gem metadata from https://rubygems.org/..........

(略)

Your bundle is complete!
It was installed into /usr/local/bundle
 ---> fd7d36d14b1e
Removing intermediate container b3fac0dae84d
Step 6 : ADD . /myapp
 ---> 5ab105adefab
Removing intermediate container 2b7359c7add1
Successfully built 5ab105adefab

docker-compose up -d でコンテナを起動。

$ docker-compose up -d
Creating composesinatra_redis_1...
Creating composesinatra_web_1...

各コンテナが起動していることを確認。

$ docker-compose ps
         Name                       Command               State            Ports          
-----------------------------------------------------------------------------------------
composesinatra_redis_1   /entrypoint.sh redis-server      Up      0.0.0.0:32845->6379/tcp 
composesinatra_web_1     bundle exec ruby app.rb -o ...   Up      0.0.0.0:14567->4567/tcp 

docker-compose logs で確認。

$ docker-compose logs web
Attaching to composesinatra_web_1
web_1 | [2015-07-19 07:29:10] INFO  WEBrick 1.3.1
web_1 | [2015-07-19 07:29:10] INFO  ruby 2.2.0 (2014-12-25) [x86_64-linux]
web_1 | == Sinatra (v1.4.6) has taken the stage on 4567 for development with backup from WEBrick
web_1 | [2015-07-19 07:29:10] INFO  WEBrick::HTTPServer#start: pid=1 port=4567

$ docker-compose logs redis
Attaching to composesinatra_redis_1
redis_1 | 1:C 19 Jul 07:29:09.431 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
redis_1 |                 _._                                                  
redis_1 |            _.-``__ ''-._                                             
redis_1 |       _.-``    `.  `_.  ''-._           Redis 3.0.3 (00000000/0) 64 bit
redis_1 |   .-`` .-```.  ```\/    _.,_ ''-._                                   
redis_1 |  (    '      ,       .-`  | `,    )     Running in standalone mode
redis_1 |  |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
redis_1 |  |    `-._   `._    /     _.-'    |     PID: 1
redis_1 |   `-._    `-._  `-./  _.-'    _.-'                                   
redis_1 |  |`-._`-._    `-.__.-'    _.-'_.-'|                                  
redis_1 |  |    `-._`-._        _.-'_.-'    |           http://redis.io        
redis_1 |   `-._    `-._`-.__.-'_.-'    _.-'                                   
redis_1 |  |`-._`-._    `-.__.-'    _.-'_.-'|                                  
redis_1 |  |    `-._`-._        _.-'_.-'    |                                  
redis_1 |   `-._    `-._`-.__.-'_.-'    _.-'                                   
redis_1 |       `-._    `-.__.-'    _.-'                                       
redis_1 |           `-._        _.-'                                           
redis_1 |               `-.__.-'                                               
redis_1 | 
redis_1 | 1:M 19 Jul 07:29:09.432 # Server started, Redis version 3.0.3

以下のようにデータを登録。

$ curl -X POST 127.0.0.1:14567/data/foo -d 'bar'

以下のようにデータを取得。

$ curl -s -X GET 127.0.0.1:14567/value/foo | python -m json.tool
{
    "key": "foo",
    "value": "bar"
}

おお、簡単。

スケールアウト、スケールイン

以下のような状態。

$ docker-compose ps
         Name                       Command               State            Ports          
-----------------------------------------------------------------------------------------
composesinatra_redis_1   /entrypoint.sh redis-server      Up      0.0.0.0:32850->6379/tcp 
composesinatra_web_1     bundle exec ruby app.rb -o ...   Up      0.0.0.0:32852->4567/tcp

Web サーバーをプラスで 2 台増やしたいなあと思ったら docker-compose scale を利用すればさくっとスケールアウト、スケールインが出来る。

以下のように docker-compose.yml を修正する。

redis:
  image: redis
  ports:
    - "6379"
web:
  build: .
  command: bundle exec ruby app.rb -o 0.0.0.0
  volumes:
    - .:/myapp
  ports:
    - "4567"
  links:
    - redis
  environment:
    - REDIS_HOST=redis

以下のように docker-compose scale の引数に web=3 のようにサービスに台数を指定して実行する。

$ docker-compose scale web=3

以下のように出力される。

$ docker-compose scale web=3
Creating composesinatra_web_2...
Creating composesinatra_web_3...
Starting composesinatra_web_2...
Starting composesinatra_web_3...

確認。

$ docker-compose ps
         Name                       Command               State            Ports          
-----------------------------------------------------------------------------------------
composesinatra_redis_1   /entrypoint.sh redis-server      Up      0.0.0.0:32850->6379/tcp 
composesinatra_web_1     bundle exec ruby app.rb -o ...   Up      0.0.0.0:32852->4567/tcp 
composesinatra_web_2     bundle exec ruby app.rb -o ...   Up      0.0.0.0:32854->4567/tcp 
composesinatra_web_3     bundle exec ruby app.rb -o ...   Up      0.0.0.0:32855->4567/tcp

おお、増えとる。

$ docker-compose logs 
Attaching to composesinatra_web_3, composesinatra_web_2, composesinatra_web_1, composesinatra_redis_1
web_3   | [2015-07-19 07:58:56] INFO  WEBrick 1.3.1
web_3   | [2015-07-19 07:58:56] INFO  ruby 2.2.0 (2014-12-25) [x86_64-linux]
web_3   | == Sinatra (v1.4.6) has taken the stage on 4567 for development with backup from WEBrick
web_3   | [2015-07-19 07:58:56] INFO  WEBrick::HTTPServer#start: pid=1 port=4567
web_2   | [2015-07-19 07:58:56] INFO  WEBrick 1.3.1
web_2   | [2015-07-19 07:58:56] INFO  ruby 2.2.0 (2014-12-25) [x86_64-linux]
web_2   | == Sinatra (v1.4.6) has taken the stage on 4567 for development with backup from WEBrick
web_2   | [2015-07-19 07:58:56] INFO  WEBrick::HTTPServer#start: pid=1 port=4567

ちゃんと web_2 と web_3 が起動している。ぢゃあ、次に減らしてみる。

$ docker-compose scale web=1
Stopping composesinatra_web_3...
Stopping composesinatra_web_2...
Removing composesinatra_web_3...
Removing composesinatra_web_2...

$ docker-compose ps
         Name                       Command               State            Ports          
-----------------------------------------------------------------------------------------
composesinatra_redis_1   /entrypoint.sh redis-server      Up      0.0.0.0:32850->6379/tcp 
composesinatra_web_1     bundle exec ruby app.rb -o ...   Up      0.0.0.0:32852->4567/tcp

おお、サクッとコンテナが減ってる。

簡単にスケールインとスケールアウトが体験出来たけど...気になるのが外部に晒すポートを固定出来なくなる点をどうするか...。


ということで

参考

簡単、便利

複数の Docker コンテナでサービスを起動しようとした時にコンテナ同士の IP やポート等を意識しなくても良いのは嬉しい(Docker Links をちゃんと理解出来ていないけど見事にそれをラッピングしてくれていると思う)。また、YAML で各サービス毎のコンテナを定義しておくことで docker-compose up 一発で起動出来るのも嬉しい限り。