Nginx の代わりに Go 言語製のリバースプロキシ Traefik を使う基本的な利点
毎月ブログ書く縛りを一日過ぎてしまいました。エイプリルフールだったので許してください。 無駄にハマってあまり余力がないのでさっさと本題に入りましょう。
Go 言語製のリバースプロキシ兼ロードバランサーの OSS である Traefik をご存知でしょうか。 最近業務で使っていて良くも悪くもまだ発展途上だなという印象なのですが、これまで定番の Nginx を使っていてどうしても解決できない問題 があったのでそのために Traefik を利用することになりました。 その問題とは、コンテナで動作するアプリケーションに対してリクエストを流す際に DNS やポート設定に不都合が生じるという点です。
具体例がないとわかりづらいと思うので、今回は Vagrant 上で動く Nomad と Consul を利用し、 Nomad 上のアプリケーションのコンテナに Nginx と Traefik からリクエストを流すサンプルを用意しました。
とりあえず vagrant up
と vagrant ssh -c "sudo nomad run /vagrant/jobs/traefik-example.nomad"
すれば動くようにはなっています。
ちなみに Nomad と Consul の設定部分は公式のデモを元にしてガチャガチャいじって無理やりなところがありますが、今回の本筋ではないので気にしないでくださいw
サンプル環境の構成
まずはサンプル環境の構成を説明します。 ”コンテナで動作するアプリケーション”のダミーとして Python の Docker イメージを使用して http サーバのコンテナを2つ Nomad 上に起動しています。そしてメインの Traefik と Nginx ですが、こちらも同じ Nomad 上に起動しています。 Nomad と Consul は Vagrant で起動された 1 台の VM 上で動いています。
Nomad のジョブファイルはこちらです。
いつの間にか Nomad の UI 画面もモダンになってますね。
が、 Nomad の話をしたいわけではないのでこちらの Consul の UI 画面のキャプチャの方がわかりやすいでしょうか。
nginx
, traefik
, httpserver0
, httpserver1
が Consul のサービスに登録してある状態なので、 httpserver0.service.consul
のようなドメインを名前解決することができるのが Consul のサービスディスカバリですね。
つまり、 Nginx からリクエストを httpserver0
に流したい場合は、バックエンドに httpserver0.service.consul
を指定すれば良い、ということになります。
Nginx でルーティング
すでに上にも書いたように、このように Nginx を設定します。
... resolver consul.service.consul:8600; ... location /0/ { set $backend httpserver0.service.consul:8000; rewrite ^/0/(.*)$ /$1 break; proxy_pass http://$backend; } location /1/ { set $backend httpserver1.service.consul:8001; rewrite ^/1/(.*)$ /$1 break; proxy_pass http://$backend; } ...
Consul 自体のアドレスも他のサービスと同じように consul.service.consul
で参照できるのでリゾルバとして設定し、あとは目的のロケーションの proxy_pass
にバックエンドの Consul のドメイン名とポート番号を設定してあげましょう。
これで何が問題なのでしょうか?普段は問題ないでしょうが、 ”httpserver0,httpserver1
が機能していない時”=” Consul のカタログにサービスが登録されていない時” に Nginx が起動すらできない という状態に陥ります。
Nginx が起動時にバックエンドの接続設定をも検証しているからなんでしょうかね。ちなみに起動している状態で httpserver0,httpserver1
が落ちた場合は Nginx のプロセスまで落ちるなんてことはないですが、当然 Nginx の再起動などもできないので困りますよね。
加えて、ドメインの解決はできても ポート番号は固定で書かなければいけない のも地味に気になりますよね。
商用の Nginx Plus だと Consul との連携もできるっぽい予感がしてますが、使ったことないですすいません。 そんなわけで Traefik を使ってみるわけです。
Traefik でルーティング
Traefik の設定ファイルはもちろんありますが Consul Catalog バックエンドの機能をオンにする、という以外は今回はスルーでいいでしょう。ルーティングにおいて注目すべきはなんと Nomad のジョブファイルだけ です。
... service { name = "httpserver0" tags = [ "traefik.tags=service", "traefik.frontend.rule=PathPrefixStrip:/0/", ] ...
Nomad ジョブ内に定義された httpserver0
のタスクの Consul サービスのタグに Traefik が認識するためのルーティングを書くだけ で、リクエストを流すことができてしまいます。これは Traefik が Consul Catalog と連携しているためです。
つまり新しいアプリケーションを追加してバックエンドに設定する場合に Traefik の設定は全くいじらず、ただタグを設定してあげるだけでそのアプリケーションへのルーティングが可能になるわけです。
また今回は Nginx のために httpserver0,httpserver1
のホスト側のポートを static なポート番号で設定していますが、 Traefik でルーティングを行う場合はそれも必要ありません。よしなに対象のコンテナの正しいポートにリクエストを流してくれます。これは便利!
ちなみに PathPrefixStrip
というのはパスがマッチした時だけ評価されて更にそのマッチしたプリフィックスを削除してバックエンドにリクエストを流す、というものになります。
リクエストの例
一応設定だけじゃなくてリクエストの結果も貼っておきます。
Nginx を通して httpserver0,httpserver1
にリクエスト
vagrant@nomad:~$ curl -i http://nginx.service.consul/0/ HTTP/1.1 200 OK Server: nginx/1.12.2 Date: Sun, 01 Apr 2018 07:22:28 GMT Content-Type: text/html Content-Length: 131 Connection: keep-alive Last-Modified: Sun, 01 Apr 2018 07:19:37 GMT <!DOCTYPE html> <head> <meta charset=utf-8> <title>traefik-example</title> </head> <body> It works on httpserver0! </body> </html> vagrant@nomad:~$ curl -i http://nginx.service.consul/1/ HTTP/1.1 200 OK Server: nginx/1.12.2 Date: Sun, 01 Apr 2018 07:22:35 GMT Content-Type: text/html Content-Length: 131 Connection: keep-alive Last-Modified: Sun, 01 Apr 2018 07:19:24 GMT <!DOCTYPE html> <head> <meta charset=utf-8> <title>traefik-example</title> </head> <body> It works on httpserver1! </body> </html>
Traefik を通して httpserver0,httpserver1
にリクエスト
vagrant@nomad:~$ curl -i http://traefik.service.consul:8080/0/ HTTP/1.1 200 OK Content-Length: 131 Content-Type: text/html Date: Sun, 01 Apr 2018 07:22:18 GMT Last-Modified: Sun, 01 Apr 2018 07:19:37 GMT Server: SimpleHTTP/0.6 Python/3.6.4 <!DOCTYPE html> <head> <meta charset=utf-8> <title>traefik-example</title> </head> <body> It works on httpserver0! </body> </html> vagrant@nomad:~$ curl -i http://traefik.service.consul:8080/1/ HTTP/1.1 200 OK Content-Length: 131 Content-Type: text/html Date: Sun, 01 Apr 2018 07:22:24 GMT Last-Modified: Sun, 01 Apr 2018 07:19:24 GMT Server: SimpleHTTP/0.6 Python/3.6.4 <!DOCTYPE html> <head> <meta charset=utf-8> <title>traefik-example</title> </head> <body> It works on httpserver1! </body> </html>
Nginx の方は Server
ヘッダが書き換わっていますが Traefik の方はまんまですね。本筋ではないので手抜きしましたが server_tokens: off;
にしておきましょう。
余談ですが、Traefik ではヘッダやもろもろの設定もルーティング設定と同じように Nomad のタスクのサービスのタグで設定可能なのですが、惜しいことに現在 stable の最新の v1.5.4 では Consul Catalog バックエンドのカスタムレスポンスヘッダ設定などが実装されていないので、次の v1.6.0 待ちになります。すでに rc 版が出てるのでもう少しの辛抱ですね。
これも余談ですが、 Traefik の設定ファイル toml じゃなくて yaml にしてくれないかなあと個人的に思ってます。ちょいちょいバグ踏んだりもしたのでほんとに発展途上という感じです。 File バックエンド指定で複雑なルーティングを手動で書いた時はめっちゃ辛かったです... まあ Nginx も辛いんですけど。
まとめ
Traefik は Consul Catalog と連携させることで、 Nomad 上に動かしたコンテナを自動で認識し、 Nomad のタスクのサービスタグを設定するだけでリクエストのルーティングが行える。また、コンテナのホスト側ポート番号を固定する必要がない。