---
title: Spring Cloud Configで動的コンフィギュレーション管理
tags: ["Java", "Spring", "Spring Cloud", "Spring Cloud Config"]
categories: ["Programming", "Java", "org", "springframework", "boot", "cloud", "config"]
date: 2014-10-25T18:55:23Z
updated: 2014-10-26T08:20:44Z
---

## Spring Cloud Configとは

[Spring Cloud Config](http://cloud.spring.io/spring-cloud-config/)は分散システムにおけるコンフィギュレーションの仕組みを提供するプロジェクト。[SpringOne 2gx 2014](http://springone2gx.com/)に参加して、一番面白いと思ったネタである。

Spring Cloud ConfigはClientとServerで構成される。

SeverはGitやファイル等の外部コンフィギュレーションを管理し、中央集権的に全てのClinetにコンフィギュレーションを提供し、
ClientはSpring Frameworkが元々もっている`Environment`や`PropertySource`といった設定の抽象化の仕組みを利用して、Severから取得したコンフィギュレーションを保持する。また、コンフィギュレーションを再読み込みする仕組みも提供する。

これらを利用したシステムの最小構成は以下のようになる。

![image](https://qiita-image-store.s3.amazonaws.com/0/1852/7e91f68c-9302-f56c-c172-8c33d86969ab.png)

コンフィギュレーションを必要とする、"通常のアプリケーション"がClientになる。Clientが複数台あっても、Serverでコンフィギュレーションを一元管理することができる。

## 使い方
**以下の内容は1.0.0.M1バージョンにおける設定方法で、今後大きく変わる可能性がある。**

### Config Serverの構築
Config Serverの構築は極めて簡単。`org.springframework.cloud:spring-cloud-config-server`の依存関係を追加して、エントリポイントに`@EnableConfigServer`を付けるだけ。

pom.xmlの設定例は後述する。

エントリポイントクラスは以下のように作成する。

``` java
package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.context.annotation.ComponentScan;

@EnableAutoConfiguration
@EnableConfigServer // important!!
@ComponentScan
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}
```

あとはコンフィギュレーションをどこから取ってくるかをクラスパス直下の`bootstrap.yml`に記述する(`application.yml`ではないので注意)。

デフォルトでは[spring-cloud-samples/config-repo](https://github.com/spring-cloud-samples/config-repo)から取得する設定になっているが、プロパティの変更を行いたいため、このプロジェクトをforkした[making/config-repo](https://github.com/making/config-repo)から取得するように変更する。Gitの共有レポジトリのURLを`spring.platform.config.server.uri`プロパティに設定する。

```
spring.platform.config.server.uri: https://github.com/making/config-repo
```

最後にpom.xmlの設定。まだ正式版がリリースされていないため、少し冗長な記述になっているが、1.0.0.RELEASEが出たらもう少し簡潔に書けるはず。重要なのは`org.springframework.cloud:spring-cloud-config-server`の設定だけ。

``` 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>demo</groupId>
    <artifactId>configserver</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.1.5.RELEASE</version>
        <relativePath/>
    </parent>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starters</artifactId>
                <version>1.0.0.M1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>demo.App</start-class>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>http://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>http://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <dependencies>
                    <dependency>
                        <groupId>org.springframework</groupId>
                        <artifactId>springloaded</artifactId>
                        <version>${spring-loaded.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

</project>
```

プロジェクト構成は以下のようになる。

![image](https://qiita-image-store.s3.amazonaws.com/0/1852/15e65480-15de-847e-472a-ea062cf3390c.png)

`App`クラスを実行すると8888番ポートでサーバーが起動する。spring-cloud-config-server-1.0.0.M1.jarにデフォルト設定が行われた`application.yml`が含まれている。

`http://localhost:8888/{name}/{env}/{label}`にアクセスすることで、アプリケーション毎の環境(profile)毎のコンフィギュレーションを取得できる。

* `name`=アプリケーション名
* `env`=profile名 (デフォルトは`default`)
* `label`=branch名 (デフォルトは`master`)

だと思えば良い。`label`は省略可能である。

[making/config-repo](https://github.com/making/config-repo)の例だと以下の扱いとなる。

![image](https://qiita-image-store.s3.amazonaws.com/0/1852/da4901b2-8f98-f3ab-bd38-dad103c976af.png)

* http://localhost:8888/foo/default にアクセスすると[foo.properties](https://github.com/making/config-repo/blob/master/foo.properties)のコンフィギュレーションを取得でき、
* http://localhost:8888/foo/development にアクセスすると[foo.properties](https://github.com/making/config-repo/blob/master/foo.properties)のコンフィギュレーションを[foo-development.properties](https://github.com/making/config-repo/blob/master/foo-development.properties)で上書きして取得できる。

``` bash
$ curl -X GET http://localhost:8888/foo/default | jq .
{
  "propertySources": [
    {
      "source": {
        "foo": "b",
        "test": "This is a test",
        "bar": "123456"
      },
      "name": "https://github.com/making/config-repo/foo.properties"
    },
    {
      "source": {
        "info.url": "https://github.com/spring-cloud-samples",
        "info.description": "Spring Cloud Samples"
      },
      "name": "https://github.com/making/config-repo/application.yml"
    }
  ],
  "label": "master",
  "name": "default"
}
```
次に`env`を`development`に変えてリクエストを送る。

``` bash
$ curl -X GET http://localhost:8888/foo/development | jq .
{
  "propertySources": [
    {
      "source": {
        "bar": "spam"
      },
      "name": "https://github.com/making/config-repo/foo-development.properties"
    },
    {
      "source": {
        "foo": "b",
        "test": "This is a test",
        "bar": "123456"
      },
      "name": "https://github.com/making/config-repo/foo.properties"
    },
    {
      "source": {
        "info.url": "https://github.com/spring-cloud-samples",
        "info.description": "Spring Cloud Samples"
      },
      "name": "https://github.com/making/config-repo/application.yml"
    }
  ],
  "label": "master",
  "name": "development"
}
```
`default`と`development`の値が両方返ってくる。Client側でどちらを使うか判断することになる(この場合は`development`を優先する)

Github上の`foo-development.properties`を以下のように変更する。

```
bar: Updated!
foo: Added!
```

再度`http://localhost:8888/foo/development`にリクエストを送ると、

``` bash
$ curl -X GET http://localhost:8888/foo/development | jq .
{
  "propertySources": [
    {
      "source": {
        "foo": "Added!",
        "bar": "Updated!"
      },
      "name": "https://github.com/making/config-repo/foo-development.properties"
    },
    {
      "source": {
        "foo": "b",
        "test": "This is a test",
        "bar": "123456"
      },
      "name": "https://github.com/making/config-repo/foo.properties"
    },
    {
      "source": {
        "info.url": "https://github.com/spring-cloud-samples",
        "info.description": "Spring Cloud Samples"
      },
      "name": "https://github.com/making/config-repo/application.yml"
    }
  ],
  "label": "master",
  "name": "development"
}
```

Gitをpullして、最新の値を返す。(以降の説明では`git push -f origin HEAD^:mastere`で内容を元に戻している)


認証・認可設定やプロパティ値の暗号化・復号に関しては[ドキュメント](http://cloud.spring.io/spring-cloud-config/spring-cloud-config.html#_spring_cloud_config_server)を参照。

### Config Clientの構築

次にClientについて説明する。Clientは普通のSpring Bootアプリケーションに`org.springframework.cloud:spring-cloud-config-client`の依存関係を追加するだけでいい。自動的にConfig Serverに接続し、Config Server経由でプロパティを使用するようになる。
`org.springframework.boot:spring-boot-starter-actuator`も依存関係に加えておく。

Clientのアプリケーション名は`bootstrap.yml`に`spring.application.name`キーで定義する。

```
spring:
  application:
    name: foo
```

Config Serverの接続先ははデフォルトでは`http://localhost:8888`の`env=default`、`label=master`を使用される。上書きしたい場合は以下のように設定できる。

```
spring:
  application:
    name: foo
  cloud:
    config:
      env: default # optional
      label: master # optional
      uri: http://localhost:8888 # optional
```

クライアントのエントリポイントとなる`ClientApp`クラスに、簡単なControllerを実装する。`bar`プロパティを使うようにしておく。

``` java
package demo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@EnableAutoConfiguration
@ComponentScan
@RestController
public class ClientApp {
    @Value("${bar:World!}")
    String bar;

    @RequestMapping("/")
    String hello() {
        return "Hello " + bar + "!";
    }

    public static void main(String[] args) {
        SpringApplication.run(ClientApp.class, args);
    }
}
```

pom.xmlは以下の通り

``` 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>demo</groupId>
    <artifactId>configclient</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.1.5.RELEASE</version>
        <relativePath/>
    </parent>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starters</artifactId>
                <version>1.0.0.M1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>demo.ClientApp</start-class>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>http://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>http://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <dependencies>
                    <dependency>
                        <groupId>org.springframework</groupId>
                        <artifactId>springloaded</artifactId>
                        <version>${spring-loaded.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

</project>
```


プロジェクト構成は以下のようになる。(`application.yml`は後で説明する。)

![image](https://qiita-image-store.s3.amazonaws.com/0/1852/691e8fd0-4f45-bac4-d162-7d6cc6923897.png)

Spring Boot Actuatorの機能で[http://localhost:8080/env](http://localhost:8080/env)にアクセスして、`PropertySource`一覧を見ると、Config Serverからプロパティを取得できていることがわかる。

![image](https://qiita-image-store.s3.amazonaws.com/0/1852/2d57ea41-1365-5a3c-ba58-e8deff2156a8.png)

単プロパティの取得も可能。

``` bash
$ curl http://localhost:8080/env/bar
123456
```

Controllerにアクセスすると、

``` bash
$ curl http://localhost:8080
Hello 123456!
```

Config Server上のプロパティがインジェクションされていることがわかる。

#### 動的コンフィギュレーション変更

次にConfig Serverの値を以下のように[書き換えて](https://github.com/making/config-repo/commit/28a401b163cc77daba5478946b845362e28cb306)みる。

```
bar: Spring Boot
```

単プロパティにアクセスすると、

``` bash
$ curl http://localhost:8080/env/bar
123456
```

この段階ではConfig Serverの変更は反映されていない。反映させるためには`refresh`エンドポイントにPOSTでアクセスする必要がある。

``` bash
$ curl -X POST http://localhost:8080/refresh
["bar"]
$ curl http://localhost:8080/env/bar
Spring Boot
```

これでクライアントの`PropertySource`には反映させることができた。ではもう一度

``` bash
$ curl http://localhost:8080
Hello 123456!
```

既にDIされたプロパティをrefreshで変更することはできない。(このControllerはSingletonスコープであるため)

再度DIするには`restart`エンドポイントにPOSTでアクセスしてDIコンテナを再起動する必要がある。

``` bash
$ curl -X POST http://localhost:8080/restart
{"message":"Restarting"}
```

ログを見ればわかるが、DIコンテナが再起動する(数秒かかる)。

``` bash
$ curl http://localhost:8080
Hello Spring Boot!
```

これでアプリケーションを再起動することなく、コンフィギュレーションを変更することが出来た。

ちなみに、`restart`エンドポイントはデフォルトでは無効になっている。これを有効にするには、`application.yml`に以下のように設定する必要がある。

```
endpoints:
  restart:
    enabled: true
```

#### Adhocなコンフィギュレーション変更

Config Serverを書き換えなくても、Client側でAdohocにコンフィギュレーションを変更することができる。

以下のように`env`エンドポイントにプロパティをPOSTすれば良い。

``` bash
$ curl -X POST http://localhost:8080/env -d bar="Spring Cloud"
{"bar":"Spring Cloud"}
```

この時点でアプリケーションの`PropertySource`は書き変わるので、
プロパティをGETすると、更新された値を取得できる。

``` bash
$ curl http://localhost:8080/env/bar
Spring Cloud
```

再DIは行われないのでControllerの結果は変わらない。

``` bash
$ curl http://localhost:8080
Hello Spring Boot!
```

`refesh`しても同じである。

``` bash
$ curl -X POST http://localhost:8080/refresh
[]
$ curl http://localhost:8080
Hello Spring Boot!
```

この場合も`restart`でDIコンテナを再起動することで、Controllerの結果を書き換えることが出来る。

``` bash
$ curl -X POST http://localhost:8080/restart
{"message":"Restarting"}
$ curl http://localhost:8080
Hello Spring Cloud!
```

#### Refreshスコープの導入

ここまで説明して、普通の人なら思うと思うが、DIコンテナの再起動は時間がかかるのであまりうれしくない。

そこで導入されたのがRefreshスコープである。`@RefreshScope`アノテーションがついたBeanはDIコンテナを再起動しなくても、`refresh`エンドポイントにPOSTすれば、そのBeanが再生成される。

`ClientApp`を修正する。

``` java
package demo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@EnableAutoConfiguration
@ComponentScan
@RestController
@RefreshScope // important!
public class ClientApp {
    @Value("${bar:World!}")
    String bar;

    @RequestMapping("/")
    String hello() {
        return "Hello " + bar + "!";
    }

    public static void main(String[] args) {
        SpringApplication.run(ClientApp.class, args);
    }
}
```

アプリケーションを再起動すると、Adhocな変更は失われていることがわかる。

```
$ curl http://localhost:8080
Hello Spring Boot!
```

再度、変更する。

```
$ curl -X POST http://localhost:8080/env -d bar="Spring Cloud Config"
{"bar":"Spring Cloud Config"}
```

次に`refresh`エンドポイントにPOSTする。
```
$ curl -X POST http://localhost:8080/refresh
[]
```

さっきはこの状態ではControllerは何も変化しなかったが、今回は`@RefreshScope`をつけたので、

```
$ curl http://localhost:8080
Hello Spring Cloud Config!
```

プロパティを反映させることができた！

ちなみに、

* `@ConfigurationProperties`が設定されたBean
* ログレベルに関するプロパティ`logging.level.*`

は最初からRefreshスコープである。


----
サンプルコードは[こちら](https://github.com/making/config)。

SpringのDIコンテナは改めて優秀だとおもった。この機能はSpring本体に取り込まれるんじゃなかろうか。
そして、こういったことをほぼ設定無しで実現できてしまうSpring Bootというか`@Conditinal`の仕組みの導入はSpringにとって革命的だったんだな〜と思う。

この記事で説明した内容は2014/11/15のJJUG CCC 2014 Fallの[僕のセッション(ハンズオン)](http://www.java-users.jp/?page_id=1292#R5-3)で扱うので、この記事を読んで手を動かして試してみたいと思った場合は是非[参加](http://jjug.doorkeeper.jp/events/16385)してほしい。
