Microservices - Configurações distribuídas com Spring Cloud Config

Por Gaspar Barancelli Junior em 04 de abril de 2024

Esse é o primeiro de uma serie de posts sobre microservices.

  1. Configurações

  2. Registro de serviços e descobertas

  3. Roteamento dinamico

  4. Comunicação

  5. Falhas

  6. Rastreamento

  7. Logs

  8. Documentações

  9. Monitoramento

  10. Migração

  11. Segurança

  12. Implantação

  13. Orquestração

Criei uma apresentação bem simples comentando sobre algumas ferramentas que veremos ao longo dessa serie sobre microservices, a apresentação esta publica e disponível para visualização através deste link.

Para facilitar os seus estudos e o entendimento sobre todos os posts que serão abordados, eu desenvolvi alguns projetos utilizando todas as ferramentas mencionadas na apresentação, então aproveita e da uma olhada nos fontes, eles tão hospedados nesse repositório no github.

Configurações

O Spring Cloud Config fornece suporte do lado do servidor e do cliente para configuração externalizada em um sistema distribuído. Com o Config Server, você tem um local central para gerenciar propriedades externas para aplicativos em todos os ambientes. Conforme um aplicativo se move pelo pipeline de implantação do desenvolvimento para o teste e para a produção, você pode gerenciar a configuração entre esses ambientes e ter certeza de que os aplicativos têm tudo de que precisam para serem executados durante a migração. A implementação padrão do backend de armazenamento do servidor usa git para que ele suporte facilmente versões rotuladas de ambientes de configuração.

Criando o nosso servidor de configurações distribuidas

Vamos criar um novo projeto utilizando o Spring Boot, para isso acesse o endereço https://start.spring.io/ e adicione a dependência Config Server, já as demais configurações ficam a sua escolha.

1

Apos adicionarmos a dependência podemos gerar o nosso projeto. Assim que o download for concluído, descompacte o diretório contendo o projeto e acesse os fontes na sua IDE preferida.

Configurando o projeto

A ideia do Spring Boot é que as dependências sejam auto configuradas, no caso do Spring Cloud precisamos adicionar algumas propriedades para que o servidor seja configurado, sendo assim abra o arquivo application.properties e adicione as seguintes configurações.

spring.application.name=config-server
server.port=${PORT:8888}
spring.cloud.config.server.git.uri=${URI:https://github.com/gasparbarancelli/microservices.git}
spring.cloud.config.server.git.username=${USERNAME}
spring.cloud.config.server.git.password=${PASSWORD}
spring.cloud.config.server.git.search-paths=properties
spring.cloud.config.label=${BRANCH}

Nas propriedades acima, declaramos um nome para a nossa aplicação, definimos em qual porta o servidor http vai responder as requisições e na sequencia adicionamos as seguintes configurações para configurarmos o Config Server.

  • spring.cloud.config.server.git.uri: Endereço do repositório GIT que contem as configurações a serem distribuídas.

  • spring.cloud.config.server.git.username: Caso o repositório GIT informado não seja publico, você deve definir as credenciais de acesso, portanto nessa propriedade você define o nome do usuário que tenha acesso ao repositório privado.

  • spring.cloud.config.server.git.password: Caso o repositório GIT informado não seja publico, você deve definir as credenciais de acesso, portanto nessa propriedade você define a senha de acesso ao repositório privado.

  • spring.cloud.config.server.git.search-paths: Essa propriedade deve ser utilizada quando os arquivos contendo as propriedades de configurações estiverem dentro de uma pasta, como no meu exemplo os arquivos estão dentro da pasta properties. Caso os arquivos estejam no diretório raiz do repositório GIT essa propriedade pode ser removida. Podemos também utilizar essa propriedade para separar as configurações por ambiente, em um cenário onde temos ambiente de desenvolvimento, homologação e produção, podemos criar uma pasta para cada ambiente, e simplesmente alterando o valor dessa variável as configurações serão lidas de acordo com o ambiente onde a aplicação está sendo executada.

  • spring.cloud.config.label: Caso seu repositório GIT tenha mais de uma branch, você pode especificar em qual branch o Config Service deve obter os arquivos de configuração, como mencionado anteriormente, você pode muito bem, separar as configurações por ambiente alterando as branchs. Caso os arquivos de configurações estejam na branch master você pode excluir essa propriedade.

Para o nosso exemplo utilizamos algumas das propriedades de configuração do Config Server, mas tem muito mais, você pode por exemplo querer criptografar os valores das propriedades, mas para isso eu recomendo fortemente a leitura da documentação.

Neste primeiro momento onde não vamos ter nenhum serviço consumindo os dados gerados pelo Config Server eu recomendo que não altere as configurações feitas acima, para que você consiga acompanhar os próximos passos desse POST.

O último passo da configuração mas não menos importante, é adicionar a anotação @EnableConfigServer na nossa classe main.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@SpringBootApplication
@EnableConfigServer
public class ConfigApplication {

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

}

Executando o serviço

Com tudo configurado agora podemos iniciar nossa aplicação, para isso execute o método main da classe ConfigApplication, ou gere o artefato da aplicação utilizando o seguinte comando do maven mvn package e depois acesse a pasta target pelo terminal e execute o seguinte comando pelo terminal java -jar config-2.0.jar, observe o número da versão no final do artefato, no meu repositório do github defini o Config Server com a versão 2, mas caso tenha criado um novo projeto utilize a versão que está declara no arquivo pom.xml.

Testando os recursos

Ao acessarmos o repositorio git que foi definido nas configurações do Config Server veremos os seguintes arquivos de configurações.

  • application.properties

  • cupons.properties

  • discovery.properties

  • email.properties

  • gateway.properties

  • produtos.properties

  • vendas.properties

O repositório que acessamos como exemplo neste post possui diversos arquivos de configurações, pois criaremos diversos serviços para os futuros posts sobre microservices, e esses serviços faram usos desses arquivos de configurações.

Mas é bom deixar bem claro que um mesmo serviço pode possuir mais de um arquivo de configuração, separando-os por ambientes. Imagine que para o serviço de vendas você tenha propriedades de configurações diferentes para o ambiente de desenvolvimento, homologação e produção, para isso poderíamos ter os seguintes arquivos vendas-dev.properties, vendas-hom.properties e vendas.prod.properties, e simplesmente alterando o profile da aplicação o Config Server consegue identificar quais as propriedades sua aplicação deseja obter para que realizar a auto configuração.

Mas para não deixar o seu primeiro contato com Config Server um tanto quanto complexo, não vamos criar um arquivo para cada ambiente.

Voltando aos arquivos que estão no repositório git mencionado anteriormente, abra seu navegador o arquivo application.properties e verifique as propriedades que estão adicionadas nele.

zuul.url=${ZUUL_URL:http://localhost:8000}

eureka.client.serviceUrl.defaultZone=${EUREKA_URL:http://localhost:8761/eureka/}
eureka.instance.preferIpAddress=true

spring.main.allow-bean-definition-overriding=true

# COMPRESSION
server.compression.enabled=true
server.compression.mime-types=application/json,application/xml,text/html,text/xml,text/plain,application/javascript,text/css

# TRACING
spring.sleuth.sampler.probability=100
spring.zipkin.base-url=${ZIPKIN_URL:http://localhost:9411/}

# ACTUATOR
management.endpoint.health.enabled=true
management.endpoints.jmx.exposure.include=*
management.endpoints.web.exposure.include=*
management.endpoints.web.base-path=/actuator
management.endpoints.web.cors.allowed-origins=true
management.endpoint.health.show-details=always
management.endpoints.enabled-by-default=false
management.endpoint.info.enabled=true
management.endpoint.prometheus.enabled=true

Neste post não vamos focar nos valores dessas configurações, saibam que elas estão ai pois serão utilizadas nos futuros posts sobre microservices.

Agora vamos acessar as configurações do arquivo acima por meio do nosso servidor. Para isso acesse em seu navegador o seguinte endereço http://localhost:8888/application/default.

{
   "name":"application",
   "profiles":[
      "container"
   ],
   "label":null,
   "version":"02f616d19f74437e1106285e8819c8c5bf1dd0cc",
   "state":null,
   "propertySources":[
      {
         "name":"https://github.com/gasparbarancelli/microservices.git/properties/application.properties",
         "source":{
            "zuul.url":"${ZUUL_URL:http://localhost:8000}",
            "eureka.client.serviceUrl.defaultZone":"${EUREKA_URL:http://localhost:8761/eureka/}",
            "eureka.instance.preferIpAddress":"true",
            "spring.main.allow-bean-definition-overriding":"true",
            "server.compression.enabled":"true",
            "server.compression.mime-types":"application/json,application/xml,text/html,text/xml,text/plain,application/javascript,text/css",
            "spring.sleuth.sampler.probability":"100",
            "spring.zipkin.base-url":"${ZIPKIN_URL:http://localhost:9411/}",
            "management.endpoint.health.enabled":"true",
            "management.endpoints.jmx.exposure.include":"*",
            "management.endpoints.web.exposure.include":"*",
            "management.endpoints.web.base-path":"/actuator",
            "management.endpoints.web.cors.allowed-origins":"true",
            "management.endpoint.health.show-details":"always",
            "management.endpoints.enabled-by-default":"false",
            "management.endpoint.info.enabled":"true",
            "management.endpoint.prometheus.enabled":"true"
         }
      }
   ]
}

Observe que todo conteúdo do arquivo application.properties veio como resposta dentro do objeto source.

Agora acesse o arquivo vendas.properties e observe as propriedades retornadas.

# APP
server.port=8082

# SECURITY
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://${KEYCLOAK_HOST:host.docker.internal}:${KEYCLOAK_PORT:18080}/auth/realms/spring-micro-main

# DATASOURCE
spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3307}/vendas
spring.datasource.username=${MYSQL_USERNAME:vendas}
spring.datasource.password=${MYSQL_PASSWORD:vendas123}

# JPA
spring.jpa.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.hibernate.ddl-auto=none

# DOC
springdoc.api-docs.path=/api-documentation
springdoc.swagger-ui.path=/documentation

# JMS
spring.activemq.broker-url=${JMS_URL:tcp://localhost:6161}
spring.activemq.user=admin
spring.activemq.password=admin

Assim como mencionado anteriormente, no momento não vamos entrar em detalhes sobre essas configurações, pois elas serão utilizadas pelo serviço de vendas que iremos desenvolver em um futuro post sobre microservices.

Agora vamos obter as configurações do serviço de vendas através da nossa aplicação, para isso devemos acessar o seguinte endereço http://localhost:8888/vendas/default.

{
   "name":"vendas",
   "profiles":[
      "default"
   ],
   "label":null,
   "version":"02f616d19f74437e1106285e8819c8c5bf1dd0cc",
   "state":null,
   "propertySources":[
      {
         "name":"https://github.com/gasparbarancelli/microservices.git/properties/vendas.properties",
         "source":{
            "server.port":"8082",
            "spring.security.oauth2.resourceserver.jwt.issuer-uri":"http://${KEYCLOAK_HOST:host.docker.internal}:${KEYCLOAK_PORT:18080}/auth/realms/spring-micro-main",
            "spring.datasource.url":"jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3307}/vendas",
            "spring.datasource.username":"${MYSQL_USERNAME:vendas}",
            "spring.datasource.password":"${MYSQL_PASSWORD:vendas123}",
            "spring.jpa.hibernate.dialect":"org.hibernate.dialect.MySQL5InnoDBDialect",
            "spring.jpa.hibernate.naming.physical-strategy":"org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl",
            "spring.jpa.hibernate.ddl-auto":"none",
            "springdoc.api-docs.path":"/api-documentation",
            "springdoc.swagger-ui.path":"/documentation",
            "spring.activemq.broker-url":"${JMS_URL:tcp://localhost:6161}",
            "spring.activemq.user":"admin",
            "spring.activemq.password":"admin"
         }
      },
      {
         "name":"https://github.com/gasparbarancelli/microservices.git/properties/application.properties",
         "source":{
            "zuul.url":"${ZUUL_URL:http://localhost:8000}",
            "eureka.client.serviceUrl.defaultZone":"${EUREKA_URL:http://localhost:8761/eureka/}",
            "eureka.instance.preferIpAddress":"true",
            "spring.main.allow-bean-definition-overriding":"true",
            "server.compression.enabled":"true",
            "server.compression.mime-types":"application/json,application/xml,text/html,text/xml,text/plain,application/javascript,text/css",
            "spring.sleuth.sampler.probability":"100",
            "spring.zipkin.base-url":"${ZIPKIN_URL:http://localhost:9411/}",
            "management.endpoint.health.enabled":"true",
            "management.endpoints.jmx.exposure.include":"*",
            "management.endpoints.web.exposure.include":"*",
            "management.endpoints.web.base-path":"/actuator",
            "management.endpoints.web.cors.allowed-origins":"true",
            "management.endpoint.health.show-details":"always",
            "management.endpoints.enabled-by-default":"false",
            "management.endpoint.info.enabled":"true",
            "management.endpoint.prometheus.enabled":"true"
         }
      }
   ]
}

Observe que o valor da propriedade propertySources é um array, e neste último exemplo foram retornados dois objetos dentro da lista, sendo o primeiro as propriedades de configurações obtidas do arquivo vendas.properties e o segundo objeto retornando as propriedades do arquivo application.properties. Isso acontece porque se entende que as configurações definidas no arquivo application.properties são propriedades comuns que devem ser atribuídas a todos os serviços, imagine que para iniciar com sucesso o serviço de vendas ele deve fazer o uso das suas propriedades em especificas que devem ficar dentro do arquivo vendas.properties mas que também depende das propriedades em comum que se encontram no arquivo application.properties. Mas fique tranquilo, caso você precise sobrescrever uma propriedade que foi definida no arquivo application.properties basta adicionar a mesma propriedade no arquivo vendas.properties e alterar o valor da mesma, caso a propriedade for encontrada em ambos os arquivos ele utiliza o valor da primeira propriedade retornada no array do propertySources.

Resumo

Neste post configuramos um servidor de configurações distribuídas, esse servidor sera a base principal para todos os outros serviços a serem desenvolvidos. Por meio deste servidor somos capazes de retornar um JSON contendo todas as configurações necessárias para que um serviço seja auto configurável, e tudo isso por meio de uma Api Rest e um repositório git.

No próximo post vamos criar um servidor de registro de serviço e descoberta, esse novo serviço vai fazer o uso do nosso servidor de configuração distribuída, então fica ligado no blog e qualquer sugestão entre em contato pelas redes sociais.

// Compartilhe esse Post