#はじめに
この記事はDocker2 Advent Calendar 2016の12日目の記事です。
このエントリではtraefikというGo製のリバースプロキシ兼ロードバランサをCoreOSクラスタ上にdockerコンテナとして展開し、サービスディスカバリを行う方法について書きます。
なお、検証で利用するCoreOSのクラスタ環境については、以前書いた、CoreOSのクラスタ環境(CoreOS+etcd+fleet)でdocker-composeを使う方法で作った環境で進めますが、適当なCoreOSのクラスタ環境であれば同じ方法で試せるかと思います。
使用ソフトウェアとバージョン
CoreOS:stable 1185.5.0
docker:1.11.2
traefik:1.1.1
#Traefikについて簡単な紹介
Traefikとは様々なバックエンド(docker、swarm、kubernetes、mesos、consul、zookeeperなど)の状態を元に設定を動的に変更することができる、ロードバランサ兼リバースプロキシです。Goで書かれており、他のGo製ツールと同じく、バイナリをひとつ置くだけで実行できます。また、綺麗なGUIも備えています。
意訳ですが、公式サイトには概ねこのような文言が書かれています。
イメージ的にはバックエンドの状態に応じて動的に振り分け設定を変更できる、NginxやHAProxyといった感じです。
ちなみにTraefikだけサクッと試してみたいという方は、公式がチュートリアルを用意しているので、そちらで大体の感触を掴むことができます。
https://www.katacoda.com/courses/traefik/deploy-load-balancer
確認の流れ
では、実際にTraefikを展開して、動作を確認していきます。以下のような流れで進めます。
- CoreOSクラスタ上にTraefikコンテナを展開
- 振り分け先のアプリケーションとしてnginxの公式イメージをクラスタに展開
- nginxのサービスをtraefikに登録してアクセスできるか確認
- nginxをスケールアウトさせ、ロードバランスの挙動を確認
CoreOSクラスタ上にTraefikコンテナを展開
まずは、Traefikの公式DockerイメージをCoreOSのクラスタに展開します。
以下のようなユニットファイルを使って、fleetctlで展開します。
[Unit]
Description=sample traefik-etcd service
After=docker.service
Requires=docker.service
[Service]
EnvironmentFile=/etc/environment
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker kill traefik
ExecStartPre=-/usr/bin/docker rm traefik
ExecStartPre=/usr/bin/docker pull traefik
ExecStart=/usr/bin/docker run --rm --name traefik -p 80:80 -p 8080:8080 \
traefik --web --etcd --etcd.endpoint=${COREOS_PRIVATE_IPV4}:2379
ExecStop=/usr/bin/docker stop traefik
[X-Fleet]
Conflicts=traefik@*.service
core@ip-172-31-30-102 ~ $ fleetctl start traefik@1.service
Unit traefik@1.service inactive
Unit traefik@1.service launched on 59541285.../172.31.30.102
core@ip-172-31-30-102 ~ $ fleetctl list-units | grep traefik
traefik@1.service 59541285.../172.31.30.102 active running
無事、配置が完了しました。
ユニットファイルにかかれているdockerコマンドについてですが、それぞれ下記のような意味を持ちます。
- dockerのオプション
- ポート80:リバースプロキシとして使用するポート
- ポート8080:GUIで使用するポート
- taraefikのオプション
-
--web
:GUIを有効化するオプション -
--etcd
:Traefikの設定のバックエンドとしてetcdを利用するオプション -
--etcd.endpoint
:利用するetcdの接続先を指定するオプションで、今回はホストのCoreOSのetcdを使用
--etcd.endpoint
で環境変数COREOS_PRIVATE_IPV4
を利用していますが、これは/etc/environment
に定義されている環境変数で、ホストのIPを渡しています。
traefikを展開したホストの8080番ポートをブラウザで覗くとtraefikのGUIに繋がります。
(CoreOS公式のCloud formationでクラスタを作成した場合、SGのポートを開けて下さい)
まだ、振り分け設定を何も入れていないので、空になっています。
振り分け先のアプリケーションとしてnginxの公式イメージをクラスタに展開
振り分け先を作るため、nginxの公式イメージをクラスタに展開します。
わかりやすさのため、ホストOSの/etc/hostnameをマウントさせて表示させる事にします。また、外に晒すポートは8081にします。
以下のようなユニットファイルを使って、fleetctlで展開します。
[Unit]
Description=sample nginx service
After=docker.service
Requires=docker.service
[Service]
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker kill nginx
ExecStartPre=-/usr/bin/docker rm nginx
ExecStartPre=/usr/bin/docker pull nginx
ExecStart=/usr/bin/docker run --rm -p 8081:80 --name nginx \
-v /etc/hostname:/usr/share/nginx/html/index.html:ro nginx
ExecStop=/usr/bin/docker stop nginx
[X-Fleet]
Conflicts=nginx@*.service
core@ip-172-31-30-102 ~ $ fleetctl start nginx@1.service
Unit nginx@1.service inactive
Unit nginx@1.service launched on 5ac56d10.../172.31.13.231
core@ip-172-31-30-102 ~ $ fleetctl list-units | grep nginx
nginx@1.service 5ac56d10.../172.31.13.231 activating start-pre
展開できました。一応、curlでちゃんと表示されるか、確認します。
(CoreOS公式のCloud formationでクラスタを作成した場合、SGのポートを開けて下さい)
core@ip-172-31-30-102 ~ $ curl 172.31.13.231:8081
ip-172-31-13-231.ap-northeast-1.compute.internal
nginxのサービスをtraefikに登録
展開したnginxをtraefikに登録してnginx.example.com
のドメインでアクセスできるようにします。
--etcd
オプションを付けてtraefikを起動すると、etcdに格納された値を元に、動的に設定変更できるようになります。
まずは、以下のコマンドにより手動でetcdに値を入れてみます。
core@ip-172-31-30-102 ~ $ etcdctl set /traefik/backends/backend1/servers/server1/url http://172.31.13.231:8081
172.31.13.231:8081
core@ip-172-31-30-102 ~ $ etcdctl set /traefik/backends/backend1/servers/server1/weight 1
1
etcdctl
を使って、etcdに値を入れることで、振り分け先グループbackend1
の振り分け対象その1(server1
)のurlを172.31.13.231:8081
にweightを1
に設定しています。
ちなみにtraefikではサーバの振り分け先グループのことをbackend
と呼びます。。。が、etcdやconsulなどの設定のロード先のこともbackendと呼んており、少しややこしいです。この記事では前者はbackend
、後者はバックエンドと記載しています。
設定後、traefikの画面を確認すると、backend1が追加されているのがわかります。
backend
の設定が完了したので、次はnginx.example.com
へのアクセスをbackend1
に結びつけるfrontend
の設定を入れます。frontend
とは受け取ったリクエストをbackend
に振り分けるための一連のルールのセットになります。
以下のコマンドにより手動でetcdに値を入れてみます。
core@ip-172-31-30-102 ~ $ etcdctl set /traefik/frontends/frontend1/backend backend1
backend1
core@ip-172-31-30-102 ~ $ etcdctl set /traefik/frontends/frontend1/routes/test_1/rule Host:nginx.example.com
Host:nginx.example.com
上記はfrontend1という名前で振り分けルールを作成しており、振り分け先にbackend1
を設定して、振り分け条件としてHost:nginx.example.com
を設定しています。これはリクエストがtraefikに飛んできた際に、リクエスト先のサーバネームがnginx.example.com
だった場合、backend1
に振り分けを行うような設定となります。
設定後、traefikの画面を確認すると、frontend1が追加されているのがわかります。
以上で、traefikを介して、nginxのアプリケーションにアクセスできるようになりました。
curlで接続確認をしてみます。
core@ip-172-31-30-102 ~ $ fleetctl list-units | grep traefik
traefik@1.service 59541285.../172.31.30.102 active running
core@ip-172-31-30-102 ~ $ curl -H Host:nginx.example.com http://172.31.30.102
ip-172-31-13-231.ap-northeast-1.compute.internal
traefikを介してnginxにアクセスできています。
ここまで、etcdの登録をすべて手作業で行いましたが、これをfleetを使って登録するようにします。
一旦、etcdの設定はすべて消します
core@ip-172-31-30-102 ~ $ etcdctl rm /traefik/frontends/frontend1/backend
PrevNode.Value: backend1
core@ip-172-31-30-102 ~ $ etcdctl rm /traefik/frontends/frontend1/routes/test_1/rule
PrevNode.Value: Host:nginx.example.com
core@ip-172-31-30-102 ~ $ etcdctl rm /traefik/backends/backend1/servers/server1/url
PrevNode.Value: http://172.31.13.231:8081
core@ip-172-31-30-102 ~ $ etcdctl rm /traefik/backends/backend1/servers/server1/weight
PrevNode.Value: 1
backend登録用のユニットファイルは以下のようになります。
[Unit]
Description=Register nginx in backend
BindsTo=nginx@%i.service
After=nginx@%i.service
[Service]
EnvironmentFile=/etc/environment
ExecStart=/bin/sh -c "while true; do \
etcdctl set /traefik/backends/backend1/servers/server%i/url http://${COREOS_PRIVATE_IPV4}:8081 --ttl 60;\
etcdctl set /traefik/backends/backend1/servers/server%i/weight 1 --ttl 60;\
sleep 45;done"
ExecStop=/bin/sh -c "\
etcdctl rm /traefik/backends/backend1/servers/server%i/url ;\
etcdctl rm /traefik/backends/backend1/servers/server%i/weight"
[X-Fleet]
MachineOf=nginx@%i.service
UnitセクションにあるBindsToはこのユニットが依存するUnitを明記しています。%iにはインスタンス番号が入る変数となっており、このUnitはnginx@%i.serviceが無いと自動で死亡するようになっています。
serviceセクションでは、多少強引ですが、whileループによってetcdの設定を更新し続けるようになっています。etcdctlの--ttl
オプションを設定することで、不意にnginxのサービスが落ちた時にetcdからも設定が消えるようになっています。X-FleetセクションのMachineOfはこのユニットが起動するホストの条件を指定しています。nginx@%i.serviceに依存するユニットなので同じホスト上で稼働させる必要があります。
core@ip-172-31-30-102 ~ $ fleetctl start register-nginx@1.service
Unit register-nginx@1.service launched on 5ac56d10.../172.31.13.231
frontend登録用ユニットファイルは以下のようになります。
[Unit]
Description=Register nginx.example.com in frontend
[Service]
ExecStart=/bin/sh -c "while true; do \
etcdctl set /traefik/frontends/frontend1/backend backend1 --ttl 60;\
etcdctl set /traefik/frontends/frontend1/routes/test_1/rule Host:nginx.example.com --ttl 60;\
sleep 45;done"
ExecStop=/bin/sh -c "\
etcdctl rm /traefik/frontends/frontend1/backend backend1 ;\
etcdctl rm /traefik/frontends/frontend1/routes/test_1/rule"
大体、backend
のユニットと同じです。ただし、こちらは何かのサービスに依存する作りにはなっていません。
core@ip-172-31-30-102 ~ $ fleetctl start register-nginx_example_com.service
Unit register-nginx_example_com.service inactive
Unit register-nginx_example_com.service launched on 89391638.../172.31.30.100
上記の2ユニットを展開すると手動でetcdを設定した時と同じ状態になります。
ユニットファイル化したのでスケールさせてみます。
nginx@2.serviceを起動してnginxをスケールしてみます。
core@ip-172-31-30-102 ~ $ fleetctl start nginx@2.service
Unit nginx@2.service inactive
Unit nginx@2.service launched on 9184f486.../172.31.30.101
core@ip-172-31-30-102 ~ $ fleetctl start register-nginx@2.service
Unit register-nginx@2.service inactive
Unit register-nginx@2.service launched on 9184f486.../172.31.30.101
traefikにアクセスするとロードバランスされていることが分かります。
core@ip-172-31-30-102 ~ $ curl -H Host:nginx.example.com http://172.31.30.102
ip-172-31-13-231.ap-northeast-1.compute.internal
core@ip-172-31-30-102 ~ $ curl -H Host:nginx.example.com http://172.31.30.102
ip-172-31-30-101.ap-northeast-1.compute.internal
#おわりに
以上でTraefikを使用して動的にnginxのサービスにリクエストを振り分けることができました。
後はnginx.example.com
を名前解決できるように、内部DNSにエントリを追加してあげれば、サービス追加時に自由なURLを介してサービスを見ることができるようになります。が、長くなるのでその辺りは割愛します。fleetのBindsToとMachineOfを使って、traefikのユニットに依存するDNS登録ユニットを追加すればできるようになるかと。
ちなみに、traefikは複数個立ち上げれば簡単にスケールさせることができます。設定もetcdから読んでくれるので立ち上げるだけで済みます。
検証しきれていないですが、traefikにはSSLの終端、パスによるリクエスト振り分け、振り分け条件に正規表現を使用、振り分け先のヘルスチェック、HTTP2.0対応などリバプロ、ロードバランサに必要な機能は一通り揃ってそうですので、興味が出た方は試してみては如何でしょうか??