Hatena::ブログ(Diary)

CLOVER

2017-01-18

WildFly Swarm+Hystrix(Circuit Breaker)を試す

WildFly Swarmには、Netflix OSSとの統合機能があります。

Stability Patterns / Circuit Breaker

目次のタイトルが「Stability Patterns / Circuit Breaker」なのに、ページのタイトルが「NetflixOSS」というのは
どうなんでしょう?というのはありますが、RibbonやHystrixと統合する機能のようです。

WildFly Swarmのチュートリアルにも登場します。

Fault tolerance and resilience | WildFly Swarm

ここではConsulとともに使われているようですが、今回はRibbonもConsulも置いておいて、単純にWildFly Swarmの提供する
Hystrix向けのFraction経由でHystrixを使うことに着目したいと思います。まあ、個人的にちょっとHystrixを把握して
おきたいということがありまして。

Hystrixとは

Netflix OSSの提供する、Circuit Breakerです。

GitHub - Netflix/Hystrix: Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and 3rd party libraries, stop cascading failure and enable resilience in complex distributed systems where failure

Circuit Breakerとは、主にMicroservicesで外部サービスを呼び出す際に、呼び出し先が障害などでエラーになったりタイムアウトに
なるような時にリクエスト(というか呼び出し)を遮断してしまうものになります。これで、外部サービスの障害に自サービスが引っ張られて
遅延していくといったことを防ぎます。

遮断された呼び出しについては、一定時間後などでまた再開するようになります。もちろん、相手側が復旧していればですが。

Hystrixのドキュメントとしては、こちらを見るとよいでしょう。

Getting Started ? Netflix/Hystrix Wiki ? GitHub

How To Use ? Netflix/Hystrix Wiki ? GitHub

WildFly Swarm+Hystrix Fraction

続いて、WildFly SwarmのHystrix向けのFractionについてです。

まず、今回用意したpom.xmlを記載しましょう。
pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.littlewings</groupId>
    <artifactId>circuit-breaker-hystrix</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <scala.major.version>2.12</scala.major.version>
        <scala.version>${scala.major.version}.1</scala.version>
        <scala.maven.plugin.version>3.2.2</scala.maven.plugin.version>

        <failOnMissingWebXml>false</failOnMissingWebXml>

        <wildfly.swarm.version>2017.1.1</wildfly.swarm.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.wildfly.swarm</groupId>
                <artifactId>bom-all</artifactId>
                <version>${wildfly.swarm.version}</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>${scala.version}</version>
        </dependency>

        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>7.0</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.wildfly.swarm</groupId>
            <artifactId>jaxrs</artifactId>
        </dependency>
        <dependency>
          <groupId>org.wildfly.swarm</groupId>
          <artifactId>hystrix</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <version>${scala.maven.plugin.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <scalaVersion>${scala.version}</scalaVersion>
                    <args>
                        <arg>-Xlint</arg>
                        <arg>-unchecked</arg>
                        <arg>-deprecation</arg>
                        <arg>-feature</arg>
                    </args>
                    <recompileMode>incremental</recompileMode>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.wildfly.swarm</groupId>
                <artifactId>wildfly-swarm-plugin</artifactId>
                <version>${wildfly.swarm.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>package</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

注目点は、ここですね。

        <dependency>
          <groupId>org.wildfly.swarm</groupId>
          <artifactId>hystrix</artifactId>
        </dependency>

これでHystrix向けのFractionが追加されるわけですが、Hystrixへのライブラリ依存関係以外に何が増えるんだろう?と思って見てみたら
HystrixMetricsStreamServletが追加されるようです。
https://github.com/wildfly-swarm/wildfly-swarm/blob/2017.1.1/fractions/netflix/hystrix/src/main/java/org/wildfly/swarm/netflix/hystrix/runtime/HystrixArchivePreparer.java

https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-metrics-event-stream

これは、HystrixのDashboardを使う時に必要になります。

呼び出し先の外部サービス

では、アプリケーションを書いていきましょう。

今回は、Spring Boot CLIで簡単なRestControllerを作ることにします。単純に「HelloWorld!!」と返すだけのRestControllerです。
hello-message.groovy

@RestController
class HelloMessageController {
    @RequestMapping("hello-message")
    def message() {
        "HelloWorld!!"
    }
}

このアプリケーションは、ポート9000でリッスンすることにしましょう。次のコマンドで起動します。

$ spring run hello-message.groovy -- --server.port=9000

以上で、呼び出し先の外部サービスの準備はおしまいです。

WildFly SwarmでHystrixを使う

続いて、WildFly Swarm+Hystrixを使ったアプリケーションを書きます。

Getting Startedの「Hello World!」を見ると、HystixCommandというクラスを継承して実装するようです。

Getting Started / Hello World!

そして、実際にどんな処理をするかは、runメソッドをオーバーライドすればよい、と。

Fail Fast

で、最初にこんなのを用意。
src/main/scala/org/littlewings/wildflyswarm/hystrix/FailFastMessageCommand.scala

package org.littlewings.wildflyswarm.hystrix

import javax.ws.rs.client.ClientBuilder

import com.netflix.hystrix.{HystrixCommand, HystrixCommandGroupKey}
import org.jboss.logging.Logger

class FailFastMessageCommand extends HystrixCommand[String](HystrixCommandGroupKey.Factory.asKey("FailFastMessageCommand")) {
  val logger: Logger = Logger.getLogger(classOf[FailFastMessageCommand])

  override def run(): String = {
    logger.infof("start fail-fast request")

    val client =
      ClientBuilder
        .newClient

    try {
      val response =
        client
          .target("http://localhost:9000/hello-message")
          .request
          .get

      val message = response.readEntity(classOf[String])

      response.close()

      logger.infof("end fail-fast request")

      message
    } finally {
      client.close()
    }
  }
}

HystrixCommandのコンストラクタには、HystrixCommandGroupKeyを渡す必要があるようです。今回は、クラスの単純名から
キーを作成しました。

class FailFastMessageCommand extends HystrixCommand[String](HystrixCommandGroupKey.Factory.asKey("FailFastMessageCommand")) {

で、クラス名がなぜかFailFastになっていますが、これはドキュメントの次の箇所で、Fail Fastなパターンとして
紹介されているからです。

How To Use / Common Patterns / Fail Fast

この実装方法を取ると、runメソッドの呼び出しに失敗する(例外がスローされる)とHystrixRuntimeExceptionにその原因が
包まれてスローされるようです。それが、呼び出し元まで伝播してくる、と。

試してみましょう。こんなJAX-RSリソースクラスを用意。
src/main/scala/org/littlewings/wildflyswarm/hystrix/MessageResource.scala

package org.littlewings.wildflyswarm.hystrix

import javax.ws.rs.core.MediaType
import javax.ws.rs.{GET, Path, Produces}

import org.jboss.logging.Logger

@Path("message")
class MessageResource {
  val logger: Logger = Logger.getLogger(classOf[MessageResource])

  @GET
  @Path("fail-fast")
  @Produces(Array(MediaType.TEXT_PLAIN))
  def failFast: String = {
    logger.infof("start resource")

    val command = new FailFastMessageCommand
    val message = command.execute()

    logger.infof("end resource")

    message + System.lineSeparator
  }
}

ポイントは、オーバーライドしたrunメソッドではなくexecuteメソッドを呼び出すことですね。

    val command = new FailFastMessageCommand
    val message = command.execute()

アプリケーションを起動します。

$ mvn wildfly-swarm:run

確認。

$ curl -i http://localhost:8080/message/fail-fast
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: text/plain
Content-Length: 13
Date: Wed, 18 Jan 2017 11:39:12 GMT

HelloWorld!!

では、続いてSpring Boot CLIで書いたアプリケーションを落としてアクセス。

$ curl -i http://localhost:8080/message/fail-fast
HTTP/1.1 500 Internal Server Error
Connection: keep-alive
Content-Type: text/html;charset=UTF-8
Content-Length: 9359
Date: Wed, 18 Jan 2017 11:39:47 GMT

<html><head><title>ERROR</title><style>body {
    font-family: "Lucida Grande", "Lucida Sans Unicode", "Trebuchet MS", Helvetica, Arial, Verdana, sans-serif;
    margin: 5px;
}

〜省略〜

エラーになります。

サーバー(WildFly Swarmアプリケーション)裏では、HystrixRuntimeExceptionがスローされています。さらにその原因をたどると
接続エラーなわけですが。

Caused by: com.netflix.hystrix.exception.HystrixRuntimeException: FailFastMessageCommand failed and no fallback available.

落としたSpring Boot CLIアプリケーションを復帰させると、アクセス可能になります。

$ curl -i http://localhost:8080/message/fail-fast
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: text/plain
Content-Length: 13
Date: Wed, 18 Jan 2017 11:41:45 GMT

HelloWorld!!

Fail Silent

続いて、Fail Silentと紹介されているパターン。

How To Use / Common Patterns / Fail Silent

このケースで用意したクラス。
src/main/scala/org/littlewings/wildflyswarm/hystrix/FailSilentCommand.scala

package org.littlewings.wildflyswarm.hystrix

import javax.ws.rs.client.ClientBuilder

import com.netflix.hystrix.{HystrixCommand, HystrixCommandGroupKey}
import org.jboss.logging.Logger

class FailSilentCommand extends HystrixCommand[String](HystrixCommandGroupKey.Factory.asKey("FailSilentCommand")) {
  val logger: Logger = Logger.getLogger(classOf[FailSilentCommand])

  override def run(): String = {
    logger.infof("start fail-silent request")

    val client =
      ClientBuilder
        .newClient

    try {
      val response =
        client
          .target("http://localhost:9000/hello-message")
          .request
          .get

      val message = response.readEntity(classOf[String])

      response.close()

      logger.infof("end fail-silent request")

      message
    } finally {
      client.close()
    }
  }

  override def getFallback: String = "Fallback Message!!"
}

Fail Fastで紹介したクラスとほとんど同じですが、今回はHystrixCommand#getFallbackメソッドをオーバーライドしています。

  override def getFallback: String = "Fallback Message!!"

JAX-RSリソースクラスにもアクセス先を追加。

@Path("message")
class MessageResource {
  val logger: Logger = Logger.getLogger(classOf[MessageResource])

  @GET
  @Path("fail-fast")
  @Produces(Array(MediaType.TEXT_PLAIN))
  def failFast: String = {
    logger.infof("start resource")

    val command = new FailFastMessageCommand
    val message = command.execute()

    logger.infof("end resource")

    message + System.lineSeparator
  }

  @GET
  @Path("fail-silent")
  @Produces(Array(MediaType.TEXT_PLAIN))
  def failSilent: String = {
    logger.infof("start resource")

    val command = new FailSilentCommand
    val message = command.execute()

    logger.infof("end resource")

    message + System.lineSeparator
  }

WildFly Swarmに乗せたアプリケーションを動かして、確認。

$ curl -i http://localhost:8080/message/fail-silent
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: text/plain
Content-Length: 13
Date: Wed, 18 Jan 2017 11:57:05 GMT

HelloWorld!!

Spring Boot CLIで作ったアプリケーションを落として確認。

$ curl -i http://localhost:8080/message/fail-silent
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: text/plain
Content-Length: 19
Date: Wed, 18 Jan 2017 11:57:40 GMT

Fallback Message!!

オーバーライドしたgetFallbackメソッドで設定した値が返ってきました。

ちなみに、サーバー側では例外が飛んだりはしていません。

2017-01-18 20:57:40,472 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-9) start resource
2017-01-18 20:57:40,473 INFO  [org.littlewings.wildflyswarm.hystrix.FailSilentCommand] (hystrix-FailSilentCommand-3) start fail-silent request
2017-01-18 20:57:40,492 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-9) end resource

というわけで、エラー発生時に代替の値を返す実装方法ですと。なので、「Silent」なわけですね。

HystrixCommandの呼び出し方法

少し、追加説明を。

今回、HystrixCommandのサブクラスを、すべてexecuteメソッドで呼び出しています。

    val command = new FailFastMessageCommand
    val message = command.execute()

これは、「Synchronous Execution」、同期呼び出しになります。

How To Use / Synchronous Execution

他にはHystrixCommand#queueで呼び出す「Asynchronous Execution」、この場合はFutureが返ります。

How To Use / Asynchronous Execution

Observable(※RxJava)を返す「Reactive Execution」(HystrixCommand#observe、HystrixCommand#toObservable)。

How To Use / Reactive Execution

そしてHystrixObservableCommandを使っての「Reactive Commands」があります。

How To Use / Reactive Commands

呼び出しパターンとして覚えておきましょう。

設定を変更する

今回はデフォルトの設定でHystrixを動かしていますが、設定を行うこともできます。

ドキュメントはこちら。

Configuration ? Netflix/Hystrix Wiki ? GitHub

今回は、「circuitBreaker.requestVolumeThreshold」というプロパティを変えてみましょう。

Configuration / circuitBreaker.requestVolumeThreshold

実は、ここまでの動作確認では、Circuit Breakerがその名の通りの動作をしていることを確認していません。

「circuitBreaker.requestVolumeThreshold」というプロパティは、ある時間あたり、何回リクエストが失敗するとCircuitが
オープンするかを設定します。デフォルトは20で、オープンした後はCircuitがオープンした時間または最後にリクエスト呼び出しを
行った時間に、「circuitBreaker.sleepWindowInMilliseconds」で指定した時間が経過したのち、Circuitがクローズします。
デフォルトは5000(5秒)です。

今回、「circuitBreaker.requestVolumeThreshold」を5に設定したCommandを用意してみます。
src/main/scala/org/littlewings/wildflyswarm/hystrix/ConfiguredMessageCommand.scala

package org.littlewings.wildflyswarm.hystrix

import javax.ws.rs.client.ClientBuilder

import com.netflix.hystrix.{HystrixCommand, HystrixCommandGroupKey, HystrixCommandProperties}
import org.jboss.logging.Logger

class ConfiguredMessageCommand
  extends HystrixCommand[String](
    HystrixCommand.Setter
      .withGroupKey(HystrixCommandGroupKey.Factory.asKey("ConfiguredMessageCommand"))
      .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withCircuitBreakerRequestVolumeThreshold(5))
  ) {
  val logger: Logger = Logger.getLogger(classOf[ConfiguredMessageCommand])

  override def run(): String = {
    logger.infof("start configured-message request")

    val client =
      ClientBuilder
        .newClient

    try {
      val response =
        client
          .target("http://localhost:9000/hello-message")
          .request
          .get

      val message = response.readEntity(classOf[String])

      response.close()

      logger.infof("end configured-message request")

      message
    } finally {
      client.close()
    }
  }

  override def getFallback: String = "Fallback Message!!"
}

ここまでのHystrixCommandの実装と異なり、HystrixCommand.SetterでGroupKeyを指定しつつ、プロパティを設定しています。

  extends HystrixCommand[String](
    HystrixCommand.Setter
      .withGroupKey(HystrixCommandGroupKey.Factory.asKey("ConfiguredMessageCommand"))
      .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withCircuitBreakerRequestVolumeThreshold(5))
  ) {

なお、Fail Silentで実装しています。

「Confiugration」のドキュメントでいくと、「How to Set Instance Default」と書かれている方法ですね。というわけで、
このインスタンス単位の設定になります。

JAX-RSリソースクラス側に、このCommandの呼び出しを追加。

  @GET
  @Path("configured")
  @Produces(Array(MediaType.TEXT_PLAIN))
  def configured: String = {
    logger.infof("start configured-message resource")

    val command = new ConfiguredMessageCommand
    val message = command.execute()

    logger.infof("end configured-message resource")

    message + System.lineSeparator
  }

まずは、Spring Boot CLIアプリケーションが起動している状態でアクセス。

$ curl http://localhost:8080/message/configured
HelloWorld!!

この時、WildFly Swarm側で動かしているアプリケーションには、こんなログが出力されます。

2017-01-18 21:26:26,933 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-2) start configured-message resource
2017-01-18 21:26:26,935 INFO  [org.littlewings.wildflyswarm.hystrix.ConfiguredMessageCommand] (hystrix-ConfiguredMessageCommand-2) start configured-message request
2017-01-18 21:26:26,995 INFO  [org.littlewings.wildflyswarm.hystrix.ConfiguredMessageCommand] (hystrix-ConfiguredMessageCommand-2) end configured-message request
2017-01-18 21:26:26,997 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-2) end configured-message resource

JAX-RSリソースクラスのログと、間にHystrixCommandnのログが挟まっている感じですね。

では、Spring Boot CLIで作ったアプリケーションを落として、連続で6回アクセスしてみます。速すぎると切り替わらなかったので、
5回に1回で1秒スリープを…。

$ for i in {1..6}; do curl http://localhost:8080/message/configured; if [ $(( $i % 5 )) -eq 0 ]; then sleep 1; fi done
Fallback Message!!
Fallback Message!!
Fallback Message!!
Fallback Message!!
Fallback Message!!
Fallback Message!!

この時のログは、こんな感じ。

2017-01-18 21:41:34,953 INFO  [org.littlewings.wildflyswarm.hystrix.ConfiguredMessageCommand] (hystrix-ConfiguredMessageCommand-10) start configured-message request
2017-01-18 21:41:34,961 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-12) end configured-message resource
2017-01-18 21:41:34,978 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-13) start configured-message resource
2017-01-18 21:41:34,979 INFO  [org.littlewings.wildflyswarm.hystrix.ConfiguredMessageCommand] (hystrix-ConfiguredMessageCommand-10) start configured-message request
2017-01-18 21:41:34,985 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-13) end configured-message resource
2017-01-18 21:41:34,997 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-14) start configured-message resource
2017-01-18 21:41:34,998 INFO  [org.littlewings.wildflyswarm.hystrix.ConfiguredMessageCommand] (hystrix-ConfiguredMessageCommand-10) start configured-message request
2017-01-18 21:41:35,002 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-14) end configured-message resource
2017-01-18 21:41:35,016 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-15) start configured-message resource
2017-01-18 21:41:35,016 INFO  [org.littlewings.wildflyswarm.hystrix.ConfiguredMessageCommand] (hystrix-ConfiguredMessageCommand-10) start configured-message request
2017-01-18 21:41:35,025 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-15) end configured-message resource
2017-01-18 21:41:35,039 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-16) start configured-message resource
2017-01-18 21:41:35,039 INFO  [org.littlewings.wildflyswarm.hystrix.ConfiguredMessageCommand] (hystrix-ConfiguredMessageCommand-10) start configured-message request
2017-01-18 21:41:35,045 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-16) end configured-message resource
2017-01-18 21:41:36,060 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-17) start configured-message resource
2017-01-18 21:41:36,061 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-17) end configured-message resource

6回目では、Command呼び出しそのものがなくなり、いきなりFallbackに移行しています。
JAX-RSリソース側のログしかない

2017-01-18 21:41:36,060 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-17) start configured-message resource
2017-01-18 21:41:36,061 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-17) end configured-message resource

これがオープンした状態ですね、と。時間を置いてアクセスする(ウィンドウをまたぐと)と、再度Command呼び出しを再開します。
この例ではまだ呼び出しに失敗する状態のままですが。

2017-01-18 21:42:05,728 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-18) start configured-message resource
2017-01-18 21:42:05,728 INFO  [org.littlewings.wildflyswarm.hystrix.ConfiguredMessageCommand] (hystrix-ConfiguredMessageCommand-10) start configured-message request
2017-01-18 21:42:05,733 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-18) end configured-message resource

先ほど実装した、単純なFail Silentの場合だと、デフォルト値なので20回まではCommandを呼び出し続けます。

$ for i in {1..21}; do curl http://localhost:8080/message/fail-silent; if [ $(( $i % 5 )) -eq 0 ]; then sleep 1; fi done

Commandの呼び出しがなくなるのは、21回目からですね。

2017-01-18 21:47:30,867 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-5) start resource
2017-01-18 21:47:30,868 INFO  [org.littlewings.wildflyswarm.hystrix.FailSilentCommand] (hystrix-FailSilentCommand-10) start fail-silent request
2017-01-18 21:47:30,872 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-5) end resource
2017-01-18 21:47:31,903 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-6) start resource
2017-01-18 21:47:31,904 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-6) end resource

全体のデフォルト設定

今回、HystrixCommandのサブクラスの設定を個別に行いましたが、全体的にデフォルト値を設定することもできるようです。

設定例としては、こんな感じ。ConfigurationManagerというクラスを使えばよいみたいです。

ConfigurationManager.getConfigInstance().setProperty("hystrix.command.default.circuitBreaker.requestVolumeThreshold", 5);

また、インスタンス単位で設定する場合は、こちら。

ConfigurationManager.getConfigInstance().setProperty("hystrix.command.[HystrixCommandGroupKey名].circuitBreaker.requestVolumeThreshold", 5);

参考)
https://github.com/Netflix/Hystrix/blob/master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/HystrixCommandDemo.java

ただ、これをWildFly Swarmでやろうとすると、実行時に依存関係(archaius-core)が見えてないっぽいです。

Caused by: java.lang.NoClassDefFoundError: com/netflix/config/ConfigurationManager

HystrixFractionを見ると、使えるモジュールが「com.netflix.hystrix」と「io.reactivex.rxjava」に絞られている
みたいなので、そうかーという感じですが。
https://github.com/wildfly-swarm/wildfly-swarm/blob/2017.1.1/fractions/netflix/hystrix/src/main/java/org/wildfly/swarm/netflix/hystrix/HystrixFraction.java#L30-L33

HystrixFractionではHystrixMetricsStreamServletのパスしか設定できないみたいなので、全体的なデフォルト値は
触ることができないっぽいですねぇ。

Hystrix Dashboard

最後に、Hystrix Dashboardを使ってみます。最初の方で、Dashboardと連携するのに使う機能があるって書きましたしね。

Dashboard ? Netflix/Hystrix Wiki ? GitHub

DashboardはWARとして提供されているので、こちらをダウンロードして使います。自分は、Maven Centralからダウンロードしました。

https://search.maven.org/#artifactdetails%7Ccom.netflix.hystrix%7Chystrix-dashboard%7C1.5.9%7Cwar

今回は、WildFly Swarmのmicroprofile-hollowswarm.jarを使います。

$ java -Dswarm.http.port=18080 -jar microprofile-2017.1.1-hollowswarm.jar hystrix-dashboard-1.5.9.war

リッスンポートは、18080としました。

この状態で、「http://localhost:18080/」にアクセスするとDashboardを見ることができます。

表示された赤枠のテキストフィールドに、今回作成したWildFly Swarmアプリケーションを起動した状態で
http://localhost:8080/hystrix.stream」をテキストフィールドに埋め込み、「Add Stream」ボタンを押します。
f:id:Kazuhira:20170118220943p:image

すると、追加したURLが現れるので、この状態で「Monitor Stream」をクリックします。
f:id:Kazuhira:20170118221153p:image

Dashboardに移行するので、あとはHystrixMetricsStreamServletによって送信されたメトリクスを確認することが
できます。
f:id:Kazuhira:20170118221606p:image

先にも書きましたが、WildFly SwarmのHystrix向けのFractionを依存関係に追加すると、HystrixMetricsStreamServletが追加されます。
https://github.com/wildfly-swarm/wildfly-swarm/blob/2017.1.1/fractions/netflix/hystrix/src/main/java/org/wildfly/swarm/netflix/hystrix/runtime/HystrixArchivePreparer.java

https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-metrics-event-stream

これで、SSE(Server-Sent Events)を使ってクライアント側にメトリクスを送り続ける仕組みになっているみたいです。
※Server-Sent Eventsよく知らないですが
Metrics and Monitoring / Metrics Event Stream

オマケ - HystrixConcurrencyStrategy

WildFly Swarmのチュートリアルでは、なにやら起動時にThreadFactoryの設定をしているコードがあります。
https://github.com/wildfly-swarm/tutorial/blob/master/microservice/everest/src/main/java/org/javaee7/wildfly/samples/everest/HystrixEEBootstrap.java
https://github.com/wildfly-swarm/tutorial/blob/master/microservice/everest/src/main/java/org/javaee7/wildfly/samples/everest/EEConcurrencyStrategy.java

このコードでは、@ResourceでインジェクションしたManagedThreadFactoryを使ってThreadPoolを作成し、HystrixPluginsに
HystrixConcurrencyStrategyとして登録します。

    @Resource
    ManagedThreadFactory threadFactory;

    @PostConstruct
    public void onStartup() {
        System.out.println("Initialising hystrix ...");
        HystrixPlugins.getInstance().registerConcurrencyStrategy(new EEConcurrencyStrategy(threadFactory));
    }

これは、Hystrixのプラグインの仕組みのひとつですね。

Plugins ? Netflix/Hystrix Wiki ? GitHub

Plugins / Concurrency Strategy

このチュートリアルのコードでは、Hystrixが使用するThreadPoolで使うThreadを、ManagedThreadFactoryを使って
作成するように変更しています。まあ、EE環境だからってことなのでしょうね。

まとめ

WildFly Swarm(自体はそこまで関係なかったですけど)を使って、HystrixのFractionを組み込み、基本的な使い方とDashboardの連携まで
見てみました。

Circuit Breakerを使ったのは初めてだったのですが、なかなか面白かったです。

今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/wildfly-swarm-scala-examples/tree/master/circuit-breaker-hystrix

参考)
マイクロサービスにレジリエンスをもたらすHystrixを試してみる - たけぞう瀕死ブログ

Spring BootでCircuit Breaker(Spring Cloud Netflix Hystrix)を試す - abcdefg.....

2017-01-15

Consulでsyslog出力設定

Consulで遊んでいるわけですが、ログが標準出力に出ているだけなのでこれはちょっと気になるところです。

起動オプションや設定を見ていると、syslogへの出力が可能みたいなので設定してみました。
※Windowsはどうしたという話はさておき

Configuration

起動オプションと設定ファイルで、微妙に設定できる項目が違いそうですね。

rsyslogのインストール

では、今回はrsyslogを使ってログ出力をすることにしましょう。対象の環境は、Ubuntu Linux 14.04 LTSとします。

インストール。

$ sudo apt-get install rsyslog

デフォルトでインストールされる設定ファイルは、以下の2つとなります。

/etc/rsyslog.conf
/etc/rsyslog.d/50-default.conf

rsyslogの設定

Consulでsyslog出力を有効にした場合、デフォルトではlocal0ファシリティを使用します。

local0の出力先を、「/var/log/consul/consul.log」とするように設定してみましょう。

ログ出力先を作成します。また、ディレクトリのオーナーはsyslogユーザーにしておきます。

$ sudo mkdir /var/log/consul
$ sudo chown -R syslog.syslog /var/log/consul

rsyslogの設定ファイルに、Consul用のログ出力設定を追加します。
/etc/rsyslog.d/consul.conf

local0.*			/var/log/consul/consul.log

また、local0のログが重複して出力されないように、「*.*」の部分からは落としておきます。
/etc/rsyslog.d/50-default.conf

## 変更前
*.*;auth,authpriv.none          -/var/log/syslog

## 変更後
*.*;auth,authpriv.none,local0.none          -/var/log/syslog

ここまでで、準備完了です。

起動オプションでログ出力

では、Consulを起動してみます。

$ ./consul agent -server -bootstrap -syslog -data-dir=data

「-syslog」オプションを付与することで、syslogへのログ出力が行われるようになります。

確認。
/var/log/consul/consul.log

Jan 15 10:04:08 47d9dbb93395 consul[468]: raft: Initial configuration (index=1): [{Suffrage:Voter ID:172.17.0.3:8300 Address:172.17.0.3:8300}]
Jan 15 10:04:08 47d9dbb93395 consul[468]: raft: Node at 172.17.0.3:8300 [Follower] entering Follower state (Leader: "")
Jan 15 10:04:08 47d9dbb93395 consul[468]: serf: EventMemberJoin: 47d9dbb93395 172.17.0.3
Jan 15 10:04:08 47d9dbb93395 consul[468]: serf: EventMemberJoin: 47d9dbb93395.dc1 172.17.0.3
Jan 15 10:04:08 47d9dbb93395 consul[468]: consul: Adding LAN server 47d9dbb93395 (Addr: tcp/172.17.0.3:8300) (DC: dc1)
Jan 15 10:04:08 47d9dbb93395 consul[468]: serf: Failed to re-join any previously known node
Jan 15 10:04:08 47d9dbb93395 consul[468]: consul: Adding WAN server 47d9dbb93395.dc1 (Addr: tcp/172.17.0.3:8300) (DC: dc1)
Jan 15 10:04:14 47d9dbb93395 consul[468]: raft: Heartbeat timeout from "" reached, starting election
Jan 15 10:04:14 47d9dbb93395 consul[468]: raft: Node at 172.17.0.3:8300 [Candidate] entering Candidate state in term 3
Jan 15 10:04:14 47d9dbb93395 consul[468]: raft: Election won. Tally: 1
Jan 15 10:04:14 47d9dbb93395 consul[468]: raft: Node at 172.17.0.3:8300 [Leader] entering Leader state
Jan 15 10:04:14 47d9dbb93395 consul[468]: consul: cluster leadership acquired
Jan 15 10:04:14 47d9dbb93395 consul[468]: consul: New leader elected: 47d9dbb93395
Jan 15 10:04:14 47d9dbb93395 consul[468]: agent: Synced node info

OKそうです。

なお、ログレベルは「-log-level」で変更できるそうな。

設定ファイルでログ出力

今度は、設定ファイルでログ出力を行ってみます。

こんな設定ファイルを用意。
server.json

{
  "bootstrap": true,
  "data_dir": "data",
  "server": true,
  "enable_syslog": true,
  "syslog_facility": "LOCAL0"
}


これで、先ほどの起動オプションとほぼ同等の設定になっています。

$ ./consul agent -config-file=server.json

と、よくよく見ると「syslog_facility」なる設定が増えているのですが、設定ファイルの場合はこちらでsyslogファシリティの設定を
することができます。

省略した場合、「LOCAL0」が指定されます。こちらを他のファシリティに変更することで、syslog送信時のファシリティを変更することが
できます。

こんなところで。

Consulでクラスタを構成、Service設定をしてみる

先日、WildFly Swarm+Consulでちょっと遊んでみたのですが、Consulのクラスタ構成がイマイチわかって
いなかったので、やり直しということで。

WildFly Swarm+Consul(+Dnsmasq)でService Discovery - CLOVER

今回は、ConsuleをSingle Nodeで、クラスタ構成する場合、あと設定ファイルを書くところをやって
みたいと思います。また、Consul Agentの動作モードにはServerとClientがありますがここでは
Serverを話題の中心に扱っています。

対象のConsulのバージョンは、0.7.2とします。

参考)
Consul は 全自動オーケストレーションの 夢を見るか?

Single NodeでConsulを使う

ここでいう「Single Node」とは、Server ModeのConsulです。Client側は置いておきます。

とりあえず、Single Nodeで雑に起動したい場合は「-bootstrap」を付与するのが正解みたいです。

$ ./consul agent -server -bootstrap -data-dir=/[data-directory]

もしくは、「-bootstrap-expect」を1に設定。

$ ./consul agent -server -bootstrap-expect=1 -data-dir=[data-directory]

Client側は、これにjoinすればOK。

$ ./consul agent -data-dir=[data-direcotry] -join=[server address]

Server側を起動する時に、「-bootstrap」もしくは「-bootstrap-expect」についてなにも考慮しないと

    2017/01/15 07:13:50 [ERR] agent: failed to sync remote state: No cluster leader

となり、そのままLeaderを探しつづけてうまく起動しません、と。

「-bootstrap-expect」を1にした場合は、1度この表記は出ますが、自身がLeaderになって起動します。

Configuration

「-bootstrap」オプションの意味ですが、やはりSingle Node向けの設定のようです。「-bootstrap-expect」は
起動時のクラスタ構成数となります。

Consul Agent(Server)でクラスタを構成する場合

次に、Consul Agent(Server)でクラスタを構成する場合を考えます。今回は、3 Nodeで構成してみます。

この場合、「-bootstrap-expect」は3となり、3 Node揃ったところでConsulクラスタが機能し始めることになります。

では、3つNodeを起動してみます。

## Node 1
$ ./consul agent -server -bootstrap-expect=3 -data-dir=[data-directory]

## Node 2
$ ./consul agent -server -bootstrap-expect=3 -data-dir=[data-directory] -join=[node 1 ip address]

## Node 3
$ ./consul agent -server -bootstrap-expect=3 -data-dir=[data-directory] -join=[node 1 ip address]

もしくは、

## Node 1
$ ./consul agent -server -bootstrap-expect=3 -data-dir=[data-directory]

## Node 2
$ ./consul agent -server -bootstrap-expect=3 -data-dir=[data-directory]

## Node 3
$ ./consul agent -server -bootstrap-expect=3 -data-dir=[data-directory]

とした後に、どこかのNodeでjoinします。

$ ./consul join [node 1 ip address] [node 2 ip address] [node 3 ip address]

ひとつのNodeを起点にでも全体がつながれば、クラスタを構成してくれます。

Consulでは、(ひとつのデータセンター内では)Serverの数が3または5が推奨で、Serverの起動数とQuorumの関係はこちらを参照するとよいでしょう。

Consensus / Deployment Table

Clientは、このクラスタにjoinすればOKです。

Quorumの確認をしてみる

先ほど、Deployment Tableについての参照を書きましたが、Nodeをダウンさせた時にどうなるのか、ちょっと確認してみましょう。

先ほどの3台構成の場合は、Quorumが2となり、1台のNode損失には耐えられるはずです。また、2台ダウンしてしまうとQuorumを
満たさなくなるので、その時にどうなるかというところですね。

このクラスタ構成にClientを加え、4 Nodeの状態でServer Nodeのひとつ(3番目)を落としてみます。

3つ目のServerの色が変わりました。
f:id:Kazuhira:20170115223655p:image

でも、ふつうに使えます。ここでさらに2番目のServer Nodeも落としてみます。
f:id:Kazuhira:20170115223755p:image

すると、Web UIにアクセスできなくなってしまいました。

ログ上はQuorumを満たさなくなったことが出力され、Leaderをロストしたことになっています。

    2017/01/15 13:30:03 [WARN] raft: Failed to contact quorum of nodes, stepping down
    2017/01/15 13:30:03 [INFO] raft: Node at 172.17.0.2:8300 [Follower] entering Follower state (Leader: "")
    2017/01/15 13:30:03 [INFO] consul: cluster leadership lost

そして、Leaderが定まらない状態になります。

    2017/01/15 13:30:17 [ERR] agent: coordinate update error: No cluster leader

この時、まったくConsulが使えなくなるかというと、そんなことはなさそうです。

DNSの機能も動いていますし、

$ dig @127.0.0.1 -p 8600 consulserver1.node.consul ANY

; <<>> DiG 9.9.5-3ubuntu0.11-Ubuntu <<>> @172.17.0.2 -p 8600 consulserver1.node.consul ANY
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29386
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;consulserver1.node.consul.	IN	ANY

;; ANSWER SECTION:
consulserver1.node.consul. 0	IN	A	172.17.0.2

;; Query time: 0 msec
;; SERVER: 172.17.0.2#8600(172.17.0.2)
;; WHEN: Sun Jan 15 22:41:48 JST 2017
;; MSG SIZE  rcvd: 59


Client側のHTTP APIも使えます。

$ curl http://localhost:8500/v1/agent/members
[{"Name":"consulclient","Addr":"172.17.0.5","Port":8301,"Tags":{"build":"0.7.2:'a9afa0c","dc":"dc1","role":"node","vsn":"2","vsn_max":"3","vsn_min":"2"},"Status":1,"ProtocolMin":1,"ProtocolMax":5,"ProtocolCur":2,"DelegateMin":2,"DelegateMax":4,"DelegateCur":4},{"Name":"consulserver2","Addr":"172.17.0.3","Port":8301,"Tags":{"build":"0.7.2:'a9afa0c","dc":"dc1","expect":"3","port":"8300","role":"consul","vsn":"2","vsn_max":"3","vsn_min":"2"},"Status":4,"ProtocolMin":1,"ProtocolMax":5,"ProtocolCur":2,"DelegateMin":2,"DelegateMax":4,"DelegateCur":4},{"Name":"consulserver3","Addr":"172.17.0.4","Port":8301,"Tags":{"build":"0.7.2:'a9afa0c","dc":"dc1","expect":"3","port":"8300","role":"consul","vsn":"2","vsn_max":"3","vsn_min":"2"},"Status":4,"ProtocolMin":1,"ProtocolMax":5,"ProtocolCur":2,"DelegateMin":2,"DelegateMax":4,"DelegateCur":4},{"Name":"consulserver1","Addr":"172.17.0.2","Port":8301,"Tags":{"build":"0.7.2:'a9afa0c","dc":"dc1","expect":"3","port":"8300","role":"consul","vsn":"2","vsn_max":"3","vsn_min":"2"},"Status":1,"ProtocolMin":1,"ProtocolMax":5,"ProtocolCur":2,"DelegateMin":2,"DelegateMax":4,"DelegateCur":4}]

このあとで新しいClientを追加してみたら、一応joinはしていそうな雰囲気になりました。

が、クラスタとしては不正な状態になっていることは変わりなさそうです。ここで、落としていたServer Nodeを起動すると
Leaderが再選出されます。

    2017/01/15 13:43:25 [INFO] consul: New leader elected: consulserver1

まだ1 Node欠けたままですが、クラスタの状態としては回復し、Web UIでもアクセス可能になります。

Consulの設定をしてみる

と、ここまでクラスタ構成についての確認をしてきました。とはいえ、起動引数に何度も書くのも微妙なので、このあたりを
設定ファイルに書きたいと思います。

Consulの設定ファイルは、JSON形式で書きます。

設定ファイルの書き方は、こちらを参照するとよいでしょう。

Configuration / Configuration Files

Server側

Server側は、今回こんな感じで作ってみました。
server.json

{
  "bootstrap_expect": 3,
  "data_dir": "[data-directory]",
  "server": true,
  "start_join": [
      "node 1 ip adress", "node 1 ip adress", "node 1 ip adress"
  ]
}

意味は、コマンドラインのオプションで指定していたものとほとんど同じです。

これで起動する場合は、以下のようなオプション指定になります。

$ ./consul agent -config-file=server.json

これで、先ほどと同様3 Node起動すれば、Consul Agnet(Server)でのクラスタが構成されます。

Client側

続いて、クライアント側。設定ファイルの書き方自体はServerと同じですが、こちらにはServiceも加えてみましょう。

加えるServiceは、Apache(httpd)とします。
※Client側には、Apacheがインストールされているものとします

Clientの設定とServiceの設定を同じ設定ファイルに書いてもいいのですが、今回は分割してみます。

client.json

{
  "data_dir": "[data-directory]",
  "start_join": [
      "node 1 ip adress", "node 1 ip adress", "node 1 ip adress"
  ]
}

ApacheのService設定。
service-httpd.json

{
  "service": {
    "name": "httpd",
    "tags": ["http"],
    "address": "",
    "port": 80,
    "checks": [
      {
        "script": "curl http://localhost >/dev/null 2>&1",
        "interval": "10s"
      }
    ]
  }
}

Health Checkは、10秒ごとにトップページに対してアクセスします。

この状態で、設定ファイルを置いたディレクトリを「-config-dir」オプションで指定して起動します。

$ ./consul agent -config-dir=[設定ファイルを置いたディレクトリ]

「-config-dir」で指定したディレクトリ内の、「.json」拡張子のファイルを読み込んでくれます。

Web UIで確認してみましょう。

Web UIを使う場合は、設定ファイルに以下の設定が必要です。外部ホストから接続する場合は、「client_addr」も。

  "ui": true

f:id:Kazuhira:20170115171305p:image

Serviceを認識してくれています。

Health Checkも入れているので、Apacheを落とすと

$ sudo service apache2 stop

Health CheckでNGとなります。
f:id:Kazuhira:20170115171655p:image

Apacheを再度起動すると、復帰します。

$ sudo service apache2 start

また、今回はひとつのファイルに単一のService定義をしましたが、複数記述することもできるようです。

Services / Multiple Service Definitions

ファイルを分割するか、一気に書くかはお好みといったところで。

まとめ

Consulのクラスタ構成に関する「-bootstrap」および「-bootstrap-expect」、それから設定ファイルの書き方といったところを見てみました。

クラスタのところは、前回ちょっともやもやしていたので、これですっきりしましたね。

2017-01-14

WildFly Swarm+Consul(+Dnsmasq)でService Discovery

WildFly Swarmには、Service Discoveryのための仕組みとしてTopologyがありますが、その実装手段としていくつかの
方法を提供しています。

Topology

今回は、Consulを試してみようかなと思います。

Topology using Hashicorp Consul

Consul?

Consulというのは、HashiCoprの提供するService Discoveryの仕組みです。

Consul

Introduction To Consul

主に以下のような機能を持ちます。

  • Service Discovery
  • Health Check
  • KVS

Consul自体はAgentとして各サーバーで動作させるものですが、動作タイプにServerとClientがあり、通常は
Serverは(データセンターあたり)3台または5台での構成を推奨しています。

Bootstrapping a Datacenter

ConsulにはDNSの機能もあり、名前解決を行うことができます。

DNS Interface

とはいえ、ConsulのDNSは8600ポートで提供されるので、他のDNS製品と合わせて通常の名前解決の仕組みに乗せる例がこちらに記載されています。

DNS Forwarding

今回は、このドキュメントに習いDnsmasqを使ってみたいと思います。

Dnsmasq - network services for small networks.

WildFly Swarm+Consul

続いて、WildFly SwarmとConsulの統合機能について。

Topology using Hashicorp Consul

Consulは、NodeまたはServiceで名前解決を行うことができます。

DNS Interface

WildFly SwarmのConsul向けの統合機能を使うと、WildFly Swarmの起動時にConsul Agentに対してServiceとして
自分自身を登録してくれるようになります。

お題

で、今回はWildFly SwarmとConsulを使って、こんなお題で試してみようと思います。

  • Consul Agent(Server)3台でServer構成
  • Consul Agent(Client)2台に、WildFly Swarmで作ったサンプルJAX-RSアプリケーションを載せてService構成
  • Consul Agent(Client)にDnsmasqをインストールし、curlでService名を解決してアクセス

つまり、こんな(クラスタ)構成です。
f:id:Kazuhira:20170114223823j:image

ConsulとWildFly Swarmについては、以下の条件で。

  • Consul Agent(Server) … 172.17.0.2 〜172.17.0.4(ホスト名:consulserver1 〜 consulserver3)
  • Consul Agent(Client - WildFly Swarm) … 172.17.0.5 〜 172.17.0.6(ホスト名:consulswarmclient1 〜 consulswarmclient2)
  • Consul Agent(Client - curl) … 172.17.0.1(ホスト名:consulcurlclient)

Consulのインストール

全Serverで行うので、共通的に1度紹介。

Consulのダウンロードページから、Consulをダウンロードします。

Download Consul

もしくは、wgetなどで。

$ wget https://releases.hashicorp.com/consul/0.7.2/consul_0.7.2_linux_amd64.zip

展開します。

$ unzip consul_0.7.2_linux_amd64.zip 
Archive:  consul_0.7.2_linux_amd64.zip
  inflating: consul

「consul」というファイルが現れるので、このファイルを起動オプションを付けて実行します。

$ ./consul agent option...

また、Consul Agentの起動に必須となるのはData Directory(-data-dirで指定)なので、こちらは
今回はとりあえずカレントディレクトリに「data」というディレクトリを作って、こちらを指定
するものとします。

$ ./consul agent -data-dir=data option...

オプションについては、Agentの種類で異なるので都度紹介します。

では、だいぶ前置きが長くなりましたがはじめていきましょう。

Consul Agent(Server)のクラスタ構成

では、まずConsul Agent(Server)を起動して、クラスタを構成します。

※追記)
Consulの起動時のオプション、クラスタ構成についてあいまいだったので、別エントリで確認しました。
Consulでクラスタを構成、Service設定をしてみる - CLOVER

各サーバーで微妙にオプションが違うので、全部記載。

## consulserver1(Leader)
$ ./consul agent -server -data-dir=data -client=172.17.0.2 -bootstrap-expect=1 -ui

## consulserver2
$ ./consul agent -server -data-dir=data -join=172.17.0.2 -client=172.17.0.3 -ui

## consulserver3
$ ./consul agent -server -data-dir=data -join=172.17.0.2 -client=172.17.0.4 -ui

オプションを説明します。

  • agent … Agentとして起動することを表します
  • -server … Serverとして起動することを表します
  • -data-dir … Consulが内部で状態保存に使用するData Directoryを指定します
  • -client … Clientから使用する際のIPアドレスを指定します(デフォルトは127.0.0.1となっているため、外部から接続できません)
  • -ui … 付けておくと、ConsouのWebインターフェースを見ることができます
  • -join … クラスタに参加する先のIPアドレスを指定します(最初のNode以外に付けています)
  • -bootstrap-expect … Consulが起動した際に、期待するConsulインスタンスの数。これを下回っているとリーダーを選出できません。今回は1に設定しています。

「-client」でバインドしたIPアドレスで公開されるのは、HTTP、HTTPSDNS、RPCなので、他のサーバーから
Webインターフェースを見る時に困ったりすると思います。DNSは、ローカルにConsul Agentを動かして
いることが多そうな気がするので、そんなに困らないかも…?

この状態で、今回の構成であれば「http://172.17.0.2:8500/ui/」にアクセスすると、Consulの状態を
確認することができます。
※1度、左の「consul」と書かれたラベルをクリックすると、左に登録してあるNodeが展開されます
f:id:Kazuhira:20170114131342p:image

デフォルトは画面上部の「SERVICES」が選ばれた状態となっているので、「NODES」を選ぶと登録してある
Nodeの一覧が見れます。
f:id:Kazuhira:20170114132430p:image

3つのNodeがいることが確認できます。

また、consulのコマンドで確認してもOKです。

$ ./consul members -rpc-addr=172.17.0.2:8400
Node           Address          Status  Type    Build  Protocol  DC
consulserver1  172.17.0.2:8301  alive   server  0.7.2  2         dc1
consulserver2  172.17.0.3:8301  alive   server  0.7.2  2         dc1
consulserver3  172.17.0.4:8301  alive   server  0.7.2  2         dc1

Consul Agent(Client - WildFly Swarm)をクラスタに参加させる

続いて、WildFly Swarmを含めたConsul Agentをクラスタに参加させます。

まずは、Agentの起動。

## consulswarmclient1
$ ./consul agent -data-dir=data -join=172.17.0.2

## consulswarmclient2
$ ./consul agent -data-dir=data -join=172.17.0.2

今回は、UIは外部から見れなくてもいいかなと思ったので、「-client」と「-ui」は付けていません。

ちなみに、この時点ではUI上の「SERVICES」には変化がありませんが、
f:id:Kazuhira:20170114131342p:image

「NODES」には起動したNodeが追加されます。

続いて、WildFly Swarmのアプリケーションを作成しましょう。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.littlewings</groupId>
    <artifactId>consul-integration</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <scala.major.version>2.12</scala.major.version>
        <scala.version>${scala.major.version}.1</scala.version>
        <scala.maven.plugin.version>3.2.2</scala.maven.plugin.version>

        <failOnMissingWebXml>false</failOnMissingWebXml>

        <wildfly.swarm.version>2017.1.1</wildfly.swarm.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.wildfly.swarm</groupId>
                <artifactId>bom</artifactId>
                <version>${wildfly.swarm.version}</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>${scala.version}</version>
        </dependency>

        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>7.0</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.wildfly.swarm</groupId>
            <artifactId>jaxrs</artifactId>
        </dependency>
        <dependency>
          <groupId>org.wildfly.swarm</groupId>
          <artifactId>topology-consul</artifactId>
        </dependency>
    </dependencies>

    <build>
        <finalName>consul-integration</finalName>
        <plugins>
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <version>${scala.maven.plugin.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <scalaVersion>${scala.version}</scalaVersion>
                    <args>
                        <arg>-Xlint</arg>
                        <arg>-unchecked</arg>
                        <arg>-deprecation</arg>
                        <arg>-feature</arg>
                    </args>
                    <recompileMode>incremental</recompileMode>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.wildfly.swarm</groupId>
                <artifactId>wildfly-swarm-plugin</artifactId>
                <version>${wildfly.swarm.version}</version>
                <configuration>
                    <mainClass>org.littlewings.wildflyswarm.consul.App</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>package</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

特徴的なのは、「topology-consul」への依存関係を加えたことですね。

        <dependency>
          <groupId>org.wildfly.swarm</groupId>
          <artifactId>topology-consul</artifactId>
        </dependency>

個人的には珍しくmainクラスを指定しています。

                <configuration>
                    <mainClass>org.littlewings.wildflyswarm.consul.App</mainClass>
                </configuration>

テスト用のJAX-RSリソースクラス。単純にホスト名を返す実装とします。
src/main/scala/org/littlewings/wildflyswarm/consul/HostNameResource.scala

package org.littlewings.wildflyswarm.consul

import java.net.InetAddress
import javax.ws.rs.core.MediaType
import javax.ws.rs.{GET, Path, Produces}

@Path("hostname")
class HostNameResource {
  @GET
  @Produces(Array(MediaType.TEXT_PLAIN))
  def get: String = InetAddress.getLocalHost.getHostName
}

続いて、mainクラス。
src/main/scala/org/littlewings/wildflyswarm/consul/App.scala

package org.littlewings.wildflyswarm.consul

import org.jboss.shrinkwrap.api.ShrinkWrap
import org.jboss.shrinkwrap.api.spec.WebArchive
import org.wildfly.swarm.Swarm
import org.wildfly.swarm.jaxrs.JAXRSArchive
import org.wildfly.swarm.topology.TopologyArchive

object App {
  def main(args: Array[String]): Unit = {
    val swarm = new Swarm(args: _*)
    val deployment = ShrinkWrap.create(classOf[JAXRSArchive])
    //deployment.as(classOf[TopologyArchive]).advertise()  // AdvertiseがWARの名前になる
    deployment.as(classOf[TopologyArchive]).advertise("swarm")

    deployment.addResource(classOf[HostNameResource])

    swarm.start().deploy(deployment)
  }
}

WildFly SwarmのTopologyのドキュメントに沿って、TopologyArchive#advertiseすればいいのですが、ここでの
advertiseの値がConsulでのService名になります。

ふつうに実行してしまうと、内部のWARの名前ってこんなことになって

2017-01-14 12:37:26,160 INFO  [org.jboss.as.server] (main) WFLYSRV0010: Deployed "c166ab56-4056-475a-a81a-a8d0cf63e12a.war" (runtime-name : "c166ab56-4056-475a-a81a-a8d0cf63e12a.war")

指定するにはとても困るので、今回はService名を「swarm」としました。

あとはパッケージングします。

$ mvn package

で、起動。

## consulswarmclient1
$ java -Dswarm.bind.address=172.17.0.5 -jar /path/to/consul-integration-swarm.jar

## consulswarmclient2
$ java -Dswarm.bind.address=172.17.0.6 -jar /path/to/consul-integration-swarm.jar

なんで「-Dswarm.bind.address」を付けたかは、あとで説明します。

Webインターフェース上の「SERVICES」では、登録されたWildFly SwarmのServiceを見ることができます。
f:id:Kazuhira:20170114221802p:image

左に、「swarm」というサービスが増えていますね。これが、TopologyArchive#advertiseで指定した名前です。

あと、よーく見ると右上部の「TAGS」に「http」が増えていますね。こちらもWildFly Swarmが登録したものですが、
名前使われます。

とりあえず、簡単に動作確認。

$ curl http://172.17.0.5:8080/hostname
consulswarmclient1

$ curl http://172.17.0.6:8080/hostname
consulswarmclient2

OKですね。

Consul Agent(Client - curl)をクラスタに参加させる

起動したWildFly SwarmのConsulを使った確認に移る前に、先にcurlを使って確認する側のConsul Agentを起動しておきます。
※これは書き手の都合上です

$ ./consul agent -data-dir=data -join=172.17.0.2

これで、Consulクラスタのひととおりのメンバーが揃いました。

動作確認など

ここからは、Consul Agent(Client - curl)側から確認してみます。

digで動作確認

さて、名前解決の動作確認をしてみましょう。

Consulでは、NodeとServiceの名前解決を行うことができます。

DNS Interface

Nodeの名前解決を行う場合の書式は

<node>.node[.datacenter].<domain>

で、Serviceの名前解決を行う場合の書式は

[tag.]<service>.service[.datacenter].<domain>

となります。

まずはNode名から。ConsulのNode名はデフォルトはホスト名ですが、「-node」オプションで任意の名前に変更することもできます。

digで確認。digの向き先は、ローカルで動いているConsul AgentのDNSポート(8600)となります。bindでアドレスを変えたりしている
場合は、その内容に合わせてください。

Nodeについては、「Node名」.node.consulで確認することができます。Datacenter名を入れてもいいのですが、今回は省略します。

## consulserver1
$ dig @localhost -p 8600 consulserver1.node.consul ANY

; <<>> DiG 9.9.5-3ubuntu0.11-Ubuntu <<>> @localhost -p 8600 consulserver1.node.consul ANY
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41638
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;consulserver1.node.consul.	IN	ANY

;; ANSWER SECTION:
consulserver1.node.consul. 0	IN	A	172.17.0.2

;; Query time: 60 msec
;; SERVER: 127.0.0.1#8600(127.0.0.1)
;; WHEN: Sat Jan 14 21:43:24 JST 2017
;; MSG SIZE  rcvd: 59


## consulswarmclient1
$ dig @localhost -p 8600 consulswarmclient1.node.consul ANY

; <<>> DiG 9.9.5-3ubuntu0.11-Ubuntu <<>> @localhost -p 8600 consulswarmclient1.node.consul ANY
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 54267
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;consulswarmclient1.node.consul.	IN	ANY

;; ANSWER SECTION:
consulswarmclient1.node.consul.	0 IN	A	172.17.0.5

;; Query time: 5 msec
;; SERVER: 127.0.0.1#8600(127.0.0.1)
;; WHEN: Sat Jan 14 21:43:38 JST 2017
;; MSG SIZE  rcvd: 64


## consulcurlclient
$ dig @localhost -p 8600 consulcurlclient.node.consul ANY

; <<>> DiG 9.9.5-3ubuntu0.11-Ubuntu <<>> @localhost -p 8600 consulcurlclient.node.consul ANY
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2666
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;consulcurlclient.node.consul.	IN	ANY

;; ANSWER SECTION:
consulcurlclient.node.consul. 0	IN	A	172.17.0.1

;; Query time: 1 msec
;; SERVER: 127.0.0.1#8600(127.0.0.1)
;; WHEN: Sat Jan 14 21:44:39 JST 2017
;; MSG SIZE  rcvd: 62

続いてServiceで確認。「swarm」というServiceが登録されているので、こちらを使います。

Serviceの場合は、「Service名」.service.consul、もしくは「タグ名」.「Service名」.service.consulとなります。

## タグを入れない場合
$ dig @localhost -p 8600 swarm.service.consul ANY

; <<>> DiG 9.9.5-3ubuntu0.11-Ubuntu <<>> @localhost -p 8600 swarm.service.consul ANY
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 23476
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;swarm.service.consul.		IN	ANY

;; ANSWER SECTION:
swarm.service.consul.	0	IN	A	172.17.0.5
swarm.service.consul.	0	IN	A	172.17.0.6

;; Query time: 0 msec
;; SERVER: 127.0.0.1#8600(127.0.0.1)
;; WHEN: Sat Jan 14 21:45:38 JST 2017
;; MSG SIZE  rcvd: 70


## タグを入れた場合
$ dig @localhost -p 8600 http.swarm.service.consul ANY

; <<>> DiG 9.9.5-3ubuntu0.11-Ubuntu <<>> @localhost -p 8600 http.swarm.service.consul ANY
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 11862
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;http.swarm.service.consul.	IN	ANY

;; ANSWER SECTION:
http.swarm.service.consul. 0	IN	A	172.17.0.6
http.swarm.service.consul. 0	IN	A	172.17.0.5

;; Query time: 18 msec
;; SERVER: 127.0.0.1#8600(127.0.0.1)
;; WHEN: Sat Jan 14 21:46:46 JST 2017
;; MSG SIZE  rcvd: 75

名前解決できていますね。

HTTP APIで確認

登録されているServiceやNodeなどを、HTTP APIで見ることもできます。

HTTP API

ここから、Serviceを登録したりすることも可能です。

今回は、NodeおよびServiceの一覧を見てみましょう。

Node一覧。

$ curl http://localhost:8500/v1/agent/members
[{"Name":"consulswarmclient1","Addr":"172.17.0.5","Port":8301,"Tags":{"build":"0.7.2:'a9afa0c","dc":"dc1","role":"node","vsn":"2","vsn_max":"3","vsn_min":"2"},"Status":1,"ProtocolMin":1,"ProtocolMax":5,"ProtocolCur":2,"DelegateMin":2,"DelegateMax":4,"DelegateCur":4},{"Name":"consulserver2","Addr":"172.17.0.3","Port":8301,"Tags":{"build":"0.7.2:'a9afa0c","dc":"dc1","port":"8300","role":"consul","vsn":"2","vsn_max":"3","vsn_min":"2"},"Status":1,"ProtocolMin":1,"ProtocolMax":5,"ProtocolCur":2,"DelegateMin":2,"DelegateMax":4,"DelegateCur":4},{"Name":"consulserver1","Addr":"172.17.0.2","Port":8301,"Tags":{"bootstrap":"1","build":"0.7.2:'a9afa0c","dc":"dc1","port":"8300","role":"consul","vsn":"2","vsn_max":"3","vsn_min":"2"},"Status":1,"ProtocolMin":1,"ProtocolMax":5,"ProtocolCur":2,"DelegateMin":2,"DelegateMax":4,"DelegateCur":4},{"Name":"consulswarmclient2","Addr":"172.17.0.6","Port":8301,"Tags":{"build":"0.7.2:'a9afa0c","dc":"dc1","role":"node","vsn":"2","vsn_max":"3","vsn_min":"2"},"Status":1,"ProtocolMin":1,"ProtocolMax":5,"ProtocolCur":2,"DelegateMin":2,"DelegateMax":4,"DelegateCur":4},{"Name":"consulcurlclient","Addr":"172.17.0.1","Port":8301,"Tags":{"build":"0.7.2:'a9afa0c","dc":"dc1","role":"node","vsn":"2","vsn_max":"3","vsn_min":"2"},"Status":1,"ProtocolMin":1,"ProtocolMax":5,"ProtocolCur":2,"DelegateMin":2,"DelegateMax":4,"DelegateCur":4},{"Name":"consulserver3","Addr":"172.17.0.4","Port":8301,"Tags":{"build":"0.7.2:'a9afa0c","dc":"dc1","port":"8300","role":"consul","vsn":"2","vsn_max":"3","vsn_min":"2"},"Status":1,"ProtocolMin":1,"ProtocolMax":5,"ProtocolCur":2,"DelegateMin":2,"DelegateMax":4,"DelegateCur":4}]

Service一覧。ただ、ServiceはローカルのAgentが管理しているものしか見えないみたいなので、こちらのコマンドはWildFly Swarmを
動かしているホストで実行しました。

$  curl http://localhost:8500/v1/agent/services
{"swarm:172.17.0.6:8080":{"ID":"swarm:172.17.0.6:8080","Service":"swarm","Tags":["http"],"Address":"172.17.0.6","Port":8080,"EnableTagOverride":false,"CreateIndex":0,"ModifyIndex":0}}

地味にportとか分かっているみたいです。

Consul Command(CLI)

HTTP API以外にも、consul実行ファイルに対してコマンドを実行することもできます。

Consul Command(CLI)

とりあえず、Node一覧を見てみましょう。

$ ./consul members
Node                Address          Status  Type    Build  Protocol  DC
consulcurlclient    172.17.0.1:8301  alive   client  0.7.2  2         dc1
consulserver1       172.17.0.2:8301  alive   server  0.7.2  2         dc1
consulserver2       172.17.0.3:8301  alive   server  0.7.2  2         dc1
consulserver3       172.17.0.4:8301  alive   server  0.7.2  2         dc1
consulswarmclient1  172.17.0.5:8301  alive   client  0.7.2  2         dc1
consulswarmclient2  172.17.0.6:8301  alive   client  0.7.2  2         dc1

Dnsmasqをインストールしてcurlで動作確認

名前解決できたりNodeやServiceの一覧が見れたのはいいのですが、このままだとcurlで名前解決ができません。

ここは、ドキュメントに習ってDnsmasqを使ってConsulでの名前解決を統合してみます。
DNS Forwarding

Dnsmasqインストール。

$ sudo apt-get install dnsmasq

ドキュメントに習い、Dnsmasqの設定をしていきます。

$ sudo vim /etc/dnsmasq.conf

dnsmasq.confの最後に、「consul」で終わるエントリはローカルのDNSサーバー(Consul)に解決するようにします。

server=/consul/127.0.0.1#8600

設定したら、Dnsmasqを再起動します。

$ sudo service dnsmasq restart

確認。Serviceで確認するので、今回は「swarm.service.consul」でアクセスします。「http.swarm.service.consul」でもOKです。

$ curl http://swarm.service.consul:8080/hostname
consulswarmclient2
$ curl http://swarm.service.consul:8080/hostname
consulswarmclient1
$ curl http://swarm.service.consul:8080/hostname
consulswarmclient1
$ curl http://swarm.service.consul:8080/hostname
consulswarmclient2

ちょっと不規則気味ですが、WildFly Swarmが動作しているServerに振り分けられていることがわかります。

また、メンバー(この場合はWildFly Swarmのアプリケーション)を停止したりすると、切り離されます。

$ curl http://swarm.service.consul:8080/hostname
consulswarmclient1
$ curl http://swarm.service.consul:8080/hostname
consulswarmclient1

もちろん、再度クラスタに参加させればまた名前解決されるメンバーに復帰します。

動作確認としてはOKそうですね。

オマケ

「swarm.bind.address」について

WildFly Swarmのアプリケーションを起動する時に、わざわざ「-Dswarm.bind.address」を指定していました。

## consulswarmclient1
$ java -Dswarm.bind.address=172.17.0.5 -jar /path/to/consul-integration-swarm.jar

## consulswarmclient2
$ java -Dswarm.bind.address=172.17.0.6 -jar /path/to/consul-integration-swarm.jar

これ、なんで付けてたかというと、何も付けずに起動すると名前解決の結果がこうなるからです。

## タグなし
$ dig @localhost -p 8600 swarm.service.consul ANY

; <<>> DiG 9.9.5-3ubuntu0.11-Ubuntu <<>> @localhost -p 8600 swarm.service.consul ANY
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 51890
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;swarm.service.consul.		IN	ANY

;; ANSWER SECTION:
swarm.service.consul.	0	IN	AAAA	::

;; Query time: 1 msec
;; SERVER: 127.0.0.1#8600(127.0.0.1)
;; WHEN: Sat Jan 14 22:25:03 JST 2017
;; MSG SIZE  rcvd: 66


## タグあり
$ dig @localhost -p 8600 http.swarm.service.consul ANY

; <<>> DiG 9.9.5-3ubuntu0.11-Ubuntu <<>> @localhost -p 8600 http.swarm.service.consul ANY
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 53860
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;http.swarm.service.consul.	IN	ANY

;; ANSWER SECTION:
http.swarm.service.consul. 0	IN	AAAA	::

;; Query time: 1 msec
;; SERVER: 127.0.0.1#8600(127.0.0.1)
;; WHEN: Sat Jan 14 22:25:05 JST 2017
;; MSG SIZE  rcvd: 71

ひとつしか返ってきませんが…?

;; ANSWER SECTION:
swarm.service.consul.	0	IN	AAAA	::

;; ANSWER SECTION:
http.swarm.service.consul. 0	IN	AAAA	::

ただ、Consulの「swarm」Service上は確かに2つ登録されています。

これ、WildFly SwarmのConsul統合部分の起動時のログを見ていたらピンとくるんですけど、こんなのが出力されます。

2017-01-14 13:24:35,447 INFO  [org.wildfly.swarm.topology.consul.runtime.Advertiser] (MSC service thread 1-7) Registered service swarm:0:0:0:0:0:0:0:0:8080

「-Dswarm.bind.address」を指定すると、こうなります。

2017-01-14 13:27:17,940 INFO  [org.wildfly.swarm.topology.consul.runtime.Advertiser] (MSC service thread 1-7) Registered service swarm:172.17.0.5:8080

つまり、何も指定しないとローカルアドレスが登録されるということで…。

WildFly Swarmは、どこのConsulにアクセスしている?

これはConsulTopologyFractionを見ると分かります。
https://github.com/wildfly-swarm/wildfly-swarm/blob/2017.1.1/fractions/topology-consul/src/main/java/org/wildfly/swarm/topology/consul/ConsulTopologyFraction.java#L100

デフォルトでは、「http://localhost:8500」にアクセスしに行こうとします。つまり、ローカルにConsul Agentがいることが前提に
なっていますね。

    static {
        URL tmp = null;
        try {
            tmp = new URL("http://localhost:8500");
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }
        DEFAULT_URL = tmp;
    }

もちろん、ConsulTopologyFractionを使用すればカスタマイズはできそうです。

アクセス先が切り替わらない

実はこの構成ですが、ほとんどDockerでやっています。で、Docker内(Ubuntuイメージ)でDnsmasqをインストールして確認して
みたのですが、ひとつのホストにアクセスし続けていてけっこう困りました。

どうもこれが原因っぽいです。
Consul DNS round robin works for host but not for containers - General - Docker Forums

今回は、curlとDnsmasqはホスト側にインストールして実行しました。

「/etc/resolv.conf」について

「/etc/resolv.conf」ファイルは、今回編集しませんでした。
/etc/resolv.conf

# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 127.0.0.1
search localdomain

nameserverが他に登録されている場合は、ローカルのnameserverを先頭に持ってくるようにしましょう。

場合によっては、「/etc/dnsmasq.conf 」ファイルに「strict-order」を書いた方がいい時もあるようです。

【Consul】dnsmasqで名前解決を行う方法を試してみた | Pocketstudio.jp log3

まとめ

WildFly SwarmのConsul統合機能を使って、Service Discovery、名前解決の仕組みとして使ってみました。

ほとんどConsulについての調べ物が多かった気がするのですが、とりあえず確認したいところまで動かせたので
満足です。

名前解決できるのはいいのですが、DNSという関係上、ポートはDNSに頼っている限りは解決できないなぁとちょっと
思いましたが、そのあたりはまあ、と…。

今回作成したコード(WildFly Swarmアプリケーションのみ)は、こちらに置いています。
https://github.com/kazuhira-r/wildfly-swarm-scala-examples/tree/master/consul-integration

2017-01-11

Ubuntu LinuxにApache Cassandra 3をインストールする

久しぶりに、Ubuntu LinuxApache Cassandraをインストールしてみようと思います。

Apache Cassandra

前に使った時は、まだバージョン 1系でしたが、今は3系なんですねー。現在は、3.9が最新の
バージョンみたいです。

なんと、Debian系にはパッケージが提供されているみたいなので、こちらを使います。
※どうしてyumでは入れられないんでしょう…?

ところで、ドキュメントについてはこちらを見ることになりそうです。

Apache Cassandra Documentation

もしくは、DataStaxのもの。

Datastax Docs home

日本語版もありそう。

Datastaxドキュメント・ホーム

インストール

では、手順に従ってインストールしてみます。

Download

Installing Cassandra

$ echo "deb http://www.apache.org/dist/cassandra/debian 39x main" | sudo tee -a /etc/apt/sources.list.d/cassandra.sources.list
$ curl https://www.apache.org/dist/cassandra/KEYS | sudo apt-key add -
$ sudo apt-get update
$ sudo apt-get install cassandra

GPGのエラーが出る場合は、追加の手順が必要みたいです。

起動。

$ sudo service cassandra start

停止。

$ sudo service cassandra stop

動作状況の確認は、「nodetool」で。

$ nodetool status
Datacenter: datacenter1
=======================
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address    Load       Tokens       Owns (effective)  Host ID                               Rack
UN  127.0.0.1  140.15 KiB  256          100.0%            b3c076ea-24d3-4582-8e3c-226f71b8c960  rack1

各種(デフォルト)ディレクトリ

設定は、「/etc/cassandra」ディレクトリに置かれます。

$ ls -l /etc/cassandra
total 100
-rw-r--r-- 1 root root 11854 Sep 26 15:05 cassandra-env.sh
-rw-r--r-- 1 root root  1200 Sep 26 14:55 cassandra-rackdc.properties
-rw-r--r-- 1 root root  1358 Sep 26 14:55 cassandra-topology.properties
-rw-r--r-- 1 root root 51739 Sep 26 15:05 cassandra.yaml
-rw-r--r-- 1 root root  2082 Sep 26 14:55 commitlog_archiving.properties
-rw-r--r-- 1 root root  9074 Sep 26 14:55 jvm.options
-rw-r--r-- 1 root root  1193 Sep 26 14:55 logback-tools.xml
-rw-r--r-- 1 root root  3785 Sep 26 14:55 logback.xml
drwxr-xr-x 2 root root  4096 Jan 11 14:00 triggers

ログは、「/var/log/cassandra」ディレクトリに出力されます。

$ ls -l /var/log/cassandra
total 200
-rw-r--r-- 1 cassandra cassandra 136219 Jan 11 14:02 debug.log
-rw-r--r-- 1 cassandra cassandra  22441 Jan 11 14:03 gc.log.0.current
-rw-r--r-- 1 cassandra cassandra  39783 Jan 11 14:02 system.log

データは、「/var/lib/cassandra」ディレクトリに配置されます、と。

$ ls -l /var/lib/cassandra
total 16
drwxr-xr-x 2 cassandra cassandra 4096 Jan 11 14:01 commitlog
drwxr-xr-x 7 cassandra cassandra 4096 Jan 11 14:01 data
drwxr-xr-x 2 cassandra cassandra 4096 Jan 11 14:01 hints
drwxr-xr-x 2 cassandra cassandra 4096 Jan 11 14:01 saved_caches

CQLで確認

最後に、CQLを使って確認してみましょう。

$ cqlsh localhost
Connected to Test Cluster at localhost:9042.
[cqlsh 5.0.1 | Cassandra 3.9 | CQL spec 3.4.2 | Native protocol v4]
Use HELP for help.
cqlsh>

CQLのドキュメントは、DataStaxのものを見た方が良さそうです。

CQL for Cassandra 2.2以降

CQL for Apache Cassandra™ 2.2 & later

キースペースの作成。

cqlsh> CREATE KEYSPACE IF NOT EXISTS test_keyspace WITH REPLICATION = { 'class': 'SimpleStrategy', 'replication_factor': 3 };

作成したキースペースの利用。

cqlsh> USE test_keyspace;
cqlsh:test_keyspace>

テーブルの作成。今回は、書籍で。

cqlsh:test_keyspace> CREATE TABLE book ( isbn text PRIMARY KEY, title text, price int);

確認。

cqlsh:test_keyspace> DESCRIBE book;

CREATE TABLE test_keyspace.book (
    isbn text PRIMARY KEY,
    price int,
    title text
) WITH bloom_filter_fp_chance = 0.01
    AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'}
    AND comment = ''
    AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'}
    AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'}
    AND crc_check_chance = 1.0
    AND dclocal_read_repair_chance = 0.1
    AND default_time_to_live = 0
    AND gc_grace_seconds = 864000
    AND max_index_interval = 2048
    AND memtable_flush_period_in_ms = 0
    AND min_index_interval = 128
    AND read_repair_chance = 0.0
    AND speculative_retry = '99PERCENTILE';

データの登録。

cqlsh:test_keyspace> INSERT INTO book ( isbn, title, price ) VALUES ('978-4873115290', 'Cassandra', 3672);
cqlsh:test_keyspace> INSERT INTO book ( isbn, title, price ) VALUES ('978-1449358549', 'Elasticsearch: The Definitive Guide', 5432);
cqlsh:test_keyspace> INSERT INTO book ( isbn, title, price ) VALUES ('978-1449305048', 'Redis Cookbook', 2505);

確認。

cqlsh:test_keyspace> SELECT * FROM book;

 isbn           | price | title
----------------+-------+-------------------------------------
 978-1449358549 |  5432 | Elasticsearch: The Definitive Guide
 978-4873115290 |  3672 |                           Cassandra
 978-1449305048 |  2505 |                      Redis Cookbook

(3 rows)

ソートは、このテーブル定義だとできません、と…。

いったん、ここまでで。

2017-01-10

Sonatype Nexus 3で、Third PartyのMavenアーティファクトをアップロードする(+リポジトリについて少し)

Sonatype Nexusですが、バージョン 2の頃はNexus上でアーティファクトのアップロードができていたようですが、

Using the User Interface (Nexus 2)

Uploading Components (Nexus 2)

バージョン 3では、どうもこれができなさそうな感じです。
※2系の頃に「Using the User Interface」にあった「Uploading Components」がなくなっている

Using the User Interface (Nexus 3)

実際、Nexusの画面上を探してみても、見つからない感じです。

となると、「mvn deploy:deploy-file」でアップロードするしかない感じですね。

Apache Maven Deploy Plugin - deploy:deploy-file

「mvn deploy:deploy-file」でMavenアーティファクトをアップロードする


アップロードする内容を、以下とします。

また、settings.xmlには、以下のような設定が入っているとします。
※アカウントはNexusデフォルト
$HOME/.m2/settings.xml

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                      https://maven.apache.org/xsd/settings-1.0.0.xsd">
    <servers>
        <server>
            <id>my-maven-hosted-repo</id>
            <username>admin</username>
            <password>admin123</password>
        </server>
    </servers>
</settings>

pom.xmlがない場合
※file、repositoryId、urlは必須なので、追加でgroupIdとartifactIdとversionを指定

$ mvn deploy:deploy-file \
-Dfile=greeting-nexus-1.0.0.jar \
-DrepositoryId=my-maven-hosted-repo \
-Durl=http://my.nexus.repo.example.com:8081/repository/my-maven-hosted-repo/ \
-DgroupId=com.example \
-DartifactId=greeting-nexus \
-Dversion=1.0.0

pom.xmlがある場合

$ mvn deploy:deploy-file \
-Dfile=greeting-nexus-1.0.0.jar \
-DrepositoryId=my-maven-hosted-repo \
-Durl=http://my.nexus.repo.example.com:8081/repository/my-maven-hosted-repo/ \
-DpomFile=pom.xml

とまあ、こんな感じでNexusの画面というよりは、mvnコマンドでデプロイする感じになるんでしょう。

既存のローカルリポジトリからリポジトリを作りたい場合

これ、ローカルリポジトリ(もしくはそのコピー)をProxy Repositoryにすればいいのかな?とか思ったのですが、
「file:///」をRemote storageに指定できなかったので断念…。

ApacheとかのWebサーバーを使ってリポジトリを公開し、そこにProxyする(Remote storageに指定する)
とかするしかないでしょうねー。

オマケ

この話と合わせて、リポジトリのバックアップとか移行について調べようかと思ったのですが、
Nexus 3では以下のような感じになっています。

Nexus Documentation - Nexus Repository Manager 3.2 - Sonatype.com

Nexus 2の頃は、割と単純だったというか、「sonatype-work/nexus/storage/[リポジトリ名]」ディレクトリの
配下にデプロイしたアーティファクトがそのまま置かれているという感じだったので、それをそのまま持っていったり
直接追加してもよさそうな感じでした。

How to Back Up Nexus Configuration and Repository Artifacts

ですが、Nexus 3ではアーティファクトの保存先がOrientDBになっているみたいで、2の頃みたいにアーティファクトを
ファイルシステム上で直接見るのは難しそうです。

個人的には、気になるポイントだったので覚えておきましょう…。