Pular para o conteúdo principal

Capítulo 3: Configuração Centralizada com Spring Cloud Config

🎯 Objetivos do Capítulo​

  • Entender a necessidade de configuração centralizada
  • Configurar um Spring Cloud Config Server
  • Conectar os serviços como Config Clients
  • Utilizar o Git como backend de configuração
  • Implementar o refresh de configuração em tempo real
  • Gerenciar a encriptação de segredos

1. Por que Configuração Centralizada?​

1.1 O Problema Sem Config Server​

microservices/
├── user-service/
│ └── application.yml # Configuração específica
├── product-service/
│ └── application.yml # Configuração específica
├── order-service/
│ └── application.yml # Configuração específica
├── payment-service/
│ └── application.yml # Configuração específica
└── inventory-service/
└── application.yml # Configuração específica

Problemas:

  • Alterar a senha do banco = modificar 5 arquivos
  • Fazer deploy de 5 serviços
  • Sem versionamento centralizado
  • Segredos em texto claro em cada serviço
  • Sem refresh sem reiniciar

1.2 A Solução: Config Server​

Vantagens:

  • Uma única fonte de verdade
  • Versionamento com Git (histórico, branches)
  • Refresh sem redeploy
  • Encriptação de segredos
  • Configuração por ambiente (dev, prod)

2. Arquitetura Spring Cloud Config​

2.1 Componentes​

2.2 Fluxo de Configuração​


3. Configuração do Config Server​

3.1 Estrutura do Projeto​

config-server/
├── pom.xml
└── src/main/
├── java/com/example/configserver/
│ └── ConfigServerApplication.java
└── resources/
└── application.yml

3.2 Dependências Maven​

<?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
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>

<groupId>com.example</groupId>
<artifactId>config-server</artifactId>
<version>1.0.0</version>

<properties>
<java.version>17</java.version>
<spring-cloud.version>2023.0.0</spring-cloud.version>
</properties>

<dependencies>
<!-- Config Server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>

<!-- Eureka Client (para registrar) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<!-- Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

3.3 Aplicação Principal​

package com.example.configserver;

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

@SpringBootApplication
@EnableConfigServer // Ativa o Config Server
public class ConfigServerApplication {

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

3.4 Configuração com Git​

# application.yml
server:
port: 8888

spring:
application:
name: config-server
cloud:
config:
server:
git:
# Repositório Git (GitHub, GitLab, Bitbucket, local)
uri: https://github.com/votre-org/config-repo
# Branch padrão
default-label: main
# Pasta no repo (opcional)
search-paths: config
# Clone ao iniciar
clone-on-start: true
# Credenciais se repo privado
username: ${GIT_USERNAME:}
password: ${GIT_PASSWORD:}
# Timeout
timeout: 10

# Eureka Client
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/

# Security
spring:
security:
user:
name: config
password: config123

3.5 Configuração Local (para dev)​

spring:
cloud:
config:
server:
native:
search-locations:
- file:///C:/config-repo
- classpath:/config
profiles:
active: native # Usar o filesystem ao invés de Git

4. Estrutura do Repositório de Configuração​

4.1 Convenção de Nomenclatura​

config-repo/
├── application.yml # Config comum a TODOS os serviços
├── application-dev.yml # Config dev para TODOS os serviços
├── application-prod.yml # Config prod para TODOS os serviços
├── user-service.yml # Config específica user-service
├── user-service-dev.yml # user-service em dev
├── user-service-prod.yml # user-service em prod
├── product-service.yml
├── product-service-dev.yml
├── product-service-prod.yml
├── order-service.yml
├── order-service-dev.yml
└── order-service-prod.yml

4.2 Prioridade de Configuração​

A configuração é mesclada nesta ordem (da menor para a maior prioridade):

  1. application.yml (base comum)
  2. application-\{profile\}.yml (perfil comum)
  3. {service-name}.yml (serviço específico)
  4. {service-name}-\{profile\}.yml (serviço + perfil)

4.3 Exemplo: application.yml (comum)​

# config-repo/application.yml
# Configuração compartilhada por TODOS os serviços

# Eureka comum
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true

# Actuator comum
management:
endpoints:
web:
exposure:
include: health,info,refresh
endpoint:
health:
show-details: always

# Logging comum
logging:
level:
root: INFO
com.example: DEBUG

4.4 Exemplo: user-service.yml​

# config-repo/user-service.yml
# Configuração específica do User Service

server:
port: 8081

spring:
datasource:
url: jdbc:postgresql://localhost:5432/users_db
username: ${DB_USERNAME:postgres}
password: ${DB_PASSWORD:postgres}
jpa:
hibernate:
ddl-auto: validate
show-sql: false

# Configuração de negócio
user:
default-role: USER
password-min-length: 8
token-expiration-hours: 24

4.5 Exemplo: user-service-dev.yml​

# config-repo/user-service-dev.yml
# Sobrescrita para o ambiente de desenvolvimento

spring:
datasource:
url: jdbc:h2:mem:users_dev
driver-class-name: org.h2.Driver
h2:
console:
enabled: true
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true

logging:
level:
com.example.userservice: TRACE
org.hibernate.SQL: DEBUG

4.6 Exemplo: user-service-prod.yml​

# config-repo/user-service-prod.yml
# Configuração de produção

spring:
datasource:
url: jdbc:postgresql://prod-db.example.com:5432/users_db
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
hikari:
maximum-pool-size: 20
minimum-idle: 5
jpa:
hibernate:
ddl-auto: none
show-sql: false

logging:
level:
root: WARN
com.example: INFO

# Desabilitar endpoints sensíveis
management:
endpoints:
web:
exposure:
include: health,info

5. Configuração do Config Client​

5.1 Dependências Maven (Serviço)​

<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Config Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>

<!-- Bootstrap (para carregar config antes do app) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

<!-- Eureka Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<!-- Actuator (para /refresh) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>

5.2 Configuração Client (Método 1: bootstrap.yml)​

# src/main/resources/bootstrap.yml
# Carregado ANTES do application.yml

spring:
application:
name: user-service # Deve corresponder ao nome dos arquivos de config
profiles:
active: dev
cloud:
config:
# URL direta do Config Server
uri: http://localhost:8888
# Credenciais se Config Server protegido
username: config
password: config123
# Retry se Config Server indisponível
fail-fast: true
retry:
initial-interval: 1000
max-attempts: 6
max-interval: 2000
multiplier: 1.1

5.3 Configuração Client (Método 2: application.yml com import)​

# src/main/resources/application.yml
# Spring Boot 2.4+ - não precisa de bootstrap.yml

spring:
application:
name: user-service
profiles:
active: dev
config:
import: "optional:configserver:http://localhost:8888"
cloud:
config:
username: config
password: config123

5.4 Descoberta via Eureka​

# Ao invés de uma URL fixa, descobrir via Eureka
spring:
cloud:
config:
discovery:
enabled: true
service-id: config-server # Nome no Eureka
fail-fast: true

eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/

6. Acesso às Propriedades no Código​

6.1 Injeção com @Value​

@RestController
@RequestMapping("/api/users")
public class UserController {

@Value("${user.default-role}")
private String defaultRole;

@Value("${user.password-min-length}")
private int passwordMinLength;

@Value("${user.token-expiration-hours}")
private int tokenExpirationHours;

@GetMapping("/config")
public Map<String, Object> getConfig() {
return Map.of(
"defaultRole", defaultRole,
"passwordMinLength", passwordMinLength,
"tokenExpirationHours", tokenExpirationHours
);
}
}

6.2 Injeção com @ConfigurationProperties​

package com.example.userservice.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "user")
public class UserProperties {

private String defaultRole;
private int passwordMinLength;
private int tokenExpirationHours;

// Getters e Setters
public String getDefaultRole() { return defaultRole; }
public void setDefaultRole(String defaultRole) {
this.defaultRole = defaultRole;
}

public int getPasswordMinLength() { return passwordMinLength; }
public void setPasswordMinLength(int passwordMinLength) {
this.passwordMinLength = passwordMinLength;
}

public int getTokenExpirationHours() { return tokenExpirationHours; }
public void setTokenExpirationHours(int tokenExpirationHours) {
this.tokenExpirationHours = tokenExpirationHours;
}
}
@Service
public class UserService {

private final UserProperties userProperties;

public UserService(UserProperties userProperties) {
this.userProperties = userProperties;
}

public User createUser(UserRequest request) {
User user = new User();
user.setRole(userProperties.getDefaultRole());
// ...
return user;
}
}

7. Refresh de Configuração em Tempo Real​

7.1 O Problema​

1. Modificar user-service-dev.yml no Git
2. Commit & Push
3. Config Server detecta a mudança
4. MAS: user-service ainda está com a configuração antiga!
(Ela foi carregada na inicialização)

7.2 Solução 1: Endpoint /actuator/refresh​

# Expor o endpoint refresh
management:
endpoints:
web:
exposure:
include: health,info,refresh
# Disparar o refresh manualmente
curl -X POST http://localhost:8081/actuator/refresh

7.3 Beans @RefreshScope​

@RestController
@RequestMapping("/api/config")
@RefreshScope // Este bean será recriado no refresh
public class ConfigController {

@Value("${user.token-expiration-hours}")
private int tokenExpirationHours;

@GetMapping("/token-expiration")
public int getTokenExpiration() {
return tokenExpirationHours;
}
}

7.4 Solução 2: Spring Cloud Bus (Propagação automática)​

Dependência:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

Disparar refresh global:

curl -X POST http://config-server:8888/actuator/busrefresh

8. Encriptação de Segredos​

8.1 Por que Encriptar?​

# ❌ PERIGO: Senha em texto claro no Git!
spring:
datasource:
password: SuperSecret123!

8.2 Configuração de Encriptação​

# Config Server - application.yml
encrypt:
key: ${ENCRYPT_KEY:my-secret-encryption-key-32chars!}

Ou com um keystore:

encrypt:
key-store:
location: classpath:/server.jks
password: keystorepassword
alias: configserver
secret: keypassword

8.3 Encriptar um Valor​

# Encriptar
curl -X POST http://localhost:8888/encrypt -d "SuperSecret123!"
# Resultado: AQB...encodedValue...

# Decriptar (para verificar)
curl -X POST http://localhost:8888/decrypt -d "AQB...encodedValue..."
# Resultado: SuperSecret123!

8.4 Usar o Valor Encriptado​

# config-repo/user-service-prod.yml
spring:
datasource:
password: '{cipher}AQB...encodedValue...'

O Config Server decripta automaticamente antes de enviar para o client.

8.5 Encriptação Assimétrica (RSA)​

# Gerar um keystore
keytool -genkeypair -alias configserver -keyalg RSA \
-dname "CN=Config Server,OU=Unit,O=Organization,L=City,S=State,C=US" \
-keypass changeme -keystore server.jks -storepass changeme
encrypt:
key-store:
location: file:/etc/config-server/server.jks
password: ${KEYSTORE_PASSWORD}
alias: configserver
secret: ${KEY_PASSWORD}

9. Endpoints do Config Server​

9.1 Padrões de URL​

/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties

9.2 Exemplos​

# Configuração do user-service com perfil dev
curl http://localhost:8888/user-service/dev
curl http://localhost:8888/user-service-dev.yml

# Configuração com branch específico
curl http://localhost:8888/user-service/dev/feature-branch

# Configuração em formato properties
curl http://localhost:8888/user-service-dev.properties

9.3 Resposta JSON​

curl http://localhost:8888/user-service/dev
{
"name": "user-service",
"profiles": ["dev"],
"label": "main",
"version": "abc123def456",
"propertySources": [
{
"name": "https://github.com/org/config-repo/user-service-dev.yml",
"source": {
"spring.datasource.url": "jdbc:h2:mem:users_dev",
"user.default-role": "USER"
}
},
{
"name": "https://github.com/org/config-repo/user-service.yml",
"source": {
"server.port": 8081
}
},
{
"name": "https://github.com/org/config-repo/application.yml",
"source": {
"eureka.client.service-url.defaultZone": "http://localhost:8761/eureka/"
}
}
]
}

10. Boas Práticas​

10.1 Separação de Ambientes​

config-repo/
├── application.yml # NUNCA coloque segredos aqui
├── application-dev.yml # Config dev (pode ter valores padrão)
├── application-staging.yml # Config staging
├── application-prod.yml # SOMENTE referências a variáveis de ambiente

10.2 Segredos em Produção​

# ❌ Ruim
spring:
datasource:
password: '{cipher}AQB...' # Mesmo encriptado, está no Git

# ✅ Bom
spring:
datasource:
password: ${DB_PASSWORD} # Variável de ambiente

10.3 Fail-Fast​

spring:
cloud:
config:
fail-fast: true # O serviço não inicia se o Config Server estiver indisponível
retry:
max-attempts: 6
initial-interval: 1000

10.4 Timeouts Apropriados​

spring:
cloud:
config:
request-connect-timeout: 5000
request-read-timeout: 10000

10.5 Health Check​

// O health check inclui automaticamente o Config Server
// GET /actuator/health
{
"status": "UP",
"components": {
"configServer": {
"status": "UP",
"details": {
"propertySources": [...]
}
}
}
}

11. Resumo​

Pontos-Chave​

✅ Config Server = Fonte única de configuração
✅ Git Backend = Versionamento, histórico, branches
✅ Convenção de nomenclatura = {service}-\{profile\}.yml
✅ @RefreshScope = Beans que recarregam
✅ Encriptação = Segredos seguros
✅ Spring Cloud Bus = Refresh automático distribuído

Ordem de Inicialização​

  1. Eureka Server (8761) - Service Discovery
  2. Config Server (8888) - Configuração
  3. Outros serviços - Recuperam sua config e então se registram

Checklist​

  • Config Server configurado com Git
  • Repositório de configuração estruturado
  • Clients configurados com bootstrap.yml ou spring.config.import
  • Endpoint /refresh exposto
  • Segredos encriptados ou via variáveis de ambiente
  • fail-fast ativado

Próximo Capítulo: API Gateway →

Voltar ao Módulo 13