追記
- 2016年2月: 以下は2015年5月時点の情報です。いずれ更新または新規投稿するかもしれませんが、本記事の情報(概念的な部分はともかく特にデモ)は古い可能性がありますのでご了承ください。
- 2016年8月: Docker1.12のSwarm modeの記事を書きました
はじめに
先日ちょうどDockerのオフィシャルなオンラインMeetupでMachine/Compose/Swarmの話をしていたので、今更ながらキャッチアップついでにまとめてみました。
初心者向けに概要を掴む程度の内容の投稿です。
なお、日本時間だと深夜1時でしたので参加された方は少ないかもしれませんが動画が公開されています。
DockerのMeetup自体は毎週行われており、Docker社のブログにまとめが載るのでチェックしておくと良いかもしれません。
次回(5/19)はRegistry2.0の話です。
ということでそろそろ本題です。
Machine
Docker MachineとはDockerがインストールされたホストマシンを作成するものです。
作成先はローカルのVM、クラウドサービスともに可能です。
現時点では以下のプロバイダに対応しています。
- aws-ec2
- digitalocean
- azure
- gce
- openstack
- rackspace
- softlayer
- virtualbox
- vmware
既に大量にサポートされていますね。
ただ、DigitalOceanで試した様子ではまだまだ挙動が怪しくて、Ubuntu-14-04-x64以外はちゃんと動きませんでした。(fedora21、Ubuntu15.04、coreos-stableを試しました)
また、Ubuntu-14-04-x64でもオプションでプライベートネットワーキングを有効にしようとしたらDockerサービスが正常に立ち上がらない状態でVMが出来上がってしまいました。
まだベータ版なので、この辺りは仕方無いですね。
なお、2015年5月時点ではバージョン0.2が最新です。
Compose
Docker Composeとはyml形式の設定ファイルに従って複数のDockerコンテナで構成される環境を構築したり操作したりするツールです。
以前はFigの名称で知られていました。
通常はローカルにDockerコンテナを立てますが、Machine/Swarmと組み合わせることでマルチノード上にDockerコンテナを立ち上げることも可能です。
また、例えば開発環境/検証環境/本番環境があった場合に、共通部分をcommon.yml等に共通化しておき、それぞれの環境用のファイル(production.yml等)でextendsするようなこともできます。(参考)
なお、2015年5月時点ではバージョン1.2が最新です。
Swarm
Docker SwarmとはDockerのクラスタリングをサポートするツールです。
複数のDockerコンテナを1台のクライアントからdockerコマンドを使って一元管理できるようになります。
前述のMeetupの動画によると現状のDockerAPIのカバー率は85%程度だそうです。0.3では100%を目指すとのことです。
dockerコマンドでの操作では様々なフィルターを追加できます。
例えば以下のようなものがあります。
-
-m 2g
- 2GBのメモリを確保する -
-e constraint:storage==ssd
- ssdというラベルが貼られたノード(Dockerホスト)上にDockerのインスタンスを立てる -
-e constraint:region!=us*
- us以外のリージョンにDockerのインスタンスを立てる -
-e affinity:image==redis
- redisと同じノード上にDockerのインスタンスを立てる
これらのフィルターはデフォルトではhard(条件に合わなければ実行できない)に適用されますが、~==
または~!=
を使うことでsoft(可能な場合は条件を満たすが不可能な場合はフィルターを捨てて実行する)に変更することもできます。
フィルターの詳細な説明はこちらにあります。
現状では残念ながらSwarmで使うラベル(上記の例だとどのノードがSSDかとか、どのUSリージョンか等)をMachineで設定することはできませんが、次バージョンではできるようになるようです。
なお、2015年5月時点ではバージョン0.2が最新です。
全部組み合わせて使ってみる
今回はDigitalOcean上にノードを立ててみようと思います。他のクラウドサービスを使っている方は適宜読み替えて頂ければと思います。
これを機にDigitalOceanを使いはじめるという方はこちらから申し込んで貰えるとお得(多分お互い)のはずです。
なお、コマンドは全てローカルで実行します。
インストール
まずはdocker(engine)をインストールします。
# 最新版のインストール
wget -qO- https://get.docker.com/ | sh
# ユーザにdockerコマンド実行可能にするためdockerグループに追加。
sudo usermod -aG docker <username>
次にMachineをインストールします。
sudo wget https://github.com/docker/machine/releases/download/v0.2.0/docker-machine_linux-amd64 -O /usr/local/bin/docker-machine
sudo chmod +x /usr/local/bin/docker-machine
次はComposeをインストールします。
sudo apt-get install python-pip
sudo pip install docker-compose
sudo wget https://raw.githubusercontent.com/docker/compose/1.2.0/contrib/completion/bash/docker-compose -O /etc/bash_completion.d/docker-compose
Swarmは明示的にインストールする必要はありません。
Machine+SwarmでSwarmのクラスタを構築
まずはSwarmで使うトークンを作成します。
docker run --rm swarm create
565537d2ba368b2788445e6f583eefda
ここで返された値(565537d2ba368b2788445e6f583eefda)がswarmで使うトークンになります。
swarmのトークン以外にも、etcd/consul/Zookeeper等も使えるようです。
machineのコマンドでこのトークンを使ってdigitalocean上にSwarmのマスタノードを作ります。
今回はメモリ2GBのdroplet(digitaloceanのVMの呼び方)にしてみました。
docker-machine create -d digitalocean --digitalocean-image "ubuntu-14-04-x64" --digitalocean-region "sfo1" --digitalocean-size "2gb" --digitalocean-access-token ${DIGITALOCEAN_API_TOKEN} --swarm --swarm-master --swarm-discovery token://565537d2ba368b2788445e6f583eefda swarm-master
マスターができたら2台のノードを追加してみます。
# swarm-node1
docker-machine create -d digitalocean --digitalocean-image "ubuntu-14-04-x64" --digitalocean-region "sfo1" --digitalocean-size "2gb" --digitalocean-access-token ${DIGITALOCEAN_API_TOKEN} --swarm --swarm-discovery token://565537d2ba368b2788445e6f583eefda swarm-node1
# swarm-node2
docker-machine create -d digitalocean --digitalocean-image "ubuntu-14-04-x64" --digitalocean-region "sfo1" --digitalocean-size "2gb" --digitalocean-access-token ${DIGITALOCEAN_API_TOKEN} --swarm --swarm-discovery token://565537d2ba368b2788445e6f583eefda swarm-node2
確認します。
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM
swarm-master digitalocean Running tcp://192.241.228.14:2376 swarm-master (master)
swarm-node1 digitalocean Running tcp://192.241.233.78:2376 swarm-master
swarm-node2 * digitalocean Running tcp://192.241.211.126:2376 swarm-master
無事に3台のマシンが出来上がりました。
swarmでコンテナを作ってみる
Composeも連携させてみる前に一旦Swarm単体で操作してみます。
まず以下のコマンドを実行すると、今後のdockerコマンドは全てswarm-master経由で実行されるようになります。
eval $(docker-machine env --swarm swarm-master)
eval付けないで実行するとこんな感じです。(実行結果のコメントは割愛)
$ docker-machine env --swarm swarm-master
export DOCKER_TLS_VERIFY=1
export DOCKER_CERT_PATH="/home/dk/.docker/machine/machines/swarm-master"
export DOCKER_HOST=tcp://192.241.228.14:3376
つまり上記をeval
つけて実行すればDOCKER_HOST
にtcp://192.241.228.14:3376
が設定され、デフォルトで使うデーモンソケット(/var/run/docker.sock
)ではなくtcp://192.241.228.14:3376
のswarm-masterと通信するようになるってことですね。
次に以下のようにコンテナを作ってみます。
今回は特に連携等は考えずにredisとnginxのコンテナを作ってみます。
# メモリ2GB割り当てるredisのコンテナを作成
docker run -p "6879:6879" -d --name redis -m 2g redis
# メモリ2GB割り当てるnginxのコンテナを2つ作成。作成するノードはredisとは別のノードにする。
docker run -p "80" -d --name web1 -e "affinity:container!=redis" -m 2g nginx
docker run -p "80" -d --name web2 -e "affinity:container!=redis" -m 2g nginx
そしてdocker ps
で結果を確認すると以下のようになっています。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
672eeb5264c2 nginx:latest "nginx -g 'daemon of 12 seconds ago Up Less than a second 443/tcp, 192.241.211.126:32778->80/tcp swarm-node2/web1
e6602df93c98 redis:latest "/entrypoint.sh redi 24 seconds ago Up Less than a second 6379/tcp, 192.241.233.78:6879->6879/tcp swarm-node1/redis
400c04e5e5d1 nginx:latest "nginx -g 'daemon of 2 minutes ago Up 2 minutes 443/tcp, 192.241.228.14:32768->80/tcp swarm-master/web2
2GBの容量しかない3ノードに2GBのコンテナを3つ立てたため、それぞれswarm-master、swarm-node1、swarm-node2に分散されて配置されています。
docker info
で結果を見ると実際にメモリが予約されているのが分かります。
$ docker info
Containers: 7
Strategy: spread
Filters: affinity, health, constraint, port, dependency
Nodes: 3
swarm-master: 192.241.228.14:2376
└ Containers: 3
└ Reserved CPUs: 0 / 2
└ Reserved Memory: 2 GiB / 2.053 GiB
swarm-node1: 192.241.233.78:2376
└ Containers: 2
└ Reserved CPUs: 0 / 2
└ Reserved Memory: 2 GiB / 2.053 GiB
swarm-node2: 192.241.211.126:2376
└ Containers: 2
└ Reserved CPUs: 0 / 2
└ Reserved Memory: 2 GiB / 2.053 GiB
ここで同じ2GBでもう一台コンテナを作ろうとするとエラーになります。
docker run -p "80" -d --name web3 -e "affinity:container!=redis" -m 2g nginx
FATA[0000] Error response from daemon: no resources available to schedule container
この辺まで確認したら一旦コンテナを削除しておきます。
$ docker stop redis web1 web2
$ docker rm redis web1 web2
Composeを使ってSwarmクラスタ上にコンテナを作成
続いてComposeと組み合わせてみます。
ベタな例ですがアプリのコンテナからredisのコンテナのカウンタをインクリメントして取得するものを作ってみます。
なお、ComposeとSwarmを連携する場合、残念ながら現状ではbuildコマンドが使えないので、DockerHubまたはプライベートRegistryにあるイメージを使うしかなさそうです。
今回はアプリ側は以下のようなシンプルなgoのアプリを使います。
redisにアクセスしてkeyという名前のkeyを取得して表示するだけです。
package main
import (
"fmt"
"net/http"
"os"
"time"
"github.com/garyburd/redigo/redis"
"github.com/zenazn/goji"
"github.com/zenazn/goji/web"
)
var (
rHost = os.Getenv("REDIS_HOST")
pool *redis.Pool
)
func newPool() *redis.Pool {
return &redis.Pool{
MaxIdle: 3,
MaxActive: 10,
IdleTimeout: 240 * time.Second,
Dial: func() (redis.Conn, error) {
c, err := redis.Dial("tcp", rHost+":6379")
if err != nil {
panic(err)
}
return c, err
},
}
}
func get(c web.C, w http.ResponseWriter, r *http.Request) {
con := pool.Get()
defer con.Close()
n, _ := redis.Int(con.Do("INCR", "key"))
fmt.Fprintf(w, "You have visited this page %#v times!\n", n)
}
func main() {
pool = newPool()
con := pool.Get()
con.Do("PING")
con.Close()
goji.Get("/", get)
goji.Serve()
}
Dockerfileは以下です。
FROM golang:1.4.2
ADD app /app
CMD /app
これはビルドしてDockerHubにあげてあります。
docker-compose.ymlは以下のようにしています。
redis:
image: redis
ports:
- "6379:6379"
environment:
- "constraint:node==swarm-master" #必ずswarm-masterで起動するようにする
web:
image: daikikohara/app01:v5 #何度か失敗してv5になっちゃいましたw
restart: always #redisのコンテナよりも先に起動した場合はエラーで落ちるので再起動させる
ports:
- "8000"
environment:
- REDIS_HOST #環境変数で設定したREDIS_HOSTの値を使う。KEY=VALUEで指定しない場合、VALUEはOSの環境変数から読まれる。
- "affinity:image!=redis" #redisと違うノードに立てる例
docker-compose単体ではlinksやexternal_linksを使ってredisと接続できますが、swarmでマルチノード上に構築する場合は現状できないようです。
そのため、ちょっと無理矢理な感じですが、アプリからは環境変数で設定したREDIS_HOSTにアクセスするようにしています。
ということで、無理矢理感満載ですが、REDIS_HOSTをswarm-masterのホストのIPにするため以下のように設定します。
export REDIS_HOST=$(docker-machine env swarm-master | grep -o -e "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+")
なお、変数の展開も今はできませんが、Issueが開いているのでいずれ対応するかと思うので、環境変数渡しはしなくてよくなりそうです。
というか普通にlinksが使えるようになるとか、もっと良いやり方が出てくると思いますが。。。
ということでdocker-compose up -d
をして結果を確認してみます。
事前にeval $(docker-machine env --swarm swarm-master)
をしてあればSwarmクラスタ上に配置してくれます。
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
132530465f28 redis:latest "/entrypoint.sh redi 33 seconds ago Up 21 seconds 192.241.228.14:6379->6379/tcp swarm-master/docker_redis_1
fe38440aaee9 daikikohara/app01:v5 "/bin/sh -c /app" 33 seconds ago Up 9 seconds 192.241.211.126:32779->8000/tcp swarm-node2/docker_web_1
ちゃんとComposeに書いたとおりに出来上がってますね。
アプリにcurlでアクセスしてもちゃんとカウンターが取れます。
$ curl http://192.241.211.126:32779
You have visited this page 1 times!
$ curl http://192.241.211.126:32779
You have visited this page 2 times!
アプリ側のスケールアウトはこんな感じでできます。
$ docker-compose scale web=3
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
de32c9e4471a daikikohara/app01:v5 "/bin/sh -c /app" 56 seconds ago Up 51 seconds 192.241.211.126:32783->8000/tcp swarm-node2/docker_web_3
c8f38ae93647 daikikohara/app01:v5 "/bin/sh -c /app" 56 seconds ago Up 51 seconds 192.241.211.126:32782->8000/tcp swarm-node2/docker_web_2
fe38440aaee9 daikikohara/app01:v5 "/bin/sh -c /app" 4 minutes ago Up 4 minutes 192.241.211.126:32779->8000/tcp swarm-node2/docker_web_1
132530465f28 redis:latest "/entrypoint.sh redi 4 minutes ago Up 3 minutes 192.241.228.14:6379->6379/tcp swarm-master/docker_redis_1
webが3台になりました。
それぞれにアクセスしてもちゃんとカウンターが取れます。
$ curl http://192.241.211.126:32782
You have visited this page 3 times!
$ curl http://192.241.211.126:32783
You have visited this page 4 times!
戻すときはdocker-compose scale web=1
とやってやればOKです。
とりあえずこんな感じです。最後に片付けをしておきましょう。
docker-machine rm swarm-master swarm-node1 swarm-node2
次期バージョン
それぞれ以下のマイルストーンです。いずれも2015年6月16日リリース予定なのでもうすぐですね。
6月22,23にはDockerconも行われるのでまだまだDockerのネタは尽きることがなさそうです。
個人的にはSwarmによるコンテナダウン時の自動リバランスが一番楽しみですね。
最後に
元figだったCompose以外はまだ出たばかりなのでまだまだこれからという印象ですが、かなり便利に使えるような印象でした。
いずれもDocker社の方針で"batteries included but removable/swappable"という原則に従ってます。
これはエコシステムの他社製ツール(MesosやKubernetes等)に配慮してDocker社としてはSwarm等のツールを出すけど、規模が大きくなった時等に他のツールに置き換え可能ですよっていう方針のようです。
実際に現状だとfailover等で明らかにDocker社からのエコシステムだけでは足りないので、他のツールを組み合わせる必要は有りますね。
ただ多分リップサービス的なところもある気がして、最終的にはDocker社としては全部Docker社製品で固めたいんじゃないかなぁって気がしなくもないですね。
以上。