CLOVER🍀

That was when it all began.

Consul Templateを使って、nginxのプロキシ先のサーバーの追加と削除を動的に反映する

Consulには、Consul Templateというテンプレートツールが別途開発されています。

Consule

GitHub - hashicorp/consul-template: Template rendering, notifier, and supervisor for @HashiCorp Consul and Vault data.

Introducing Consul Template

Consul Templateとはなにか?ですが、こんな感じのもののようです。

  • daemonである
  • あらかじめテンプレートを用意しておく
  • ConsulやVaultに問い合わせを行い、その結果を元にテンプレートに適用し、結果をファイルシステムに出力する
  • ConsulやVaultの問い合わせ結果は、Consul Templateの実行中にイベントとして継続的に通知される
  • テンプレートからのファイル出力、更新時に、追加でコマンドを実行できる

典型的な例としては、ConsulにServiceとしてApacheやnginx、HAProxyなどでロードバランサを構成しておき、
Consulサーバーへの問い合わせ結果を元にバックエンドのサーバーの接続先を取得、動的に設定ファイルを
更新し、Apache、nginx、HAProxyへ設定を反映するコマンドを実行する、などのようです。

というか、ブログの例としてもそんな感じに書いてあります。

Introducing Consul Template

で、今回せっかくなのでちょっと触ってみようかと。

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 チートシート

刺身タンポポを殲滅する Consul Template

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がちょっと慣れない感じなのですが、書いていけば慣れる…かな?