Spring Cloud Config

iundarigun
Dev Cave
Published in
7 min readFeb 23, 2018

Uma das premissas quando desenvolvemos micro-serviços é ter uma configuração suficientemente ágil como para não precisar recompilar o pacote para alterar um parâmetro específico. Inclusive o ponto 3 do The twelve-factor fala sobre isso. Mas hoje, quando criamos um micro-serviço deixamos muitas coisas no application.properties (ou yml) pois é o jeito mais fácil de começar.

Inicialmente, muitas dessas configurações não mudam. Por exemplo, a URL de conexão para o banco de dados raramente muda, pois o que pode mudar é a IP da máquina física, e o DNS resolve esse problema para nós. Mas sempre existe aquele grupo de propriedades que podem e devem mudar. Estou falando de propriedades tipo email do destinatário de tal feature, ou flag de liga/desliga para certo processo, ou qualquer coisa mais volátil que consiga imaginar.

Para resolver esse tipo de situações, podemos usar estratégias diferentes. Uma mais clássica é guardar esses parâmetros mutáveis no banco de dados, mas falando de micro-serviços nem sempre vamos ter um banco de dados disponível. Outra solução é usar alguma ferramenta de configuração remota (remote config). Aqui entra nosso componente que apresentamos hoje.

Temos outras alternativas no mercado, como o reconfig, mas aqui vamos focar no Spring Cloud Config.

Como funciona

Basicamente, o Spring Cloud Config é um micro-serviço onde outros micro-serviços consultam as propriedades da própria aplicação. Quando a aplicação “cliente” sobe, usa a configuração de servidor de configuração e pergunta para o serviço de Spring Cloud Config quais configurações precisa aplicar, se identificando pelo nome da aplicação.

O servidor usa arquivos físicos para armazenar os arquivos por aplicação. Lógico que não seria muito inteligente usar arquivos físicos pois uma das premissas é que o micro-serviço deve poder morrer e nascer sem dependências da máquina física. E um arquivo é uma dependência. Então, ele usa um repositório git e faz o checkout quando precisa consultar os arquivos.

Como sempre, pode ver o código no github:

> git clone https://github.com/iundarigun/cloud-config.git
> git clone
https://github.com/iundarigun/repoconfig.git

O primeiro repositório tem os exemplos que vamos apresentar. O segundo é o repositório usado para manter as propriedades ou atributos desejados.

Servidor

Vamos criar um servidor do Spring Cloud Config. Entrando no start.spring.io, escolhemos só a opção de cloud config server.

O config server está dentro das opções de Cloud Config

O código está na pasta configserver. Agora, só precisamos duas configurações mais. Uma, anotamos a classe principal como @EnableConfigServer:

A segunda, evidentemente, é especificar qual git vamos usar para recuperar as configurações das aplicações:

Agora podemos criar um novo micro-serviço cliente. Além do que podemos precisar nele, precisamos indicar que vai ser cliente de configuração colocando como dependência o config client. O código está na pasta configclient.

Criei para testar um endpoint de teste para devolver uma variável especificada nas properties, test.url. Criamos uma configuração padrão no application.yml:

Antes de continuar. Estamos usando o padrão de arquivos .yml, mas o exemplo funciona de igual forma com padrão .properties.

O nosso endpoint só responde o valor da variável:

Se bater no endpoint com essas configurações, ele vai devolver http://noapplicationlocal:8080. Para especificar que ele vai ser um cliente, temos varias ações a serem realizadas.
Colocamos a anotation @RefreshScope na classe onde temos properties que estarão no server. Na classe acima, podemos colocar a anotação abaixo ou acima do @RestController.
Depois criamos o arquivo bootstrap.yml na mesma pasta onde temos o application.yml, e especificamos nele o nome da aplicação cliente e a url da aplicação servidor de configuração:

Agora no git onde o servidor consulta as propriedades, comitamos um arquivo com a propriedade e com o nome da aplicação, test-app.yml:

Se levantamos o servidor e depois o cliente, nessa ordem, testando o endpoint de test, veremos que realmente está pegando o valor especificado no git, devolvendo http://test-app-remote:8080

Funciona também com os profiles do Spring. Se aplicação levantar como profile de dev, por exemplo, vai consultar o arquivo test-app-dev.yml. Diferente do que acontece nos arquivos yml do projeto, não usa a hierarquia. Isso é, se uma propriedade está no test-app.yml no git, mas não está no test-app-dev.yml, quando subir a aplicação test-app com profile de dev, aquela propriedade que só está no test-app.yml do git não será lida.

Até aqui parece que só estamos adicionando complexidade. Então vamos fazer o seguinte. Sem derrumbar nem cliente nem servidor, vamos alterar o valor do test.url no test-app.yml do git para http://test-app-remote-refresh:8080, pushar e consultar o endpoint novamente. Infelizmente, não está develovendo valor atualizado, mas sim o valor existente quando a aplicação subir. Mas se precisamos reiniciar a aplicação toda vez, não parece um ganho muito bom né?

Calma, temos que aplicar umas config a mais. A primeira é colocar, se não tiver já, o actuator no cliente. E liberar os endpoints com a property management.security.enabled=false. Coloca no bootstrap.yml do client. Podemos liberar todos por enquanto, depois se preferir (desejável na verdade), podemos limitar para aqueles que sejam necessários.

Para forçar o refresh das propriedades, podemos fazer um POST no /refresh da aplicação:

curl -X POST –d {} localhost:8090/refresh

Agora sim, ele vai pegar as propriedades novas sem precisar reiniciar.

Múltiplos clientes

Quando trabalhamos em micro-serviços, principalmente pensamos em escalar verticalmente quando precisar. Em lugar de colocar uma máquina mais potente para aquele monolítico enorme, colocamos mais instâncias daquele micro-serviço que precisa mais recursos, deixando os outros micro-serviços que precisam de menos com poucas ou uma instâncias.

Como lida nesse cenário o Spring Cloud Config? Por ora, não muito bem na verdade. Podemos subir duas instâncias por exemplo do nosso configclient:

> ./gradlew build
> cd build/libs
> java -jar -Dserver.port=8090 configclient-0.0.1-SNAPSHOT.jar
> java -jar -Dserver.port=8091 configclient-0.0.1-SNAPSHOT.jar

Como podemos imaginar, agora precisamos fazer dois POST em dois serviços diferentes para atualizar as mesmas propriedades. Imagina que mudamos varias propriedades de diferentes micro-serviços com varias instâncias. Perderemos o controle bem rápido, mais se temos alguma política de autoscale.

Para resolver esse problema, usamos o Spring Cloud Bus para propagação. A ideia é muito simples. Os clientes do Spring Cloud Config assinam para receber notificações de alterações de configuração em algum sistema de notificação assíncrona, como pode ser uma fila. Quando um dos clientes recebe o refresh via POST, ele notifica todos os outros clientes que precisam se atualizar. Logo mais vemos como.

Para usar esta estratégia, basta colocar a dependência do bus-amqp nos nosso projetos cliente.

Ele usará por padrão o Rabbit como sistema de notificação. Então precisamos ter o Rabbit up na nossa máquina. Para subir com Docker:

> docker run -d — hostname my-rabbit — name some-rabbit -p 15672:15672 -p 5671:5671 -p 5672:5672 rabbitmq:3-management

Depois, já podemos levantar duas instâncias do configclient e duas do configclient2. Alteramos as configurações no git repo-config para os dois projetos e fazemos o refresh da seguinte forma:

> curl -X POST –d {} localhost:8090/bus/refresh

Veremos que vai atualizar as propriedades das 4 instâncias. Mas como funciona realmente? Cada aplicação cria uma fila no rabbit:

As filas ficam atreladas ao tópico SpringCloudBus e quando a aplicação morre, a fila também desaparece. Quando uma mensagem é publicada no tópico, é distribuída para as filas plugadas nele, assim que aquelas aplicações que não estão mais up, não são notificadas pois a fila não existe mais:

Por último, se queremos fazer o refresh só as instâncias de um determinado projeto, podemos especificar o serviço como parâmetro no próprio post:

curl -X POST –d {} localhost:8090/bus/refresh?destination=test-app2:**

Nota: Se o post é feito contra uma instância de um serviço diferente do especificado no destination, aquela instância do outro serviço também vai refrescar as propriedades antes de notificar as instâncias do outro serviço. No exemplo, na porta 8090 temos uma instância do test-app. Antes de notificar o test-app2, vai refrescar as propriedades dele mesmo. Porém, se o test-app tiver uma segunda instância na porta 8091, essa segunda instância não refrescaria as propriedades, podendo ter inconsistências entre instâncias da mesma aplicação.

Estruturando o Repo-config

Por ora, não estruturamos o repositório de com as configurações. Colocamos todos os arquivos la raiz do projeto. Mas se nosso número de micro serviços aumentar significativamente, pode ficar um pouco desorganizado.

Achei interessante duas opções de configuração, compatíveis entre elas. A primeira é organizar por pastas. Por exemplo, podemos colocar tudo numa pasta config, cada projeto numa pasta com o nome da aplicação e organizar os profiles por pasta também.

Estrutura de pastas

Para usar a estrutura do exemplo, precisamos alterar o server para suportar essa estrutura. Alteramos o bootstrap.yml do projeto adicionando a propriedade searchParth:

A outra configuração interessante é organizando por branchs. Nesse caso, só precisamos criar a branch e commitar com a estrutura escolhida, para depois o cliente dizer de qual branch vai querer pegar as propriedades. Precisamos só alterar o bootstrap.yml do cliente acrescentando a propriedade label para indicar de qual branch deve recuperar as propriedades:

Considerações sobre o bus

Quando usamos o bus no exemplo ficou meio transparente. Foram usadas as configurações básicas do Rabbit para propagar o refresh. Quando nossa aplicação já tenha alguma conexão com Rabbit vamos precisar tomar cuidado com os seguintes pontos:
1- Todas as aplicações devem apontar para o mesmo Rabbit, pois a filas ficam conectadas ao mesmo tópico.
2- O Spring Cloud Bus usa o Rabbit marcado como @primary para fazer a propagação.

Bom, até aqui cheguei com o Spring Cloud Config. Alguma dúvida, sugestão? Comenta aqui!

--

--

iundarigun
Dev Cave

Java and Kotlin software engineer at Clearpay