Usual Software Engineer

よくあるソフトウェアエンジニアのブログ

Nginx の代わりに Go 言語製のリバースプロキシ Traefik を使う基本的な利点

毎月ブログ書く縛りを一日過ぎてしまいました。エイプリルフールだったので許してください。 無駄にハマってあまり余力がないのでさっさと本題に入りましょう。

Go 言語製のリバースプロキシ兼ロードバランサーOSS である Traefik をご存知でしょうか。 最近業務で使っていて良くも悪くもまだ発展途上だなという印象なのですが、これまで定番の Nginx を使っていてどうしても解決できない問題 があったのでそのために Traefik を利用することになりました。 その問題とは、コンテナで動作するアプリケーションに対してリクエストを流す際に DNS やポート設定に不都合が生じるという点です。

具体例がないとわかりづらいと思うので、今回は Vagrant 上で動く Nomad と Consul を利用し、 Nomad 上のアプリケーションのコンテナに Nginx と Traefik からリクエストを流すサンプルを用意しました。 とりあえず vagrant upvagrant ssh -c "sudo nomad run /vagrant/jobs/traefik-example.nomad" すれば動くようにはなっています。

github.com

ちなみに Nomad と Consul の設定部分は公式のデモを元にしてガチャガチャいじって無理やりなところがありますが、今回の本筋ではないので気にしないでくださいw

サンプル環境の構成

まずはサンプル環境の構成を説明します。 ”コンテナで動作するアプリケーション”のダミーとして Python の Docker イメージを使用して http サーバのコンテナを2つ Nomad 上に起動しています。そしてメインの Traefik と Nginx ですが、こちらも同じ Nomad 上に起動しています。 Nomad と Consul は Vagrant で起動された 1 台の VM 上で動いています。

Nomad のジョブファイルはこちらです。

traefik-nomad-example/traefik-example.nomad at c52ec18bc0d41313aafd7f934478cec4413d5b46 · innossh/traefik-nomad-example · GitHub

いつの間にか Nomad の UI 画面もモダンになってますね。

f:id:innossh:20180401175316p:plain

が、 Nomad の話をしたいわけではないのでこちらの Consul の UI 画面のキャプチャの方がわかりやすいでしょうか。

f:id:innossh:20180401165023p:plain

nginx , traefik , httpserver0 , httpserver1 が Consul のサービスに登録してある状態なので、 httpserver0.service.consul のようなドメインを名前解決することができるのが Consul のサービスディスカバリですね。 つまり、 Nginx からリクエストを httpserver0 に流したい場合は、バックエンドに httpserver0.service.consul を指定すれば良い、ということになります。

Nginx でルーティング

すでに上にも書いたように、このように Nginx を設定します。

traefik-nomad-example/default.conf at c52ec18bc0d41313aafd7f934478cec4413d5b46 · innossh/traefik-nomad-example · GitHub

...
    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 のジョブファイルだけ です。

traefik-nomad-example/traefik-example.nomad at c52ec18bc0d41313aafd7f934478cec4413d5b46 · innossh/traefik-nomad-example · GitHub

...
      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 のタスクのサービスタグを設定するだけでリクエストのルーティングが行える。また、コンテナのホスト側ポート番号を固定する必要がない。