Consulには、Consul Templateというテンプレートツールが別途開発されています。
Consul Templateとはなにか?ですが、こんな感じのもののようです。
- daemonである
- あらかじめテンプレートを用意しておく
- ConsulやVaultに問い合わせを行い、その結果を元にテンプレートに適用し、結果をファイルシステムに出力する
- ConsulやVaultの問い合わせ結果は、Consul Templateの実行中にイベントとして継続的に通知される
- テンプレートからのファイル出力、更新時に、追加でコマンドを実行できる
典型的な例としては、ConsulにServiceとしてApacheやnginx、HAProxyなどでロードバランサを構成しておき、
Consulサーバーへの問い合わせ結果を元にバックエンドのサーバーの接続先を取得、動的に設定ファイルを
更新し、Apache、nginx、HAProxyへ設定を反映するコマンドを実行する、などのようです。
というか、ブログの例としてもそんな感じに書いてあります。
で、今回せっかくなのでちょっと触ってみようかと。
Consul Templateを使うには
Consul Templateはzipアーカイブとして提供されているので、それを展開して実行するだけです。
https://releases.hashicorp.com/consul-template/
Consul Templateの実行に、Consul自体は必要ありません。
また、Consul Templateで使うテンプレートですが、こちらはGo Templateを使うようです。
Comsul Template / Templating Language
template - The Go Programming Language
初めて使うんですけど…。
今回試すシナリオ
今回は、nginxを使って背後に簡単なSpring Bootアプリケーションを使って試してみたいと思います。
用意するサーバーは、次の感じで。
- Consul Server … 172.17.0.2
- nginx with Cousul Template … 172.17.0.3
- Spring Bootアプリケーションが稼働するサーバー with Consul Agent … 172.17.0.4〜6(appserver1〜3)
Consul Templateはnginxのサーバーに入れて、Consul Serverへ問い合わせを行い、背後のアプリケーションの構成に合わせてロードバランスする
設定を生成・反映する感じにしたいと思います。
なお、nginxはインストールおよび起動済みとします。nginxはUbuntu Linux上でインストールしており、「/etc/nginx/conf.d」ディレクトリに
ファイルを配置すると、自動的に読み込むタイプのものにしています。
Spring Bootアプリケーション
確認用のSpring Bootアプリケーションは、Spring Boot CLIでサクッと作ります。
ヘルスチェックと、アクセス結果にサーバー名を返すようなアプリケーションです。
server.groovy
@RestController class HelloController { def logger = org.slf4j.LoggerFactory.getLogger(getClass()) @GetMapping('health') def health() { "OK!!" + System.lineSeparator() } @GetMapping('hello') def hello(javax.servlet.http.HttpServletRequest request) { logger.info(java.time.LocalDateTime.now().toString() + ": access " + request.requestURI) "Hello " + InetAddress.localHost.hostName + "!!" + System.lineSeparator() } }
これを、Uber JARにして各サーバーで実行するものとします。
$ spring jar server.jar server.groovy
Consul Serverの起動
Consul Serverは、簡単に1台構成でいきます。
まずはConsulのダウンロードと展開。
$ wget https://releases.hashicorp.com/consul/0.8.4/consul_0.8.4_linux_amd64.zip $ unzip consul_0.8.4_linux_amd64.zip
Consul Serverの起動。「-client」でIPアドレスを指定して他ホストからの接続を可能にして、データディレクトリは「/var/lib/consul/data」とします。
$ ./consul agent -bootstrap -server -client=`hostname -i` -data-dir=/var/lib/consul/data
これで、Consul Serverの準備はおしまいです。
Spring Bootアプリケーション with Consul Agent
続いて、先ほど作成したSpring BootアプリケーションをConsulと一緒に動かします。
Consulのダウンロードと展開。
>|sh|
$ wget https://releases.hashicorp.com/consul/0.8.4/consul_0.8.4_linux_amd64.zip
$ unzip consul_0.8.4_linux_amd64.zip
こちら側のConsulは、設定ファイルを使いましょう。
まずは、Clientとしての設定。
/var/lib/consul/conf/client.json
{ "data_dir": "/var/lib/consul/data", "start_join": [ "172.17.0.2" ] }
続いて、登録するServiceの設定。
/var/lib/consul/conf/service-app.json
{ "service": { "name": "appbackend", "tags": ["app.backend"], "address": "", "port": 8080, "checks": [ { "script": "curl http://localhost:8080/health >/dev/null 2>&1", "interval": "10s" } ] } }
これでConsulを起動すればよいのですが、そのまま実行するとnode-idが重複してると怒られまして…。
==> Starting Consul agent... ==> Joining cluster... ==> 1 error(s) occurred: * Failed to join 172.17.0.2: Member '893d5cb95975' has conflicting node ID '4a8ac05f-85ae-0a43-4ba2-536b7034e6bb' with this agent's ID
これかな?
Failed to join: Member has conflicting node ID · Issue #3070 · hashicorp/consul · GitHub
仕方ないので、今回はUUIDをuuidgenで生成して対応することにしました。
sudo apt-get install uuid-runtime
これで、Consulを起動します。
./consul agent -config-dir=/var/lib/consul/conf -node-id=`uuidgen`
ここまでを、3台のサーバーで行います。
まだSpring Bootアプリケーションは最初の1台のみ起動し、残りはあとから順次追加していきましょう。この状態だと、ヘルスチェックで失敗するので他の2つは
エラーになります。
$ java -jar server.jar
nginx with Consul Templateと動作確認
最後に、nginxが稼働しているサーバー上で、Consul Templateの設定を行っていきます。
Consul Templateのインストールから。
※なぜか、このサーバーのみ諸事情でrootユーザーですが…
# wget https://releases.hashicorp.com/consul-template/0.18.5/consul-template_0.18.5_linux_amd64.zip # unzip consul-template_0.18.5_linux_amd64.zip Archive: consul-template_0.18.5_linux_amd64.zip inflating: consul-template
Consul Templateは、Consul同様に単一のファイルとして配布されているだけとなります。
では、テンプレートを書いてみましょう。
Consul TemplateのGitHubリポジトリに、nginxの設定のサンプルがあるのでこちらを参考にしてみます。
consul-template/nginx.md at master · hashicorp/consul-template · GitHub
で、書いたテンプレートはこちら。
nginx.ctmpl
upstream appbackend { {{ range service "appbackend" }} {{ $name := .Name }} {{ $service := service .Name }} server {{ .Address }}:{{ .Port }}; {{ end }} } server { listen 8080; server_name localhost; location / { proxy_pass http://appbackend; } }
先ほどのSpring Bootアプリケーションが動作するConsul Agentの起動時に指定した設定ファイルで、Service名「appbackend」のデータを
引き抜き、そこからupstreamの内容を構成します。
upstream appbackend { {{ range service "appbackend" }} {{ $name := .Name }} {{ $service := service .Name }} server {{ .Address }}:{{ .Port }}; {{ end }} }
「.」で、その階層の名前を指定することで値を取得できるようです。「range」はループですね。
consul-templateのイベント発火トリガーについて調査した
serverは固定で構成しました。
server { listen 8080; server_name localhost; location / { proxy_pass http://appbackend; } }
実行してみます。カレントディレクトリにあるテンプレートを元に、nginxのロードバランサの設定を生成し、設定ファイル生成後には
nginxの設定を再読み込みさせる…というコマンドはこのようになります。。
# ./consul-template -consul-addr 172.17.0.2:8500 -template "nginx.ctmpl:/etc/nginx/conf.d/balancer.conf:service nginx reload"
Consul Serverと同じホストで実行していない場合は、「-consul-addr」でConsul Serverの接続先を指定する必要があります。
「-template」オプションでは、「:」区切りで「テンプレートファイルのパス:出力するファイルのパス:ファイル出力時に実行するコマンド」
となります。以下の例だとカレントディレクトリにあるnginx.ctmplをテンプレートファイルとして、「/etc/nginx/conf.d/balancer.conf」に
ファイルを生成し、終わったらnginxを再読み込み、となります。
-template "nginx.ctmpl:/etc/nginx/conf.d/balancer.conf:service nginx reload"
今回のnginxでは、「/etc/nginx/conf.d」ディレクトリ配下に設定ファイルを置いて再読み込みすると、includeする設定となっています。
「-template」オプションは、複数指定可能みたいです。
Consul Template / Command Line Flags
最後のファイル生成後のコマンドは省略可能です。
また「-dry」オプションを指定することで、実際に動作させず結果だけを見ることができます。例えば、このような感じ。
# ./consul-template -consul-addr 172.17.0.2:8500 -template "nginx.ctmpl:/etc/nginx/conf.d/balancer.conf:service nginx reload" -dry > /etc/nginx/conf.d/balancer.conf upstream appbackend { server 172.17.0.4:8080; } server { listen 8080; server_name localhost; location / { proxy_pass http://appbackend; } }
今は、バックエンドのサーバーがひとつしかないので、このような設定になります。
では実行してみます。
# ./consul-template -consul-addr 172.17.0.2:8500 -template "nginx.ctmpl:/etc/nginx/conf.d/balancer.conf:service nginx reload" * Reloading nginx nginx [ OK ]
いきなりnginxがreloadされました。Consul Templateは、このまま待機状態になります。
他のホストから、curlでアクセスしてみます。
$ curl http://172.17.0.3:8080/hello
Hello appserver1!!
OKそうです。
ここで、他の2つのサーバーでもSpring Bootアプリケーションを起動してみます。
$ java -jar server.jar
少し待っていると、Consul Template側にnginxのreloadが表示され
* Reloading nginx nginx [ OK ]
追加したサーバーがレスポンスに現れるようになります。
$ curl http://172.17.0.3:8080/hello Hello appserver1!! $ curl http://172.17.0.3:8080/hello Hello appserver2!! $ curl http://172.17.0.3:8080/hello Hello appserver3!!
この時の、生成されたnginxの設定ファイルはこのように。
/etc/nginx/conf.d/balancer.conf
upstream appbackend { server 172.17.0.4:8080; server 172.17.0.5:8080; server 172.17.0.6:8080; } server { listen 8080; server_name localhost; location / { proxy_pass http://appbackend; } }
さらにここで、2つ目のサーバーを落とすとやはりnginxがリロードされ、落としたサーバーがロードバランス先から外されます。
$ curl http://172.17.0.3:8080/hello Hello appserver1!! $ curl http://172.17.0.3:8080/hello Hello appserver3!!
OKそうですね。
まとめ
Consul Templateを使って、nginxをロードバランサとして背後のサーバーの追加、削除をnginxの設定に反映させてみました。
ConsulをService Discoveryとして使っている場合は、なかなか便利な感じでしょうか…?
Go Templateがちょっと慣れない感じなのですが、書いていけば慣れる…かな?