Spring boot admin でAplicationのメトリックスを可視化

Spring boot adminはSpring bootで作られたアプリケーションの
管理者向けGUIツールです。
こんな風にSpring actuatorで提供されている情報を美しく表示したり
メモリの使用量等も見やすいですね。
https://raw.githubusercontent.com/codecentric/spring-boot-admin/master/screenshot-details.png

こんな風にアプリのログレベルをロガーごとにオンラインで変更できます。
https://raw.githubusercontent.com/codecentric/spring-boot-admin/master/screenshot-logging.png


導入方法は非常にシンプルです。
基本的には公式に書いてある通りにやればOKです。
https://github.com/codecentric/spring-boot-admin

構築にはeureka(service discovery)の仕組みを用いて
間接的にサーバーがクライアントを認識する方法と
クライアントから直接サーバーに存在を通知する方法の2通りあります。
今回は後者のクライアントから直接サーバーにクライアントの存在を通知する方法を取ります。
eurekaはまだ勉強中なので・・・

冗長構成の取り方等まだまだ調べることがありますが、運用支援ツールなので
今のところそこまで可用性を求めていません。
落ちていてもサービス維持に直結しませんから

サーバー

https://start.spring.io/ に行って適当にひな形プロジェクトを作りました。
pom.xmlにspring boot adminのserverとuiを追加します。

<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-server</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-server-ui</artifactId>
    <version>1.2.3</version>
</dependency>

@EnableAdminServerを追加してサーバー機能を有効化します。

@Configuration
@EnableAutoConfiguration
@EnableAdminServer //ここ重要
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

application.propertiesを作成して任意のポートを指定しておきます。
server.port=8888

クライアント

pom.xmlにclientの依存を追加

<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-client</artifactId>
    <version>1.2.3</version>
</dependency>

application.propertiesに先ほど立てたサーバーのサーバー名とPort番号を設定します。
spring.boot.admin.url=http://localhost:8888

私はspring actuatorのパス(management.context-path)を変更しているのですが、
Spring boot adminは特にそのパスを明示的に定義しなくても動作しました。

Spring RestTemplate でBasic認証

SpringのRestTemplateでBasic認証を求めてくるサーバーに接続する必要がある場合
少し調べたら全てのendpointで

String plainCreds = "username@password";
byte[] plainCredsBytes = plainCreds.getBytes();
byte[] base64CredsBytes = Base64.encodeBase64(plainCredsBytes);
String base64Creds = new String(base64CredsBytes);

HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + base64Creds);

HttpEntity<String> request = new HttpEntity<String>(headers);
ResponseEntity<Account> response = restTemplate.exchange(url, HttpMethod.GET, request, Account.class);
Account account = response.getBody();

てなことをする必要あり、もっと認証をリクエスト時に意識せずに
書ける方法がないか調べてみました。
postForObject使いたかったしね

以下調べた方法

ApacheのHTTP clientを使ってBasic認証を通過するようにしてやる。
AuthScope.ANYにしているところで認証を使うアクションを絞ることもできそうですが、
今回は全てのendpointで必用なのでこれでOK

BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
        new UsernamePasswordCredentials("username", "password"));

HttpClientBuilder httpClientBuilder =
        HttpClientBuilder.create().setDefaultCredentialsProvider(credentialsProvider);
ClientHttpRequestFactory factory =
        new HttpComponentsClientHttpRequestFactory(httpClientBuilder.build());
new RestTemplate(factory)

pom.xmlapache http clientt追加
versionはSpring IOにおまかせ

<dependency>
  <groupId>org.apache.httpcomponents</groupId>
  <artifactId>httpclient</artifactId>
</dependency>

Spring Data RedisでSentinelに接続する

可用性を確保するためにRedis+Sentinelを利用している場合は、
クライアントアプリケーションから直接Redisのマスタを参照せずにクライアントライブラリに対して
Sentinelのノード情報を教えてあげることでマスターがFail overしたときに自動的にクライントが
Fail overに追従できます。
Spring Data Redisを使ってSentinelの設定を行う方法を説明ましす。
参考(5.4. Redis Sentinel Support):
http://docs.spring.io/spring-data/redis/docs/current/reference/html/#redis:sentinel

@Bean
public RedisConnectionFactory jedisConnectionFactory() {
  RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration() .master("mymaster")
  .sentinel("127.0.0.1", 26379) .sentinel("127.0.0.1", 26380);
  return new JedisConnectionFactory(sentinelConfig);
}

こうすることでクラスタの名前(mymaster)とSentinelのノード(普通は3台以上)を定義できますが、
ハードコーディングはしませんよね?

application.propertiesに定義できます。

spring.redis.sentinel.master: mymaster

spring.redis.sentinel.nodes: 127.0.0.1:26379,127.0.0.1:26380

設定ファイルにsentinelの情報を逃すとSpringが設定情報を注入してくれます。

@Bean
public RedisConnectionFactory jedisConnectionFactory() {
  return new JedisConnectionFactory();
}

SpringDataRedisを使ってJson形式でオブジェクトをRedisに保存する方法

SpringDataRedisを使ってObjectを保存するとデフォルト設定ではKey、Value共に可読性の悪いserializeされたデータになります 

テストや運用の効率を考慮して可読性の高いJson形式でデータを保存したくなったので方法を記録しておきます。

pom.xml

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-redis</artifactId>
</dependency>
<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
</dependency>
<!-- Json に変換するために必要 -->
<dependency>
  <groupId>org.codehaus.jackson</groupId>
  <artifactId>jackson-mapper-asl</artifactId>
  <version>1.9.13</version>
</dependency>

Configクラス

@Configuration
@ComponentScan(basePackages="redisSample")
@EnableWebMvc
public class WebApplicationConfig extends WebMvcConfigurerAdapter {
    @Bean
    public JedisConnectionFactory jedisConnectionFactory() {
        JedisConnectionFactory cf = new JedisConnectionFactory();
        cf.setUsePool(true);
        cf.setHostName("localhost");
        cf.setPort(6379);
        return cf;
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> rt = new RedisTemplate<String, Object>();
        rt.setConnectionFactory(jedisConnectionFactory());
        // key のSerialize方法を指定
        rt.setKeySerializer(new StringRedisSerializer());
        // Value のSerialize方法を指定
        rt.setValueSerializer(new JacksonJsonRedisSerializer<>(Object.class));

        return rt;
    }
}

コントローラー

オブジェクトを出したり入れたりする

@RestController
@EnableWebMvc
public class SampleController {

    @Autowired
    RedisTemplate<String, Object> redisTemplate;

    @RequestMapping(value = "/getData", method = RequestMethod.GET)
    public void getData(@RequestParam("key") String key, HttpServletResponse res) throws IOException {
        res.getWriter().write(redisTemplate.opsForValue().get(key).toString());
    }

    @RequestMapping(value = "/setData", method = RequestMethod.GET)
    public void setData(@RequestParam("key") String key,@RequestParam("value") String value, HttpServletResponse res) throws IOException {
        SampleDto dto = new SampleDto();
        dto.value = value;
        dto.creationDateTime = System.currentTimeMillis();
        redisTemplate.opsForValue().set(key, dto);
        res.getWriter().write("OK");
    }
}

保存するオブジェクト

public class SampleDto {
    public String value;
    public long creationDateTime;
}

確認

起動

java -jar target/redisSample-0.0.1-SNAPSHOT.jar

set

wget "http://localhost:8080/setData?value=hoge&key=key1"

get

wget "http://localhost:8080/getData?key=key1"
cat getData\?key\=key1
# {value=hoge, creationDateTime=1440077026083}

redis

get key1
# "{\"value\":\"hoge\",\"creationDateTime\":1440077026083}"


github.com

Spring開発環境構築 Spring Tool Suite(STG)に追加のプラグイン入れるまで

MacでSpringの開発環境を作ったときの記録を残しておきます。

※本当はIntellijを使いたかったけどライセンスを購入してもらえるまでSTSで我慢します。

STSをインストールした時点で入っていない追加でインストールが必要と思ったプラグインの入れ方までを説明します。

Mac初心者なのでMarketplacesを使わずにインストールする方法でつまづきました。

STSをダウンロードして展開

https://spring.io/tools

Pleiades (プレアデス)

複数プラグインを一度に導入できます。私が絶対必要と思ったのは下記です。

インストール方法

STSを起動してEclipseのバーションを確認します。

Help → Instlation Details

4.5でした。

f:id:Greenkun:20150730230039p:plain

PlediesからEclipseのバージョンと同じバージョンのzipをダウンロードします。

http://mergedoc.osdn.jp/

本体はSTSを使いますのでStanderd editionのUltimateを選択します。

ダウンロードできたら展開します。

Plediesに含まれているプラグインSTSにインストールします。

STSのインストールディレクトリを開ます。(/Applications/sts-bundle)←私はここに置きました。

Finderで開くと葉っぱのアイコンになっていて分かり辛いのですがターミナルで確認するとSTS.appというディレクトリがあるのでコピー先のディレクトリまでターミナルで移動してターミナルからFinderを開いて作業しました。

open /Applications/sts-bundle/STS.app/Contents/Eclipse/dropins

f:id:Greenkun:20150730230239p:plain

STSのdropoinsディレクトリを開いたらPlediesのeclipse/dropinsに含まれているディレクトリをコピーします。

STSを起動してプラグインが入っているか確認して終了

Plediesは日本語化が主な機能の一つのようですが、外国人もいる職場なので頑張って英語のまま使います。

Marketplaceから入れたプラグイン

最後にPlediesには含まれていなかったカバレッジ測定ツールを入れました。

EclEmma(コードカバレッジ)