nginx-proxy, docker-gen という便利ツール

先日ようやく Docker を触り始めたわけですが thumbor を使う場合、前段に nginx を置いてキャッシュさせるべきだから nginx の docker も必要だなっと思って調べてたら jwilder/nginx-proxy というものを見つけました。
大変便利そうな docker image だったのでこれについて書いてみます。

nginx-proxy は nginx の official image をベースに docker-gen というツールを使って template から nginx の設定ファイルを生成するように出来ています。

nginx.tmpl

consul-template の docker API 版ですね。consul の変更ではなく docker の変更を監視してファイルを更新します。

前回作った thumbor を2つ動かしてその手前に nginx-proxy を置くとどうなるかというと

EXPOSE されていれば -p で port forwarding する必要はありません。
thumbor の image は 8000 番を EXPOSE してあります。
VIRTUAL_HOST 環境変数を指定して起動します。

$ docker run -d --name thumbor1 -e VIRTUAL_HOST=thumbor.example.com thumbor-centos
4930455aadf4a63055a29016dfde521c2dfa7c7fd34ee75498a4c3850adff56c
$ docker run -d --name thumbor2 -e VIRTUAL_HOST=thumbor.example.com thumbor-centos
4318322fa2a6f66374474bc7b7a0ce1964a6aa828eae7852027b1f12faadd416
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
4318322fa2a6        thumbor-centos      "/bin/bash /bin/thumb"   4 seconds ago       Up 3 seconds        8000/tcp            thumbor2
4930455aadf4        thumbor-centos      "/bin/bash /bin/thumb"   12 seconds ago      Up 11 seconds       8000/tcp            thumbor1

nginx-proxy は先に起動していても、後から起動しても良いですがホスト側の docker.sock にアクセスできるように volume 指定する必要があります。この socket を通じて Docker API で監視しています。

$ docker run -d -p 80:80 --name nginx -v /var/run/docker.sock:/tmp/docker.sock:ro jwilder/nginx-proxy
f259f6c444aadb5c9573f3ee598e6a101ac2598d2a018bb101b33fd7d6477d03

nginx-proxy を起動したところで /etc/nginx/conf.d/default.conf を確認してみます。

$ docker exec nginx cat /etc/nginx/conf.d/default.conf
# If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the
# scheme used to connect to this server
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
  default $http_x_forwarded_proto;
  ''      $scheme;
}
# If we receive Upgrade, set Connection to "upgrade"; otherwise, delete any
# Connection header that may have been passed to this server
map $http_upgrade $proxy_connection {
  default upgrade;
  '' close;
}
gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
log_format vhost '$host $remote_addr - $remote_user [$time_local] '
                 '"$request" $status $body_bytes_sent '
                 '"$http_referer" "$http_user_agent"';
access_log off;
# HTTP 1.1 support
proxy_http_version 1.1;
proxy_buffering off;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
server {
	server_name _; # This is just an invalid value which will never trigger on a real hostname.
	listen 80;
	access_log /var/log/nginx/access.log vhost;
	return 503;
}
upstream thumbor.example.com {
			# thumbor2
			server 172.17.0.3:8000;
			# thumbor1
			server 172.17.0.2:8000;
}
server {
	server_name thumbor.example.com;
	listen 80 ;
	access_log /var/log/nginx/access.log vhost;
	location / {
		proxy_pass http://thumbor.example.com;
	}
}

thumbor.example.com にアクセスすると thumbor1, thumbor2 に振り分けられる設定になっています。
VIRTUAL_HOST という環境変数を設定するだけで。

thumbor1 を停止してみます。

$ docker stop thumbor1
thumbor1
$ docker exec nginx cat /etc/nginx/conf.d/default.conf | grep -C 5 ^upstream
	server_name _; # This is just an invalid value which will never trigger on a real hostname.
	listen 80;
	access_log /var/log/nginx/access.log vhost;
	return 503;
}
upstream thumbor.example.com {
			# thumbor2
			server 172.17.0.3:8000;
}
server {
	server_name thumbor.example.com;

upstream thumbor.example.com のメンバーが減りました。また起動させれば復活します。
別の VIRTUAL_HOST を指定すれば別の virtual host 設定が追加されます。

$ docker run -d --name thumbor3 -e VIRTUAL_HOST=foo.example.com thumbor-centos 
6f933fdd9948a0793788c4d15088ba5c298b5d6606ee7c57a982e8de9b044b4e

これで

upstream foo.example.com {
			# thumbor3
			server 172.17.0.2:8000;
}
server {
	server_name foo.example.com;
	listen 80 ;
	access_log /var/log/nginx/access.log vhost;
	location / {
		proxy_pass http://foo.example.com;
	}
}

が default.conf に追加されました。

docker logs nginx でログを確認してみます

$ docker logs nginx
forego     | starting nginx.1 on port 5000
forego     | starting dockergen.1 on port 5100
dockergen.1 | 2016/02/01 15:02:40 Generated '/etc/nginx/conf.d/default.conf' from 3 containers
dockergen.1 | 2016/02/01 15:02:40 Running 'nginx -s reload'
dockergen.1 | 2016/02/01 15:02:40 Watching docker events
dockergen.1 | 2016/02/01 15:16:46 Received event die for container 4930455aadf4
dockergen.1 | 2016/02/01 15:16:46 Generated '/etc/nginx/conf.d/default.conf' from 2 containers
dockergen.1 | 2016/02/01 15:16:46 Running 'nginx -s reload'
dockergen.1 | 2016/02/01 15:16:46 Received event stop for container 4930455aadf4
dockergen.1 | 2016/02/01 15:16:46 Contents of /etc/nginx/conf.d/default.conf did not change. Skipping notification 'nginx -s reload'
dockergen.1 | 2016/02/01 15:19:35 Received event start for container 6f933fdd9948
dockergen.1 | 2016/02/01 15:19:35 Generated '/etc/nginx/conf.d/default.conf' from 3 containers
dockergen.1 | 2016/02/01 15:19:35 Running 'nginx -s reload'

forego ってのが登場しました。
Foreman in Go だそうです。

nginx-proxy には Procfile があって forego がこれを参照しているようです。

forego が nginx と docker-gen を起動させるようになっています。上の docker logs を見てもわかるように、どのプロセスが出力したメッセージかわかるように装飾してくれます。色までついています。docker のログを fluentd で処理したりするには不便ですが、目で見るには人に優しい感じです。

docker を動かすホストでログを fluentd で送る設定を動的に更新できる https://github.com/jwilder/docker-gen/blob/master/templates/fluentd.conf.tmpl も便利そうです。